Skip to content
6 changes: 4 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
61 changes: 60 additions & 1 deletion reporters/default_reporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type DefaultReporter struct {
specDenoter string
retryDenoter string
formatter formatter.Formatter
fdHierarchy []string

runningInParallel bool
lock *sync.Mutex
Expand Down Expand Up @@ -67,6 +68,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 {
Expand Down Expand Up @@ -123,6 +127,10 @@ func (r *DefaultReporter) SuiteWillBegin(report types.Report) {
}

func (r *DefaultReporter) SuiteDidEnd(report types.Report) {
if r.conf.FdOutput {
r.emitSuiteFooter(report)
return
}
failures := report.SpecReports.WithState(types.SpecStateFailureStates)
if len(failures) > 0 {
r.emitBlock("\n")
Expand All @@ -147,8 +155,10 @@ 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
Expand Down Expand Up @@ -219,6 +229,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

Expand Down Expand Up @@ -358,6 +372,51 @@ 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.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.fdHierarchy) && divergeAt < len(hierarchy) &&
r.fdHierarchy[divergeAt] == hierarchy[divergeAt] {
divergeAt++
}
for i := divergeAt; i < len(hierarchy); i++ {
fmt.Fprintf(r.writer, "%s%s\n", strings.Repeat(" ", i), hierarchy[i])
}

// leaf label
depth := len(hierarchy)
indent := strings.Repeat(" ", depth)
label := report.LeafNodeText

switch report.State {
case types.SpecStateFailed, types.SpecStatePanicked:
label = fmt.Sprintf("%s (FAILED)", label)
case types.SpecStatePending:
label = fmt.Sprintf("%s (PENDING)", label)
case types.SpecStateSkipped:
label = fmt.Sprintf("%s (SKIPPED)", label)
}

color := r.highlightColorForState(report.State)
fmt.Fprintf(r.writer, "%s%s\n", indent, r.f(color+"%s{{/}}", label))
r.fdHierarchy = hierarchy
}

func (r *DefaultReporter) highlightColorForState(state types.SpecState) string {
switch state {
case types.SpecStatePassed:
Expand Down
110 changes: 110 additions & 0 deletions reporters/default_reporter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2951,4 +2951,114 @@ 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("Passed"))
})
})

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)"))
})
It("prints the failures section", func() {
Expect(buf.String()).To(ContainSubstring("FAIL!"))
})
})

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"))
Expect(buf.String()).To(ContainSubstring("\n\nDescribeB"))
})
})
})
})
3 changes: 3 additions & 0 deletions types/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ type ReporterConfig struct {
GithubOutput bool
SilenceSkips bool
ForceNewlines bool
FdOutput bool

JSONReport string
GoJSONReport string
Expand Down Expand Up @@ -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."},
Expand Down