Compare commits

...

2 commits

Author SHA1 Message Date
Pieter Noordhuis e067c98fad
Merge 8e2c9596e1 into bd914e58d6 2024-03-12 05:24:00 -06:00
Pieter Noordhuis 8e2c9596e1
Always propagate root context to child command
The context passed to the root command should propagate to its children
not only on the first execution but also subsequent calls.
Calling the same command multiple times is common when testing cobra applications.
2022-12-11 08:36:49 +01:00
2 changed files with 93 additions and 6 deletions

View file

@ -1058,7 +1058,7 @@ func (c *Command) ExecuteC() (cmd *Command, err error) {
// Regardless of what command execute is called on, run on Root only
if c.HasParent() {
return c.Root().ExecuteC()
return c.Root().ExecuteContextC(c.ctx)
}
// windows hook
@ -1108,11 +1108,8 @@ func (c *Command) ExecuteC() (cmd *Command, err error) {
cmd.commandCalledAs.name = cmd.Name()
}
// We have to pass global context to children command
// if context is present on the parent command.
if cmd.ctx == nil {
cmd.ctx = c.ctx
}
// Pass context of root command to child command.
cmd.ctx = c.ctx
err = cmd.execute(flags)
if err != nil {

View file

@ -232,6 +232,96 @@ func TestExecuteContextC(t *testing.T) {
}
}
// This tests that the context passed to the root command propagates to its children
// not only on the first execution but also subsequent calls.
// Calling the same command multiple times is common when testing cobra applications.
func TestExecuteContextMultiple(t *testing.T) {
var key string
// Define unique contexts so we can tell them apart below.
ctxs := []context.Context{
context.WithValue(context.Background(), &key, "1"),
context.WithValue(context.Background(), &key, "2"),
}
// Shared reference to the context in the current iteration.
var currentCtx context.Context
ctxRun := func(cmd *Command, args []string) {
if cmd.Context() != currentCtx {
t.Errorf("Command %q must have context with value %s", cmd.Use, currentCtx.Value(&key))
}
}
rootCmd := &Command{Use: "root", Run: ctxRun, PreRun: ctxRun}
childCmd := &Command{Use: "child", Run: ctxRun, PreRun: ctxRun}
granchildCmd := &Command{Use: "grandchild", Run: ctxRun, PreRun: ctxRun}
childCmd.AddCommand(granchildCmd)
rootCmd.AddCommand(childCmd)
for i := 0; i < 2; i++ {
currentCtx = ctxs[i]
if _, err := executeCommandWithContext(currentCtx, rootCmd, ""); err != nil {
t.Errorf("Root command must not fail: %+v", err)
}
if _, err := executeCommandWithContext(currentCtx, rootCmd, "child"); err != nil {
t.Errorf("Subcommand must not fail: %+v", err)
}
if _, err := executeCommandWithContext(currentCtx, rootCmd, "child", "grandchild"); err != nil {
t.Errorf("Command child must not fail: %+v", err)
}
}
}
// This tests that the context passed to a subcommand propagates to the root.
// If the entry point happens to be different from the root command, the
// context should still propagate throughout the execution.
func TestExecuteContextOnSubcommand(t *testing.T) {
var key string
// Define unique contexts so we can tell them apart below.
ctxs := []context.Context{
context.WithValue(context.Background(), &key, "1"),
context.WithValue(context.Background(), &key, "2"),
context.WithValue(context.Background(), &key, "3"),
}
// Shared reference to the context in the current iteration.
var currentCtx context.Context
ctxRun := func(cmd *Command, args []string) {
if cmd.Context() != currentCtx {
t.Errorf("Command %q must have context with value %s", cmd.Use, currentCtx.Value(&key))
}
}
rootCmd := &Command{Use: "root", Run: ctxRun, PreRun: ctxRun}
childCmd := &Command{Use: "child", Run: ctxRun, PreRun: ctxRun}
granchildCmd := &Command{Use: "grandchild", Run: ctxRun, PreRun: ctxRun}
childCmd.AddCommand(granchildCmd)
rootCmd.AddCommand(childCmd)
currentCtx = ctxs[0]
if _, err := executeCommandWithContext(currentCtx, rootCmd, ""); err != nil {
t.Errorf("Root command must not fail: %+v", err)
}
currentCtx = ctxs[1]
if _, err := executeCommandWithContext(currentCtx, childCmd, "child"); err != nil {
t.Errorf("Subcommand must not fail: %+v", err)
}
currentCtx = ctxs[2]
if _, err := executeCommandWithContext(currentCtx, granchildCmd, "child", "grandchild"); err != nil {
t.Errorf("Command child must not fail: %+v", err)
}
}
func TestExecute_NoContext(t *testing.T) {
run := func(cmd *Command, args []string) {
if cmd.Context() != context.Background() {