package commandkit_test import ( "context" "errors" "io" "sync" "testing" commandkit "go.maximumdirect.net/commandkit" ) func TestInvocationReportingMode(t *testing.T) { if got := (commandkit.Invocation{}).ReportingMode(); got != commandkit.ReportingModeOrdinary { t.Fatalf("zero invocation mode = %v", got) } var retained commandkit.Invocation var observed commandkit.ReportingMode command := commandkit.NewCommand(commandkit.CommandSpec[struct{}]{Name: "run", Run: func(inv commandkit.Invocation, _ struct{}) commandkit.Outcome { retained = inv observed = inv.ReportingMode() return commandkit.Success() }}) app, err := commandkit.New(commandkit.Identity{Name: "example"}, command) if err != nil { t.Fatal(err) } for _, tc := range []struct { flag string want commandkit.ReportingMode }{{"", commandkit.ReportingModeOrdinary}, {"--quiet", commandkit.ReportingModeQuiet}, {"--verbose", commandkit.ReportingModeVerbose}, {"--debug", commandkit.ReportingModeDebug}, {"", commandkit.ReportingModeOrdinary}} { args := []string{"run"} if tc.flag != "" { args = append(args, tc.flag) } result := app.Execute(context.Background(), args, commandkit.Streams{Out: io.Discard, Err: io.Discard}) if result.Kind != commandkit.OutcomeSuccess || observed != tc.want || retained.ReportingMode() != tc.want { t.Fatalf("%q: outcome=%#v mode=%v", tc.flag, result, retained.ReportingMode()) } } } func TestInvocationReportingModeConcurrentReads(t *testing.T) { command := commandkit.NewCommand(commandkit.CommandSpec[struct{}]{Name: "run", Run: func(inv commandkit.Invocation, _ struct{}) commandkit.Outcome { start := make(chan struct{}) var wg sync.WaitGroup for i := 0; i < 4; i++ { wg.Add(1) go func() { defer wg.Done() <-start for j := 0; j < 100; j++ { if inv.ReportingMode() != commandkit.ReportingModeDebug { t.Error("mode changed") } _ = inv.Reporter().Debug("diagnostic") } }() } close(start) wg.Wait() return commandkit.Success() }}) app, err := commandkit.New(commandkit.Identity{Name: "example"}, command) if err != nil { t.Fatal(err) } if outcome := app.Execute(context.Background(), []string{"run", "--debug"}, commandkit.Streams{Out: io.Discard, Err: io.Discard}); outcome.Kind != commandkit.OutcomeSuccess { t.Fatal(outcome) } } func TestSharedDebugBindingAndReportingMode(t *testing.T) { type input struct { debug commandkit.Supplied[bool] before, after commandkit.Supplied[string] operand commandkit.Supplied[string] } var received input var mode commandkit.ReportingMode factories, calls := 0, 0 parse := func(s string) (string, error) { return s, nil } cmd := commandkit.NewCommand(commandkit.CommandSpec[input]{Name: "run", NewInput: func() input { factories++; return input{} }, Flags: []commandkit.Flag[input]{ commandkit.ValueFlag(commandkit.FlagMeta{Name: "before"}, (*string)(nil), parse, func(i *input, v commandkit.Supplied[string]) { i.before = v }), commandkit.BoolFlag(commandkit.FlagMeta{Name: "debug", Short: "d"}, false, func(i *input, v commandkit.Supplied[bool]) { i.debug = v }), commandkit.ValueFlag(commandkit.FlagMeta{Name: "after"}, (*string)(nil), parse, func(i *input, v commandkit.Supplied[string]) { i.after = v }), }, Arguments: []commandkit.Argument[input]{commandkit.ValueArgument(commandkit.ArgumentMeta{Name: "operand"}, false, parse, func(i *input, v commandkit.Supplied[string]) { i.operand = v })}, Run: func(inv commandkit.Invocation, i input) commandkit.Outcome { calls++ received = i mode = inv.ReportingMode() return commandkit.Success() }}) app, err := commandkit.New(commandkit.Identity{Name: "example"}, cmd, commandkit.FlagAlias("-x", "--debug")) if err != nil { t.Fatal(err) } for _, tc := range []struct { args []string value, set bool mode commandkit.ReportingMode }{ {nil, false, false, commandkit.ReportingModeOrdinary}, {[]string{"--debug"}, true, true, commandkit.ReportingModeDebug}, {[]string{"--debug", "--quiet"}, true, true, commandkit.ReportingModeQuiet}, {[]string{"--quiet", "--debug"}, true, true, commandkit.ReportingModeDebug}, {[]string{"--debug", "--verbose"}, true, true, commandkit.ReportingModeVerbose}, {[]string{"--debug", "--debug=false"}, false, true, commandkit.ReportingModeOrdinary}, {[]string{"--verbose", "--debug", "--debug=false"}, false, true, commandkit.ReportingModeOrdinary}, {[]string{"--debug", "--quiet=false"}, true, true, commandkit.ReportingModeDebug}, {[]string{"--debug", "--progress=false"}, true, true, commandkit.ReportingModeDebug}, {[]string{"-x"}, true, true, commandkit.ReportingModeDebug}, {[]string{"-d=false"}, false, true, commandkit.ReportingModeOrdinary}, } { args := append([]string{"run", "--before=one", "--after=two"}, tc.args...) got := app.Execute(context.Background(), args, commandkit.Streams{Out: io.Discard, Err: io.Discard}) if got.Kind != commandkit.OutcomeSuccess || received.debug.Value != tc.value || received.debug.Set != tc.set || mode != tc.mode || received.before.Value != "one" || received.after.Value != "two" { t.Fatalf("%v: result=%#v input=%#v mode=%v", tc.args, got, received, mode) } } for _, value := range []string{"--debug=", "--debug=invalid"} { beforeFactories, beforeCalls := factories, calls got := app.Execute(context.Background(), []string{"run", value}, commandkit.Streams{Out: io.Discard, Err: io.Discard}) if got.Kind != commandkit.OutcomeUsage || factories != beforeFactories || calls != beforeCalls { t.Fatalf("%s result=%#v factories=%d calls=%d", value, got, factories, calls) } } got := app.Execute(context.Background(), []string{"run", "--before", "--debug"}, commandkit.Streams{Out: io.Discard, Err: io.Discard}) if got.Kind != commandkit.OutcomeSuccess || received.before.Value != "--debug" || received.debug.Set || mode != commandkit.ReportingModeOrdinary { t.Fatalf("option-looking value: %#v %#v mode=%v", got, received, mode) } got = app.Execute(context.Background(), []string{"run", "--", "--debug"}, commandkit.Streams{Out: io.Discard, Err: io.Discard}) if got.Kind != commandkit.OutcomeSuccess || received.operand.Value != "--debug" || received.debug.Set || mode != commandkit.ReportingModeOrdinary { t.Fatalf("operand after terminator: %#v %#v mode=%v", got, received, mode) } } func TestSharedDebugCapturedDefaultsAndRequiredness(t *testing.T) { type inner struct{ debug commandkit.Supplied[bool] } type outer struct{ flags commandkit.FlagValues[inner] } for _, required := range []bool{false, true} { flag := commandkit.BoolFlag(commandkit.FlagMeta{Name: "debug", Short: "d", Required: required}, true, func(i *inner, v commandkit.Supplied[bool]) { i.debug = v }) captured := commandkit.CaptureFlags([]commandkit.Flag[inner]{flag}, func(i *outer) *commandkit.FlagValues[inner] { return &i.flags }) var applied, supplied inner var applyErr error var mode commandkit.ReportingMode cmd := commandkit.NewCommand(commandkit.CommandSpec[outer]{Name: "run", Flags: captured, Run: func(inv commandkit.Invocation, i outer) commandkit.Outcome { mode = inv.ReportingMode() applied = inner{} supplied = inner{debug: commandkit.Supplied[bool]{Value: true, Set: true}} applyErr = i.flags.ApplyTo(&applied, flag) if err := i.flags.ApplySuppliedTo(&supplied, flag); err != nil && applyErr == nil { applyErr = err } return commandkit.Success() }}) app, err := commandkit.New(commandkit.Identity{Name: "example"}, cmd, commandkit.FlagAliasFor("run", "-x", "-d")) if err != nil { t.Fatal(err) } result := app.Execute(context.Background(), []string{"run"}, commandkit.Streams{}) if result.Kind != commandkit.OutcomeSuccess || mode != commandkit.ReportingModeOrdinary { t.Fatalf("omission=%#v mode=%v", result, mode) } if required { if !errors.Is(applyErr, commandkit.ErrFlagInput) { t.Fatalf("missing required: %v", applyErr) } } else if applyErr != nil || !applied.debug.Value || applied.debug.Set || !supplied.debug.Value || !supplied.debug.Set { t.Fatalf("defaults=%#v %#v %v", applied, supplied, applyErr) } result = app.Execute(context.Background(), []string{"run", "-x=false"}, commandkit.Streams{}) if result.Kind != commandkit.OutcomeSuccess || applyErr != nil || applied.debug.Value || !applied.debug.Set || supplied.debug.Value || !supplied.debug.Set || mode != commandkit.ReportingModeOrdinary { t.Fatalf("false=%#v %#v %#v mode=%v err=%v", result, applied, supplied, mode, applyErr) } } } func TestSharedDebugDirectRequiredAndRenamedBinding(t *testing.T) { type input struct{ debug commandkit.Supplied[bool] } var got input var mode commandkit.ReportingMode original := commandkit.BoolFlag(commandkit.FlagMeta{Name: "debug", Required: true}, true, func(i *input, v commandkit.Supplied[bool]) { i.debug = v }) required := commandkit.NewCommand(commandkit.CommandSpec[input]{Name: "required", Flags: []commandkit.Flag[input]{original}, Run: func(inv commandkit.Invocation, i input) commandkit.Outcome { got = i mode = inv.ReportingMode() return commandkit.Success() }}) renamed := commandkit.NewCommand(commandkit.CommandSpec[input]{Name: "renamed", Flags: []commandkit.Flag[input]{commandkit.RenameFlag(original, "trace")}, Run: func(inv commandkit.Invocation, i input) commandkit.Outcome { got = i mode = inv.ReportingMode() return commandkit.Success() }}) ordinary := commandkit.BoolFlag(commandkit.FlagMeta{Name: "local"}, false, func(i *input, v commandkit.Supplied[bool]) { i.debug = v }) attached := commandkit.NewCommand(commandkit.CommandSpec[input]{Name: "attached", Flags: []commandkit.Flag[input]{commandkit.RenameFlag(ordinary, "debug")}, Run: func(inv commandkit.Invocation, i input) commandkit.Outcome { got = i mode = inv.ReportingMode() return commandkit.Success() }}) app, err := commandkit.New(commandkit.Identity{Name: "example"}, required, renamed, attached) if err != nil { t.Fatal(err) } if r := app.Execute(context.Background(), []string{"required"}, commandkit.Streams{}); r.Kind != commandkit.OutcomeUsage { t.Fatalf("required omission=%#v", r) } if r := app.Execute(context.Background(), []string{"required", "--debug=false"}, commandkit.Streams{}); r.Kind != commandkit.OutcomeSuccess || got.debug.Value || !got.debug.Set || mode != commandkit.ReportingModeOrdinary { t.Fatalf("explicit false=%#v %#v", r, got) } if r := app.Execute(context.Background(), []string{"renamed", "--trace=false", "--debug"}, commandkit.Streams{}); r.Kind != commandkit.OutcomeSuccess || got.debug.Value || !got.debug.Set || mode != commandkit.ReportingModeDebug { t.Fatalf("renamed=%#v %#v mode=%v", r, got, mode) } if r := app.Execute(context.Background(), []string{"attached", "--debug"}, commandkit.Streams{}); r.Kind != commandkit.OutcomeSuccess || !got.debug.Value || !got.debug.Set || mode != commandkit.ReportingModeDebug { t.Fatalf("renamed to debug=%#v %#v mode=%v", r, got, mode) } }