package commandkit import ( "bytes" "context" "io" "reflect" "strings" "testing" ) func TestRunnableParentKeepsNonCommandTokensAsOperands(t *testing.T) { type input struct { words []string format Supplied[string] } var received input var entered, canonical string built, parentRuns, childRuns := 0, 0, 0 child := NewCommand(CommandSpec[struct{}]{Name: "child", Run: func(inv Invocation, _ struct{}) Outcome { childRuns++ entered, canonical = inv.EnteredPath(), inv.CanonicalPath() return Success() }}) parent := NewCommand(CommandSpec[input]{ Name: "run", Children: []Command{child}, NewInput: func() input { built++; return input{} }, Arguments: []Argument[input]{RepeatArgument(ArgumentMeta{Name: "ARG"}, 0, -1, func(value string) (string, error) { return value, nil }, func(target *input, value Supplied[[]string]) { target.words = value.Value })}, Flags: []Flag[input]{ValueFlag(FlagMeta{Name: "format", Short: "f"}, (*string)(nil), func(value string) (string, error) { return value, nil }, func(target *input, value Supplied[string]) { target.format = value })}, Run: func(inv Invocation, value input) Outcome { parentRuns++ received, entered, canonical = value, inv.EnteredPath(), inv.CanonicalPath() return Success() }, }) app, err := New(Identity{Name: "Demo"}, parent, Alias("r", "run"), FlagAliasFor("run", "--fmt", "--format")) if err != nil { t.Fatal(err) } for _, spelling := range []string{"run", "r"} { for _, operand := range []string{"", "-"} { t.Run(spelling+"/"+operand, func(t *testing.T) { beforeBuilt, beforeRuns := built, parentRuns args := []string{spelling, operand, "child", "--fmt=yaml"} var output, diagnostics bytes.Buffer outcome := app.Execute(context.Background(), args, Streams{Out: &output, Err: &diagnostics}) if outcome.Kind != OutcomeSuccess || built != beforeBuilt+1 || parentRuns != beforeRuns+1 || childRuns != 0 { t.Fatalf("outcome = %#v, builds/runs = %d/%d/%d, diagnostics %q", outcome, built, parentRuns, childRuns, diagnostics.String()) } if !reflect.DeepEqual(received.words, []string{operand, "child"}) || received.format != (Supplied[string]{Value: "yaml", Set: true}) || entered != spelling || canonical != "run" { t.Fatalf("parent input = %#v, paths = %q/%q", received, entered, canonical) } outcome = app.Execute(context.Background(), append(args, "--help"), Streams{Out: &output, Err: &diagnostics}) if outcome.Kind != OutcomeSuccess || built != beforeBuilt+1 || parentRuns != beforeRuns+1 || !strings.Contains(output.String(), "Demo "+spelling+" [ARG...]") || !strings.Contains(output.String(), "--fmt → --format") { t.Fatalf("parent help = %#v, output %q, diagnostics %q", outcome, output.String(), diagnostics.String()) } outcome = app.Execute(context.Background(), []string{spelling, "--", operand, "child", "--fmt=yaml"}, Streams{Out: io.Discard, Err: &diagnostics}) if outcome.Kind != OutcomeSuccess || !reflect.DeepEqual(received.words, []string{operand, "child", "--fmt=yaml"}) || received.format.Set { t.Fatalf("terminated parent invocation = %#v, input %#v, diagnostics %q", outcome, received, diagnostics.String()) } }) } } for _, spelling := range []string{"run", "r"} { beforeBuilt, beforeRuns, beforeChildren := built, parentRuns, childRuns outcome := app.Execute(context.Background(), []string{spelling, "child"}, Streams{Out: io.Discard, Err: io.Discard}) if outcome.Kind != OutcomeSuccess || built != beforeBuilt || parentRuns != beforeRuns || childRuns != beforeChildren+1 || entered != spelling+" child" || canonical != "run child" { t.Fatalf("genuine child selection = %#v, paths %q/%q, builds/runs %d/%d/%d", outcome, entered, canonical, built, parentRuns, childRuns) } } }