Present help failures through the common diagnostic path

This commit is contained in:
2026-10-07 16:47:55 +00:00
parent 4cb795cc20
commit 3ec30d5eb8
3 changed files with 29 additions and 2 deletions

View File

@@ -54,6 +54,9 @@ architecture. The issues below affect behavior within the implemented structure.
- R-03 complete: standard inspection handlers check cancellation before runtime - R-03 complete: standard inspection handlers check cancellation before runtime
construction/acquisition, after acquisition, and before prepared report delivery. construction/acquisition, after acquisition, and before prepared report delivery.
- R-04 complete: every help entry form finalizes report failures through the
common diagnostic presentation path once.
## Findings and priority ## Findings and priority
P1 means a supported invocation is blocked or its report content is corrupted. P1 means a supported invocation is blocked or its report content is corrupted.

View File

@@ -29,13 +29,13 @@ func (a *App) Execute(ctx context.Context, args []string, streams Streams) Outco
} }
command, ok := a.commandAt(parsed.Path) command, ok := a.commandAt(parsed.Path)
if !ok || command.payload == nil { if !ok || command.payload == nil {
return a.renderHelpOutcome(parsed.Path, streams) return a.present(a.renderHelpOutcome(parsed.Path, streams), parsed.Path, streams)
} }
if parsed.HelpRequested || !command.payload.hasRun() { if parsed.HelpRequested || !command.payload.hasRun() {
if _, err := command.payload.prepare(parsed.FlagValues, parsed.Operands, false); err != nil { if _, err := command.payload.prepare(parsed.FlagValues, parsed.Operands, false); err != nil {
return a.present(Usage(err), parsed.Path, streams) return a.present(Usage(err), parsed.Path, streams)
} }
return a.renderHelpOutcome(parsed.Path, streams) return a.present(a.renderHelpOutcome(parsed.Path, streams), parsed.Path, streams)
} }
buildInput, err := command.payload.prepare(parsed.FlagValues, parsed.Operands, true) buildInput, err := command.payload.prepare(parsed.FlagValues, parsed.Operands, true)
if err != nil { if err != nil {

View File

@@ -413,3 +413,27 @@ func (shortWriter) Write(data []byte) (int, error) {
} }
return len(data) - 1, nil return len(data) - 1, nil
} }
func TestHelpEntryFormsPresentReportAndDiagnosticFailures(t *testing.T) {
group, err := commandkit.Group("notes", "Notes metadata.")
if err != nil {
t.Fatal(err)
}
app, err := commandkit.New(commandkit.Identity{Name: "example"}, group)
if err != nil {
t.Fatal(err)
}
reportErr := errors.New("report failed")
diagnosticErr := errors.New("diagnostic failed")
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 {
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) {
t.Fatalf("%v: result=%#v", args, result)
}
}
}