Reconcile reporting tests and boundaries

This commit is contained in:
2026-10-26 03:51:14 +00:00
parent 6264a87183
commit a6c0fb5b0d
7 changed files with 124 additions and 106 deletions

View File

@@ -32,7 +32,7 @@ func TestDebugEventsFollowModeAndFinalizationOrder(t *testing.T) {
for _, fragment := range []string{
"resolved command alias \"shortcut\" to \"run\"",
"selected debug reporting mode using --trace (alias for --debug)",
"INFO: deferred summary",
"deferred summary",
"handler completed for \"run\" with operational outcome and status 1",
"ERROR: operation failed",
} {
@@ -46,7 +46,7 @@ func TestDebugEventsFollowModeAndFinalizationOrder(t *testing.T) {
ordered := []string{
"resolved command alias",
"selected debug reporting mode",
"INFO: deferred summary",
"deferred summary",
"handler completed",
"ERROR: operation failed",
}

View File

@@ -1,6 +1,6 @@
# Invocation reporting implementation plan
Status: Stages 1–17 complete. Stage 18 is next.
Status: Stages 1–18 complete. Stage 19 is next.
## Objective and authority
@@ -676,7 +676,7 @@ ConfigKit, aliased quiet help, and offline PromptKit smoke commands also pass.
## Stage 18: Reconcile the full test suite and architectural boundaries
Status: pending.
Status: complete.
1. Find and migrate remaining callers of removed constructors, raw invocation
streams, terminal `SeverityError`, outcome diagnostic submission, and old
@@ -704,6 +704,19 @@ Validation: the suite should now build as a whole using the local three-module
workspace. Focused reporter race testing is justified by the selected concurrent
reporter contract; it does not establish a new permanent repository-wide gate.
Completion: migrated the remaining core tests and benchmark caller to reporter,
explicit explanation/cause, and presentation-result APIs. Updated reporting
expectations for unprefixed Info, routed flag-alias parsing, quiet selection on
routing failures, and causes remaining separate from terminal explanations.
Reporter tests cover delivery states, retained failures, ignored errors,
finalization, domain statuses, quiet late handles, independent healthy
destinations, complete concurrent operations, and caller-owned writers.
`GOTOOLCHAIN=local go test -count=1 ./...` passes in the root, ckconfig, and
ckprompt modules; `GOTOOLCHAIN=local go test -race -run '^TestReporter' .` passes.
Boundary checks confirm Cobra/pflag imports stay under `internal/cobra`, public
Go documentation exposes no backend or internal types, and core does not depend
on either optional integration module.
## Stage 19: Update canonical documentation and compatibility guidance
Status: pending.

View File

@@ -19,7 +19,7 @@ func TestExecuteRunsTypedCommandsWithFreshInvocationState(t *testing.T) {
}
var inputs []input
var contexts []context.Context
var streams []commandkit.Streams
var reporters []*commandkit.Reporter
firstContext := context.WithValue(context.Background(), struct{}{}, "first")
secondContext := context.WithValue(context.Background(), struct{}{}, "second")
var firstOut, secondOut, firstDiagnostic, secondDiagnostic bytes.Buffer
@@ -32,7 +32,12 @@ func TestExecuteRunsTypedCommandsWithFreshInvocationState(t *testing.T) {
Run: func(invocation commandkit.Invocation, value input) commandkit.Outcome {
inputs = append(inputs, value)
contexts = append(contexts, invocation.Context)
streams = append(streams, invocation.Streams)
reporter := invocation.Reporter()
reporters = append(reporters, reporter)
_ = reporter.RenderOutput(func(writer io.Writer) error {
_, err := io.WriteString(writer, value.name.Value+"\n")
return err
})
return commandkit.Success()
},
})
@@ -52,11 +57,11 @@ func TestExecuteRunsTypedCommandsWithFreshInvocationState(t *testing.T) {
if inputs[1].name.Set || inputs[1].name.Value != "" || inputs[1].ids.Set {
t.Fatalf("second invocation inherited state: %#v", inputs[1])
}
if streams[2].Out != io.Discard || streams[2].Err != io.Discard {
t.Fatalf("nil streams were not replaced with io.Discard: %#v", streams[2])
if reporters[0] == nil || reporters[1] == nil || reporters[2] == nil || reporters[0] == reporters[1] || reporters[0] == reporters[2] || reporters[1] == reporters[2] {
t.Fatalf("invocations did not receive fresh reporters: %#v", reporters)
}
if contexts[0] != firstContext || contexts[1] != secondContext || streams[0].Out != &firstOut || streams[0].Err != &firstDiagnostic || streams[1].Out != &secondOut || streams[1].Err != &secondDiagnostic {
t.Fatalf("handler invocation contexts or streams were not forwarded independently")
if firstOut.String() != "first\n" || secondOut.String() != "\n" || contexts[0] != firstContext || contexts[1] != secondContext {
t.Fatalf("handler invocation contexts or reporter destinations were not independent: out=%q/%q contexts=%v/%v", firstOut.String(), secondOut.String(), contexts[0], contexts[1])
}
}
@@ -65,7 +70,7 @@ func TestOmittedInputFactoryUsesExactPointerZeroValue(t *testing.T) {
Name: "run",
Run: func(_ commandkit.Invocation, value *int) commandkit.Outcome {
if value != nil {
return commandkit.Failure(errors.New("omitted input factory allocated a pointer"))
return commandkit.Failure("omitted input factory allocated a pointer")
}
return commandkit.Success()
},
@@ -136,9 +141,9 @@ func TestExecutePresentsHandlerUsageAfterRegisteringCommand(t *testing.T) {
Run: func(commandkit.Invocation, struct{}) commandkit.Outcome {
calls++
if err := app.Register(late); err != nil {
return commandkit.Failure(err)
return commandkit.Failure(err.Error()).WithCause(err)
}
return commandkit.Usage(usageCause)
return commandkit.Usage("The handler requests usage.").WithCause(usageCause)
},
})
app, err := commandkit.New(commandkit.Identity{Name: "example"}, command)
@@ -195,12 +200,11 @@ func TestExecuteReturnsUsageForParsingRequirementsAndInputChecks(t *testing.T) {
func TestExecutePreservesDomainOutcomesAndWarningDiagnostics(t *testing.T) {
domainCause := errors.New("domain rejected the request")
warning := commandkit.MessageDiagnostic(commandkit.SeverityWarning, "review needed")
command := commandkit.NewCommand(commandkit.CommandSpec[struct{}]{
Name: "run",
Run: func(commandkit.Invocation, struct{}) commandkit.Outcome {
outcome := commandkit.Domain("sample", 2, domainCause)
outcome.Diagnostics = []commandkit.Diagnostic{warning}
Run: func(invocation commandkit.Invocation, _ struct{}) commandkit.Outcome {
_ = invocation.Reporter().Warn("review needed")
outcome := commandkit.Domain("sample", 2).WithExplanation("The request was rejected.").WithCause(domainCause)
return outcome
},
})
@@ -209,19 +213,18 @@ func TestExecutePreservesDomainOutcomesAndWarningDiagnostics(t *testing.T) {
t.Fatal(err)
}
var diagnostic bytes.Buffer
outcome := app.Execute(context.Background(), []string{"run"}, commandkit.Streams{Err: &diagnostic})
outcome := app.Execute(context.Background(), []string{"run", "--verbose"}, commandkit.Streams{Err: &diagnostic})
if outcome.Kind != commandkit.OutcomeDomain || outcome.Domain != "sample" || outcome.Status != 2 || !errors.Is(outcome.Cause, domainCause) {
t.Fatalf("domain outcome changed: %#v", outcome)
}
if diagnostic.String() != "review needed\ndomain rejected the request\n" || outcome.DiagnosticDelivery != commandkit.DeliveryComplete {
if diagnostic.String() != "WARN: review needed\nERROR: The request was rejected.\n" || outcome.Presentation.Diagnostics.State != commandkit.DeliveryComplete {
t.Fatalf("warning delivery = %q, %#v", diagnostic.String(), outcome)
}
warningOnly := commandkit.NewCommand(commandkit.CommandSpec[struct{}]{
Name: "warn",
Run: func(commandkit.Invocation, struct{}) commandkit.Outcome {
result := commandkit.Success()
result.Diagnostics = []commandkit.Diagnostic{warning}
return result
Run: func(invocation commandkit.Invocation, _ struct{}) commandkit.Outcome {
_ = invocation.Reporter().Warn("review needed")
return commandkit.Success()
},
})
app, err = commandkit.New(commandkit.Identity{Name: "example"}, warningOnly)
@@ -229,23 +232,18 @@ func TestExecutePreservesDomainOutcomesAndWarningDiagnostics(t *testing.T) {
t.Fatal(err)
}
diagnostic.Reset()
outcome = app.Execute(context.Background(), []string{"warn"}, commandkit.Streams{Err: &diagnostic})
if outcome.Kind != commandkit.OutcomeSuccess || diagnostic.String() != "review needed\n" {
outcome = app.Execute(context.Background(), []string{"warn", "--verbose"}, commandkit.Streams{Err: &diagnostic})
if outcome.Kind != commandkit.OutcomeSuccess || diagnostic.String() != "WARN: review needed\n" {
t.Fatalf("warning-only outcome = %#v, diagnostics %q", outcome, diagnostic.String())
}
}
func TestExecuteDoesNotRepeatCompletedDiagnosticsOrPresentedCauses(t *testing.T) {
cause := errors.New("already shown")
func TestExecuteDoesNotPresentOperationCauseWithoutExplanation(t *testing.T) {
cause := errors.New("private operation detail")
command := commandkit.NewCommand(commandkit.CommandSpec[struct{}]{
Name: "run",
Run: func(commandkit.Invocation, struct{}) commandkit.Outcome {
outcome := commandkit.Domain("sample", 2, cause)
outcome.Diagnostics = []commandkit.Diagnostic{{Severity: commandkit.SeverityError, Write: func(writer io.Writer) error {
_, err := io.WriteString(writer, "presented\n")
return err
}}}
return outcome
return commandkit.Domain("sample", 2).WithCause(cause)
},
})
app, err := commandkit.New(commandkit.Identity{Name: "example"}, command)
@@ -254,21 +252,11 @@ func TestExecuteDoesNotRepeatCompletedDiagnosticsOrPresentedCauses(t *testing.T)
}
var diagnostic bytes.Buffer
outcome := app.Execute(context.Background(), []string{"run"}, commandkit.Streams{Err: &diagnostic})
if outcome.Kind != commandkit.OutcomeDomain || diagnostic.String() != "presented\n" {
t.Fatalf("presented cause was duplicated: %#v, %q", outcome, diagnostic.String())
if outcome.Kind != commandkit.OutcomeDomain || !errors.Is(outcome.Cause, cause) {
t.Fatalf("domain outcome changed: %#v", outcome)
}
diagnostic.Reset()
completed := commandkit.Domain("sample", 2, cause)
completed.DiagnosticDelivery = commandkit.DeliveryComplete
command = commandkit.NewCommand(commandkit.CommandSpec[struct{}]{Name: "done", Run: func(commandkit.Invocation, struct{}) commandkit.Outcome { return completed }})
app, err = commandkit.New(commandkit.Identity{Name: "example"}, command)
if err != nil {
t.Fatal(err)
}
outcome = app.Execute(context.Background(), []string{"done"}, commandkit.Streams{Err: &diagnostic})
if diagnostic.Len() != 0 || outcome.DiagnosticDelivery != commandkit.DeliveryComplete {
t.Fatalf("completed diagnostics were repeated: %#v, %q", outcome, diagnostic.String())
if strings.Contains(diagnostic.String(), cause.Error()) || !strings.Contains(diagnostic.String(), "ERROR: Command failed.") {
t.Fatalf("cause was presented instead of the fallback explanation: %#v, %q", outcome, diagnostic.String())
}
}
@@ -278,9 +266,7 @@ func TestExecuteRetainsOperationAndDiagnosticDeliveryFailures(t *testing.T) {
command := commandkit.NewCommand(commandkit.CommandSpec[struct{}]{
Name: "run",
Run: func(commandkit.Invocation, struct{}) commandkit.Outcome {
outcome := commandkit.Failure(operationFailure)
outcome.ReportIncomplete = true
return outcome
return commandkit.Failure("The operation was canceled.").WithCause(operationFailure)
},
})
app, err := commandkit.New(commandkit.Identity{Name: "example"}, command)
@@ -288,12 +274,12 @@ func TestExecuteRetainsOperationAndDiagnosticDeliveryFailures(t *testing.T) {
t.Fatal(err)
}
outcome := app.Execute(context.Background(), []string{"run"}, commandkit.Streams{Err: failingWriter{err: writeFailure}})
if outcome.Kind != commandkit.OutcomeOperational || outcome.Status != 1 || outcome.DiagnosticDelivery != commandkit.DeliveryIncomplete || !outcome.ReportIncomplete || !errors.Is(outcome.Cause, operationFailure) || !errors.Is(outcome.DeliveryError, writeFailure) {
if outcome.Kind != commandkit.OutcomeOperational || outcome.Status != 1 || outcome.Presentation.Diagnostics.State != commandkit.DeliveryIncomplete || !errors.Is(outcome.Cause, operationFailure) || !errors.Is(outcome.Presentation.Diagnostics.WriteError, writeFailure) {
t.Fatalf("combined operation and delivery failure = %#v", outcome)
}
short := app.Execute(context.Background(), []string{"run"}, commandkit.Streams{Err: shortWriter{}})
if !errors.Is(short.DeliveryError, io.ErrShortWrite) || short.DiagnosticDelivery != commandkit.DeliveryIncomplete {
if !errors.Is(short.Presentation.Diagnostics.WriteError, io.ErrShortWrite) || short.Presentation.Diagnostics.State != commandkit.DeliveryIncomplete {
t.Fatalf("short write was not detected: %#v", short)
}
}
@@ -339,7 +325,7 @@ func TestExecuteRunsApplicationOwnedNestedHelp(t *testing.T) {
)},
Run: func(_ commandkit.Invocation, value input) commandkit.Outcome {
received = append(received, value)
return commandkit.Domain("session-help", 7, domainCause)
return commandkit.Domain("session-help", 7).WithExplanation("Session help is unavailable.").WithCause(domainCause)
},
})
session := commandkit.Group("session", "Session commands.", help)
@@ -463,7 +449,7 @@ func TestHelpRendersRegisteredMetadataAndResolvesExactPaths(t *testing.T) {
t.Fatalf("version invoked an application command: %#v calls=%d/%d", versionOutcome, factoryCalls, runCalls)
}
failed := app.Execute(context.Background(), []string{"config", "show", "--help"}, commandkit.Streams{Out: shortWriter{}, Err: &diagnostic})
if failed.Kind != commandkit.OutcomeOperational || !failed.ReportIncomplete || !errors.Is(failed.DeliveryError, io.ErrShortWrite) {
if failed.Kind != commandkit.OutcomeOperational || failed.Presentation.Output.State != commandkit.DeliveryIncomplete || !errors.Is(failed.Presentation.Output.WriteError, io.ErrShortWrite) {
t.Fatalf("failed help delivery = %#v", failed)
}
out.Reset()
@@ -503,7 +489,7 @@ func TestVersionCommandFormatsApplicationAndCommandKitIdentity(t *testing.T) {
t.Fatalf("invalid version scope = %#v, output %q", invalid, out.String())
}
failed := app.Execute(context.Background(), []string{"version"}, commandkit.Streams{Out: shortWriter{}, Err: &diagnostic})
if failed.Kind != commandkit.OutcomeOperational || !failed.ReportIncomplete || !errors.Is(failed.DeliveryError, io.ErrShortWrite) {
if failed.Kind != commandkit.OutcomeOperational || failed.Presentation.Output.State != commandkit.DeliveryIncomplete || !errors.Is(failed.Presentation.Output.WriteError, io.ErrShortWrite) {
t.Fatalf("failed version delivery = %#v", failed)
}
}
@@ -532,11 +518,11 @@ func TestHelpEntryFormsPresentReportAndDiagnosticFailures(t *testing.T) {
for _, args := range [][]string{nil, {"notes"}, {"--help"}, {"-h"}, {"notes", "--help"}, {"help"}, {"help", "notes"}} {
var diagnostics bytes.Buffer
result := app.Execute(context.Background(), args, commandkit.Streams{Out: failingWriter{reportErr}, Err: &diagnostics})
if result.Kind != commandkit.OutcomeOperational || !result.ReportIncomplete || !errors.Is(result.Cause, reportErr) || !errors.Is(result.DeliveryError, reportErr) || result.DiagnosticDelivery != commandkit.DeliveryComplete || strings.Count(diagnostics.String(), reportErr.Error()) != 1 {
if result.Kind != commandkit.OutcomeOperational || result.Presentation.Output.State != commandkit.DeliveryIncomplete || !errors.Is(result.Presentation.Output.WriteError, reportErr) || result.Presentation.Diagnostics.State != commandkit.DeliveryComplete || !strings.Contains(diagnostics.String(), "ERROR: Command failed.") {
t.Fatalf("%v: result=%#v diagnostics=%q", args, result, diagnostics.String())
}
result = app.Execute(context.Background(), args, commandkit.Streams{Out: failingWriter{reportErr}, Err: failingWriter{diagnosticErr}})
if result.DiagnosticDelivery != commandkit.DeliveryIncomplete || !errors.Is(result.Cause, reportErr) || !errors.Is(result.DeliveryError, reportErr) || !errors.Is(result.DeliveryError, diagnosticErr) {
if result.Presentation.Output.State != commandkit.DeliveryIncomplete || !errors.Is(result.Presentation.Output.WriteError, reportErr) || result.Presentation.Diagnostics.State != commandkit.DeliveryIncomplete || !errors.Is(result.Presentation.Diagnostics.WriteError, diagnosticErr) {
t.Fatalf("%v: result=%#v", args, result)
}
}
@@ -560,46 +546,34 @@ func TestExecuteNormalizesOutcomeDefaultsAndPreservesDomainValues(t *testing.T)
if err != nil {
t.Fatal(err)
}
result := app.Execute(context.Background(), []string{"run"}, commandkit.Streams{})
wantDelivery := commandkit.DeliveryNone
if tc.cause != nil {
wantDelivery = commandkit.DeliveryComplete
}
if result.Kind != tc.kind || result.Status != tc.status || result.Domain != tc.domain || result.Cause != tc.cause || result.DiagnosticDelivery != wantDelivery {
var diagnostic bytes.Buffer
result := app.Execute(context.Background(), []string{"run"}, commandkit.Streams{Err: &diagnostic})
if result.Kind != tc.kind || result.Status != tc.status || result.Domain != tc.domain || result.Cause != tc.cause {
t.Fatalf("result=%#v expected=%#v", result, tc)
}
if tc.status != 0 && !strings.Contains(diagnostic.String(), "ERROR: Command failed.") {
t.Fatalf("failed outcome omitted its fallback explanation: %q", diagnostic.String())
}
if tc.cause != nil && strings.Contains(diagnostic.String(), tc.cause.Error()) {
t.Fatalf("operation cause was presented automatically: %q", diagnostic.String())
}
}
}
func TestNilDiagnosticWritersHaveNoOutputAndDoNotHideCauses(t *testing.T) {
func TestNilDiagnosticWriterDoesNotHideOperationCause(t *testing.T) {
cause := errors.New("operation failed")
for _, base := range []commandkit.Outcome{commandkit.Success(), commandkit.Domain("needs action", 2, nil), commandkit.Failure(cause)} {
base.Diagnostics = []commandkit.Diagnostic{{}, {Severity: commandkit.SeverityWarning}, {Severity: commandkit.SeverityError}}
command := commandkit.NewCommand(commandkit.CommandSpec[struct{}]{Name: "run", Run: func(commandkit.Invocation, struct{}) commandkit.Outcome { return base }})
app, err := commandkit.New(commandkit.Identity{Name: "example"}, command)
if err != nil {
t.Fatal(err)
}
var diagnostics bytes.Buffer
result := app.Execute(context.Background(), []string{"run"}, commandkit.Streams{Err: &diagnostics})
want := commandkit.DeliveryNone
if base.Cause != nil {
want = commandkit.DeliveryComplete
}
if result.Kind != base.Kind || result.Status != base.Status || result.Domain != base.Domain || result.Cause != base.Cause || result.DeliveryError != nil || result.DiagnosticDelivery != want {
t.Fatalf("result=%#v base=%#v", result, base)
}
if base.Cause == nil && diagnostics.Len() != 0 {
t.Fatalf("unexpected output=%q", diagnostics.String())
}
if base.Cause != nil && strings.Count(diagnostics.String(), cause.Error()) != 1 {
t.Fatalf("cause output=%q", diagnostics.String())
}
if base.Cause != nil {
result = app.Execute(context.Background(), []string{"run"}, commandkit.Streams{Err: failingWriter{io.ErrClosedPipe}})
if !errors.Is(result.Cause, cause) || !errors.Is(result.DeliveryError, io.ErrClosedPipe) || result.DiagnosticDelivery != commandkit.DeliveryIncomplete {
t.Fatalf("failed delivery=%#v", result)
}
}
command := commandkit.NewCommand(commandkit.CommandSpec[struct{}]{
Name: "run",
Run: func(commandkit.Invocation, struct{}) commandkit.Outcome {
return commandkit.Failure("The operation failed.").WithCause(cause)
},
})
app, err := commandkit.New(commandkit.Identity{Name: "example"}, command)
if err != nil {
t.Fatal(err)
}
result := app.Execute(context.Background(), []string{"run"}, commandkit.Streams{})
if result.Kind != commandkit.OutcomeOperational || !errors.Is(result.Cause, cause) || result.Presentation.Diagnostics.State != commandkit.DeliveryComplete {
t.Fatalf("nil diagnostic destination changed the outcome: %#v", result)
}
}

View File

@@ -21,6 +21,12 @@ func newReportingParseApp(t *testing.T) *App {
return app
}
func parseReportingArgs(app *App, args []string) commandmeta.Result {
route := resolveEnteredRoute(args, app.routeSnapshot())
current := &execution{app: app, route: route}
return current.parse(route.args)
}
func TestReportingModeReductionFollowsFlagOccurrenceOrder(t *testing.T) {
for _, test := range []struct {
name string
@@ -37,7 +43,7 @@ func TestReportingModeReductionFollowsFlagOccurrenceOrder(t *testing.T) {
{"disabled quiet leaves verbose", []string{"run", "--verbose", "--quiet=false"}, commandmeta.ReportingModeVerbose},
} {
t.Run(test.name, func(t *testing.T) {
result := newReportingParseApp(t).parse(test.args, commandmeta.ParseOptions{})
result := parseReportingArgs(newReportingParseApp(t), test.args)
if result.Failure != nil || result.Reporting.Mode != test.mode {
t.Fatalf("parse result = %#v, want mode %d", result, test.mode)
}
@@ -60,7 +66,7 @@ func TestProgressSelectionIsIndependentAndUsesLastOccurrence(t *testing.T) {
{"unset by default", []string{"run", "--verbose"}, commandmeta.ReportingModeVerbose, false, false},
} {
t.Run(test.name, func(t *testing.T) {
result := newReportingParseApp(t).parse(test.args, commandmeta.ParseOptions{})
result := parseReportingArgs(newReportingParseApp(t), test.args)
selection := result.Reporting
if result.Failure != nil || selection.Mode != test.mode || selection.Progress != test.progress || selection.ProgressSet != test.progressSet {
t.Fatalf("parse selection = %#v, failure = %v", selection, result.Failure)
@@ -71,9 +77,9 @@ func TestProgressSelectionIsIndependentAndUsesLastOccurrence(t *testing.T) {
func TestReportingParseRetainsAliasesAndConsumedPrefix(t *testing.T) {
app := newReportingParseApp(t)
result := app.parse([]string{"run", "--verbose", "-q"}, commandmeta.ParseOptions{})
result := parseReportingArgs(app, []string{"run", "--verbose", "-q"})
if result.Failure != nil || result.Reporting.Mode != commandmeta.ReportingModeQuiet {
t.Fatalf("aliased selection = %#v", result)
t.Fatalf("aliased selection = %#v, failure=%+v", result, result.Failure)
}
want := []commandmeta.FlagOccurrence{
{Name: "verbose", Spelling: "--verbose", RawValue: "true"},
@@ -83,12 +89,12 @@ func TestReportingParseRetainsAliasesAndConsumedPrefix(t *testing.T) {
t.Fatalf("occurrences = %#v, want %#v", result.FlagOccurrences, want)
}
result = app.parse([]string{"run", "--verbose", "--unknown", "--quiet"}, commandmeta.ParseOptions{})
result = parseReportingArgs(app, []string{"run", "--verbose", "--unknown", "--quiet"})
if result.Failure == nil || result.Reporting.Mode != commandmeta.ReportingModeVerbose || len(result.FlagOccurrences) != 1 {
t.Fatalf("unknown flag partial selection = %#v", result)
}
result = app.parse([]string{"run", "--verbose", "--quiet=invalid", "--debug"}, commandmeta.ParseOptions{})
result = parseReportingArgs(app, []string{"run", "--verbose", "--quiet=invalid", "--debug"})
if result.Failure == nil || !result.Failure.Usage || result.Reporting.Mode != commandmeta.ReportingModeVerbose || len(result.FlagOccurrences) != 2 {
t.Fatalf("invalid boolean partial selection = %#v", result)
}
@@ -96,7 +102,7 @@ func TestReportingParseRetainsAliasesAndConsumedPrefix(t *testing.T) {
func TestReportingParsePreservesValuesHelpAndTerminator(t *testing.T) {
app := newReportingParseApp(t)
result := app.parse([]string{"run", "--message=--quiet", "--verbose", "--", "--debug"}, commandmeta.ParseOptions{})
result := parseReportingArgs(app, []string{"run", "--message=--quiet", "--verbose", "--", "--debug"})
if result.Failure != nil || result.Reporting.Mode != commandmeta.ReportingModeVerbose || !reflect.DeepEqual(result.Operands, []string{"--debug"}) {
t.Fatalf("option-looking value or terminator = %#v", result)
}
@@ -104,17 +110,17 @@ func TestReportingParsePreservesValuesHelpAndTerminator(t *testing.T) {
t.Fatalf("reporting-like value became a flag occurrence: %#v", result.FlagOccurrences)
}
result = app.parse([]string{"run", "--help=bad", "--quiet"}, commandmeta.ParseOptions{})
result = parseReportingArgs(app, []string{"run", "--help=bad", "--quiet"})
if result.Failure == nil || result.Reporting.Mode != commandmeta.ReportingModeNormal || len(result.FlagOccurrences) != 1 || result.FlagOccurrences[0].Name != "help" {
t.Fatalf("invalid help boolean consumed later flags: %#v", result)
}
result = app.parse([]string{"run", "--help=false", "--quiet"}, commandmeta.ParseOptions{})
result = parseReportingArgs(app, []string{"run", "--help=false", "--quiet"})
if result.Failure != nil || result.HelpRequested || result.Reporting.Mode != commandmeta.ReportingModeQuiet {
t.Fatalf("valid false help boolean changed handling: %#v", result)
}
result = app.parse([]string{"missing", "--quiet"}, commandmeta.ParseOptions{})
if result.Failure == nil || len(result.FlagOccurrences) != 0 {
t.Fatalf("malformed path produced flag occurrences: %#v", result)
result = parseReportingArgs(app, []string{"missing", "--quiet"})
if result.Failure == nil || result.Reporting.Mode != commandmeta.ReportingModeQuiet || len(result.FlagOccurrences) != 1 || result.FlagOccurrences[0].Name != "quiet" {
t.Fatalf("routing failure did not retain reporting selection: %#v", result)
}
}

View File

@@ -124,7 +124,7 @@ func performanceFlatCommands(tb testing.TB, count int, prefix string) []commandk
run := func(commandkit.Invocation, struct{}) commandkit.Outcome { return commandkit.Success() }
if prefix == "cmd" && index == 4 {
run = func(commandkit.Invocation, struct{}) commandkit.Outcome {
return commandkit.Usage(performanceUsageCause)
return commandkit.Usage("The benchmark command needs usage.").WithCause(performanceUsageCause)
}
}
command := commandkit.NewCommand(commandkit.CommandSpec[struct{}]{Name: name, Summary: "Benchmark command.", Flags: flags, Run: run})

View File

@@ -49,6 +49,31 @@ func TestReporterOutputSubmitsOnWrite(t *testing.T) {
}
}
type callerOwnedWriter struct {
bytes.Buffer
closeCalls int
}
func (w *callerOwnedWriter) Close() error {
w.closeCalls++
return nil
}
func TestReporterLeavesCallerWriterOpen(t *testing.T) {
destination := &callerOwnedWriter{}
reporter := newReporter(Streams{Out: destination})
_, _ = reporter.Output().Write([]byte("report"))
_ = reporter.RenderOutput(func(writer io.Writer) error {
_, err := io.WriteString(writer, " rendered")
return err
})
_ = reporter.finalize(Success(), nil)
if destination.String() != "report rendered" || destination.closeCalls != 0 {
t.Fatalf("destination = %q, Close calls = %d", destination.String(), destination.closeCalls)
}
}
func TestReporterQuietOutputDiscardsSuccessfully(t *testing.T) {
var destination bytes.Buffer
reporter := newReporter(Streams{Out: &destination})

View File

@@ -1020,7 +1020,7 @@ func TestAliasHelpTargetsGroupsAndDiscoveryViews(t *testing.T) {
var diagnostics bytes.Buffer
outcome := app.Execute(context.Background(), []string{"list"}, Streams{Out: io.Discard, Err: &diagnostics})
if outcome.Kind != OutcomeUsage || !strings.Contains(outcome.Cause.Error(), "--help") {
if outcome.Kind != OutcomeUsage || !strings.Contains(outcome.Explanation.Message, "--help") {
t.Fatalf("bare discovery prefix = %#v, diagnostics %q; want usage pointing to list --help", outcome, diagnostics.String())
}
if inputBuilds != 0 || promptRuns != 0 {