package commandkit import ( "bytes" "errors" "fmt" "io" "strings" "testing" ) func TestHiddenSimpleDeferredMessagesDoNotAccumulate(t *testing.T) { for _, mode := range []ReportingMode{ReportingModeOrdinary, ReportingModeQuiet} { t.Run(fmt.Sprint(mode), func(t *testing.T) { reporter := newReporter(Streams{}) setReporterVisibility(reporter, mode, false, false) for index := 0; index < 256; index++ { if err := reporter.AtEnd().Debug(fmt.Sprintf("hidden %d", index), nil); err != nil { t.Fatal(err) } if err := reporter.AtEnd().Warn("hidden warning"); err != nil { t.Fatal(err) } } // This assertion owns the storage optimization, not a public queue API. if len(reporter.state.deferred) != 0 { t.Fatalf("retained %d renderer-free hidden messages", len(reporter.state.deferred)) } got := reporter.finalize(Success(), nil) if got.Status != 0 || got.Presentation.Diagnostics.State != DeliverySuppressed { t.Fatalf("outcome=%#v", got) } if err := reporter.AtEnd().Debug("late"); !errors.Is(err, ErrReporterClosed) { t.Fatalf("closed hidden submission=%v", err) } }) } noContent := newReporter(Streams{}) _ = noContent.AtEnd().Warn("", nil) if got := noContent.finalize(Success(), nil); got.Presentation.Diagnostics.State != DeliveryNone { t.Fatalf("empty deferred submission became content: %#v", got) } } func TestHiddenDeferredDetailsRemainDeferredAndCaptured(t *testing.T) { var diagnostics bytes.Buffer reporter := newReporter(Streams{Err: &diagnostics}) setReporterVisibility(reporter, ReportingModeQuiet, false, false) failure := errors.New("hidden rendering failed") var order []string details := []Detail{func(w io.Writer) error { order = append(order, "first") _, _ = io.WriteString(w, "hidden detail") return failure }} _ = reporter.AtEnd().Warn("hidden", details...) details[0] = func(io.Writer) error { t.Error("caller changed captured callback"); return nil } _ = reporter.AtEnd().Info("", func(io.Writer) error { order = append(order, "second"); return nil }) if len(order) != 0 { t.Fatal("details ran at submission") } got := reporter.finalize(Success(), nil) if strings.Join(order, ",") != "first,second" || !errors.Is(got.Presentation.Diagnostics.RenderError, failure) || got.Status != 1 { t.Fatalf("order=%v outcome=%#v", order, got) } if strings.Contains(diagnostics.String(), "hidden") || len(reporter.state.deferred) != 0 { t.Fatalf("hidden content leaked or queue retained: %q", diagnostics.String()) } } type gatedDiagnosticWriter struct { bytes.Buffer entered chan struct{} release chan struct{} } func (w *gatedDiagnosticWriter) Write(p []byte) (int, error) { if w.entered != nil { close(w.entered) w.entered = nil <-w.release } return w.Buffer.Write(p) } func TestReporterDeferredAcceptanceEndsBeforeDelivery(t *testing.T) { writer := &gatedDiagnosticWriter{entered: make(chan struct{}), release: make(chan struct{})} entered := writer.entered reporter := newReporter(Streams{Err: writer}) setReporterVisibility(reporter, ReportingModeVerbose, false, false) deferred := reporter.AtEnd() if err := deferred.Warn("accepted"); err != nil { t.Fatal(err) } completionEntered, finishCompletion := make(chan struct{}), make(chan struct{}) done := make(chan Outcome, 1) go func() { done <- reporter.finalize(Failure("terminal"), func(r *Reporter) { close(completionEntered) <-finishCompletion _ = r.Info("completion") }) }() <-entered late := make(chan error, 1) go func() { late <- deferred.Warn("late") }() close(writer.release) <-completionEntered if err := <-late; !errors.Is(err, ErrReporterClosed) { t.Errorf("late submission = %v", err) } if err := deferred.Debug(""); !errors.Is(err, ErrReporterClosed) { t.Errorf("empty hidden submission = %v", err) } close(finishCompletion) outcome := <-done if got := writer.String(); got != "WARN: accepted\ncompletion\nERROR: terminal\n" { t.Errorf("diagnostics = %q", got) } if outcome.Presentation.Diagnostics.State != DeliveryComplete { t.Fatalf("outcome = %#v", outcome) } }