Retain validation report delivery errors in outcomes
This commit is contained in:
@@ -639,7 +639,7 @@ func TestConfigValidationYAMLWarningsAndShortWrites(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
outcome = app.Execute(context.Background(), []string{"config", "validate"}, commandkit.Streams{Out: configKitPartialWriter{}, Err: io.Discard})
|
outcome = app.Execute(context.Background(), []string{"config", "validate"}, commandkit.Streams{Out: configKitPartialWriter{}, Err: io.Discard})
|
||||||
if outcome.Kind != commandkit.OutcomeOperational || !outcome.ReportIncomplete || !errors.Is(outcome.Cause, io.ErrShortWrite) {
|
if outcome.Kind != commandkit.OutcomeOperational || !outcome.ReportIncomplete || !errors.Is(outcome.Cause, io.ErrShortWrite) || !errors.Is(outcome.DeliveryError, io.ErrShortWrite) {
|
||||||
t.Fatalf("report short write outcome = %#v", outcome)
|
t.Fatalf("report short write outcome = %#v", outcome)
|
||||||
}
|
}
|
||||||
var delivered bytes.Buffer
|
var delivered bytes.Buffer
|
||||||
@@ -657,3 +657,64 @@ func (configKitPartialWriter) Write(data []byte) (int, error) {
|
|||||||
}
|
}
|
||||||
return len(data) - 1, nil
|
return len(data) - 1, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestValidationReportDeliveryErrorsAtEachWriteBoundary(t *testing.T) {
|
||||||
|
reportErr := errors.New("report failed")
|
||||||
|
diagnosticErr := errors.New("diagnostic failed")
|
||||||
|
app, _ := commandkit.New(commandkit.Identity{Name: "example"})
|
||||||
|
acquire := func(context.Context, commandkit.ConfigRequest, struct{}) (commandkit.Acquired[commandkit.ConfigView], error) {
|
||||||
|
return commandkit.Acquired[commandkit.ConfigView]{}, nil
|
||||||
|
}
|
||||||
|
if err := commandkit.RegisterConfig(app, commandkit.ConfigSuite[struct{}]{Scopes: []commandkit.ConfigScope[struct{}]{
|
||||||
|
{Name: "app", Operations: []commandkit.ConfigOperation{commandkit.ConfigValidate}, Acquire: acquire},
|
||||||
|
{Name: "promptkit", Operations: []commandkit.ConfigOperation{commandkit.ConfigValidate}, Acquire: acquire},
|
||||||
|
}}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, format := range []string{"text", "yaml"} {
|
||||||
|
writes := 2
|
||||||
|
if format == "yaml" {
|
||||||
|
writes = 3
|
||||||
|
}
|
||||||
|
for at := 1; at <= writes; at++ {
|
||||||
|
for _, brokenErr := range []bool{false, true} {
|
||||||
|
out := &validationBoundaryWriter{failAt: at, err: reportErr}
|
||||||
|
errStream := io.Writer(io.Discard)
|
||||||
|
if brokenErr {
|
||||||
|
errStream = failingWriter{diagnosticErr}
|
||||||
|
}
|
||||||
|
result := app.Execute(context.Background(), []string{"config", "validate", "--scope", "all", "--format", format}, commandkit.Streams{Out: out, Err: errStream})
|
||||||
|
if result.Kind != commandkit.OutcomeOperational || !result.ReportIncomplete || !errors.Is(result.Cause, reportErr) || !errors.Is(result.DeliveryError, reportErr) || (brokenErr && !errors.Is(result.DeliveryError, diagnosticErr)) {
|
||||||
|
t.Fatalf("format=%s at=%d brokenErr=%t result=%#v", format, at, brokenErr, result)
|
||||||
|
}
|
||||||
|
if at > 1 && out.written.Len() == 0 {
|
||||||
|
t.Fatal("lost completed prefix")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
failedApp, _ := commandkit.New(commandkit.Identity{Name: "example"})
|
||||||
|
if err := commandkit.RegisterConfig(failedApp, commandkit.ConfigSuite[struct{}]{Scopes: []commandkit.ConfigScope[struct{}]{{Name: "app", Operations: []commandkit.ConfigOperation{commandkit.ConfigValidate}, Acquire: func(context.Context, commandkit.ConfigRequest, struct{}) (commandkit.Acquired[commandkit.ConfigView], error) {
|
||||||
|
return commandkit.Acquired[commandkit.ConfigView]{}, reportErr
|
||||||
|
}}}}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
result := failedApp.Execute(context.Background(), []string{"config", "validate"}, commandkit.Streams{})
|
||||||
|
if !errors.Is(result.Cause, reportErr) || result.DeliveryError != nil {
|
||||||
|
t.Fatalf("acquisition failure=%#v", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type validationBoundaryWriter struct {
|
||||||
|
failAt, calls int
|
||||||
|
err error
|
||||||
|
written bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *validationBoundaryWriter) Write(p []byte) (int, error) {
|
||||||
|
w.calls++
|
||||||
|
if w.calls == w.failAt {
|
||||||
|
return 0, w.err
|
||||||
|
}
|
||||||
|
return w.written.Write(p)
|
||||||
|
}
|
||||||
|
|||||||
@@ -57,6 +57,9 @@ architecture. The issues below affect behavior within the implemented structure.
|
|||||||
- R-04 complete: every help entry form finalizes report failures through the
|
- R-04 complete: every help entry form finalizes report failures through the
|
||||||
common diagnostic presentation path once.
|
common diagnostic presentation path once.
|
||||||
|
|
||||||
|
- R-05 complete: validation header/record write failures retain `DeliveryError`
|
||||||
|
separately from acquisition failures and combined diagnostic-stream failures.
|
||||||
|
|
||||||
## 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.
|
||||||
|
|||||||
@@ -68,11 +68,15 @@ func runConfigValidation[C any](invocation Invocation, suite ConfigSuite[C], inp
|
|||||||
}
|
}
|
||||||
if input.Format == "yaml" && !writtenRecord {
|
if input.Format == "yaml" && !writtenRecord {
|
||||||
if err := writeReport(invocation.Streams.Out, []byte("scopes:\n")); err != nil {
|
if err := writeReport(invocation.Streams.Out, []byte("scopes:\n")); err != nil {
|
||||||
return validationFailure(err, diagnostics, true)
|
outcome := validationFailure(err, diagnostics, true)
|
||||||
|
outcome.DeliveryError = err
|
||||||
|
return outcome
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := writeReport(invocation.Streams.Out, record); err != nil {
|
if err := writeReport(invocation.Streams.Out, record); err != nil {
|
||||||
return validationFailure(err, diagnostics, true)
|
outcome := validationFailure(err, diagnostics, true)
|
||||||
|
outcome.DeliveryError = err
|
||||||
|
return outcome
|
||||||
}
|
}
|
||||||
writtenRecord = true
|
writtenRecord = true
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user