package commandkit import ( "bytes" "context" "errors" "fmt" "io" "strings" "testing" ) func TestExecuteGivesEachInvocationItsReporterAndAppliesSelection(t *testing.T) { var reporters []*Reporter command := NewCommand(CommandSpec[struct{}]{ Name: "run", Run: func(inv Invocation, _ struct{}) Outcome { reporter := inv.Reporter() reporters = append(reporters, reporter) if _, err := fmt.Fprint(reporter.Output(), "result\n"); err != nil { t.Errorf("write result: %v", err) } if err := reporter.Warn("attention"); err != nil { t.Errorf("submit warning: %v", err) } return Outcome{ Kind: OutcomeDomain, Domain: "accepted", Status: 0, Presentation: PresentationResult{ Output: DeliveryResult{State: DeliveryIncomplete, WriteError: io.ErrClosedPipe}, }, } }, }) app, err := New(Identity{Name: "Example"}, command) if err != nil { t.Fatal(err) } var quietOut, quietErr bytes.Buffer quiet := app.Execute(context.Background(), []string{"run", "--quiet"}, Streams{Out: &quietOut, Err: &quietErr}) if quiet.Kind != OutcomeDomain || quiet.Status != 0 || quiet.Domain != "accepted" { t.Fatalf("quiet outcome = %#v", quiet) } if quietOut.Len() != 0 || quietErr.Len() != 0 { t.Fatalf("quiet output = %q, diagnostics = %q", quietOut.String(), quietErr.String()) } if quiet.Presentation.Output.State != DeliverySuppressed || quiet.Presentation.Diagnostics.State != DeliverySuppressed { t.Fatalf("quiet presentation = %#v", quiet.Presentation) } var verboseOut, verboseErr bytes.Buffer verbose := app.Execute(context.Background(), []string{"run", "--verbose"}, Streams{Out: &verboseOut, Err: &verboseErr}) if verbose.Kind != OutcomeDomain || verbose.Status != 0 { t.Fatalf("verbose outcome = %#v", verbose) } if verboseOut.String() != "result\n" || verboseErr.String() != "WARN: attention\n" { t.Fatalf("verbose output = %q, diagnostics = %q", verboseOut.String(), verboseErr.String()) } if verbose.Presentation.Output.State != DeliveryComplete || verbose.Presentation.Diagnostics.State != DeliveryComplete { t.Fatalf("verbose presentation = %#v", verbose.Presentation) } if len(reporters) != 2 || reporters[0] == nil || reporters[1] == nil || reporters[0] == reporters[1] { t.Fatalf("invocation reporters = %#v", reporters) } if _, err := reporters[0].Output().Write([]byte("late")); !errors.Is(err, ErrReporterClosed) { t.Fatalf("write through finalized reporter error = %v", err) } } func TestExecuteQuietUsageKeepsAliasGuidanceVisible(t *testing.T) { command := NewCommand(CommandSpec[struct{}]{ Name: "run", Run: func(Invocation, struct{}) Outcome { return Success() }, }) app, err := New(Identity{Name: "Example"}, command, Alias("r", "run")) if err != nil { t.Fatal(err) } var output, diagnostics bytes.Buffer outcome := app.Execute(context.Background(), []string{"r", "--quiet", "--unknown"}, Streams{Out: &output, Err: &diagnostics}) if outcome.Kind != OutcomeUsage || outcome.Status != 2 { t.Fatalf("outcome = %#v", outcome) } if output.Len() != 0 { t.Fatalf("usage output = %q", output.String()) } if got := diagnostics.String(); !strings.Contains(got, "ERROR: unknown flag") || !strings.Contains(got, "Usage: Example r") || !strings.Contains(got, "Alias for: Example run") { t.Fatalf("usage diagnostics = %q", got) } if outcome.Presentation.Diagnostics.State != DeliveryComplete { t.Fatalf("diagnostic presentation = %#v", outcome.Presentation.Diagnostics) } } type executeReportingFailureWriter struct{ err error } func (w executeReportingFailureWriter) Write([]byte) (int, error) { return 0, w.err } func TestExecuteFinalizesEarlyAndIgnoredWriterFailures(t *testing.T) { command := NewCommand(CommandSpec[struct{}]{ Name: "run", Run: func(inv Invocation, _ struct{}) Outcome { _, _ = inv.Reporter().Output().Write([]byte("report")) return Success() }, }) app, err := New(Identity{Name: "Example"}, command) if err != nil { t.Fatal(err) } writeErr := errors.New("result destination failed") var diagnostics bytes.Buffer outcome := app.Execute(context.Background(), []string{"run"}, Streams{ Out: executeReportingFailureWriter{err: writeErr}, Err: &diagnostics, }) if outcome.Kind != OutcomeOperational || outcome.Status != 1 || !errors.Is(outcome.Presentation.Output.WriteError, writeErr) { t.Fatalf("ignored output failure outcome = %#v", outcome) } if !strings.Contains(diagnostics.String(), "deliver") || !strings.Contains(diagnostics.String(), "output") { t.Fatalf("fallback diagnostic = %q", diagnostics.String()) } diagnostics.Reset() outcome = app.Execute(nil, []string{"run"}, Streams{Err: &diagnostics}) if outcome.Kind != OutcomeOperational || outcome.Status != 1 || outcome.Cause == nil { t.Fatalf("nil context outcome = %#v", outcome) } if !strings.Contains(diagnostics.String(), "ERROR: invocation context must not be nil\n") { t.Fatalf("nil context diagnostic = %q", diagnostics.String()) } diagnostics.Reset() var nilApp *App outcome = nilApp.Execute(context.Background(), []string{"run"}, Streams{Err: &diagnostics}) if outcome.Kind != OutcomeOperational || outcome.Status != 1 || outcome.Cause == nil { t.Fatalf("nil app outcome = %#v", outcome) } if !strings.Contains(diagnostics.String(), "ERROR: cannot execute commands on a nil app\n") { t.Fatalf("nil app diagnostic = %q", diagnostics.String()) } }