1
8 package reporters
9
10 import (
11 "fmt"
12 "io"
13 "runtime"
14 "strings"
15 "sync"
16 "time"
17
18 "github.com/onsi/ginkgo/v2/formatter"
19 "github.com/onsi/ginkgo/v2/types"
20 )
21
22 type DefaultReporter struct {
23 conf types.ReporterConfig
24 writer io.Writer
25
26
27 lastCharWasNewline bool
28 lastEmissionWasDelimiter bool
29
30
31 specDenoter string
32 retryDenoter string
33 formatter formatter.Formatter
34
35 runningInParallel bool
36 lock *sync.Mutex
37 }
38
39 func NewDefaultReporterUnderTest(conf types.ReporterConfig, writer io.Writer) *DefaultReporter {
40 reporter := NewDefaultReporter(conf, writer)
41 reporter.formatter = formatter.New(formatter.ColorModePassthrough)
42
43 return reporter
44 }
45
46 func NewDefaultReporter(conf types.ReporterConfig, writer io.Writer) *DefaultReporter {
47 reporter := &DefaultReporter{
48 conf: conf,
49 writer: writer,
50
51 lastCharWasNewline: true,
52 lastEmissionWasDelimiter: false,
53
54 specDenoter: "•",
55 retryDenoter: "↺",
56 formatter: formatter.NewWithNoColorBool(conf.NoColor),
57 lock: &sync.Mutex{},
58 }
59 if runtime.GOOS == "windows" {
60 reporter.specDenoter = "+"
61 reporter.retryDenoter = "R"
62 }
63
64 return reporter
65 }
66
67
68
69 func (r *DefaultReporter) SuiteWillBegin(report types.Report) {
70 if r.conf.Verbosity().Is(types.VerbosityLevelSuccinct) {
71 r.emit(r.f("[%d] {{bold}}%s{{/}} ", report.SuiteConfig.RandomSeed, report.SuiteDescription))
72 if len(report.SuiteLabels) > 0 {
73 r.emit(r.f("{{coral}}[%s]{{/}} ", strings.Join(report.SuiteLabels, ", ")))
74 }
75 r.emit(r.f("- %d/%d specs ", report.PreRunStats.SpecsThatWillRun, report.PreRunStats.TotalSpecs))
76 if report.SuiteConfig.ParallelTotal > 1 {
77 r.emit(r.f("- %d procs ", report.SuiteConfig.ParallelTotal))
78 }
79 } else {
80 banner := r.f("Running Suite: %s - %s", report.SuiteDescription, report.SuitePath)
81 r.emitBlock(banner)
82 bannerWidth := len(banner)
83 if len(report.SuiteLabels) > 0 {
84 labels := strings.Join(report.SuiteLabels, ", ")
85 r.emitBlock(r.f("{{coral}}[%s]{{/}} ", labels))
86 if len(labels)+2 > bannerWidth {
87 bannerWidth = len(labels) + 2
88 }
89 }
90 r.emitBlock(strings.Repeat("=", bannerWidth))
91
92 out := r.f("Random Seed: {{bold}}%d{{/}}", report.SuiteConfig.RandomSeed)
93 if report.SuiteConfig.RandomizeAllSpecs {
94 out += r.f(" - will randomize all specs")
95 }
96 r.emitBlock(out)
97 r.emit("\n")
98 r.emitBlock(r.f("Will run {{bold}}%d{{/}} of {{bold}}%d{{/}} specs", report.PreRunStats.SpecsThatWillRun, report.PreRunStats.TotalSpecs))
99 if report.SuiteConfig.ParallelTotal > 1 {
100 r.emitBlock(r.f("Running in parallel across {{bold}}%d{{/}} processes", report.SuiteConfig.ParallelTotal))
101 }
102 }
103 }
104
105 func (r *DefaultReporter) SuiteDidEnd(report types.Report) {
106 failures := report.SpecReports.WithState(types.SpecStateFailureStates)
107 if len(failures) > 0 {
108 r.emitBlock("\n")
109 if len(failures) > 1 {
110 r.emitBlock(r.f("{{red}}{{bold}}Summarizing %d Failures:{{/}}", len(failures)))
111 } else {
112 r.emitBlock(r.f("{{red}}{{bold}}Summarizing 1 Failure:{{/}}"))
113 }
114 for _, specReport := range failures {
115 highlightColor, heading := "{{red}}", "[FAIL]"
116 switch specReport.State {
117 case types.SpecStatePanicked:
118 highlightColor, heading = "{{magenta}}", "[PANICKED!]"
119 case types.SpecStateAborted:
120 highlightColor, heading = "{{coral}}", "[ABORTED]"
121 case types.SpecStateTimedout:
122 highlightColor, heading = "{{orange}}", "[TIMEDOUT]"
123 case types.SpecStateInterrupted:
124 highlightColor, heading = "{{orange}}", "[INTERRUPTED]"
125 }
126 locationBlock := r.codeLocationBlock(specReport, highlightColor, false, true)
127 r.emitBlock(r.fi(1, highlightColor+"%s{{/}} %s", heading, locationBlock))
128 }
129 }
130
131
132 if r.conf.Verbosity().Is(types.VerbosityLevelSuccinct) && report.SuiteSucceeded {
133 r.emit(r.f(" {{green}}SUCCESS!{{/}} %s ", report.RunTime))
134 return
135 }
136
137 r.emitBlock("\n")
138 color, status := "{{green}}{{bold}}", "SUCCESS!"
139 if !report.SuiteSucceeded {
140 color, status = "{{red}}{{bold}}", "FAIL!"
141 }
142
143 specs := report.SpecReports.WithLeafNodeType(types.NodeTypeIt)
144 r.emitBlock(r.f(color+"Ran %d of %d Specs in %.3f seconds{{/}}",
145 specs.CountWithState(types.SpecStatePassed)+specs.CountWithState(types.SpecStateFailureStates),
146 report.PreRunStats.TotalSpecs,
147 report.RunTime.Seconds()),
148 )
149
150 switch len(report.SpecialSuiteFailureReasons) {
151 case 0:
152 r.emit(r.f(color+"%s{{/}} -- ", status))
153 case 1:
154 r.emit(r.f(color+"%s - %s{{/}} -- ", status, report.SpecialSuiteFailureReasons[0]))
155 default:
156 r.emitBlock(r.f(color+"%s - %s{{/}}\n", status, strings.Join(report.SpecialSuiteFailureReasons, ", ")))
157 }
158
159 if len(specs) == 0 && report.SpecReports.WithLeafNodeType(types.NodeTypeBeforeSuite|types.NodeTypeSynchronizedBeforeSuite).CountWithState(types.SpecStateFailureStates) > 0 {
160 r.emit(r.f("{{cyan}}{{bold}}A BeforeSuite node failed so all tests were skipped.{{/}}\n"))
161 } else {
162 r.emit(r.f("{{green}}{{bold}}%d Passed{{/}} | ", specs.CountWithState(types.SpecStatePassed)))
163 r.emit(r.f("{{red}}{{bold}}%d Failed{{/}} | ", specs.CountWithState(types.SpecStateFailureStates)))
164 if specs.CountOfFlakedSpecs() > 0 {
165 r.emit(r.f("{{light-yellow}}{{bold}}%d Flaked{{/}} | ", specs.CountOfFlakedSpecs()))
166 }
167 if specs.CountOfRepeatedSpecs() > 0 {
168 r.emit(r.f("{{light-yellow}}{{bold}}%d Repeated{{/}} | ", specs.CountOfRepeatedSpecs()))
169 }
170 r.emit(r.f("{{yellow}}{{bold}}%d Pending{{/}} | ", specs.CountWithState(types.SpecStatePending)))
171 r.emit(r.f("{{cyan}}{{bold}}%d Skipped{{/}}\n", specs.CountWithState(types.SpecStateSkipped)))
172 }
173 }
174
175 func (r *DefaultReporter) WillRun(report types.SpecReport) {
176 v := r.conf.Verbosity()
177 if v.LT(types.VerbosityLevelVerbose) || report.State.Is(types.SpecStatePending|types.SpecStateSkipped) || report.RunningInParallel {
178 return
179 }
180
181 r.emitDelimiter(0)
182 r.emitBlock(r.f(r.codeLocationBlock(report, "{{/}}", v.Is(types.VerbosityLevelVeryVerbose), false)))
183 }
184
185 func (r *DefaultReporter) wrapTextBlock(sectionName string, fn func()) {
186 r.emitBlock("\n")
187 if r.conf.GithubOutput {
188 r.emitBlock(r.fi(1, "::group::%s", sectionName))
189 } else {
190 r.emitBlock(r.fi(1, "{{gray}}%s >>{{/}}", sectionName))
191 }
192 fn()
193 if r.conf.GithubOutput {
194 r.emitBlock(r.fi(1, "::endgroup::"))
195 } else {
196 r.emitBlock(r.fi(1, "{{gray}}<< %s{{/}}", sectionName))
197 }
198
199 }
200
201 func (r *DefaultReporter) DidRun(report types.SpecReport) {
202 v := r.conf.Verbosity()
203 inParallel := report.RunningInParallel
204
205 header := r.specDenoter
206 if report.LeafNodeType.Is(types.NodeTypesForSuiteLevelNodes) {
207 header = fmt.Sprintf("[%s]", report.LeafNodeType)
208 }
209 highlightColor := r.highlightColorForState(report.State)
210
211
212 timelineHasBeenStreaming := v.GTE(types.VerbosityLevelVerbose) && !inParallel
213
214
215 var timeline types.Timeline
216 showTimeline := !timelineHasBeenStreaming && (v.GTE(types.VerbosityLevelVerbose) || report.Failed())
217 if showTimeline {
218 timeline = report.Timeline().WithoutHiddenReportEntries()
219 keepVeryVerboseSpecEvents := v.Is(types.VerbosityLevelVeryVerbose) ||
220 (v.Is(types.VerbosityLevelVerbose) && r.conf.ShowNodeEvents) ||
221 (report.Failed() && r.conf.ShowNodeEvents)
222 if !keepVeryVerboseSpecEvents {
223 timeline = timeline.WithoutVeryVerboseSpecEvents()
224 }
225 if len(timeline) == 0 && report.CapturedGinkgoWriterOutput == "" {
226
227 showTimeline = false
228 }
229 if v.LT(types.VerbosityLevelVeryVerbose) && report.CapturedGinkgoWriterOutput == "" && len(timeline) > 0 {
230
231 failure, isFailure := timeline[0].(types.Failure)
232 if isFailure && (len(timeline) == 1 || (len(timeline) == 2 && failure.AdditionalFailure != nil)) {
233 showTimeline = false
234 }
235 }
236 }
237
238
239 showSeparateVisibilityAlwaysReportsSection := !timelineHasBeenStreaming && !showTimeline && report.ReportEntries.HasVisibility(types.ReportEntryVisibilityAlways)
240
241
242 showSeparateStdSection := inParallel && (report.CapturedStdOutErr != "")
243
244
245 reportHasContent := v.Is(types.VerbosityLevelVeryVerbose) || showTimeline || showSeparateVisibilityAlwaysReportsSection || showSeparateStdSection || report.Failed() || (v.Is(types.VerbosityLevelVerbose) && !report.State.Is(types.SpecStateSkipped))
246
247
248 includeRuntime := !report.State.Is(types.SpecStateSkipped|types.SpecStatePending) || (report.State.Is(types.SpecStateSkipped) && report.Failure.Message != "")
249
250
251 showCodeLocation := !timelineHasBeenStreaming || !report.State.Is(types.SpecStatePassed)
252
253 switch report.State {
254 case types.SpecStatePassed:
255 if report.LeafNodeType.Is(types.NodeTypesForSuiteLevelNodes) && !reportHasContent {
256 return
257 }
258 if report.LeafNodeType.Is(types.NodeTypesForSuiteLevelNodes) {
259 header = fmt.Sprintf("%s PASSED", header)
260 }
261 if report.NumAttempts > 1 && report.MaxFlakeAttempts > 1 {
262 header, reportHasContent = fmt.Sprintf("%s [FLAKEY TEST - TOOK %d ATTEMPTS TO PASS]", r.retryDenoter, report.NumAttempts), true
263 }
264 case types.SpecStatePending:
265 header = "P"
266 if v.GT(types.VerbosityLevelSuccinct) {
267 header, reportHasContent = "P [PENDING]", true
268 }
269 case types.SpecStateSkipped:
270 header = "S"
271 if v.Is(types.VerbosityLevelVeryVerbose) || (v.Is(types.VerbosityLevelVerbose) && report.Failure.Message != "") {
272 header, reportHasContent = "S [SKIPPED]", true
273 }
274 default:
275 header = fmt.Sprintf("%s [%s]", header, r.humanReadableState(report.State))
276 if report.MaxMustPassRepeatedly > 1 {
277 header = fmt.Sprintf("%s DURING REPETITION #%d", header, report.NumAttempts)
278 }
279 }
280
281
282 if !reportHasContent {
283 r.emit(r.f(highlightColor + header + "{{/}}"))
284 return
285 }
286
287 if includeRuntime {
288 header = r.f("%s [%.3f seconds]", header, report.RunTime.Seconds())
289 }
290
291
292 if !timelineHasBeenStreaming {
293 r.emitDelimiter(0)
294 }
295 r.emitBlock(r.f(highlightColor + header + "{{/}}"))
296 if showCodeLocation {
297 r.emitBlock(r.codeLocationBlock(report, highlightColor, v.Is(types.VerbosityLevelVeryVerbose), false))
298 }
299
300
301 if showSeparateStdSection {
302 r.wrapTextBlock("Captured StdOut/StdErr Output", func() {
303 r.emitBlock(r.fi(1, "%s", report.CapturedStdOutErr))
304 })
305 }
306
307 if showSeparateVisibilityAlwaysReportsSection {
308 r.wrapTextBlock("Report Entries", func() {
309 for _, entry := range report.ReportEntries.WithVisibility(types.ReportEntryVisibilityAlways) {
310 r.emitReportEntry(1, entry)
311 }
312 })
313 }
314
315 if showTimeline {
316 r.wrapTextBlock("Timeline", func() {
317 r.emitTimeline(1, report, timeline)
318 })
319 }
320
321
322 if !report.Failure.IsZero() && !v.Is(types.VerbosityLevelVeryVerbose) {
323 r.emitBlock("\n")
324 r.emitFailure(1, report.State, report.Failure, true)
325 if len(report.AdditionalFailures) > 0 {
326 r.emitBlock(r.fi(1, "\nThere were {{bold}}{{red}}additional failures{{/}} detected. To view them in detail run {{bold}}ginkgo -vv{{/}}"))
327 }
328 }
329
330 r.emitDelimiter(0)
331 }
332
333 func (r *DefaultReporter) highlightColorForState(state types.SpecState) string {
334 switch state {
335 case types.SpecStatePassed:
336 return "{{green}}"
337 case types.SpecStatePending:
338 return "{{yellow}}"
339 case types.SpecStateSkipped:
340 return "{{cyan}}"
341 case types.SpecStateFailed:
342 return "{{red}}"
343 case types.SpecStateTimedout:
344 return "{{orange}}"
345 case types.SpecStatePanicked:
346 return "{{magenta}}"
347 case types.SpecStateInterrupted:
348 return "{{orange}}"
349 case types.SpecStateAborted:
350 return "{{coral}}"
351 default:
352 return "{{gray}}"
353 }
354 }
355
356 func (r *DefaultReporter) humanReadableState(state types.SpecState) string {
357 return strings.ToUpper(state.String())
358 }
359
360 func (r *DefaultReporter) emitTimeline(indent uint, report types.SpecReport, timeline types.Timeline) {
361 isVeryVerbose := r.conf.Verbosity().Is(types.VerbosityLevelVeryVerbose)
362 gw := report.CapturedGinkgoWriterOutput
363 cursor := 0
364 for _, entry := range timeline {
365 tl := entry.GetTimelineLocation()
366 if tl.Offset < len(gw) {
367 r.emit(r.fi(indent, "%s", gw[cursor:tl.Offset]))
368 cursor = tl.Offset
369 } else if cursor < len(gw) {
370 r.emit(r.fi(indent, "%s", gw[cursor:]))
371 cursor = len(gw)
372 }
373 switch x := entry.(type) {
374 case types.Failure:
375 if isVeryVerbose {
376 r.emitFailure(indent, report.State, x, false)
377 } else {
378 r.emitShortFailure(indent, report.State, x)
379 }
380 case types.AdditionalFailure:
381 if isVeryVerbose {
382 r.emitFailure(indent, x.State, x.Failure, true)
383 } else {
384 r.emitShortFailure(indent, x.State, x.Failure)
385 }
386 case types.ReportEntry:
387 r.emitReportEntry(indent, x)
388 case types.ProgressReport:
389 r.emitProgressReport(indent, false, x)
390 case types.SpecEvent:
391 if isVeryVerbose || !x.IsOnlyVisibleAtVeryVerbose() || r.conf.ShowNodeEvents {
392 r.emitSpecEvent(indent, x, isVeryVerbose)
393 }
394 }
395 }
396 if cursor < len(gw) {
397 r.emit(r.fi(indent, "%s", gw[cursor:]))
398 }
399 }
400
401 func (r *DefaultReporter) EmitFailure(state types.SpecState, failure types.Failure) {
402 if r.conf.Verbosity().Is(types.VerbosityLevelVerbose) {
403 r.emitShortFailure(1, state, failure)
404 } else if r.conf.Verbosity().Is(types.VerbosityLevelVeryVerbose) {
405 r.emitFailure(1, state, failure, true)
406 }
407 }
408
409 func (r *DefaultReporter) emitShortFailure(indent uint, state types.SpecState, failure types.Failure) {
410 r.emitBlock(r.fi(indent, r.highlightColorForState(state)+"[%s]{{/}} in [%s] - %s {{gray}}@ %s{{/}}",
411 r.humanReadableState(state),
412 failure.FailureNodeType,
413 failure.Location,
414 failure.TimelineLocation.Time.Format(types.GINKGO_TIME_FORMAT),
415 ))
416 }
417
418 func (r *DefaultReporter) emitFailure(indent uint, state types.SpecState, failure types.Failure, includeAdditionalFailure bool) {
419 highlightColor := r.highlightColorForState(state)
420 r.emitBlock(r.fi(indent, highlightColor+"[%s] %s{{/}}", r.humanReadableState(state), failure.Message))
421 if r.conf.GithubOutput {
422 level := "error"
423 if state.Is(types.SpecStateSkipped) {
424 level = "notice"
425 }
426 r.emitBlock(r.fi(indent, "::%s file=%s,line=%d::%s %s", level, failure.Location.FileName, failure.Location.LineNumber, failure.FailureNodeType, failure.TimelineLocation.Time.Format(types.GINKGO_TIME_FORMAT)))
427 } else {
428 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)))
429 }
430 if failure.ForwardedPanic != "" {
431 r.emitBlock("\n")
432 r.emitBlock(r.fi(indent, highlightColor+"%s{{/}}", failure.ForwardedPanic))
433 }
434
435 if r.conf.FullTrace || failure.ForwardedPanic != "" {
436 r.emitBlock("\n")
437 r.emitBlock(r.fi(indent, highlightColor+"Full Stack Trace{{/}}"))
438 r.emitBlock(r.fi(indent+1, "%s", failure.Location.FullStackTrace))
439 }
440
441 if !failure.ProgressReport.IsZero() {
442 r.emitBlock("\n")
443 r.emitProgressReport(indent, false, failure.ProgressReport)
444 }
445
446 if failure.AdditionalFailure != nil && includeAdditionalFailure {
447 r.emitBlock("\n")
448 r.emitFailure(indent, failure.AdditionalFailure.State, failure.AdditionalFailure.Failure, true)
449 }
450 }
451
452 func (r *DefaultReporter) EmitProgressReport(report types.ProgressReport) {
453 r.emitDelimiter(1)
454
455 if report.RunningInParallel {
456 r.emit(r.fi(1, "{{coral}}Progress Report for Ginkgo Process #{{bold}}%d{{/}}\n", report.ParallelProcess))
457 }
458 shouldEmitGW := report.RunningInParallel || r.conf.Verbosity().LT(types.VerbosityLevelVerbose)
459 r.emitProgressReport(1, shouldEmitGW, report)
460 r.emitDelimiter(1)
461 }
462
463 func (r *DefaultReporter) emitProgressReport(indent uint, emitGinkgoWriterOutput bool, report types.ProgressReport) {
464 if report.Message != "" {
465 r.emitBlock(r.fi(indent, report.Message+"\n"))
466 indent += 1
467 }
468 if report.LeafNodeText != "" {
469 subjectIndent := indent
470 if len(report.ContainerHierarchyTexts) > 0 {
471 r.emit(r.fi(indent, r.cycleJoin(report.ContainerHierarchyTexts, " ")))
472 r.emit(" ")
473 subjectIndent = 0
474 }
475 r.emit(r.fi(subjectIndent, "{{bold}}{{orange}}%s{{/}} (Spec Runtime: %s)\n", report.LeafNodeText, report.Time().Sub(report.SpecStartTime).Round(time.Millisecond)))
476 r.emit(r.fi(indent+1, "{{gray}}%s{{/}}\n", report.LeafNodeLocation))
477 indent += 1
478 }
479 if report.CurrentNodeType != types.NodeTypeInvalid {
480 r.emit(r.fi(indent, "In {{bold}}{{orange}}[%s]{{/}}", report.CurrentNodeType))
481 if report.CurrentNodeText != "" && !report.CurrentNodeType.Is(types.NodeTypeIt) {
482 r.emit(r.f(" {{bold}}{{orange}}%s{{/}}", report.CurrentNodeText))
483 }
484
485 r.emit(r.f(" (Node Runtime: %s)\n", report.Time().Sub(report.CurrentNodeStartTime).Round(time.Millisecond)))
486 r.emit(r.fi(indent+1, "{{gray}}%s{{/}}\n", report.CurrentNodeLocation))
487 indent += 1
488 }
489 if report.CurrentStepText != "" {
490 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)))
491 r.emit(r.fi(indent+1, "{{gray}}%s{{/}}\n", report.CurrentStepLocation))
492 indent += 1
493 }
494
495 if indent > 0 {
496 indent -= 1
497 }
498
499 if emitGinkgoWriterOutput && report.CapturedGinkgoWriterOutput != "" {
500 r.emit("\n")
501 r.emitBlock(r.fi(indent, "{{gray}}Begin Captured GinkgoWriter Output >>{{/}}"))
502 limit, lines := 10, strings.Split(report.CapturedGinkgoWriterOutput, "\n")
503 if len(lines) <= limit {
504 r.emitBlock(r.fi(indent+1, "%s", report.CapturedGinkgoWriterOutput))
505 } else {
506 r.emitBlock(r.fi(indent+1, "{{gray}}...{{/}}"))
507 for _, line := range lines[len(lines)-limit-1:] {
508 r.emitBlock(r.fi(indent+1, "%s", line))
509 }
510 }
511 r.emitBlock(r.fi(indent, "{{gray}}<< End Captured GinkgoWriter Output{{/}}"))
512 }
513
514 if !report.SpecGoroutine().IsZero() {
515 r.emit("\n")
516 r.emit(r.fi(indent, "{{bold}}{{underline}}Spec Goroutine{{/}}\n"))
517 r.emitGoroutines(indent, report.SpecGoroutine())
518 }
519
520 if len(report.AdditionalReports) > 0 {
521 r.emit("\n")
522 r.emitBlock(r.fi(indent, "{{gray}}Begin Additional Progress Reports >>{{/}}"))
523 for i, additionalReport := range report.AdditionalReports {
524 r.emit(r.fi(indent+1, additionalReport))
525 if i < len(report.AdditionalReports)-1 {
526 r.emitBlock(r.fi(indent+1, "{{gray}}%s{{/}}", strings.Repeat("-", 10)))
527 }
528 }
529 r.emitBlock(r.fi(indent, "{{gray}}<< End Additional Progress Reports{{/}}"))
530 }
531
532 highlightedGoroutines := report.HighlightedGoroutines()
533 if len(highlightedGoroutines) > 0 {
534 r.emit("\n")
535 r.emit(r.fi(indent, "{{bold}}{{underline}}Goroutines of Interest{{/}}\n"))
536 r.emitGoroutines(indent, highlightedGoroutines...)
537 }
538
539 otherGoroutines := report.OtherGoroutines()
540 if len(otherGoroutines) > 0 {
541 r.emit("\n")
542 r.emit(r.fi(indent, "{{gray}}{{bold}}{{underline}}Other Goroutines{{/}}\n"))
543 r.emitGoroutines(indent, otherGoroutines...)
544 }
545 }
546
547 func (r *DefaultReporter) EmitReportEntry(entry types.ReportEntry) {
548 if r.conf.Verbosity().LT(types.VerbosityLevelVerbose) || entry.Visibility == types.ReportEntryVisibilityNever {
549 return
550 }
551 r.emitReportEntry(1, entry)
552 }
553
554 func (r *DefaultReporter) emitReportEntry(indent uint, entry types.ReportEntry) {
555 r.emitBlock(r.fi(indent, "{{bold}}"+entry.Name+"{{gray}} "+fmt.Sprintf("- %s @ %s{{/}}", entry.Location, entry.Time.Format(types.GINKGO_TIME_FORMAT))))
556 if representation := entry.StringRepresentation(); representation != "" {
557 r.emitBlock(r.fi(indent+1, representation))
558 }
559 }
560
561 func (r *DefaultReporter) EmitSpecEvent(event types.SpecEvent) {
562 v := r.conf.Verbosity()
563 if v.Is(types.VerbosityLevelVeryVerbose) || (v.Is(types.VerbosityLevelVerbose) && (r.conf.ShowNodeEvents || !event.IsOnlyVisibleAtVeryVerbose())) {
564 r.emitSpecEvent(1, event, r.conf.Verbosity().Is(types.VerbosityLevelVeryVerbose))
565 }
566 }
567
568 func (r *DefaultReporter) emitSpecEvent(indent uint, event types.SpecEvent, includeLocation bool) {
569 location := ""
570 if includeLocation {
571 location = fmt.Sprintf("- %s ", event.CodeLocation.String())
572 }
573 switch event.SpecEventType {
574 case types.SpecEventInvalid:
575 return
576 case types.SpecEventByStart:
577 r.emitBlock(r.fi(indent, "{{bold}}STEP:{{/}} %s {{gray}}%s@ %s{{/}}", event.Message, location, event.TimelineLocation.Time.Format(types.GINKGO_TIME_FORMAT)))
578 case types.SpecEventByEnd:
579 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)))
580 case types.SpecEventNodeStart:
581 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)))
582 case types.SpecEventNodeEnd:
583 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)))
584 case types.SpecEventSpecRepeat:
585 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)))
586 case types.SpecEventSpecRetry:
587 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)))
588 }
589 }
590
591 func (r *DefaultReporter) emitGoroutines(indent uint, goroutines ...types.Goroutine) {
592 for idx, g := range goroutines {
593 color := "{{gray}}"
594 if g.HasHighlights() {
595 color = "{{orange}}"
596 }
597 r.emit(r.fi(indent, color+"goroutine %d [%s]{{/}}\n", g.ID, g.State))
598 for _, fc := range g.Stack {
599 if fc.Highlight {
600 r.emit(r.fi(indent, color+"{{bold}}> %s{{/}}\n", fc.Function))
601 r.emit(r.fi(indent+2, color+"{{bold}}%s:%d{{/}}\n", fc.Filename, fc.Line))
602 r.emitSource(indent+3, fc)
603 } else {
604 r.emit(r.fi(indent+1, "{{gray}}%s{{/}}\n", fc.Function))
605 r.emit(r.fi(indent+2, "{{gray}}%s:%d{{/}}\n", fc.Filename, fc.Line))
606 }
607 }
608
609 if idx+1 < len(goroutines) {
610 r.emit("\n")
611 }
612 }
613 }
614
615 func (r *DefaultReporter) emitSource(indent uint, fc types.FunctionCall) {
616 lines := fc.Source
617 if len(lines) == 0 {
618 return
619 }
620
621 lTrim := 100000
622 for _, line := range lines {
623 lTrimLine := len(line) - len(strings.TrimLeft(line, " \t"))
624 if lTrimLine < lTrim && len(line) > 0 {
625 lTrim = lTrimLine
626 }
627 }
628 if lTrim == 100000 {
629 lTrim = 0
630 }
631
632 for idx, line := range lines {
633 if len(line) > lTrim {
634 line = line[lTrim:]
635 }
636 if idx == fc.SourceHighlight {
637 r.emit(r.fi(indent, "{{bold}}{{orange}}> %s{{/}}\n", line))
638 } else {
639 r.emit(r.fi(indent, "| %s\n", line))
640 }
641 }
642 }
643
644
645 func (r *DefaultReporter) emit(s string) {
646 r._emit(s, false, false)
647 }
648
649 func (r *DefaultReporter) emitBlock(s string) {
650 r._emit(s, true, false)
651 }
652
653 func (r *DefaultReporter) emitDelimiter(indent uint) {
654 r._emit(r.fi(indent, "{{gray}}%s{{/}}", strings.Repeat("-", 30)), true, true)
655 }
656
657
658 func (r *DefaultReporter) _emit(s string, block bool, isDelimiter bool) {
659 if len(s) == 0 {
660 return
661 }
662 r.lock.Lock()
663 defer r.lock.Unlock()
664 if isDelimiter && r.lastEmissionWasDelimiter {
665 return
666 }
667 if block && !r.lastCharWasNewline {
668 r.writer.Write([]byte("\n"))
669 }
670 r.lastCharWasNewline = (s[len(s)-1:] == "\n")
671 r.writer.Write([]byte(s))
672 if block && !r.lastCharWasNewline {
673 r.writer.Write([]byte("\n"))
674 r.lastCharWasNewline = true
675 }
676 r.lastEmissionWasDelimiter = isDelimiter
677 }
678
679
680 func (r *DefaultReporter) f(format string, args ...interface{}) string {
681 return r.formatter.F(format, args...)
682 }
683
684 func (r *DefaultReporter) fi(indentation uint, format string, args ...interface{}) string {
685 return r.formatter.Fi(indentation, format, args...)
686 }
687
688 func (r *DefaultReporter) cycleJoin(elements []string, joiner string) string {
689 return r.formatter.CycleJoin(elements, joiner, []string{"{{/}}", "{{gray}}"})
690 }
691
692 func (r *DefaultReporter) codeLocationBlock(report types.SpecReport, highlightColor string, veryVerbose bool, usePreciseFailureLocation bool) string {
693 texts, locations, labels := []string{}, []types.CodeLocation{}, [][]string{}
694 texts, locations, labels = append(texts, report.ContainerHierarchyTexts...), append(locations, report.ContainerHierarchyLocations...), append(labels, report.ContainerHierarchyLabels...)
695
696 if report.LeafNodeType.Is(types.NodeTypesForSuiteLevelNodes) {
697 texts = append(texts, r.f("[%s] %s", report.LeafNodeType, report.LeafNodeText))
698 } else {
699 texts = append(texts, r.f(report.LeafNodeText))
700 }
701 labels = append(labels, report.LeafNodeLabels)
702 locations = append(locations, report.LeafNodeLocation)
703
704 failureLocation := report.Failure.FailureNodeLocation
705 if usePreciseFailureLocation {
706 failureLocation = report.Failure.Location
707 }
708
709 highlightIndex := -1
710 switch report.Failure.FailureNodeContext {
711 case types.FailureNodeAtTopLevel:
712 texts = append([]string{fmt.Sprintf("TOP-LEVEL [%s]", report.Failure.FailureNodeType)}, texts...)
713 locations = append([]types.CodeLocation{failureLocation}, locations...)
714 labels = append([][]string{{}}, labels...)
715 highlightIndex = 0
716 case types.FailureNodeInContainer:
717 i := report.Failure.FailureNodeContainerIndex
718 texts[i] = fmt.Sprintf("%s [%s]", texts[i], report.Failure.FailureNodeType)
719 locations[i] = failureLocation
720 highlightIndex = i
721 case types.FailureNodeIsLeafNode:
722 i := len(texts) - 1
723 texts[i] = fmt.Sprintf("[%s] %s", report.LeafNodeType, report.LeafNodeText)
724 locations[i] = failureLocation
725 highlightIndex = i
726 default:
727
728 highlightIndex = len(texts) - 1
729 }
730
731 out := ""
732 if veryVerbose {
733 for i := range texts {
734 if i == highlightIndex {
735 out += r.fi(uint(i), highlightColor+"{{bold}}%s{{/}}", texts[i])
736 } else {
737 out += r.fi(uint(i), "%s", texts[i])
738 }
739 if len(labels[i]) > 0 {
740 out += r.f(" {{coral}}[%s]{{/}}", strings.Join(labels[i], ", "))
741 }
742 out += "\n"
743 out += r.fi(uint(i), "{{gray}}%s{{/}}\n", locations[i])
744 }
745 } else {
746 for i := range texts {
747 style := "{{/}}"
748 if i%2 == 1 {
749 style = "{{gray}}"
750 }
751 if i == highlightIndex {
752 style = highlightColor + "{{bold}}"
753 }
754 out += r.f(style+"%s", texts[i])
755 if i < len(texts)-1 {
756 out += " "
757 } else {
758 out += r.f("{{/}}")
759 }
760 }
761 flattenedLabels := report.Labels()
762 if len(flattenedLabels) > 0 {
763 out += r.f(" {{coral}}[%s]{{/}}", strings.Join(flattenedLabels, ", "))
764 }
765 out += "\n"
766 if usePreciseFailureLocation {
767 out += r.f("{{gray}}%s{{/}}", failureLocation)
768 } else {
769 leafLocation := locations[len(locations)-1]
770 if (report.Failure.FailureNodeLocation != types.CodeLocation{}) && (report.Failure.FailureNodeLocation != leafLocation) {
771 out += r.fi(1, highlightColor+"[%s]{{/}} {{gray}}%s{{/}}\n", report.Failure.FailureNodeType, report.Failure.FailureNodeLocation)
772 out += r.fi(1, "{{gray}}[%s] %s{{/}}", report.LeafNodeType, leafLocation)
773 } else {
774 out += r.f("{{gray}}%s{{/}}", leafLocation)
775 }
776 }
777
778 }
779 return out
780 }
781
View as plain text