...

Source file src/github.com/onsi/ginkgo/v2/reporters/default_reporter.go

Documentation: github.com/onsi/ginkgo/v2/reporters

     1  /*
     2  Ginkgo's Default Reporter
     3  
     4  A number of command line flags are available to tweak Ginkgo's default output.
     5  
     6  These are documented [here](http://onsi.github.io/ginkgo/#running_tests)
     7  */
     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  	// managing the emission stream
    27  	lastCharWasNewline       bool
    28  	lastEmissionWasDelimiter bool
    29  
    30  	// rendering
    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  /* The Reporter Interface */
    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  	//summarize the suite
   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) //exclude any suite setup nodes
   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  	// have we already been streaming the timeline?
   212  	timelineHasBeenStreaming := v.GTE(types.VerbosityLevelVerbose) && !inParallel
   213  
   214  	// should we show the timeline?
   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  			// the timeline is completely empty - don't show it
   227  			showTimeline = false
   228  		}
   229  		if v.LT(types.VerbosityLevelVeryVerbose) && report.CapturedGinkgoWriterOutput == "" && len(timeline) > 0 {
   230  			//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
   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  	// should we have a separate section for always-visible reports?
   239  	showSeparateVisibilityAlwaysReportsSection := !timelineHasBeenStreaming && !showTimeline && report.ReportEntries.HasVisibility(types.ReportEntryVisibilityAlways)
   240  
   241  	// should we have a separate section for captured stdout/stderr
   242  	showSeparateStdSection := inParallel && (report.CapturedStdOutErr != "")
   243  
   244  	// given all that - do we have any actual content to show? or are we a single denoter in a stream?
   245  	reportHasContent := v.Is(types.VerbosityLevelVeryVerbose) || showTimeline || showSeparateVisibilityAlwaysReportsSection || showSeparateStdSection || report.Failed() || (v.Is(types.VerbosityLevelVerbose) && !report.State.Is(types.SpecStateSkipped))
   246  
   247  	// should we show a runtime?
   248  	includeRuntime := !report.State.Is(types.SpecStateSkipped|types.SpecStatePending) || (report.State.Is(types.SpecStateSkipped) && report.Failure.Message != "")
   249  
   250  	// should we show the codelocation block?
   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  	// If we have no content to show, jsut emit the header and return
   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  	// Emit header
   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  	//Emit Stdout/Stderr Output
   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  	// Emit Failure Message
   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  /* Emitting to the writer */
   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  // a bit ugly - but we're trying to minimize locking on this hot codepath
   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  /* Rendering text */
   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  		//there is no failure, so we highlight the leaf ndoe
   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