From f5b2c2ed4aa758b2d84e7b976b8579deef9693dc Mon Sep 17 00:00:00 2001 From: John Woodell Date: Thu, 4 Jun 2026 13:58:27 -0700 Subject: [PATCH 01/13] feat: add -fd flag for RSpec-style output. --- reporters/default_reporter.go | 109 +++++++++++++++++++++++++++ reporters/default_reporter_test.go | 114 +++++++++++++++++++++++++++++ types/config.go | 3 + 3 files changed, 226 insertions(+) diff --git a/reporters/default_reporter.go b/reporters/default_reporter.go index be5719a8e..9c329098d 100644 --- a/reporters/default_reporter.go +++ b/reporters/default_reporter.go @@ -34,6 +34,17 @@ type DefaultReporter struct { runningInParallel bool lock *sync.Mutex + + // fd output state + fdPrevHierarchy []string + fdFailures []fdFailure +} + +type fdFailure struct { + n int + full []string + message string + location string } func NewDefaultReporterUnderTest(conf types.ReporterConfig, writer io.Writer) *DefaultReporter { @@ -67,6 +78,9 @@ func NewDefaultReporter(conf types.ReporterConfig, writer io.Writer) *DefaultRep /* The Reporter Interface */ func (r *DefaultReporter) SuiteWillBegin(report types.Report) { + if r.conf.FdOutput { + return + } if r.conf.Verbosity().Is(types.VerbosityLevelSuccinct) { r.emit(r.f("[%d] {{bold}}%s{{/}} ", report.SuiteConfig.RandomSeed, report.SuiteDescription)) if len(report.SuiteLabels) > 0 { @@ -123,6 +137,10 @@ func (r *DefaultReporter) SuiteWillBegin(report types.Report) { } func (r *DefaultReporter) SuiteDidEnd(report types.Report) { + if r.conf.FdOutput { + r.suiteDidEndFd(report) + return + } failures := report.SpecReports.WithState(types.SpecStateFailureStates) if len(failures) > 0 { r.emitBlock("\n") @@ -192,6 +210,41 @@ func (r *DefaultReporter) SuiteDidEnd(report types.Report) { } } +func (r *DefaultReporter) suiteDidEndFd(report types.Report) { + if len(r.fdFailures) > 0 { + fmt.Fprintln(r.writer, "\nFailures:") + for _, f := range r.fdFailures { + fmt.Fprintf(r.writer, "\n %d) %s\n", f.n, strings.Join(f.full, " ")) + for _, line := range strings.Split(strings.TrimSpace(f.message), "\n") { + fmt.Fprintf(r.writer, " %s\n", line) + } + fmt.Fprintf(r.writer, " # %s\n", f.location) + } + } + + specs := report.SpecReports.WithLeafNodeType(types.NodeTypeIt) + total := len(specs) + failed := specs.CountWithState(types.SpecStateFailureStates) + pending := specs.CountWithState(types.SpecStatePending) + skipped := specs.CountWithState(types.SpecStateSkipped) + + fmt.Fprintf(r.writer, "\nFinished in %s\n", report.RunTime.Round(time.Millisecond)) + + parts := []string{fmt.Sprintf("%d examples", total)} + if failed > 0 { + parts = append(parts, fmt.Sprintf("%d failure", failed)) + } else { + parts = append(parts, "0 failures") + } + if pending > 0 { + parts = append(parts, fmt.Sprintf("%d pending", pending)) + } + if skipped > 0 { + parts = append(parts, fmt.Sprintf("%d skipped", skipped)) + } + fmt.Fprintln(r.writer, strings.Join(parts, ", ")) +} + func (r *DefaultReporter) WillRun(report types.SpecReport) { v := r.conf.Verbosity() if v.LT(types.VerbosityLevelVerbose) || report.State.Is(types.SpecStatePending|types.SpecStateSkipped) || report.RunningInParallel { @@ -219,6 +272,10 @@ func (r *DefaultReporter) wrapTextBlock(sectionName string, fn func()) { } func (r *DefaultReporter) DidRun(report types.SpecReport) { + if r.conf.FdOutput { + r.didRunFd(report) + return + } v := r.conf.Verbosity() inParallel := report.RunningInParallel @@ -358,6 +415,58 @@ func (r *DefaultReporter) DidRun(report types.SpecReport) { r.emitDelimiter(0) } +func (r *DefaultReporter) didRunFd(report types.SpecReport) { + r.lock.Lock() + defer r.lock.Unlock() + + if !report.LeafNodeType.Is(types.NodeTypeIt) { + return + } + + hierarchy := report.ContainerHierarchyTexts + + // blank line when top-level container changes + if len(r.fdPrevHierarchy) > 0 && + (len(hierarchy) == 0 || hierarchy[0] != r.fdPrevHierarchy[0]) { + fmt.Fprintln(r.writer) + } + + // emit newly-diverged container lines + divergeAt := 0 + for divergeAt < len(r.fdPrevHierarchy) && divergeAt < len(hierarchy) && + r.fdPrevHierarchy[divergeAt] == hierarchy[divergeAt] { + divergeAt++ + } + for i := divergeAt; i < len(hierarchy); i++ { + fmt.Fprintf(r.writer, "%s%s\n", strings.Repeat(" ", i+1), hierarchy[i]) + } + + // leaf label + depth := len(hierarchy) + 1 + indent := strings.Repeat(" ", depth) + label := report.LeafNodeText + + switch report.State { + case types.SpecStateFailed, types.SpecStatePanicked: + n := len(r.fdFailures) + 1 + label = fmt.Sprintf("%s (FAILED - %d)", label, n) + full := append(append([]string{}, hierarchy...), report.LeafNodeText) + r.fdFailures = append(r.fdFailures, fdFailure{ + n: n, + full: full, + message: report.Failure.Message, + location: report.Failure.Location.String(), + }) + case types.SpecStatePending: + label = fmt.Sprintf("%s (PENDING)", label) + case types.SpecStateSkipped: + label = fmt.Sprintf("%s (SKIPPED)", label) + } + + fmt.Fprintf(r.writer, "%s%s\n", indent, label) + r.fdPrevHierarchy = hierarchy +} + func (r *DefaultReporter) highlightColorForState(state types.SpecState) string { switch state { case types.SpecStatePassed: diff --git a/reporters/default_reporter_test.go b/reporters/default_reporter_test.go index 9788a5fd4..565ff7282 100644 --- a/reporters/default_reporter_test.go +++ b/reporters/default_reporter_test.go @@ -2951,4 +2951,118 @@ var _ = Describe("DefaultReporter", func() { "", ), ) + + Describe("Rendering with FdOutput", func() { + var buf strings.Builder + var reporter *reporters.DefaultReporter + + BeforeEach(func() { + buf.Reset() + reporter = reporters.NewDefaultReporterUnderTest(types.ReporterConfig{FdOutput: true}, &buf) + }) + + Context("with a passing report", func() { + BeforeEach(func() { + reporter.SuiteWillBegin(types.Report{SuiteDescription: "Something"}) + reporter.DidRun(types.SpecReport{ + ContainerHierarchyTexts: []string{"checkAttachmentDir", "when the path is missing"}, + LeafNodeText: "creates the directory", + LeafNodeType: types.NodeTypeIt, + State: types.SpecStatePassed, + }) + reporter.DidRun(types.SpecReport{ + ContainerHierarchyTexts: []string{"checkAttachmentDir", "when the path is missing"}, + LeafNodeText: "does not error", + LeafNodeType: types.NodeTypeIt, + State: types.SpecStatePassed, + }) + reporter.DidRun(types.SpecReport{ + ContainerHierarchyTexts: []string{"checkAttachmentDir", "when the path is a symlink"}, + LeafNodeText: "does not error", + LeafNodeType: types.NodeTypeIt, + State: types.SpecStatePassed, + }) + reporter.SuiteDidEnd(types.Report{ + SpecReports: types.SpecReports{ + {LeafNodeType: types.NodeTypeIt, State: types.SpecStatePassed}, + {LeafNodeType: types.NodeTypeIt, State: types.SpecStatePassed}, + {LeafNodeType: types.NodeTypeIt, State: types.SpecStatePassed}, + }, + }) + }) + + It("emits no banner", func() { + Expect(buf.String()).NotTo(ContainSubstring("Running Suite")) + }) + It("indents container hierarchy", func() { + Expect(buf.String()).To(ContainSubstring(" checkAttachmentDir")) + Expect(buf.String()).To(ContainSubstring(" when the path is missing")) + }) + It("indents leaf nodes", func() { + Expect(buf.String()).To(ContainSubstring(" creates the directory")) + }) + It("deduplicates shared hierarchy", func() { + Expect(strings.Count(buf.String(), "when the path is missing")).To(Equal(1)) + }) + It("prints the summary", func() { + Expect(buf.String()).To(ContainSubstring("3 examples, 0 failures")) + }) + }) + + Context("with a failing report", func() { + BeforeEach(func() { + reporter.SuiteWillBegin(types.Report{}) + reporter.DidRun(types.SpecReport{ + ContainerHierarchyTexts: []string{"checkAttachmentDir", "when the path is missing"}, + LeafNodeText: "creates the directory", + LeafNodeType: types.NodeTypeIt, + State: types.SpecStateFailed, + Failure: types.Failure{ + Message: "Expected file to exist", + Location: types.CodeLocation{FileName: "main_test.go", LineNumber: 42}, + }, + }) + reporter.SuiteDidEnd(types.Report{ + SpecReports: types.SpecReports{ + {LeafNodeType: types.NodeTypeIt, State: types.SpecStateFailed}, + }, + }) + }) + + It("annotates the failed spec", func() { + Expect(buf.String()).To(ContainSubstring("creates the directory (FAILED - 1)")) + }) + It("prints the failures section", func() { + Expect(buf.String()).To(ContainSubstring("Failures:")) + Expect(buf.String()).To(ContainSubstring("Expected file to exist")) + Expect(buf.String()).To(ContainSubstring("main_test.go:42")) + }) + It("prints the summary with failure count", func() { + Expect(buf.String()).To(ContainSubstring("1 examples, 1 failure")) + }) + }) + + Context("with a blank line between top-level containers", func() { + BeforeEach(func() { + reporter.SuiteWillBegin(types.Report{}) + reporter.DidRun(types.SpecReport{ + ContainerHierarchyTexts: []string{"DescribeA"}, + LeafNodeText: "does something", + LeafNodeType: types.NodeTypeIt, + State: types.SpecStatePassed, + }) + reporter.DidRun(types.SpecReport{ + ContainerHierarchyTexts: []string{"DescribeB"}, + LeafNodeText: "does something else", + LeafNodeType: types.NodeTypeIt, + State: types.SpecStatePassed, + }) + reporter.SuiteDidEnd(types.Report{SpecReports: types.SpecReports{}}) + }) + + It("emits a blank line between top-level containers", func() { + Expect(buf.String()).To(ContainSubstring(" DescribeA\n does something\n\n DescribeB")) + }) + }) + }) }) diff --git a/types/config.go b/types/config.go index ca64acb27..49270b0c7 100644 --- a/types/config.go +++ b/types/config.go @@ -94,6 +94,7 @@ type ReporterConfig struct { GithubOutput bool SilenceSkips bool ForceNewlines bool + FdOutput bool JSONReport string GoJSONReport string @@ -358,6 +359,8 @@ var ReporterConfigFlags = GinkgoFlags{ Usage: "If set, default reporter will not print out skipped tests."}, {KeyPath: "R.ForceNewlines", Name: "force-newlines", SectionKey: "output", Usage: "If set, default reporter will ensure a newline appears after each test."}, + {KeyPath: "R.FdOutput", Name: "fd", SectionKey: "output", + Usage: "If set, emits RSpec-style 'format documentation' output instead of Ginkgo's default output."}, {KeyPath: "R.JSONReport", Name: "json-report", UsageArgument: "filename.json", SectionKey: "output", Usage: "If set, Ginkgo will generate a JSON-formatted test report at the specified location."}, From 5fdb3301a52fe0f92d148df74700afb5ea74ac65 Mon Sep 17 00:00:00 2001 From: John Woodell Date: Thu, 4 Jun 2026 14:38:17 -0700 Subject: [PATCH 02/13] feat: add color output for -fd flag --- reporters/default_reporter.go | 9 +++++++-- reporters/default_reporter_test.go | 5 +++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/reporters/default_reporter.go b/reporters/default_reporter.go index 9c329098d..7a565cf67 100644 --- a/reporters/default_reporter.go +++ b/reporters/default_reporter.go @@ -242,7 +242,11 @@ func (r *DefaultReporter) suiteDidEndFd(report types.Report) { if skipped > 0 { parts = append(parts, fmt.Sprintf("%d skipped", skipped)) } - fmt.Fprintln(r.writer, strings.Join(parts, ", ")) + color := "{{green}}" + if failed > 0 { + color = "{{red}}" + } + fmt.Fprintln(r.writer, r.f(color+strings.Join(parts, ", ")+"{{/}}")) } func (r *DefaultReporter) WillRun(report types.SpecReport) { @@ -463,7 +467,8 @@ func (r *DefaultReporter) didRunFd(report types.SpecReport) { label = fmt.Sprintf("%s (SKIPPED)", label) } - fmt.Fprintf(r.writer, "%s%s\n", indent, label) + color := r.highlightColorForState(report.State) + fmt.Fprintf(r.writer, "%s%s\n", indent, r.f(color+"%s{{/}}", label)) r.fdPrevHierarchy = hierarchy } diff --git a/reporters/default_reporter_test.go b/reporters/default_reporter_test.go index 565ff7282..4bcbe25b7 100644 --- a/reporters/default_reporter_test.go +++ b/reporters/default_reporter_test.go @@ -2999,7 +2999,7 @@ var _ = Describe("DefaultReporter", func() { Expect(buf.String()).To(ContainSubstring(" when the path is missing")) }) It("indents leaf nodes", func() { - Expect(buf.String()).To(ContainSubstring(" creates the directory")) + Expect(buf.String()).To(ContainSubstring("creates the directory")) }) It("deduplicates shared hierarchy", func() { Expect(strings.Count(buf.String(), "when the path is missing")).To(Equal(1)) @@ -3061,7 +3061,8 @@ var _ = Describe("DefaultReporter", func() { }) It("emits a blank line between top-level containers", func() { - Expect(buf.String()).To(ContainSubstring(" DescribeA\n does something\n\n DescribeB")) + Expect(buf.String()).To(ContainSubstring(" DescribeA")) + Expect(buf.String()).To(ContainSubstring("\n\n DescribeB")) }) }) }) From bf16df715eebe6b418911b2b112d809bb3dd107d Mon Sep 17 00:00:00 2001 From: John Woodell Date: Thu, 4 Jun 2026 15:10:45 -0700 Subject: [PATCH 03/13] Update the documentation. --- docs/index.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/index.md b/docs/index.md index 0746c72ed..9b5a60fea 100644 --- a/docs/index.md +++ b/docs/index.md @@ -3764,12 +3764,14 @@ Ginkgo emits a real-time report of the progress of your spec suite to the consol There are several CLI flags that allow you to tweak this output: #### Controlling Verbosity -Ginkgo has four verbosity settings: succinct (the default when running multiple suites), normal (the default when running a single suite), verbose, and very-verbose. +Ginkgo has five verbosity settings: succinct (the default when running multiple suites), normal (the default when running a single suite), verbose, and very-verbose. -You can opt into succinct mode with `ginkgo --succinct`, verbose mode with `ginkgo -v` and very-verbose mode with `ginkgo -vv`. +You can opt into succinct mode with `ginkgo --succinct`, format documentation mode with `ginkgo -fd`, verbose mode with `ginkgo -v` and very-verbose mode with `ginkgo -vv`. These settings control the amount of information emitted with each spec. By default (i.e. succinct and normal) Ginkgo only emits detailed information about specs that fail. That includes the location of the spec/failure and a timeline that includes any captured `GinkgoWriter` content alongside a series of relevant spec events. +You can opt into documentation format output with `ginkgo -fd`. This emits a hierarchical tree of spec descriptions, with each spec's name colored green for passing, red for failing, yellow for pending, and cyan for skipped, along with a summary of failures at the end of the suite. This mode will be familiar to Rspec users and has more concise output for very large test suites. + The two verbose settings are most helpful when debugging spec suites. They make Ginkgo emit the detailed timeline information for _every_ spec regardless of failure or success. When running in series with `-v` or `-vv` mode Ginkgo will stream out the timeline in real-time while specs are running. A real-time stream isn't possible when running in parallel (the [streams would be interleaved](https://www.youtube.com/watch?v=jyaLZHiJJnE)); instead Ginkgo emits all this information about each spec right after it completes. Very-verbose mode contains additional information over verbose mode. In particular, `-vv` timelines indicate when individual nodes start and end and also include the full failure descriptions for _every_ failure encountered by the spec. Verbose mode does not include the node start/end events (though this can be turned on with `--show-node-events`) and does not include detailed failure information for anything other than the first (primary) failure. (Additional/subsequent failures typically occur in clean-up nodes and are not as relevant as the primary failure that occurs in a subject or setup node). From bc79626060872be0e4333f0788de6ec0bc95d65b Mon Sep 17 00:00:00 2001 From: John Woodell Date: Thu, 4 Jun 2026 15:24:29 -0700 Subject: [PATCH 04/13] fix: correct indentation to match RSpec output --- reporters/default_reporter.go | 4 ++-- reporters/default_reporter_test.go | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/reporters/default_reporter.go b/reporters/default_reporter.go index 7a565cf67..8adb4950e 100644 --- a/reporters/default_reporter.go +++ b/reporters/default_reporter.go @@ -442,11 +442,11 @@ func (r *DefaultReporter) didRunFd(report types.SpecReport) { divergeAt++ } for i := divergeAt; i < len(hierarchy); i++ { - fmt.Fprintf(r.writer, "%s%s\n", strings.Repeat(" ", i+1), hierarchy[i]) + fmt.Fprintf(r.writer, "%s%s\n", strings.Repeat(" ", i), hierarchy[i]) } // leaf label - depth := len(hierarchy) + 1 + depth := len(hierarchy) indent := strings.Repeat(" ", depth) label := report.LeafNodeText diff --git a/reporters/default_reporter_test.go b/reporters/default_reporter_test.go index 4bcbe25b7..443ac1330 100644 --- a/reporters/default_reporter_test.go +++ b/reporters/default_reporter_test.go @@ -2995,8 +2995,8 @@ var _ = Describe("DefaultReporter", func() { Expect(buf.String()).NotTo(ContainSubstring("Running Suite")) }) It("indents container hierarchy", func() { - Expect(buf.String()).To(ContainSubstring(" checkAttachmentDir")) - Expect(buf.String()).To(ContainSubstring(" when the path is missing")) + Expect(buf.String()).To(ContainSubstring("checkAttachmentDir")) + Expect(buf.String()).To(ContainSubstring(" when the path is missing")) }) It("indents leaf nodes", func() { Expect(buf.String()).To(ContainSubstring("creates the directory")) @@ -3061,8 +3061,8 @@ var _ = Describe("DefaultReporter", func() { }) It("emits a blank line between top-level containers", func() { - Expect(buf.String()).To(ContainSubstring(" DescribeA")) - Expect(buf.String()).To(ContainSubstring("\n\n DescribeB")) + Expect(buf.String()).To(ContainSubstring("DescribeA")) + Expect(buf.String()).To(ContainSubstring("\n\nDescribeB")) }) }) }) From baf524c3d5db69ca224d062cbab9ef6de53b44d5 Mon Sep 17 00:00:00 2001 From: John Woodell Date: Thu, 4 Jun 2026 17:30:44 -0700 Subject: [PATCH 05/13] Keep the same footer. --- reporters/default_reporter.go | 50 ++++++++++++++++++------------ reporters/default_reporter_test.go | 5 +-- 2 files changed, 33 insertions(+), 22 deletions(-) diff --git a/reporters/default_reporter.go b/reporters/default_reporter.go index 8adb4950e..4023d887e 100644 --- a/reporters/default_reporter.go +++ b/reporters/default_reporter.go @@ -221,32 +221,42 @@ func (r *DefaultReporter) suiteDidEndFd(report types.Report) { fmt.Fprintf(r.writer, " # %s\n", f.location) } } + r.emitBlock("\n") + color, status := "{{green}}{{bold}}", "SUCCESS!" + if !report.SuiteSucceeded { + color, status = "{{red}}{{bold}}", "FAIL!" + } specs := report.SpecReports.WithLeafNodeType(types.NodeTypeIt) - total := len(specs) - failed := specs.CountWithState(types.SpecStateFailureStates) - pending := specs.CountWithState(types.SpecStatePending) - skipped := specs.CountWithState(types.SpecStateSkipped) + r.emitBlock(r.f(color+"Ran %d of %d Specs in %.3f seconds{{/}}", + specs.CountWithState(types.SpecStatePassed)+specs.CountWithState(types.SpecStateFailureStates), + report.PreRunStats.TotalSpecs, + report.RunTime.Seconds()), + ) - fmt.Fprintf(r.writer, "\nFinished in %s\n", report.RunTime.Round(time.Millisecond)) + switch len(report.SpecialSuiteFailureReasons) { + case 0: + r.emit(r.f(color+"%s{{/}} -- ", status)) + case 1: + r.emit(r.f(color+"%s - %s{{/}} -- ", status, report.SpecialSuiteFailureReasons[0])) + default: + r.emitBlock(r.f(color+"%s - %s{{/}}\n", status, strings.Join(report.SpecialSuiteFailureReasons, ", "))) + } - parts := []string{fmt.Sprintf("%d examples", total)} - if failed > 0 { - parts = append(parts, fmt.Sprintf("%d failure", failed)) + if len(specs) == 0 && report.SpecReports.WithLeafNodeType(types.NodeTypeBeforeSuite|types.NodeTypeSynchronizedBeforeSuite).CountWithState(types.SpecStateFailureStates) > 0 { + r.emit(r.f("{{cyan}}{{bold}}A BeforeSuite node failed so all tests were skipped.{{/}}\n")) } else { - parts = append(parts, "0 failures") - } - if pending > 0 { - parts = append(parts, fmt.Sprintf("%d pending", pending)) - } - if skipped > 0 { - parts = append(parts, fmt.Sprintf("%d skipped", skipped)) - } - color := "{{green}}" - if failed > 0 { - color = "{{red}}" + r.emit(r.f("{{green}}{{bold}}%d Passed{{/}} | ", specs.CountWithState(types.SpecStatePassed))) + r.emit(r.f("{{red}}{{bold}}%d Failed{{/}} | ", specs.CountWithState(types.SpecStateFailureStates))) + if specs.CountOfFlakedSpecs() > 0 { + r.emit(r.f("{{light-yellow}}{{bold}}%d Flaked{{/}} | ", specs.CountOfFlakedSpecs())) + } + if specs.CountOfRepeatedSpecs() > 0 { + r.emit(r.f("{{light-yellow}}{{bold}}%d Repeated{{/}} | ", specs.CountOfRepeatedSpecs())) + } + r.emit(r.f("{{yellow}}{{bold}}%d Pending{{/}} | ", specs.CountWithState(types.SpecStatePending))) + r.emit(r.f("{{cyan}}{{bold}}%d Skipped{{/}}\n", specs.CountWithState(types.SpecStateSkipped))) } - fmt.Fprintln(r.writer, r.f(color+strings.Join(parts, ", ")+"{{/}}")) } func (r *DefaultReporter) WillRun(report types.SpecReport) { diff --git a/reporters/default_reporter_test.go b/reporters/default_reporter_test.go index 443ac1330..c99399199 100644 --- a/reporters/default_reporter_test.go +++ b/reporters/default_reporter_test.go @@ -3005,7 +3005,8 @@ var _ = Describe("DefaultReporter", func() { Expect(strings.Count(buf.String(), "when the path is missing")).To(Equal(1)) }) It("prints the summary", func() { - Expect(buf.String()).To(ContainSubstring("3 examples, 0 failures")) + Expect(buf.String()).To(ContainSubstring("Passed")) + }) }) @@ -3038,7 +3039,7 @@ var _ = Describe("DefaultReporter", func() { Expect(buf.String()).To(ContainSubstring("main_test.go:42")) }) It("prints the summary with failure count", func() { - Expect(buf.String()).To(ContainSubstring("1 examples, 1 failure")) + Expect(buf.String()).To(ContainSubstring("FAIL!")) }) }) From 7812ad2c4bab18d8cf52ca01bb89bc6fb55e27c2 Mon Sep 17 00:00:00 2001 From: John Woodell Date: Thu, 4 Jun 2026 18:01:57 -0700 Subject: [PATCH 06/13] refactor: extract emitSuiteFooter to avoid duplication --- reporters/default_reporter.go | 44 +++++------------------------------ 1 file changed, 6 insertions(+), 38 deletions(-) diff --git a/reporters/default_reporter.go b/reporters/default_reporter.go index 4023d887e..04dd992cb 100644 --- a/reporters/default_reporter.go +++ b/reporters/default_reporter.go @@ -165,8 +165,11 @@ func (r *DefaultReporter) SuiteDidEnd(report types.Report) { r.emitBlock(r.fi(1, highlightColor+"%s{{/}} %s", heading, locationBlock)) } } + r.emitSuiteFooter(report) +} + - //summarize the suite +func (r *DefaultReporter) emitSuiteFooter(report types.Report) { if r.conf.Verbosity().Is(types.VerbosityLevelSuccinct) && report.SuiteSucceeded { r.emit(r.f(" {{green}}SUCCESS!{{/}} %s ", report.RunTime)) return @@ -178,7 +181,7 @@ func (r *DefaultReporter) SuiteDidEnd(report types.Report) { color, status = "{{red}}{{bold}}", "FAIL!" } - specs := report.SpecReports.WithLeafNodeType(types.NodeTypeIt) //exclude any suite setup nodes + specs := report.SpecReports.WithLeafNodeType(types.NodeTypeIt) r.emitBlock(r.f(color+"Ran %d of %d Specs in %.3f seconds{{/}}", specs.CountWithState(types.SpecStatePassed)+specs.CountWithState(types.SpecStateFailureStates), report.PreRunStats.TotalSpecs, @@ -221,42 +224,7 @@ func (r *DefaultReporter) suiteDidEndFd(report types.Report) { fmt.Fprintf(r.writer, " # %s\n", f.location) } } - r.emitBlock("\n") - color, status := "{{green}}{{bold}}", "SUCCESS!" - if !report.SuiteSucceeded { - color, status = "{{red}}{{bold}}", "FAIL!" - } - - specs := report.SpecReports.WithLeafNodeType(types.NodeTypeIt) - r.emitBlock(r.f(color+"Ran %d of %d Specs in %.3f seconds{{/}}", - specs.CountWithState(types.SpecStatePassed)+specs.CountWithState(types.SpecStateFailureStates), - report.PreRunStats.TotalSpecs, - report.RunTime.Seconds()), - ) - - switch len(report.SpecialSuiteFailureReasons) { - case 0: - r.emit(r.f(color+"%s{{/}} -- ", status)) - case 1: - r.emit(r.f(color+"%s - %s{{/}} -- ", status, report.SpecialSuiteFailureReasons[0])) - default: - r.emitBlock(r.f(color+"%s - %s{{/}}\n", status, strings.Join(report.SpecialSuiteFailureReasons, ", "))) - } - - if len(specs) == 0 && report.SpecReports.WithLeafNodeType(types.NodeTypeBeforeSuite|types.NodeTypeSynchronizedBeforeSuite).CountWithState(types.SpecStateFailureStates) > 0 { - r.emit(r.f("{{cyan}}{{bold}}A BeforeSuite node failed so all tests were skipped.{{/}}\n")) - } else { - r.emit(r.f("{{green}}{{bold}}%d Passed{{/}} | ", specs.CountWithState(types.SpecStatePassed))) - r.emit(r.f("{{red}}{{bold}}%d Failed{{/}} | ", specs.CountWithState(types.SpecStateFailureStates))) - if specs.CountOfFlakedSpecs() > 0 { - r.emit(r.f("{{light-yellow}}{{bold}}%d Flaked{{/}} | ", specs.CountOfFlakedSpecs())) - } - if specs.CountOfRepeatedSpecs() > 0 { - r.emit(r.f("{{light-yellow}}{{bold}}%d Repeated{{/}} | ", specs.CountOfRepeatedSpecs())) - } - r.emit(r.f("{{yellow}}{{bold}}%d Pending{{/}} | ", specs.CountWithState(types.SpecStatePending))) - r.emit(r.f("{{cyan}}{{bold}}%d Skipped{{/}}\n", specs.CountWithState(types.SpecStateSkipped))) - } + r.emitSuiteFooter(report) } func (r *DefaultReporter) WillRun(report types.SpecReport) { From 8ec181720efb0d3ae962d7e3bd4f0e8abbb878aa Mon Sep 17 00:00:00 2001 From: John Woodell Date: Thu, 4 Jun 2026 18:06:08 -0700 Subject: [PATCH 07/13] Restore comments. --- reporters/default_reporter.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/reporters/default_reporter.go b/reporters/default_reporter.go index 04dd992cb..b3a07c698 100644 --- a/reporters/default_reporter.go +++ b/reporters/default_reporter.go @@ -168,7 +168,7 @@ func (r *DefaultReporter) SuiteDidEnd(report types.Report) { r.emitSuiteFooter(report) } - + //summarize the suite func (r *DefaultReporter) emitSuiteFooter(report types.Report) { if r.conf.Verbosity().Is(types.VerbosityLevelSuccinct) && report.SuiteSucceeded { r.emit(r.f(" {{green}}SUCCESS!{{/}} %s ", report.RunTime)) @@ -181,7 +181,7 @@ func (r *DefaultReporter) emitSuiteFooter(report types.Report) { color, status = "{{red}}{{bold}}", "FAIL!" } - specs := report.SpecReports.WithLeafNodeType(types.NodeTypeIt) + specs := report.SpecReports.WithLeafNodeType(types.NodeTypeIt) //exclude any suite setup nodes r.emitBlock(r.f(color+"Ran %d of %d Specs in %.3f seconds{{/}}", specs.CountWithState(types.SpecStatePassed)+specs.CountWithState(types.SpecStateFailureStates), report.PreRunStats.TotalSpecs, From 210c4ac6605ba95388200a248f4bc166023bc26e Mon Sep 17 00:00:00 2001 From: John Woodell Date: Thu, 4 Jun 2026 18:39:36 -0700 Subject: [PATCH 08/13] Use the same failure summary. --- reporters/default_reporter.go | 52 +++++++++--------------------- reporters/default_reporter_test.go | 6 ++-- 2 files changed, 17 insertions(+), 41 deletions(-) diff --git a/reporters/default_reporter.go b/reporters/default_reporter.go index b3a07c698..284b814d3 100644 --- a/reporters/default_reporter.go +++ b/reporters/default_reporter.go @@ -37,14 +37,6 @@ type DefaultReporter struct { // fd output state fdPrevHierarchy []string - fdFailures []fdFailure -} - -type fdFailure struct { - n int - full []string - message string - location string } func NewDefaultReporterUnderTest(conf types.ReporterConfig, writer io.Writer) *DefaultReporter { @@ -136,11 +128,7 @@ func (r *DefaultReporter) SuiteWillBegin(report types.Report) { } } -func (r *DefaultReporter) SuiteDidEnd(report types.Report) { - if r.conf.FdOutput { - r.suiteDidEndFd(report) - return - } +func (r *DefaultReporter) emitSuiteFailures(report types.Report) { failures := report.SpecReports.WithState(types.SpecStateFailureStates) if len(failures) > 0 { r.emitBlock("\n") @@ -165,10 +153,22 @@ func (r *DefaultReporter) SuiteDidEnd(report types.Report) { r.emitBlock(r.fi(1, highlightColor+"%s{{/}} %s", heading, locationBlock)) } } +} + +func (r *DefaultReporter) suiteDidEndFd(report types.Report) { + r.emitSuiteFailures(report) + r.emitSuiteFooter(report) +} + +func (r *DefaultReporter) SuiteDidEnd(report types.Report) { + if r.conf.FdOutput { + r.suiteDidEndFd(report) + return + } + r.emitSuiteFailures(report) r.emitSuiteFooter(report) } - //summarize the suite func (r *DefaultReporter) emitSuiteFooter(report types.Report) { if r.conf.Verbosity().Is(types.VerbosityLevelSuccinct) && report.SuiteSucceeded { r.emit(r.f(" {{green}}SUCCESS!{{/}} %s ", report.RunTime)) @@ -213,20 +213,6 @@ func (r *DefaultReporter) emitSuiteFooter(report types.Report) { } } -func (r *DefaultReporter) suiteDidEndFd(report types.Report) { - if len(r.fdFailures) > 0 { - fmt.Fprintln(r.writer, "\nFailures:") - for _, f := range r.fdFailures { - fmt.Fprintf(r.writer, "\n %d) %s\n", f.n, strings.Join(f.full, " ")) - for _, line := range strings.Split(strings.TrimSpace(f.message), "\n") { - fmt.Fprintf(r.writer, " %s\n", line) - } - fmt.Fprintf(r.writer, " # %s\n", f.location) - } - } - r.emitSuiteFooter(report) -} - func (r *DefaultReporter) WillRun(report types.SpecReport) { v := r.conf.Verbosity() if v.LT(types.VerbosityLevelVerbose) || report.State.Is(types.SpecStatePending|types.SpecStateSkipped) || report.RunningInParallel { @@ -430,15 +416,7 @@ func (r *DefaultReporter) didRunFd(report types.SpecReport) { switch report.State { case types.SpecStateFailed, types.SpecStatePanicked: - n := len(r.fdFailures) + 1 - label = fmt.Sprintf("%s (FAILED - %d)", label, n) - full := append(append([]string{}, hierarchy...), report.LeafNodeText) - r.fdFailures = append(r.fdFailures, fdFailure{ - n: n, - full: full, - message: report.Failure.Message, - location: report.Failure.Location.String(), - }) + label = fmt.Sprintf("%s (FAILED)", label) case types.SpecStatePending: label = fmt.Sprintf("%s (PENDING)", label) case types.SpecStateSkipped: diff --git a/reporters/default_reporter_test.go b/reporters/default_reporter_test.go index c99399199..576e83a11 100644 --- a/reporters/default_reporter_test.go +++ b/reporters/default_reporter_test.go @@ -3031,12 +3031,10 @@ var _ = Describe("DefaultReporter", func() { }) It("annotates the failed spec", func() { - Expect(buf.String()).To(ContainSubstring("creates the directory (FAILED - 1)")) + Expect(buf.String()).To(ContainSubstring("creates the directory (FAILED)")) }) It("prints the failures section", func() { - Expect(buf.String()).To(ContainSubstring("Failures:")) - Expect(buf.String()).To(ContainSubstring("Expected file to exist")) - Expect(buf.String()).To(ContainSubstring("main_test.go:42")) + Expect(buf.String()).To(ContainSubstring("Summarizing 1 Failure:")) }) It("prints the summary with failure count", func() { Expect(buf.String()).To(ContainSubstring("FAIL!")) From 2c982a16bf7d6ee37da0bcc6d1d342d8565bb99b Mon Sep 17 00:00:00 2001 From: John Woodell Date: Thu, 4 Jun 2026 18:51:25 -0700 Subject: [PATCH 09/13] Remove refactor duplication. --- reporters/default_reporter.go | 9 --------- 1 file changed, 9 deletions(-) diff --git a/reporters/default_reporter.go b/reporters/default_reporter.go index 284b814d3..6ec34537c 100644 --- a/reporters/default_reporter.go +++ b/reporters/default_reporter.go @@ -155,16 +155,7 @@ func (r *DefaultReporter) emitSuiteFailures(report types.Report) { } } -func (r *DefaultReporter) suiteDidEndFd(report types.Report) { - r.emitSuiteFailures(report) - r.emitSuiteFooter(report) -} - func (r *DefaultReporter) SuiteDidEnd(report types.Report) { - if r.conf.FdOutput { - r.suiteDidEndFd(report) - return - } r.emitSuiteFailures(report) r.emitSuiteFooter(report) } From e68ed3fdd6797a2ee7da2bceb9c215e6c843ef61 Mon Sep 17 00:00:00 2001 From: John Woodell Date: Thu, 4 Jun 2026 18:57:59 -0700 Subject: [PATCH 10/13] Shuffle around functions for cleaner diff. --- reporters/default_reporter.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/reporters/default_reporter.go b/reporters/default_reporter.go index 6ec34537c..881d598b3 100644 --- a/reporters/default_reporter.go +++ b/reporters/default_reporter.go @@ -128,6 +128,11 @@ func (r *DefaultReporter) SuiteWillBegin(report types.Report) { } } +func (r *DefaultReporter) SuiteDidEnd(report types.Report) { + r.emitSuiteFailures(report) + r.emitSuiteFooter(report) +} + func (r *DefaultReporter) emitSuiteFailures(report types.Report) { failures := report.SpecReports.WithState(types.SpecStateFailureStates) if len(failures) > 0 { @@ -155,11 +160,6 @@ func (r *DefaultReporter) emitSuiteFailures(report types.Report) { } } -func (r *DefaultReporter) SuiteDidEnd(report types.Report) { - r.emitSuiteFailures(report) - r.emitSuiteFooter(report) -} - func (r *DefaultReporter) emitSuiteFooter(report types.Report) { if r.conf.Verbosity().Is(types.VerbosityLevelSuccinct) && report.SuiteSucceeded { r.emit(r.f(" {{green}}SUCCESS!{{/}} %s ", report.RunTime)) From 0397b45c851a7253cc78a5a6a9e8f772784b4b5d Mon Sep 17 00:00:00 2001 From: John Woodell Date: Thu, 4 Jun 2026 19:18:50 -0700 Subject: [PATCH 11/13] Clean up more refactoring cruft. --- reporters/default_reporter.go | 12 ++++++------ reporters/default_reporter_test.go | 3 --- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/reporters/default_reporter.go b/reporters/default_reporter.go index 881d598b3..68ed91320 100644 --- a/reporters/default_reporter.go +++ b/reporters/default_reporter.go @@ -129,11 +129,10 @@ func (r *DefaultReporter) SuiteWillBegin(report types.Report) { } func (r *DefaultReporter) SuiteDidEnd(report types.Report) { - r.emitSuiteFailures(report) - r.emitSuiteFooter(report) -} - -func (r *DefaultReporter) emitSuiteFailures(report types.Report) { + if r.conf.FdOutput { + r.emitSuiteFooter(report) + return + } failures := report.SpecReports.WithState(types.SpecStateFailureStates) if len(failures) > 0 { r.emitBlock("\n") @@ -158,8 +157,9 @@ func (r *DefaultReporter) emitSuiteFailures(report types.Report) { r.emitBlock(r.fi(1, highlightColor+"%s{{/}} %s", heading, locationBlock)) } } + //summarize the suite + r.emitSuiteFooter(report) } - func (r *DefaultReporter) emitSuiteFooter(report types.Report) { if r.conf.Verbosity().Is(types.VerbosityLevelSuccinct) && report.SuiteSucceeded { r.emit(r.f(" {{green}}SUCCESS!{{/}} %s ", report.RunTime)) diff --git a/reporters/default_reporter_test.go b/reporters/default_reporter_test.go index 576e83a11..8f42d70d4 100644 --- a/reporters/default_reporter_test.go +++ b/reporters/default_reporter_test.go @@ -3034,9 +3034,6 @@ var _ = Describe("DefaultReporter", func() { Expect(buf.String()).To(ContainSubstring("creates the directory (FAILED)")) }) It("prints the failures section", func() { - Expect(buf.String()).To(ContainSubstring("Summarizing 1 Failure:")) - }) - It("prints the summary with failure count", func() { Expect(buf.String()).To(ContainSubstring("FAIL!")) }) }) From 0505118581ffc8f23524607995443171672022cc Mon Sep 17 00:00:00 2001 From: John Woodell Date: Thu, 4 Jun 2026 19:25:53 -0700 Subject: [PATCH 12/13] Clean up the change. --- reporters/default_reporter.go | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/reporters/default_reporter.go b/reporters/default_reporter.go index 68ed91320..6643b0dbf 100644 --- a/reporters/default_reporter.go +++ b/reporters/default_reporter.go @@ -31,12 +31,10 @@ type DefaultReporter struct { specDenoter string retryDenoter string formatter formatter.Formatter + fdHierarchy []string runningInParallel bool lock *sync.Mutex - - // fd output state - fdPrevHierarchy []string } func NewDefaultReporterUnderTest(conf types.ReporterConfig, writer io.Writer) *DefaultReporter { @@ -157,7 +155,6 @@ func (r *DefaultReporter) SuiteDidEnd(report types.Report) { r.emitBlock(r.fi(1, highlightColor+"%s{{/}} %s", heading, locationBlock)) } } - //summarize the suite r.emitSuiteFooter(report) } func (r *DefaultReporter) emitSuiteFooter(report types.Report) { @@ -385,15 +382,15 @@ func (r *DefaultReporter) didRunFd(report types.SpecReport) { hierarchy := report.ContainerHierarchyTexts // blank line when top-level container changes - if len(r.fdPrevHierarchy) > 0 && - (len(hierarchy) == 0 || hierarchy[0] != r.fdPrevHierarchy[0]) { + if len(r.fdHierarchy) > 0 && + (len(hierarchy) == 0 || hierarchy[0] != r.fdHierarchy[0]) { fmt.Fprintln(r.writer) } // emit newly-diverged container lines divergeAt := 0 - for divergeAt < len(r.fdPrevHierarchy) && divergeAt < len(hierarchy) && - r.fdPrevHierarchy[divergeAt] == hierarchy[divergeAt] { + for divergeAt < len(r.fdHierarchy) && divergeAt < len(hierarchy) && + r.fdHierarchy[divergeAt] == hierarchy[divergeAt] { divergeAt++ } for i := divergeAt; i < len(hierarchy); i++ { @@ -416,7 +413,7 @@ func (r *DefaultReporter) didRunFd(report types.SpecReport) { color := r.highlightColorForState(report.State) fmt.Fprintf(r.writer, "%s%s\n", indent, r.f(color+"%s{{/}}", label)) - r.fdPrevHierarchy = hierarchy + r.fdHierarchy = hierarchy } func (r *DefaultReporter) highlightColorForState(state types.SpecState) string { From b2d5db3918c973df0a488f6070abd626b3dfbd45 Mon Sep 17 00:00:00 2001 From: John Woodell Date: Thu, 4 Jun 2026 19:33:05 -0700 Subject: [PATCH 13/13] Fix a couple issues gofmt missed. --- reporters/default_reporter.go | 1 + reporters/default_reporter_test.go | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/reporters/default_reporter.go b/reporters/default_reporter.go index 6643b0dbf..3c863e678 100644 --- a/reporters/default_reporter.go +++ b/reporters/default_reporter.go @@ -157,6 +157,7 @@ func (r *DefaultReporter) SuiteDidEnd(report types.Report) { } r.emitSuiteFooter(report) } + func (r *DefaultReporter) emitSuiteFooter(report types.Report) { if r.conf.Verbosity().Is(types.VerbosityLevelSuccinct) && report.SuiteSucceeded { r.emit(r.f(" {{green}}SUCCESS!{{/}} %s ", report.RunTime)) diff --git a/reporters/default_reporter_test.go b/reporters/default_reporter_test.go index 8f42d70d4..d965a42f5 100644 --- a/reporters/default_reporter_test.go +++ b/reporters/default_reporter_test.go @@ -3006,7 +3006,6 @@ var _ = Describe("DefaultReporter", func() { }) It("prints the summary", func() { Expect(buf.String()).To(ContainSubstring("Passed")) - }) })