2023-05-06 00:42:41 +00:00
|
|
|
/*
|
|
|
|
Ginkgo's Default Reporter
|
|
|
|
|
|
|
|
A number of command line flags are available to tweak Ginkgo's default output.
|
|
|
|
|
|
|
|
These are documented [here](http://onsi.github.io/ginkgo/#running_tests)
|
|
|
|
*/
|
|
|
|
package reporters
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"runtime"
|
|
|
|
"strings"
|
2023-12-04 09:49:00 +00:00
|
|
|
"sync"
|
2023-05-06 00:42:41 +00:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/onsi/ginkgo/v2/formatter"
|
|
|
|
"github.com/onsi/ginkgo/v2/types"
|
|
|
|
)
|
|
|
|
|
|
|
|
type DefaultReporter struct {
|
|
|
|
conf types.ReporterConfig
|
|
|
|
writer io.Writer
|
|
|
|
|
|
|
|
// managing the emission stream
|
2023-12-04 09:49:00 +00:00
|
|
|
lastCharWasNewline bool
|
2023-05-06 00:42:41 +00:00
|
|
|
lastEmissionWasDelimiter bool
|
|
|
|
|
|
|
|
// rendering
|
|
|
|
specDenoter string
|
|
|
|
retryDenoter string
|
|
|
|
formatter formatter.Formatter
|
2023-12-04 09:49:00 +00:00
|
|
|
|
|
|
|
runningInParallel bool
|
|
|
|
lock *sync.Mutex
|
2023-05-06 00:42:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func NewDefaultReporterUnderTest(conf types.ReporterConfig, writer io.Writer) *DefaultReporter {
|
|
|
|
reporter := NewDefaultReporter(conf, writer)
|
|
|
|
reporter.formatter = formatter.New(formatter.ColorModePassthrough)
|
|
|
|
|
|
|
|
return reporter
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewDefaultReporter(conf types.ReporterConfig, writer io.Writer) *DefaultReporter {
|
|
|
|
reporter := &DefaultReporter{
|
|
|
|
conf: conf,
|
|
|
|
writer: writer,
|
|
|
|
|
2023-12-04 09:49:00 +00:00
|
|
|
lastCharWasNewline: true,
|
2023-05-06 00:42:41 +00:00
|
|
|
lastEmissionWasDelimiter: false,
|
|
|
|
|
|
|
|
specDenoter: "•",
|
|
|
|
retryDenoter: "↺",
|
|
|
|
formatter: formatter.NewWithNoColorBool(conf.NoColor),
|
2023-12-04 09:49:00 +00:00
|
|
|
lock: &sync.Mutex{},
|
2023-05-06 00:42:41 +00:00
|
|
|
}
|
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
reporter.specDenoter = "+"
|
|
|
|
reporter.retryDenoter = "R"
|
|
|
|
}
|
|
|
|
|
|
|
|
return reporter
|
|
|
|
}
|
|
|
|
|
|
|
|
/* The Reporter Interface */
|
|
|
|
|
|
|
|
func (r *DefaultReporter) SuiteWillBegin(report types.Report) {
|
|
|
|
if r.conf.Verbosity().Is(types.VerbosityLevelSuccinct) {
|
|
|
|
r.emit(r.f("[%d] {{bold}}%s{{/}} ", report.SuiteConfig.RandomSeed, report.SuiteDescription))
|
|
|
|
if len(report.SuiteLabels) > 0 {
|
|
|
|
r.emit(r.f("{{coral}}[%s]{{/}} ", strings.Join(report.SuiteLabels, ", ")))
|
|
|
|
}
|
|
|
|
r.emit(r.f("- %d/%d specs ", report.PreRunStats.SpecsThatWillRun, report.PreRunStats.TotalSpecs))
|
|
|
|
if report.SuiteConfig.ParallelTotal > 1 {
|
|
|
|
r.emit(r.f("- %d procs ", report.SuiteConfig.ParallelTotal))
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
banner := r.f("Running Suite: %s - %s", report.SuiteDescription, report.SuitePath)
|
|
|
|
r.emitBlock(banner)
|
|
|
|
bannerWidth := len(banner)
|
|
|
|
if len(report.SuiteLabels) > 0 {
|
|
|
|
labels := strings.Join(report.SuiteLabels, ", ")
|
|
|
|
r.emitBlock(r.f("{{coral}}[%s]{{/}} ", labels))
|
|
|
|
if len(labels)+2 > bannerWidth {
|
|
|
|
bannerWidth = len(labels) + 2
|
|
|
|
}
|
|
|
|
}
|
|
|
|
r.emitBlock(strings.Repeat("=", bannerWidth))
|
|
|
|
|
|
|
|
out := r.f("Random Seed: {{bold}}%d{{/}}", report.SuiteConfig.RandomSeed)
|
|
|
|
if report.SuiteConfig.RandomizeAllSpecs {
|
|
|
|
out += r.f(" - will randomize all specs")
|
|
|
|
}
|
|
|
|
r.emitBlock(out)
|
|
|
|
r.emit("\n")
|
|
|
|
r.emitBlock(r.f("Will run {{bold}}%d{{/}} of {{bold}}%d{{/}} specs", report.PreRunStats.SpecsThatWillRun, report.PreRunStats.TotalSpecs))
|
|
|
|
if report.SuiteConfig.ParallelTotal > 1 {
|
|
|
|
r.emitBlock(r.f("Running in parallel across {{bold}}%d{{/}} processes", report.SuiteConfig.ParallelTotal))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-04 09:49:00 +00:00
|
|
|
func (r *DefaultReporter) SuiteDidEnd(report types.Report) {
|
|
|
|
failures := report.SpecReports.WithState(types.SpecStateFailureStates)
|
|
|
|
if len(failures) > 0 {
|
|
|
|
r.emitBlock("\n")
|
|
|
|
if len(failures) > 1 {
|
|
|
|
r.emitBlock(r.f("{{red}}{{bold}}Summarizing %d Failures:{{/}}", len(failures)))
|
|
|
|
} else {
|
|
|
|
r.emitBlock(r.f("{{red}}{{bold}}Summarizing 1 Failure:{{/}}"))
|
|
|
|
}
|
|
|
|
for _, specReport := range failures {
|
|
|
|
highlightColor, heading := "{{red}}", "[FAIL]"
|
|
|
|
switch specReport.State {
|
|
|
|
case types.SpecStatePanicked:
|
|
|
|
highlightColor, heading = "{{magenta}}", "[PANICKED!]"
|
|
|
|
case types.SpecStateAborted:
|
|
|
|
highlightColor, heading = "{{coral}}", "[ABORTED]"
|
|
|
|
case types.SpecStateTimedout:
|
|
|
|
highlightColor, heading = "{{orange}}", "[TIMEDOUT]"
|
|
|
|
case types.SpecStateInterrupted:
|
|
|
|
highlightColor, heading = "{{orange}}", "[INTERRUPTED]"
|
|
|
|
}
|
|
|
|
locationBlock := r.codeLocationBlock(specReport, highlightColor, false, true)
|
|
|
|
r.emitBlock(r.fi(1, highlightColor+"%s{{/}} %s", heading, locationBlock))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//summarize the suite
|
|
|
|
if r.conf.Verbosity().Is(types.VerbosityLevelSuccinct) && report.SuiteSucceeded {
|
|
|
|
r.emit(r.f(" {{green}}SUCCESS!{{/}} %s ", report.RunTime))
|
2023-05-06 00:42:41 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-12-04 09:49:00 +00:00
|
|
|
r.emitBlock("\n")
|
|
|
|
color, status := "{{green}}{{bold}}", "SUCCESS!"
|
|
|
|
if !report.SuiteSucceeded {
|
|
|
|
color, status = "{{red}}{{bold}}", "FAIL!"
|
|
|
|
}
|
|
|
|
|
|
|
|
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,
|
|
|
|
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"))
|
2023-05-06 00:42:41 +00:00
|
|
|
} else {
|
2023-12-04 09:49:00 +00:00
|
|
|
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()))
|
2023-05-06 00:42:41 +00:00
|
|
|
}
|
2023-12-04 09:49:00 +00:00
|
|
|
if specs.CountOfRepeatedSpecs() > 0 {
|
|
|
|
r.emit(r.f("{{light-yellow}}{{bold}}%d Repeated{{/}} | ", specs.CountOfRepeatedSpecs()))
|
2023-05-06 00:42:41 +00:00
|
|
|
}
|
2023-12-04 09:49:00 +00:00
|
|
|
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)))
|
2023-05-06 00:42:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-04 09:49:00 +00:00
|
|
|
func (r *DefaultReporter) WillRun(report types.SpecReport) {
|
2023-05-06 00:42:41 +00:00
|
|
|
v := r.conf.Verbosity()
|
2023-12-04 09:49:00 +00:00
|
|
|
if v.LT(types.VerbosityLevelVerbose) || report.State.Is(types.SpecStatePending|types.SpecStateSkipped) || report.RunningInParallel {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
r.emitDelimiter(0)
|
|
|
|
r.emitBlock(r.f(r.codeLocationBlock(report, "{{/}}", v.Is(types.VerbosityLevelVeryVerbose), false)))
|
|
|
|
}
|
2023-05-06 00:42:41 +00:00
|
|
|
|
2023-12-04 09:49:00 +00:00
|
|
|
func (r *DefaultReporter) DidRun(report types.SpecReport) {
|
|
|
|
v := r.conf.Verbosity()
|
|
|
|
inParallel := report.RunningInParallel
|
2023-05-06 00:42:41 +00:00
|
|
|
|
2023-12-04 09:49:00 +00:00
|
|
|
header := r.specDenoter
|
2023-05-06 00:42:41 +00:00
|
|
|
if report.LeafNodeType.Is(types.NodeTypesForSuiteLevelNodes) {
|
2023-12-04 09:49:00 +00:00
|
|
|
header = fmt.Sprintf("[%s]", report.LeafNodeType)
|
|
|
|
}
|
|
|
|
highlightColor := r.highlightColorForState(report.State)
|
|
|
|
|
|
|
|
// have we already been streaming the timeline?
|
|
|
|
timelineHasBeenStreaming := v.GTE(types.VerbosityLevelVerbose) && !inParallel
|
|
|
|
|
|
|
|
// should we show the timeline?
|
|
|
|
var timeline types.Timeline
|
|
|
|
showTimeline := !timelineHasBeenStreaming && (v.GTE(types.VerbosityLevelVerbose) || report.Failed())
|
|
|
|
if showTimeline {
|
|
|
|
timeline = report.Timeline().WithoutHiddenReportEntries()
|
|
|
|
keepVeryVerboseSpecEvents := v.Is(types.VerbosityLevelVeryVerbose) ||
|
|
|
|
(v.Is(types.VerbosityLevelVerbose) && r.conf.ShowNodeEvents) ||
|
|
|
|
(report.Failed() && r.conf.ShowNodeEvents)
|
|
|
|
if !keepVeryVerboseSpecEvents {
|
|
|
|
timeline = timeline.WithoutVeryVerboseSpecEvents()
|
|
|
|
}
|
|
|
|
if len(timeline) == 0 && report.CapturedGinkgoWriterOutput == "" {
|
|
|
|
// the timeline is completely empty - don't show it
|
|
|
|
showTimeline = false
|
|
|
|
}
|
|
|
|
if v.LT(types.VerbosityLevelVeryVerbose) && report.CapturedGinkgoWriterOutput == "" && len(timeline) > 0 {
|
|
|
|
//if we aren't -vv and the timeline only has a single failure, don't show it as it will appear at the end of the report
|
|
|
|
failure, isFailure := timeline[0].(types.Failure)
|
|
|
|
if isFailure && (len(timeline) == 1 || (len(timeline) == 2 && failure.AdditionalFailure != nil)) {
|
|
|
|
showTimeline = false
|
|
|
|
}
|
|
|
|
}
|
2023-05-06 00:42:41 +00:00
|
|
|
}
|
|
|
|
|
2023-12-04 09:49:00 +00:00
|
|
|
// should we have a separate section for always-visible reports?
|
|
|
|
showSeparateVisibilityAlwaysReportsSection := !timelineHasBeenStreaming && !showTimeline && report.ReportEntries.HasVisibility(types.ReportEntryVisibilityAlways)
|
|
|
|
|
|
|
|
// should we have a separate section for captured stdout/stderr
|
|
|
|
showSeparateStdSection := inParallel && (report.CapturedStdOutErr != "")
|
|
|
|
|
|
|
|
// given all that - do we have any actual content to show? or are we a single denoter in a stream?
|
|
|
|
reportHasContent := v.Is(types.VerbosityLevelVeryVerbose) || showTimeline || showSeparateVisibilityAlwaysReportsSection || showSeparateStdSection || report.Failed() || (v.Is(types.VerbosityLevelVerbose) && !report.State.Is(types.SpecStateSkipped))
|
|
|
|
|
|
|
|
// should we show a runtime?
|
|
|
|
includeRuntime := !report.State.Is(types.SpecStateSkipped|types.SpecStatePending) || (report.State.Is(types.SpecStateSkipped) && report.Failure.Message != "")
|
|
|
|
|
|
|
|
// should we show the codelocation block?
|
|
|
|
showCodeLocation := !timelineHasBeenStreaming || !report.State.Is(types.SpecStatePassed)
|
2023-05-06 00:42:41 +00:00
|
|
|
|
|
|
|
switch report.State {
|
|
|
|
case types.SpecStatePassed:
|
2023-12-04 09:49:00 +00:00
|
|
|
if report.LeafNodeType.Is(types.NodeTypesForSuiteLevelNodes) && !reportHasContent {
|
|
|
|
return
|
|
|
|
}
|
2023-05-06 00:42:41 +00:00
|
|
|
if report.LeafNodeType.Is(types.NodeTypesForSuiteLevelNodes) {
|
2023-12-04 09:49:00 +00:00
|
|
|
header = fmt.Sprintf("%s PASSED", header)
|
2023-05-06 00:42:41 +00:00
|
|
|
}
|
2023-12-04 09:49:00 +00:00
|
|
|
if report.NumAttempts > 1 && report.MaxFlakeAttempts > 1 {
|
|
|
|
header, reportHasContent = fmt.Sprintf("%s [FLAKEY TEST - TOOK %d ATTEMPTS TO PASS]", r.retryDenoter, report.NumAttempts), true
|
2023-05-06 00:42:41 +00:00
|
|
|
}
|
|
|
|
case types.SpecStatePending:
|
2023-12-04 09:49:00 +00:00
|
|
|
header = "P"
|
|
|
|
if v.GT(types.VerbosityLevelSuccinct) {
|
|
|
|
header, reportHasContent = "P [PENDING]", true
|
2023-05-06 00:42:41 +00:00
|
|
|
}
|
|
|
|
case types.SpecStateSkipped:
|
2023-12-04 09:49:00 +00:00
|
|
|
header = "S"
|
|
|
|
if v.Is(types.VerbosityLevelVeryVerbose) || (v.Is(types.VerbosityLevelVerbose) && report.Failure.Message != "") {
|
|
|
|
header, reportHasContent = "S [SKIPPED]", true
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
header = fmt.Sprintf("%s [%s]", header, r.humanReadableState(report.State))
|
|
|
|
if report.MaxMustPassRepeatedly > 1 {
|
|
|
|
header = fmt.Sprintf("%s DURING REPETITION #%d", header, report.NumAttempts)
|
2023-05-06 00:42:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-04 09:49:00 +00:00
|
|
|
// If we have no content to show, jsut emit the header and return
|
|
|
|
if !reportHasContent {
|
2023-05-06 00:42:41 +00:00
|
|
|
r.emit(r.f(highlightColor + header + "{{/}}"))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if includeRuntime {
|
|
|
|
header = r.f("%s [%.3f seconds]", header, report.RunTime.Seconds())
|
|
|
|
}
|
|
|
|
|
2023-12-04 09:49:00 +00:00
|
|
|
// Emit header
|
|
|
|
if !timelineHasBeenStreaming {
|
|
|
|
r.emitDelimiter(0)
|
|
|
|
}
|
|
|
|
r.emitBlock(r.f(highlightColor + header + "{{/}}"))
|
|
|
|
if showCodeLocation {
|
|
|
|
r.emitBlock(r.codeLocationBlock(report, highlightColor, v.Is(types.VerbosityLevelVeryVerbose), false))
|
|
|
|
}
|
2023-05-06 00:42:41 +00:00
|
|
|
|
|
|
|
//Emit Stdout/Stderr Output
|
2023-12-04 09:49:00 +00:00
|
|
|
if showSeparateStdSection {
|
2023-05-06 00:42:41 +00:00
|
|
|
r.emitBlock("\n")
|
2023-12-04 09:49:00 +00:00
|
|
|
r.emitBlock(r.fi(1, "{{gray}}Captured StdOut/StdErr Output >>{{/}}"))
|
|
|
|
r.emitBlock(r.fi(1, "%s", report.CapturedStdOutErr))
|
|
|
|
r.emitBlock(r.fi(1, "{{gray}}<< Captured StdOut/StdErr Output{{/}}"))
|
2023-05-06 00:42:41 +00:00
|
|
|
}
|
|
|
|
|
2023-12-04 09:49:00 +00:00
|
|
|
if showSeparateVisibilityAlwaysReportsSection {
|
2023-05-06 00:42:41 +00:00
|
|
|
r.emitBlock("\n")
|
2023-12-04 09:49:00 +00:00
|
|
|
r.emitBlock(r.fi(1, "{{gray}}Report Entries >>{{/}}"))
|
|
|
|
for _, entry := range report.ReportEntries.WithVisibility(types.ReportEntryVisibilityAlways) {
|
|
|
|
r.emitReportEntry(1, entry)
|
|
|
|
}
|
|
|
|
r.emitBlock(r.fi(1, "{{gray}}<< Report Entries{{/}}"))
|
2023-05-06 00:42:41 +00:00
|
|
|
}
|
|
|
|
|
2023-12-04 09:49:00 +00:00
|
|
|
if showTimeline {
|
2023-05-06 00:42:41 +00:00
|
|
|
r.emitBlock("\n")
|
2023-12-04 09:49:00 +00:00
|
|
|
r.emitBlock(r.fi(1, "{{gray}}Timeline >>{{/}}"))
|
|
|
|
r.emitTimeline(1, report, timeline)
|
|
|
|
r.emitBlock(r.fi(1, "{{gray}}<< Timeline{{/}}"))
|
2023-05-06 00:42:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Emit Failure Message
|
2023-12-04 09:49:00 +00:00
|
|
|
if !report.Failure.IsZero() && !v.Is(types.VerbosityLevelVeryVerbose) {
|
2023-05-06 00:42:41 +00:00
|
|
|
r.emitBlock("\n")
|
2023-12-04 09:49:00 +00:00
|
|
|
r.emitFailure(1, report.State, report.Failure, true)
|
|
|
|
if len(report.AdditionalFailures) > 0 {
|
|
|
|
r.emitBlock(r.fi(1, "\nThere were {{bold}}{{red}}additional failures{{/}} detected. To view them in detail run {{bold}}ginkgo -vv{{/}}"))
|
2023-05-06 00:42:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-04 09:49:00 +00:00
|
|
|
r.emitDelimiter(0)
|
2023-05-06 00:42:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (r *DefaultReporter) highlightColorForState(state types.SpecState) string {
|
|
|
|
switch state {
|
|
|
|
case types.SpecStatePassed:
|
|
|
|
return "{{green}}"
|
|
|
|
case types.SpecStatePending:
|
|
|
|
return "{{yellow}}"
|
|
|
|
case types.SpecStateSkipped:
|
|
|
|
return "{{cyan}}"
|
|
|
|
case types.SpecStateFailed:
|
|
|
|
return "{{red}}"
|
|
|
|
case types.SpecStateTimedout:
|
|
|
|
return "{{orange}}"
|
|
|
|
case types.SpecStatePanicked:
|
|
|
|
return "{{magenta}}"
|
|
|
|
case types.SpecStateInterrupted:
|
|
|
|
return "{{orange}}"
|
|
|
|
case types.SpecStateAborted:
|
|
|
|
return "{{coral}}"
|
|
|
|
default:
|
|
|
|
return "{{gray}}"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *DefaultReporter) humanReadableState(state types.SpecState) string {
|
|
|
|
return strings.ToUpper(state.String())
|
|
|
|
}
|
|
|
|
|
2023-12-04 09:49:00 +00:00
|
|
|
func (r *DefaultReporter) emitTimeline(indent uint, report types.SpecReport, timeline types.Timeline) {
|
|
|
|
isVeryVerbose := r.conf.Verbosity().Is(types.VerbosityLevelVeryVerbose)
|
|
|
|
gw := report.CapturedGinkgoWriterOutput
|
|
|
|
cursor := 0
|
|
|
|
for _, entry := range timeline {
|
|
|
|
tl := entry.GetTimelineLocation()
|
|
|
|
if tl.Offset < len(gw) {
|
|
|
|
r.emit(r.fi(indent, "%s", gw[cursor:tl.Offset]))
|
|
|
|
cursor = tl.Offset
|
|
|
|
} else if cursor < len(gw) {
|
|
|
|
r.emit(r.fi(indent, "%s", gw[cursor:]))
|
|
|
|
cursor = len(gw)
|
|
|
|
}
|
|
|
|
switch x := entry.(type) {
|
|
|
|
case types.Failure:
|
|
|
|
if isVeryVerbose {
|
|
|
|
r.emitFailure(indent, report.State, x, false)
|
|
|
|
} else {
|
|
|
|
r.emitShortFailure(indent, report.State, x)
|
|
|
|
}
|
|
|
|
case types.AdditionalFailure:
|
|
|
|
if isVeryVerbose {
|
|
|
|
r.emitFailure(indent, x.State, x.Failure, true)
|
|
|
|
} else {
|
|
|
|
r.emitShortFailure(indent, x.State, x.Failure)
|
|
|
|
}
|
|
|
|
case types.ReportEntry:
|
|
|
|
r.emitReportEntry(indent, x)
|
|
|
|
case types.ProgressReport:
|
|
|
|
r.emitProgressReport(indent, false, x)
|
|
|
|
case types.SpecEvent:
|
|
|
|
if isVeryVerbose || !x.IsOnlyVisibleAtVeryVerbose() || r.conf.ShowNodeEvents {
|
|
|
|
r.emitSpecEvent(indent, x, isVeryVerbose)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if cursor < len(gw) {
|
|
|
|
r.emit(r.fi(indent, "%s", gw[cursor:]))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *DefaultReporter) EmitFailure(state types.SpecState, failure types.Failure) {
|
|
|
|
if r.conf.Verbosity().Is(types.VerbosityLevelVerbose) {
|
|
|
|
r.emitShortFailure(1, state, failure)
|
|
|
|
} else if r.conf.Verbosity().Is(types.VerbosityLevelVeryVerbose) {
|
|
|
|
r.emitFailure(1, state, failure, true)
|
2023-05-06 00:42:41 +00:00
|
|
|
}
|
2023-12-04 09:49:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (r *DefaultReporter) emitShortFailure(indent uint, state types.SpecState, failure types.Failure) {
|
|
|
|
r.emitBlock(r.fi(indent, r.highlightColorForState(state)+"[%s]{{/}} in [%s] - %s {{gray}}@ %s{{/}}",
|
|
|
|
r.humanReadableState(state),
|
|
|
|
failure.FailureNodeType,
|
|
|
|
failure.Location,
|
|
|
|
failure.TimelineLocation.Time.Format(types.GINKGO_TIME_FORMAT),
|
|
|
|
))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *DefaultReporter) emitFailure(indent uint, state types.SpecState, failure types.Failure, includeAdditionalFailure bool) {
|
|
|
|
highlightColor := r.highlightColorForState(state)
|
|
|
|
r.emitBlock(r.fi(indent, highlightColor+"[%s] %s{{/}}", r.humanReadableState(state), failure.Message))
|
|
|
|
r.emitBlock(r.fi(indent, highlightColor+"In {{bold}}[%s]{{/}}"+highlightColor+" at: {{bold}}%s{{/}} {{gray}}@ %s{{/}}\n", failure.FailureNodeType, failure.Location, failure.TimelineLocation.Time.Format(types.GINKGO_TIME_FORMAT)))
|
2023-05-06 00:42:41 +00:00
|
|
|
if failure.ForwardedPanic != "" {
|
|
|
|
r.emitBlock("\n")
|
|
|
|
r.emitBlock(r.fi(indent, highlightColor+"%s{{/}}", failure.ForwardedPanic))
|
|
|
|
}
|
|
|
|
|
|
|
|
if r.conf.FullTrace || failure.ForwardedPanic != "" {
|
|
|
|
r.emitBlock("\n")
|
|
|
|
r.emitBlock(r.fi(indent, highlightColor+"Full Stack Trace{{/}}"))
|
|
|
|
r.emitBlock(r.fi(indent+1, "%s", failure.Location.FullStackTrace))
|
|
|
|
}
|
|
|
|
|
|
|
|
if !failure.ProgressReport.IsZero() {
|
|
|
|
r.emitBlock("\n")
|
|
|
|
r.emitProgressReport(indent, false, failure.ProgressReport)
|
|
|
|
}
|
|
|
|
|
2023-12-04 09:49:00 +00:00
|
|
|
if failure.AdditionalFailure != nil && includeAdditionalFailure {
|
|
|
|
r.emitBlock("\n")
|
|
|
|
r.emitFailure(indent, failure.AdditionalFailure.State, failure.AdditionalFailure.Failure, true)
|
2023-05-06 00:42:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *DefaultReporter) EmitProgressReport(report types.ProgressReport) {
|
2023-12-04 09:49:00 +00:00
|
|
|
r.emitDelimiter(1)
|
2023-05-06 00:42:41 +00:00
|
|
|
|
|
|
|
if report.RunningInParallel {
|
2023-12-04 09:49:00 +00:00
|
|
|
r.emit(r.fi(1, "{{coral}}Progress Report for Ginkgo Process #{{bold}}%d{{/}}\n", report.ParallelProcess))
|
2023-05-06 00:42:41 +00:00
|
|
|
}
|
2023-12-04 09:49:00 +00:00
|
|
|
shouldEmitGW := report.RunningInParallel || r.conf.Verbosity().LT(types.VerbosityLevelVerbose)
|
|
|
|
r.emitProgressReport(1, shouldEmitGW, report)
|
|
|
|
r.emitDelimiter(1)
|
2023-05-06 00:42:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (r *DefaultReporter) emitProgressReport(indent uint, emitGinkgoWriterOutput bool, report types.ProgressReport) {
|
|
|
|
if report.Message != "" {
|
|
|
|
r.emitBlock(r.fi(indent, report.Message+"\n"))
|
|
|
|
indent += 1
|
|
|
|
}
|
|
|
|
if report.LeafNodeText != "" {
|
|
|
|
subjectIndent := indent
|
|
|
|
if len(report.ContainerHierarchyTexts) > 0 {
|
|
|
|
r.emit(r.fi(indent, r.cycleJoin(report.ContainerHierarchyTexts, " ")))
|
|
|
|
r.emit(" ")
|
|
|
|
subjectIndent = 0
|
|
|
|
}
|
2023-12-04 09:49:00 +00:00
|
|
|
r.emit(r.fi(subjectIndent, "{{bold}}{{orange}}%s{{/}} (Spec Runtime: %s)\n", report.LeafNodeText, report.Time().Sub(report.SpecStartTime).Round(time.Millisecond)))
|
2023-05-06 00:42:41 +00:00
|
|
|
r.emit(r.fi(indent+1, "{{gray}}%s{{/}}\n", report.LeafNodeLocation))
|
|
|
|
indent += 1
|
|
|
|
}
|
|
|
|
if report.CurrentNodeType != types.NodeTypeInvalid {
|
|
|
|
r.emit(r.fi(indent, "In {{bold}}{{orange}}[%s]{{/}}", report.CurrentNodeType))
|
|
|
|
if report.CurrentNodeText != "" && !report.CurrentNodeType.Is(types.NodeTypeIt) {
|
|
|
|
r.emit(r.f(" {{bold}}{{orange}}%s{{/}}", report.CurrentNodeText))
|
|
|
|
}
|
|
|
|
|
2023-12-04 09:49:00 +00:00
|
|
|
r.emit(r.f(" (Node Runtime: %s)\n", report.Time().Sub(report.CurrentNodeStartTime).Round(time.Millisecond)))
|
2023-05-06 00:42:41 +00:00
|
|
|
r.emit(r.fi(indent+1, "{{gray}}%s{{/}}\n", report.CurrentNodeLocation))
|
|
|
|
indent += 1
|
|
|
|
}
|
|
|
|
if report.CurrentStepText != "" {
|
2023-12-04 09:49:00 +00:00
|
|
|
r.emit(r.fi(indent, "At {{bold}}{{orange}}[By Step] %s{{/}} (Step Runtime: %s)\n", report.CurrentStepText, report.Time().Sub(report.CurrentStepStartTime).Round(time.Millisecond)))
|
2023-05-06 00:42:41 +00:00
|
|
|
r.emit(r.fi(indent+1, "{{gray}}%s{{/}}\n", report.CurrentStepLocation))
|
|
|
|
indent += 1
|
|
|
|
}
|
|
|
|
|
|
|
|
if indent > 0 {
|
|
|
|
indent -= 1
|
|
|
|
}
|
|
|
|
|
2023-12-04 09:49:00 +00:00
|
|
|
if emitGinkgoWriterOutput && report.CapturedGinkgoWriterOutput != "" {
|
2023-05-06 00:42:41 +00:00
|
|
|
r.emit("\n")
|
2023-12-04 09:49:00 +00:00
|
|
|
r.emitBlock(r.fi(indent, "{{gray}}Begin Captured GinkgoWriter Output >>{{/}}"))
|
|
|
|
limit, lines := 10, strings.Split(report.CapturedGinkgoWriterOutput, "\n")
|
|
|
|
if len(lines) <= limit {
|
|
|
|
r.emitBlock(r.fi(indent+1, "%s", report.CapturedGinkgoWriterOutput))
|
|
|
|
} else {
|
|
|
|
r.emitBlock(r.fi(indent+1, "{{gray}}...{{/}}"))
|
|
|
|
for _, line := range lines[len(lines)-limit-1:] {
|
|
|
|
r.emitBlock(r.fi(indent+1, "%s", line))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
r.emitBlock(r.fi(indent, "{{gray}}<< End Captured GinkgoWriter Output{{/}}"))
|
2023-05-06 00:42:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if !report.SpecGoroutine().IsZero() {
|
|
|
|
r.emit("\n")
|
|
|
|
r.emit(r.fi(indent, "{{bold}}{{underline}}Spec Goroutine{{/}}\n"))
|
|
|
|
r.emitGoroutines(indent, report.SpecGoroutine())
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(report.AdditionalReports) > 0 {
|
|
|
|
r.emit("\n")
|
|
|
|
r.emitBlock(r.fi(indent, "{{gray}}Begin Additional Progress Reports >>{{/}}"))
|
|
|
|
for i, additionalReport := range report.AdditionalReports {
|
|
|
|
r.emit(r.fi(indent+1, additionalReport))
|
|
|
|
if i < len(report.AdditionalReports)-1 {
|
|
|
|
r.emitBlock(r.fi(indent+1, "{{gray}}%s{{/}}", strings.Repeat("-", 10)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
r.emitBlock(r.fi(indent, "{{gray}}<< End Additional Progress Reports{{/}}"))
|
|
|
|
}
|
|
|
|
|
|
|
|
highlightedGoroutines := report.HighlightedGoroutines()
|
|
|
|
if len(highlightedGoroutines) > 0 {
|
|
|
|
r.emit("\n")
|
|
|
|
r.emit(r.fi(indent, "{{bold}}{{underline}}Goroutines of Interest{{/}}\n"))
|
|
|
|
r.emitGoroutines(indent, highlightedGoroutines...)
|
|
|
|
}
|
|
|
|
|
|
|
|
otherGoroutines := report.OtherGoroutines()
|
|
|
|
if len(otherGoroutines) > 0 {
|
|
|
|
r.emit("\n")
|
|
|
|
r.emit(r.fi(indent, "{{gray}}{{bold}}{{underline}}Other Goroutines{{/}}\n"))
|
|
|
|
r.emitGoroutines(indent, otherGoroutines...)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-04 09:49:00 +00:00
|
|
|
func (r *DefaultReporter) EmitReportEntry(entry types.ReportEntry) {
|
|
|
|
if r.conf.Verbosity().LT(types.VerbosityLevelVerbose) || entry.Visibility == types.ReportEntryVisibilityNever {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
r.emitReportEntry(1, entry)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *DefaultReporter) emitReportEntry(indent uint, entry types.ReportEntry) {
|
|
|
|
r.emitBlock(r.fi(indent, "{{bold}}"+entry.Name+"{{gray}} "+fmt.Sprintf("- %s @ %s{{/}}", entry.Location, entry.Time.Format(types.GINKGO_TIME_FORMAT))))
|
|
|
|
if representation := entry.StringRepresentation(); representation != "" {
|
|
|
|
r.emitBlock(r.fi(indent+1, representation))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *DefaultReporter) EmitSpecEvent(event types.SpecEvent) {
|
|
|
|
v := r.conf.Verbosity()
|
|
|
|
if v.Is(types.VerbosityLevelVeryVerbose) || (v.Is(types.VerbosityLevelVerbose) && (r.conf.ShowNodeEvents || !event.IsOnlyVisibleAtVeryVerbose())) {
|
|
|
|
r.emitSpecEvent(1, event, r.conf.Verbosity().Is(types.VerbosityLevelVeryVerbose))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *DefaultReporter) emitSpecEvent(indent uint, event types.SpecEvent, includeLocation bool) {
|
|
|
|
location := ""
|
|
|
|
if includeLocation {
|
|
|
|
location = fmt.Sprintf("- %s ", event.CodeLocation.String())
|
|
|
|
}
|
|
|
|
switch event.SpecEventType {
|
|
|
|
case types.SpecEventInvalid:
|
|
|
|
return
|
|
|
|
case types.SpecEventByStart:
|
|
|
|
r.emitBlock(r.fi(indent, "{{bold}}STEP:{{/}} %s {{gray}}%s@ %s{{/}}", event.Message, location, event.TimelineLocation.Time.Format(types.GINKGO_TIME_FORMAT)))
|
|
|
|
case types.SpecEventByEnd:
|
|
|
|
r.emitBlock(r.fi(indent, "{{bold}}END STEP:{{/}} %s {{gray}}%s@ %s (%s){{/}}", event.Message, location, event.TimelineLocation.Time.Format(types.GINKGO_TIME_FORMAT), event.Duration.Round(time.Millisecond)))
|
|
|
|
case types.SpecEventNodeStart:
|
|
|
|
r.emitBlock(r.fi(indent, "> Enter {{bold}}[%s]{{/}} %s {{gray}}%s@ %s{{/}}", event.NodeType.String(), event.Message, location, event.TimelineLocation.Time.Format(types.GINKGO_TIME_FORMAT)))
|
|
|
|
case types.SpecEventNodeEnd:
|
|
|
|
r.emitBlock(r.fi(indent, "< Exit {{bold}}[%s]{{/}} %s {{gray}}%s@ %s (%s){{/}}", event.NodeType.String(), event.Message, location, event.TimelineLocation.Time.Format(types.GINKGO_TIME_FORMAT), event.Duration.Round(time.Millisecond)))
|
|
|
|
case types.SpecEventSpecRepeat:
|
|
|
|
r.emitBlock(r.fi(indent, "\n{{bold}}Attempt #%d {{green}}Passed{{/}}{{bold}}. Repeating %s{{/}} {{gray}}@ %s{{/}}\n\n", event.Attempt, r.retryDenoter, event.TimelineLocation.Time.Format(types.GINKGO_TIME_FORMAT)))
|
|
|
|
case types.SpecEventSpecRetry:
|
|
|
|
r.emitBlock(r.fi(indent, "\n{{bold}}Attempt #%d {{red}}Failed{{/}}{{bold}}. Retrying %s{{/}} {{gray}}@ %s{{/}}\n\n", event.Attempt, r.retryDenoter, event.TimelineLocation.Time.Format(types.GINKGO_TIME_FORMAT)))
|
2023-05-06 00:42:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *DefaultReporter) emitGoroutines(indent uint, goroutines ...types.Goroutine) {
|
|
|
|
for idx, g := range goroutines {
|
|
|
|
color := "{{gray}}"
|
|
|
|
if g.HasHighlights() {
|
|
|
|
color = "{{orange}}"
|
|
|
|
}
|
|
|
|
r.emit(r.fi(indent, color+"goroutine %d [%s]{{/}}\n", g.ID, g.State))
|
|
|
|
for _, fc := range g.Stack {
|
|
|
|
if fc.Highlight {
|
|
|
|
r.emit(r.fi(indent, color+"{{bold}}> %s{{/}}\n", fc.Function))
|
|
|
|
r.emit(r.fi(indent+2, color+"{{bold}}%s:%d{{/}}\n", fc.Filename, fc.Line))
|
|
|
|
r.emitSource(indent+3, fc)
|
|
|
|
} else {
|
|
|
|
r.emit(r.fi(indent+1, "{{gray}}%s{{/}}\n", fc.Function))
|
|
|
|
r.emit(r.fi(indent+2, "{{gray}}%s:%d{{/}}\n", fc.Filename, fc.Line))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if idx+1 < len(goroutines) {
|
|
|
|
r.emit("\n")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *DefaultReporter) emitSource(indent uint, fc types.FunctionCall) {
|
|
|
|
lines := fc.Source
|
|
|
|
if len(lines) == 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
lTrim := 100000
|
|
|
|
for _, line := range lines {
|
|
|
|
lTrimLine := len(line) - len(strings.TrimLeft(line, " \t"))
|
|
|
|
if lTrimLine < lTrim && len(line) > 0 {
|
|
|
|
lTrim = lTrimLine
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if lTrim == 100000 {
|
|
|
|
lTrim = 0
|
|
|
|
}
|
|
|
|
|
|
|
|
for idx, line := range lines {
|
|
|
|
if len(line) > lTrim {
|
|
|
|
line = line[lTrim:]
|
|
|
|
}
|
|
|
|
if idx == fc.SourceHighlight {
|
|
|
|
r.emit(r.fi(indent, "{{bold}}{{orange}}> %s{{/}}\n", line))
|
|
|
|
} else {
|
|
|
|
r.emit(r.fi(indent, "| %s\n", line))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Emitting to the writer */
|
|
|
|
func (r *DefaultReporter) emit(s string) {
|
2023-12-04 09:49:00 +00:00
|
|
|
r._emit(s, false, false)
|
2023-05-06 00:42:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (r *DefaultReporter) emitBlock(s string) {
|
2023-12-04 09:49:00 +00:00
|
|
|
r._emit(s, true, false)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *DefaultReporter) emitDelimiter(indent uint) {
|
|
|
|
r._emit(r.fi(indent, "{{gray}}%s{{/}}", strings.Repeat("-", 30)), true, true)
|
2023-05-06 00:42:41 +00:00
|
|
|
}
|
|
|
|
|
2023-12-04 09:49:00 +00:00
|
|
|
// a bit ugly - but we're trying to minimize locking on this hot codepath
|
|
|
|
func (r *DefaultReporter) _emit(s string, block bool, isDelimiter bool) {
|
|
|
|
if len(s) == 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
r.lock.Lock()
|
|
|
|
defer r.lock.Unlock()
|
|
|
|
if isDelimiter && r.lastEmissionWasDelimiter {
|
2023-05-06 00:42:41 +00:00
|
|
|
return
|
|
|
|
}
|
2023-12-04 09:49:00 +00:00
|
|
|
if block && !r.lastCharWasNewline {
|
|
|
|
r.writer.Write([]byte("\n"))
|
|
|
|
}
|
|
|
|
r.lastCharWasNewline = (s[len(s)-1:] == "\n")
|
|
|
|
r.writer.Write([]byte(s))
|
|
|
|
if block && !r.lastCharWasNewline {
|
|
|
|
r.writer.Write([]byte("\n"))
|
|
|
|
r.lastCharWasNewline = true
|
|
|
|
}
|
|
|
|
r.lastEmissionWasDelimiter = isDelimiter
|
2023-05-06 00:42:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Rendering text */
|
|
|
|
func (r *DefaultReporter) f(format string, args ...interface{}) string {
|
|
|
|
return r.formatter.F(format, args...)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *DefaultReporter) fi(indentation uint, format string, args ...interface{}) string {
|
|
|
|
return r.formatter.Fi(indentation, format, args...)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *DefaultReporter) cycleJoin(elements []string, joiner string) string {
|
|
|
|
return r.formatter.CycleJoin(elements, joiner, []string{"{{/}}", "{{gray}}"})
|
|
|
|
}
|
|
|
|
|
2023-12-04 09:49:00 +00:00
|
|
|
func (r *DefaultReporter) codeLocationBlock(report types.SpecReport, highlightColor string, veryVerbose bool, usePreciseFailureLocation bool) string {
|
2023-05-06 00:42:41 +00:00
|
|
|
texts, locations, labels := []string{}, []types.CodeLocation{}, [][]string{}
|
|
|
|
texts, locations, labels = append(texts, report.ContainerHierarchyTexts...), append(locations, report.ContainerHierarchyLocations...), append(labels, report.ContainerHierarchyLabels...)
|
2023-12-04 09:49:00 +00:00
|
|
|
|
2023-05-06 00:42:41 +00:00
|
|
|
if report.LeafNodeType.Is(types.NodeTypesForSuiteLevelNodes) {
|
|
|
|
texts = append(texts, r.f("[%s] %s", report.LeafNodeType, report.LeafNodeText))
|
|
|
|
} else {
|
2023-12-04 09:49:00 +00:00
|
|
|
texts = append(texts, r.f(report.LeafNodeText))
|
2023-05-06 00:42:41 +00:00
|
|
|
}
|
|
|
|
labels = append(labels, report.LeafNodeLabels)
|
|
|
|
locations = append(locations, report.LeafNodeLocation)
|
|
|
|
|
|
|
|
failureLocation := report.Failure.FailureNodeLocation
|
|
|
|
if usePreciseFailureLocation {
|
|
|
|
failureLocation = report.Failure.Location
|
|
|
|
}
|
|
|
|
|
2023-12-04 09:49:00 +00:00
|
|
|
highlightIndex := -1
|
2023-05-06 00:42:41 +00:00
|
|
|
switch report.Failure.FailureNodeContext {
|
|
|
|
case types.FailureNodeAtTopLevel:
|
2023-12-04 09:49:00 +00:00
|
|
|
texts = append([]string{fmt.Sprintf("TOP-LEVEL [%s]", report.Failure.FailureNodeType)}, texts...)
|
2023-05-06 00:42:41 +00:00
|
|
|
locations = append([]types.CodeLocation{failureLocation}, locations...)
|
|
|
|
labels = append([][]string{{}}, labels...)
|
2023-12-04 09:49:00 +00:00
|
|
|
highlightIndex = 0
|
2023-05-06 00:42:41 +00:00
|
|
|
case types.FailureNodeInContainer:
|
|
|
|
i := report.Failure.FailureNodeContainerIndex
|
2023-12-04 09:49:00 +00:00
|
|
|
texts[i] = fmt.Sprintf("%s [%s]", texts[i], report.Failure.FailureNodeType)
|
2023-05-06 00:42:41 +00:00
|
|
|
locations[i] = failureLocation
|
2023-12-04 09:49:00 +00:00
|
|
|
highlightIndex = i
|
2023-05-06 00:42:41 +00:00
|
|
|
case types.FailureNodeIsLeafNode:
|
|
|
|
i := len(texts) - 1
|
2023-12-04 09:49:00 +00:00
|
|
|
texts[i] = fmt.Sprintf("[%s] %s", report.LeafNodeType, report.LeafNodeText)
|
2023-05-06 00:42:41 +00:00
|
|
|
locations[i] = failureLocation
|
2023-12-04 09:49:00 +00:00
|
|
|
highlightIndex = i
|
|
|
|
default:
|
|
|
|
//there is no failure, so we highlight the leaf ndoe
|
|
|
|
highlightIndex = len(texts) - 1
|
2023-05-06 00:42:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
out := ""
|
2023-12-04 09:49:00 +00:00
|
|
|
if veryVerbose {
|
|
|
|
for i := range texts {
|
|
|
|
if i == highlightIndex {
|
|
|
|
out += r.fi(uint(i), highlightColor+"{{bold}}%s{{/}}", texts[i])
|
|
|
|
} else {
|
|
|
|
out += r.fi(uint(i), "%s", texts[i])
|
|
|
|
}
|
|
|
|
if len(labels[i]) > 0 {
|
|
|
|
out += r.f(" {{coral}}[%s]{{/}}", strings.Join(labels[i], ", "))
|
|
|
|
}
|
|
|
|
out += "\n"
|
|
|
|
out += r.fi(uint(i), "{{gray}}%s{{/}}\n", locations[i])
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for i := range texts {
|
|
|
|
style := "{{/}}"
|
|
|
|
if i%2 == 1 {
|
|
|
|
style = "{{gray}}"
|
|
|
|
}
|
|
|
|
if i == highlightIndex {
|
|
|
|
style = highlightColor + "{{bold}}"
|
|
|
|
}
|
|
|
|
out += r.f(style+"%s", texts[i])
|
|
|
|
if i < len(texts)-1 {
|
|
|
|
out += " "
|
|
|
|
} else {
|
|
|
|
out += r.f("{{/}}")
|
|
|
|
}
|
|
|
|
}
|
2023-05-06 00:42:41 +00:00
|
|
|
flattenedLabels := report.Labels()
|
|
|
|
if len(flattenedLabels) > 0 {
|
|
|
|
out += r.f(" {{coral}}[%s]{{/}}", strings.Join(flattenedLabels, ", "))
|
|
|
|
}
|
|
|
|
out += "\n"
|
|
|
|
if usePreciseFailureLocation {
|
|
|
|
out += r.f("{{gray}}%s{{/}}", failureLocation)
|
|
|
|
} else {
|
2023-12-04 09:49:00 +00:00
|
|
|
leafLocation := locations[len(locations)-1]
|
|
|
|
if (report.Failure.FailureNodeLocation != types.CodeLocation{}) && (report.Failure.FailureNodeLocation != leafLocation) {
|
|
|
|
out += r.fi(1, highlightColor+"[%s]{{/}} {{gray}}%s{{/}}\n", report.Failure.FailureNodeType, report.Failure.FailureNodeLocation)
|
|
|
|
out += r.fi(1, "{{gray}}[%s] %s{{/}}", report.LeafNodeType, leafLocation)
|
|
|
|
} else {
|
|
|
|
out += r.f("{{gray}}%s{{/}}", leafLocation)
|
2023-05-06 00:42:41 +00:00
|
|
|
}
|
|
|
|
}
|
2023-12-04 09:49:00 +00:00
|
|
|
|
2023-05-06 00:42:41 +00:00
|
|
|
}
|
|
|
|
return out
|
|
|
|
}
|