Update ginkgo to v2 in go.mod, go.sum, vendor

This commit updates ginkgo to v2.
Note that because ginkgo/v2 requires go1.18, it was
updated as well.

Signed-off-by: liornoy <lnoy@redhat.com>
Co-authored-by: Sascha Grunert <sgrunert@redhat.com>
This commit is contained in:
liornoy
2023-02-13 21:09:03 +02:00
parent fb92605570
commit 23c2134110
173 changed files with 14109 additions and 10510 deletions

View File

@@ -0,0 +1,750 @@
/*
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"
"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
lastChar string
lastEmissionWasDelimiter bool
// rendering
specDenoter string
retryDenoter string
formatter formatter.Formatter
runningInParallel bool
}
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,
lastChar: "\n",
lastEmissionWasDelimiter: false,
specDenoter: "•",
retryDenoter: "↺",
formatter: formatter.NewWithNoColorBool(conf.NoColor),
}
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))
}
}
}
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))
return
}
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"))
} else {
r.emit(r.f("{{green}}{{bold}}%d Passed{{/}} | ", specs.CountWithState(types.SpecStatePassed)))
r.emit(r.f("{{red}}{{bold}}%d Failed{{/}} | ", specs.CountWithState(types.SpecStateFailureStates)))
if specs.CountOfFlakedSpecs() > 0 {
r.emit(r.f("{{light-yellow}}{{bold}}%d Flaked{{/}} | ", specs.CountOfFlakedSpecs()))
}
if specs.CountOfRepeatedSpecs() > 0 {
r.emit(r.f("{{light-yellow}}{{bold}}%d Repeated{{/}} | ", specs.CountOfRepeatedSpecs()))
}
r.emit(r.f("{{yellow}}{{bold}}%d Pending{{/}} | ", specs.CountWithState(types.SpecStatePending)))
r.emit(r.f("{{cyan}}{{bold}}%d Skipped{{/}}\n", specs.CountWithState(types.SpecStateSkipped)))
}
}
func (r *DefaultReporter) WillRun(report types.SpecReport) {
v := r.conf.Verbosity()
if v.LT(types.VerbosityLevelVerbose) || report.State.Is(types.SpecStatePending|types.SpecStateSkipped) || report.RunningInParallel {
return
}
r.emitDelimiter(0)
r.emitBlock(r.f(r.codeLocationBlock(report, "{{/}}", v.Is(types.VerbosityLevelVeryVerbose), false)))
}
func (r *DefaultReporter) DidRun(report types.SpecReport) {
v := r.conf.Verbosity()
inParallel := report.RunningInParallel
header := r.specDenoter
if report.LeafNodeType.Is(types.NodeTypesForSuiteLevelNodes) {
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
}
}
}
// 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)
switch report.State {
case types.SpecStatePassed:
if report.LeafNodeType.Is(types.NodeTypesForSuiteLevelNodes) && !reportHasContent {
return
}
if report.LeafNodeType.Is(types.NodeTypesForSuiteLevelNodes) {
header = fmt.Sprintf("%s PASSED", header)
}
if report.NumAttempts > 1 && report.MaxFlakeAttempts > 1 {
header, reportHasContent = fmt.Sprintf("%s [FLAKEY TEST - TOOK %d ATTEMPTS TO PASS]", r.retryDenoter, report.NumAttempts), true
}
case types.SpecStatePending:
header = "P"
if v.GT(types.VerbosityLevelSuccinct) {
header, reportHasContent = "P [PENDING]", true
}
case types.SpecStateSkipped:
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)
}
}
// If we have no content to show, jsut emit the header and return
if !reportHasContent {
r.emit(r.f(highlightColor + header + "{{/}}"))
return
}
if includeRuntime {
header = r.f("%s [%.3f seconds]", header, report.RunTime.Seconds())
}
// 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))
}
//Emit Stdout/Stderr Output
if showSeparateStdSection {
r.emitBlock("\n")
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{{/}}"))
}
if showSeparateVisibilityAlwaysReportsSection {
r.emitBlock("\n")
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{{/}}"))
}
if showTimeline {
r.emitBlock("\n")
r.emitBlock(r.fi(1, "{{gray}}Timeline >>{{/}}"))
r.emitTimeline(1, report, timeline)
r.emitBlock(r.fi(1, "{{gray}}<< Timeline{{/}}"))
}
// Emit Failure Message
if !report.Failure.IsZero() && !v.Is(types.VerbosityLevelVeryVerbose) {
r.emitBlock("\n")
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{{/}}"))
}
}
r.emitDelimiter(0)
}
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())
}
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)
}
}
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)))
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)
}
if failure.AdditionalFailure != nil && includeAdditionalFailure {
r.emitBlock("\n")
r.emitFailure(indent, failure.AdditionalFailure.State, failure.AdditionalFailure.Failure, true)
}
}
func (r *DefaultReporter) EmitProgressReport(report types.ProgressReport) {
r.emitDelimiter(1)
if report.RunningInParallel {
r.emit(r.fi(1, "{{coral}}Progress Report for Ginkgo Process #{{bold}}%d{{/}}\n", report.ParallelProcess))
}
shouldEmitGW := report.RunningInParallel || r.conf.Verbosity().LT(types.VerbosityLevelVerbose)
r.emitProgressReport(1, shouldEmitGW, report)
r.emitDelimiter(1)
}
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
}
r.emit(r.fi(subjectIndent, "{{bold}}{{orange}}%s{{/}} (Spec Runtime: %s)\n", report.LeafNodeText, report.Time().Sub(report.SpecStartTime).Round(time.Millisecond)))
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))
}
r.emit(r.f(" (Node Runtime: %s)\n", report.Time().Sub(report.CurrentNodeStartTime).Round(time.Millisecond)))
r.emit(r.fi(indent+1, "{{gray}}%s{{/}}\n", report.CurrentNodeLocation))
indent += 1
}
if report.CurrentStepText != "" {
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)))
r.emit(r.fi(indent+1, "{{gray}}%s{{/}}\n", report.CurrentStepLocation))
indent += 1
}
if indent > 0 {
indent -= 1
}
if emitGinkgoWriterOutput && report.CapturedGinkgoWriterOutput != "" {
r.emit("\n")
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{{/}}"))
}
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...)
}
}
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}} - %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)))
}
}
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) {
if len(s) > 0 {
r.lastChar = s[len(s)-1:]
r.lastEmissionWasDelimiter = false
r.writer.Write([]byte(s))
}
}
func (r *DefaultReporter) emitBlock(s string) {
if len(s) > 0 {
if r.lastChar != "\n" {
r.emit("\n")
}
r.emit(s)
if r.lastChar != "\n" {
r.emit("\n")
}
}
}
func (r *DefaultReporter) emitDelimiter(indent uint) {
if r.lastEmissionWasDelimiter {
return
}
r.emitBlock(r.fi(indent, "{{gray}}%s{{/}}", strings.Repeat("-", 30)))
r.lastEmissionWasDelimiter = true
}
/* 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}}"})
}
func (r *DefaultReporter) codeLocationBlock(report types.SpecReport, highlightColor string, veryVerbose bool, usePreciseFailureLocation bool) string {
texts, locations, labels := []string{}, []types.CodeLocation{}, [][]string{}
texts, locations, labels = append(texts, report.ContainerHierarchyTexts...), append(locations, report.ContainerHierarchyLocations...), append(labels, report.ContainerHierarchyLabels...)
if report.LeafNodeType.Is(types.NodeTypesForSuiteLevelNodes) {
texts = append(texts, r.f("[%s] %s", report.LeafNodeType, report.LeafNodeText))
} else {
texts = append(texts, r.f(report.LeafNodeText))
}
labels = append(labels, report.LeafNodeLabels)
locations = append(locations, report.LeafNodeLocation)
failureLocation := report.Failure.FailureNodeLocation
if usePreciseFailureLocation {
failureLocation = report.Failure.Location
}
highlightIndex := -1
switch report.Failure.FailureNodeContext {
case types.FailureNodeAtTopLevel:
texts = append([]string{fmt.Sprintf("TOP-LEVEL [%s]", report.Failure.FailureNodeType)}, texts...)
locations = append([]types.CodeLocation{failureLocation}, locations...)
labels = append([][]string{{}}, labels...)
highlightIndex = 0
case types.FailureNodeInContainer:
i := report.Failure.FailureNodeContainerIndex
texts[i] = fmt.Sprintf("%s [%s]", texts[i], report.Failure.FailureNodeType)
locations[i] = failureLocation
highlightIndex = i
case types.FailureNodeIsLeafNode:
i := len(texts) - 1
texts[i] = fmt.Sprintf("[%s] %s", report.LeafNodeType, report.LeafNodeText)
locations[i] = failureLocation
highlightIndex = i
default:
//there is no failure, so we highlight the leaf ndoe
highlightIndex = len(texts) - 1
}
out := ""
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("{{/}}")
}
}
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 {
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)
}
}
}
return out
}

View File

@@ -0,0 +1,149 @@
package reporters
import (
"github.com/onsi/ginkgo/v2/config"
"github.com/onsi/ginkgo/v2/types"
)
// Deprecated: DeprecatedReporter was how Ginkgo V1 provided support for CustomReporters
// this has been removed in V2.
// Please read the documentation at:
// https://onsi.github.io/ginkgo/MIGRATING_TO_V2#removed-custom-reporters
// for Ginkgo's new behavior and for a migration path.
type DeprecatedReporter interface {
SuiteWillBegin(config config.GinkgoConfigType, summary *types.SuiteSummary)
BeforeSuiteDidRun(setupSummary *types.SetupSummary)
SpecWillRun(specSummary *types.SpecSummary)
SpecDidComplete(specSummary *types.SpecSummary)
AfterSuiteDidRun(setupSummary *types.SetupSummary)
SuiteDidEnd(summary *types.SuiteSummary)
}
// ReportViaDeprecatedReporter takes a V1 custom reporter and a V2 report and
// calls the custom reporter's methods with appropriately transformed data from the V2 report.
//
// ReportViaDeprecatedReporter should be called in a `ReportAfterSuite()`
//
// Deprecated: ReportViaDeprecatedReporter method exists to help developer bridge between deprecated V1 functionality and the new
// reporting support in V2. It will be removed in a future minor version of Ginkgo.
func ReportViaDeprecatedReporter(reporter DeprecatedReporter, report types.Report) {
conf := config.DeprecatedGinkgoConfigType{
RandomSeed: report.SuiteConfig.RandomSeed,
RandomizeAllSpecs: report.SuiteConfig.RandomizeAllSpecs,
FocusStrings: report.SuiteConfig.FocusStrings,
SkipStrings: report.SuiteConfig.SkipStrings,
FailOnPending: report.SuiteConfig.FailOnPending,
FailFast: report.SuiteConfig.FailFast,
FlakeAttempts: report.SuiteConfig.FlakeAttempts,
EmitSpecProgress: false,
DryRun: report.SuiteConfig.DryRun,
ParallelNode: report.SuiteConfig.ParallelProcess,
ParallelTotal: report.SuiteConfig.ParallelTotal,
SyncHost: report.SuiteConfig.ParallelHost,
StreamHost: report.SuiteConfig.ParallelHost,
}
summary := &types.DeprecatedSuiteSummary{
SuiteDescription: report.SuiteDescription,
SuiteID: report.SuitePath,
NumberOfSpecsBeforeParallelization: report.PreRunStats.TotalSpecs,
NumberOfTotalSpecs: report.PreRunStats.TotalSpecs,
NumberOfSpecsThatWillBeRun: report.PreRunStats.SpecsThatWillRun,
}
reporter.SuiteWillBegin(conf, summary)
for _, spec := range report.SpecReports {
switch spec.LeafNodeType {
case types.NodeTypeBeforeSuite, types.NodeTypeSynchronizedBeforeSuite:
setupSummary := &types.DeprecatedSetupSummary{
ComponentType: spec.LeafNodeType,
CodeLocation: spec.LeafNodeLocation,
State: spec.State,
RunTime: spec.RunTime,
Failure: failureFor(spec),
CapturedOutput: spec.CombinedOutput(),
SuiteID: report.SuitePath,
}
reporter.BeforeSuiteDidRun(setupSummary)
case types.NodeTypeAfterSuite, types.NodeTypeSynchronizedAfterSuite:
setupSummary := &types.DeprecatedSetupSummary{
ComponentType: spec.LeafNodeType,
CodeLocation: spec.LeafNodeLocation,
State: spec.State,
RunTime: spec.RunTime,
Failure: failureFor(spec),
CapturedOutput: spec.CombinedOutput(),
SuiteID: report.SuitePath,
}
reporter.AfterSuiteDidRun(setupSummary)
case types.NodeTypeIt:
componentTexts, componentCodeLocations := []string{}, []types.CodeLocation{}
componentTexts = append(componentTexts, spec.ContainerHierarchyTexts...)
componentCodeLocations = append(componentCodeLocations, spec.ContainerHierarchyLocations...)
componentTexts = append(componentTexts, spec.LeafNodeText)
componentCodeLocations = append(componentCodeLocations, spec.LeafNodeLocation)
specSummary := &types.DeprecatedSpecSummary{
ComponentTexts: componentTexts,
ComponentCodeLocations: componentCodeLocations,
State: spec.State,
RunTime: spec.RunTime,
Failure: failureFor(spec),
NumberOfSamples: spec.NumAttempts,
CapturedOutput: spec.CombinedOutput(),
SuiteID: report.SuitePath,
}
reporter.SpecWillRun(specSummary)
reporter.SpecDidComplete(specSummary)
switch spec.State {
case types.SpecStatePending:
summary.NumberOfPendingSpecs += 1
case types.SpecStateSkipped:
summary.NumberOfSkippedSpecs += 1
case types.SpecStateFailed, types.SpecStatePanicked, types.SpecStateInterrupted:
summary.NumberOfFailedSpecs += 1
case types.SpecStatePassed:
summary.NumberOfPassedSpecs += 1
if spec.NumAttempts > 1 {
summary.NumberOfFlakedSpecs += 1
}
}
}
}
summary.SuiteSucceeded = report.SuiteSucceeded
summary.RunTime = report.RunTime
reporter.SuiteDidEnd(summary)
}
func failureFor(spec types.SpecReport) types.DeprecatedSpecFailure {
if spec.Failure.IsZero() {
return types.DeprecatedSpecFailure{}
}
index := 0
switch spec.Failure.FailureNodeContext {
case types.FailureNodeInContainer:
index = spec.Failure.FailureNodeContainerIndex
case types.FailureNodeAtTopLevel:
index = -1
case types.FailureNodeIsLeafNode:
index = len(spec.ContainerHierarchyTexts) - 1
if spec.LeafNodeText != "" {
index += 1
}
}
return types.DeprecatedSpecFailure{
Message: spec.Failure.Message,
Location: spec.Failure.Location,
ForwardedPanic: spec.Failure.ForwardedPanic,
ComponentIndex: index,
ComponentType: spec.Failure.FailureNodeType,
ComponentCodeLocation: spec.Failure.FailureNodeLocation,
}
}

View File

@@ -0,0 +1,60 @@
package reporters
import (
"encoding/json"
"fmt"
"os"
"github.com/onsi/ginkgo/v2/types"
)
//GenerateJSONReport produces a JSON-formatted report at the passed in destination
func GenerateJSONReport(report types.Report, destination string) error {
f, err := os.Create(destination)
if err != nil {
return err
}
enc := json.NewEncoder(f)
enc.SetIndent("", " ")
err = enc.Encode([]types.Report{
report,
})
if err != nil {
return err
}
return f.Close()
}
//MergeJSONReports produces a single JSON-formatted report at the passed in destination by merging the JSON-formatted reports provided in sources
//It skips over reports that fail to decode but reports on them via the returned messages []string
func MergeAndCleanupJSONReports(sources []string, destination string) ([]string, error) {
messages := []string{}
allReports := []types.Report{}
for _, source := range sources {
reports := []types.Report{}
data, err := os.ReadFile(source)
if err != nil {
messages = append(messages, fmt.Sprintf("Could not open %s:\n%s", source, err.Error()))
continue
}
err = json.Unmarshal(data, &reports)
if err != nil {
messages = append(messages, fmt.Sprintf("Could not decode %s:\n%s", source, err.Error()))
continue
}
os.Remove(source)
allReports = append(allReports, reports...)
}
f, err := os.Create(destination)
if err != nil {
return messages, err
}
enc := json.NewEncoder(f)
enc.SetIndent("", " ")
err = enc.Encode(allReports)
if err != nil {
return messages, err
}
return messages, f.Close()
}

View File

@@ -0,0 +1,359 @@
/*
JUnit XML Reporter for Ginkgo
For usage instructions: http://onsi.github.io/ginkgo/#generating_junit_xml_output
The schema used for the generated JUnit xml file was adapted from https://llg.cubic.org/docs/junit/
*/
package reporters
import (
"encoding/xml"
"fmt"
"os"
"strings"
"github.com/onsi/ginkgo/v2/config"
"github.com/onsi/ginkgo/v2/types"
)
type JunitReportConfig struct {
// Spec States for which no timeline should be emitted for system-err
// set this to types.SpecStatePassed|types.SpecStateSkipped|types.SpecStatePending to only match failing specs
OmitTimelinesForSpecState types.SpecState
// Enable OmitFailureMessageAttr to prevent failure messages appearing in the "message" attribute of the Failure and Error tags
OmitFailureMessageAttr bool
//Enable OmitCapturedStdOutErr to prevent captured stdout/stderr appearing in system-out
OmitCapturedStdOutErr bool
// Enable OmitSpecLabels to prevent labels from appearing in the spec name
OmitSpecLabels bool
// Enable OmitLeafNodeType to prevent the spec leaf node type from appearing in the spec name
OmitLeafNodeType bool
}
type JUnitTestSuites struct {
XMLName xml.Name `xml:"testsuites"`
// Tests maps onto the total number of specs in all test suites (this includes any suite nodes such as BeforeSuite)
Tests int `xml:"tests,attr"`
// Disabled maps onto specs that are pending and/or skipped
Disabled int `xml:"disabled,attr"`
// Errors maps onto specs that panicked or were interrupted
Errors int `xml:"errors,attr"`
// Failures maps onto specs that failed
Failures int `xml:"failures,attr"`
// Time is the time in seconds to execute all test suites
Time float64 `xml:"time,attr"`
//The set of all test suites
TestSuites []JUnitTestSuite `xml:"testsuite"`
}
type JUnitTestSuite struct {
// Name maps onto the description of the test suite - maps onto Report.SuiteDescription
Name string `xml:"name,attr"`
// Package maps onto the absolute path to the test suite - maps onto Report.SuitePath
Package string `xml:"package,attr"`
// Tests maps onto the total number of specs in the test suite (this includes any suite nodes such as BeforeSuite)
Tests int `xml:"tests,attr"`
// Disabled maps onto specs that are pending
Disabled int `xml:"disabled,attr"`
// Skiped maps onto specs that are skipped
Skipped int `xml:"skipped,attr"`
// Errors maps onto specs that panicked or were interrupted
Errors int `xml:"errors,attr"`
// Failures maps onto specs that failed
Failures int `xml:"failures,attr"`
// Time is the time in seconds to execute all the test suite - maps onto Report.RunTime
Time float64 `xml:"time,attr"`
// Timestamp is the ISO 8601 formatted start-time of the suite - maps onto Report.StartTime
Timestamp string `xml:"timestamp,attr"`
//Properties captures the information stored in the rest of the Report type (including SuiteConfig) as key-value pairs
Properties JUnitProperties `xml:"properties"`
//TestCases capture the individual specs
TestCases []JUnitTestCase `xml:"testcase"`
}
type JUnitProperties struct {
Properties []JUnitProperty `xml:"property"`
}
func (jup JUnitProperties) WithName(name string) string {
for _, property := range jup.Properties {
if property.Name == name {
return property.Value
}
}
return ""
}
type JUnitProperty struct {
Name string `xml:"name,attr"`
Value string `xml:"value,attr"`
}
type JUnitTestCase struct {
// Name maps onto the full text of the spec - equivalent to "[SpecReport.LeafNodeType] SpecReport.FullText()"
Name string `xml:"name,attr"`
// Classname maps onto the name of the test suite - equivalent to Report.SuiteDescription
Classname string `xml:"classname,attr"`
// Status maps onto the string representation of SpecReport.State
Status string `xml:"status,attr"`
// Time is the time in seconds to execute the spec - maps onto SpecReport.RunTime
Time float64 `xml:"time,attr"`
//Skipped is populated with a message if the test was skipped or pending
Skipped *JUnitSkipped `xml:"skipped,omitempty"`
//Error is populated if the test panicked or was interrupted
Error *JUnitError `xml:"error,omitempty"`
//Failure is populated if the test failed
Failure *JUnitFailure `xml:"failure,omitempty"`
//SystemOut maps onto any captured stdout/stderr output - maps onto SpecReport.CapturedStdOutErr
SystemOut string `xml:"system-out,omitempty"`
//SystemOut maps onto any captured GinkgoWriter output - maps onto SpecReport.CapturedGinkgoWriterOutput
SystemErr string `xml:"system-err,omitempty"`
}
type JUnitSkipped struct {
// Message maps onto "pending" if the test was marked pending, "skipped" if the test was marked skipped, and "skipped - REASON" if the user called Skip(REASON)
Message string `xml:"message,attr"`
}
type JUnitError struct {
//Message maps onto the panic/exception thrown - equivalent to SpecReport.Failure.ForwardedPanic - or to "interrupted"
Message string `xml:"message,attr"`
//Type is one of "panicked" or "interrupted"
Type string `xml:"type,attr"`
//Description maps onto the captured stack trace for a panic, or the failure message for an interrupt which will include the dump of running goroutines
Description string `xml:",chardata"`
}
type JUnitFailure struct {
//Message maps onto the failure message - equivalent to SpecReport.Failure.Message
Message string `xml:"message,attr"`
//Type is "failed"
Type string `xml:"type,attr"`
//Description maps onto the location and stack trace of the failure
Description string `xml:",chardata"`
}
func GenerateJUnitReport(report types.Report, dst string) error {
return GenerateJUnitReportWithConfig(report, dst, JunitReportConfig{})
}
func GenerateJUnitReportWithConfig(report types.Report, dst string, config JunitReportConfig) error {
suite := JUnitTestSuite{
Name: report.SuiteDescription,
Package: report.SuitePath,
Time: report.RunTime.Seconds(),
Timestamp: report.StartTime.Format("2006-01-02T15:04:05"),
Properties: JUnitProperties{
Properties: []JUnitProperty{
{"SuiteSucceeded", fmt.Sprintf("%t", report.SuiteSucceeded)},
{"SuiteHasProgrammaticFocus", fmt.Sprintf("%t", report.SuiteHasProgrammaticFocus)},
{"SpecialSuiteFailureReason", strings.Join(report.SpecialSuiteFailureReasons, ",")},
{"SuiteLabels", fmt.Sprintf("[%s]", strings.Join(report.SuiteLabels, ","))},
{"RandomSeed", fmt.Sprintf("%d", report.SuiteConfig.RandomSeed)},
{"RandomizeAllSpecs", fmt.Sprintf("%t", report.SuiteConfig.RandomizeAllSpecs)},
{"LabelFilter", report.SuiteConfig.LabelFilter},
{"FocusStrings", strings.Join(report.SuiteConfig.FocusStrings, ",")},
{"SkipStrings", strings.Join(report.SuiteConfig.SkipStrings, ",")},
{"FocusFiles", strings.Join(report.SuiteConfig.FocusFiles, ";")},
{"SkipFiles", strings.Join(report.SuiteConfig.SkipFiles, ";")},
{"FailOnPending", fmt.Sprintf("%t", report.SuiteConfig.FailOnPending)},
{"FailFast", fmt.Sprintf("%t", report.SuiteConfig.FailFast)},
{"FlakeAttempts", fmt.Sprintf("%d", report.SuiteConfig.FlakeAttempts)},
{"DryRun", fmt.Sprintf("%t", report.SuiteConfig.DryRun)},
{"ParallelTotal", fmt.Sprintf("%d", report.SuiteConfig.ParallelTotal)},
{"OutputInterceptorMode", report.SuiteConfig.OutputInterceptorMode},
},
},
}
for _, spec := range report.SpecReports {
name := fmt.Sprintf("[%s]", spec.LeafNodeType)
if config.OmitLeafNodeType {
name = ""
}
if spec.FullText() != "" {
name = name + " " + spec.FullText()
}
labels := spec.Labels()
if len(labels) > 0 && !config.OmitSpecLabels {
name = name + " [" + strings.Join(labels, ", ") + "]"
}
name = strings.TrimSpace(name)
test := JUnitTestCase{
Name: name,
Classname: report.SuiteDescription,
Status: spec.State.String(),
Time: spec.RunTime.Seconds(),
}
if !spec.State.Is(config.OmitTimelinesForSpecState) {
test.SystemErr = systemErrForUnstructuredReporters(spec)
}
if !config.OmitCapturedStdOutErr {
test.SystemOut = systemOutForUnstructuredReporters(spec)
}
suite.Tests += 1
switch spec.State {
case types.SpecStateSkipped:
message := "skipped"
if spec.Failure.Message != "" {
message += " - " + spec.Failure.Message
}
test.Skipped = &JUnitSkipped{Message: message}
suite.Skipped += 1
case types.SpecStatePending:
test.Skipped = &JUnitSkipped{Message: "pending"}
suite.Disabled += 1
case types.SpecStateFailed:
test.Failure = &JUnitFailure{
Message: spec.Failure.Message,
Type: "failed",
Description: failureDescriptionForUnstructuredReporters(spec),
}
if config.OmitFailureMessageAttr {
test.Failure.Message = ""
}
suite.Failures += 1
case types.SpecStateTimedout:
test.Failure = &JUnitFailure{
Message: spec.Failure.Message,
Type: "timedout",
Description: failureDescriptionForUnstructuredReporters(spec),
}
if config.OmitFailureMessageAttr {
test.Failure.Message = ""
}
suite.Failures += 1
case types.SpecStateInterrupted:
test.Error = &JUnitError{
Message: spec.Failure.Message,
Type: "interrupted",
Description: failureDescriptionForUnstructuredReporters(spec),
}
if config.OmitFailureMessageAttr {
test.Error.Message = ""
}
suite.Errors += 1
case types.SpecStateAborted:
test.Failure = &JUnitFailure{
Message: spec.Failure.Message,
Type: "aborted",
Description: failureDescriptionForUnstructuredReporters(spec),
}
if config.OmitFailureMessageAttr {
test.Failure.Message = ""
}
suite.Errors += 1
case types.SpecStatePanicked:
test.Error = &JUnitError{
Message: spec.Failure.ForwardedPanic,
Type: "panicked",
Description: failureDescriptionForUnstructuredReporters(spec),
}
if config.OmitFailureMessageAttr {
test.Error.Message = ""
}
suite.Errors += 1
}
suite.TestCases = append(suite.TestCases, test)
}
junitReport := JUnitTestSuites{
Tests: suite.Tests,
Disabled: suite.Disabled + suite.Skipped,
Errors: suite.Errors,
Failures: suite.Failures,
Time: suite.Time,
TestSuites: []JUnitTestSuite{suite},
}
f, err := os.Create(dst)
if err != nil {
return err
}
f.WriteString(xml.Header)
encoder := xml.NewEncoder(f)
encoder.Indent(" ", " ")
encoder.Encode(junitReport)
return f.Close()
}
func MergeAndCleanupJUnitReports(sources []string, dst string) ([]string, error) {
messages := []string{}
mergedReport := JUnitTestSuites{}
for _, source := range sources {
report := JUnitTestSuites{}
f, err := os.Open(source)
if err != nil {
messages = append(messages, fmt.Sprintf("Could not open %s:\n%s", source, err.Error()))
continue
}
err = xml.NewDecoder(f).Decode(&report)
if err != nil {
messages = append(messages, fmt.Sprintf("Could not decode %s:\n%s", source, err.Error()))
continue
}
os.Remove(source)
mergedReport.Tests += report.Tests
mergedReport.Disabled += report.Disabled
mergedReport.Errors += report.Errors
mergedReport.Failures += report.Failures
mergedReport.Time += report.Time
mergedReport.TestSuites = append(mergedReport.TestSuites, report.TestSuites...)
}
f, err := os.Create(dst)
if err != nil {
return messages, err
}
f.WriteString(xml.Header)
encoder := xml.NewEncoder(f)
encoder.Indent(" ", " ")
encoder.Encode(mergedReport)
return messages, f.Close()
}
func failureDescriptionForUnstructuredReporters(spec types.SpecReport) string {
out := &strings.Builder{}
NewDefaultReporter(types.ReporterConfig{NoColor: true, VeryVerbose: true}, out).emitFailure(0, spec.State, spec.Failure, true)
if len(spec.AdditionalFailures) > 0 {
out.WriteString("\nThere were additional failures detected after the initial failure. These are visible in the timeline\n")
}
return out.String()
}
func systemErrForUnstructuredReporters(spec types.SpecReport) string {
out := &strings.Builder{}
NewDefaultReporter(types.ReporterConfig{NoColor: true, VeryVerbose: true}, out).emitTimeline(0, spec, spec.Timeline())
return out.String()
}
func systemOutForUnstructuredReporters(spec types.SpecReport) string {
return spec.CapturedStdOutErr
}
// Deprecated JUnitReporter (so folks can still compile their suites)
type JUnitReporter struct{}
func NewJUnitReporter(_ string) *JUnitReporter { return &JUnitReporter{} }
func (reporter *JUnitReporter) SuiteWillBegin(_ config.GinkgoConfigType, _ *types.SuiteSummary) {}
func (reporter *JUnitReporter) BeforeSuiteDidRun(_ *types.SetupSummary) {}
func (reporter *JUnitReporter) SpecWillRun(_ *types.SpecSummary) {}
func (reporter *JUnitReporter) SpecDidComplete(_ *types.SpecSummary) {}
func (reporter *JUnitReporter) AfterSuiteDidRun(_ *types.SetupSummary) {}
func (reporter *JUnitReporter) SuiteDidEnd(_ *types.SuiteSummary) {}

29
vendor/github.com/onsi/ginkgo/v2/reporters/reporter.go generated vendored Normal file
View File

@@ -0,0 +1,29 @@
package reporters
import (
"github.com/onsi/ginkgo/v2/types"
)
type Reporter interface {
SuiteWillBegin(report types.Report)
WillRun(report types.SpecReport)
DidRun(report types.SpecReport)
SuiteDidEnd(report types.Report)
//Timeline emission
EmitFailure(state types.SpecState, failure types.Failure)
EmitProgressReport(progressReport types.ProgressReport)
EmitReportEntry(entry types.ReportEntry)
EmitSpecEvent(event types.SpecEvent)
}
type NoopReporter struct{}
func (n NoopReporter) SuiteWillBegin(report types.Report) {}
func (n NoopReporter) WillRun(report types.SpecReport) {}
func (n NoopReporter) DidRun(report types.SpecReport) {}
func (n NoopReporter) SuiteDidEnd(report types.Report) {}
func (n NoopReporter) EmitFailure(state types.SpecState, failure types.Failure) {}
func (n NoopReporter) EmitProgressReport(progressReport types.ProgressReport) {}
func (n NoopReporter) EmitReportEntry(entry types.ReportEntry) {}
func (n NoopReporter) EmitSpecEvent(event types.SpecEvent) {}

View File

@@ -0,0 +1,101 @@
/*
TeamCity Reporter for Ginkgo
Makes use of TeamCity's support for Service Messages
http://confluence.jetbrains.com/display/TCD7/Build+Script+Interaction+with+TeamCity#BuildScriptInteractionwithTeamCity-ReportingTests
*/
package reporters
import (
"fmt"
"os"
"strings"
"github.com/onsi/ginkgo/v2/types"
)
func tcEscape(s string) string {
s = strings.ReplaceAll(s, "|", "||")
s = strings.ReplaceAll(s, "'", "|'")
s = strings.ReplaceAll(s, "\n", "|n")
s = strings.ReplaceAll(s, "\r", "|r")
s = strings.ReplaceAll(s, "[", "|[")
s = strings.ReplaceAll(s, "]", "|]")
return s
}
func GenerateTeamcityReport(report types.Report, dst string) error {
f, err := os.Create(dst)
if err != nil {
return err
}
name := report.SuiteDescription
labels := report.SuiteLabels
if len(labels) > 0 {
name = name + " [" + strings.Join(labels, ", ") + "]"
}
fmt.Fprintf(f, "##teamcity[testSuiteStarted name='%s']\n", tcEscape(name))
for _, spec := range report.SpecReports {
name := fmt.Sprintf("[%s]", spec.LeafNodeType)
if spec.FullText() != "" {
name = name + " " + spec.FullText()
}
labels := spec.Labels()
if len(labels) > 0 {
name = name + " [" + strings.Join(labels, ", ") + "]"
}
name = tcEscape(name)
fmt.Fprintf(f, "##teamcity[testStarted name='%s']\n", name)
switch spec.State {
case types.SpecStatePending:
fmt.Fprintf(f, "##teamcity[testIgnored name='%s' message='pending']\n", name)
case types.SpecStateSkipped:
message := "skipped"
if spec.Failure.Message != "" {
message += " - " + spec.Failure.Message
}
fmt.Fprintf(f, "##teamcity[testIgnored name='%s' message='%s']\n", name, tcEscape(message))
case types.SpecStateFailed:
details := failureDescriptionForUnstructuredReporters(spec)
fmt.Fprintf(f, "##teamcity[testFailed name='%s' message='failed - %s' details='%s']\n", name, tcEscape(spec.Failure.Message), tcEscape(details))
case types.SpecStatePanicked:
details := failureDescriptionForUnstructuredReporters(spec)
fmt.Fprintf(f, "##teamcity[testFailed name='%s' message='panicked - %s' details='%s']\n", name, tcEscape(spec.Failure.ForwardedPanic), tcEscape(details))
case types.SpecStateTimedout:
details := failureDescriptionForUnstructuredReporters(spec)
fmt.Fprintf(f, "##teamcity[testFailed name='%s' message='timedout - %s' details='%s']\n", name, tcEscape(spec.Failure.Message), tcEscape(details))
case types.SpecStateInterrupted:
details := failureDescriptionForUnstructuredReporters(spec)
fmt.Fprintf(f, "##teamcity[testFailed name='%s' message='interrupted - %s' details='%s']\n", name, tcEscape(spec.Failure.Message), tcEscape(details))
case types.SpecStateAborted:
details := failureDescriptionForUnstructuredReporters(spec)
fmt.Fprintf(f, "##teamcity[testFailed name='%s' message='aborted - %s' details='%s']\n", name, tcEscape(spec.Failure.Message), tcEscape(details))
}
fmt.Fprintf(f, "##teamcity[testStdOut name='%s' out='%s']\n", name, tcEscape(systemOutForUnstructuredReporters(spec)))
fmt.Fprintf(f, "##teamcity[testStdErr name='%s' out='%s']\n", name, tcEscape(systemErrForUnstructuredReporters(spec)))
fmt.Fprintf(f, "##teamcity[testFinished name='%s' duration='%d']\n", name, int(spec.RunTime.Seconds()*1000.0))
}
fmt.Fprintf(f, "##teamcity[testSuiteFinished name='%s']\n", tcEscape(report.SuiteDescription))
return f.Close()
}
func MergeAndCleanupTeamcityReports(sources []string, dst string) ([]string, error) {
messages := []string{}
merged := []byte{}
for _, source := range sources {
data, err := os.ReadFile(source)
if err != nil {
messages = append(messages, fmt.Sprintf("Could not open %s:\n%s", source, err.Error()))
continue
}
os.Remove(source)
merged = append(merged, data...)
}
return messages, os.WriteFile(dst, merged, 0666)
}