Skip to content

Ginkgo Reporter slice-bounds panic — timeline offsets can go backward #1598

@slfletch

Description

@slfletch

DefaultReporter.emitTimeline can panic with slice bounds out of range when timeline entries arrive out of order relative to CapturedGinkgoWriterOutput (gw). The code slices using gw[cursor:tl.Offset] after only checking tl.Offset < len(gw). If tl.Offset < cursor, the slice start > end triggers a panic.

Actual behavior
Occasional panic in reporter:

panic: runtime error: slice bounds out of range [start:cursor] with capacity len
at ginkgo/v2/reporters/default_reporter.go:emitTimeline
r.emit(r.fi(indent, "%s", gw[cursor:tl.Offset]))

Expected behavior
Reporter should never panic; it should tolerate out-of-order or beyond-end offsets and still emit the buffered writer output.

Root cause

emitTimeline assumes cursor ≤ tl.Offset ≤ len(gw). With concurrency, timeline entries can be recorded/emitted out of order, so tl.Offset can be less than cursor, violating the assumption.

Minimal fix:

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()

    end := tl.Offset
    if end > len(gw) { end = len(gw) }
    if end < cursor  { end = cursor  }
    if cursor < end {
        r.emit(r.fi(indent, "%s", gw[cursor:end]))
        cursor = end
    } else if cursor < len(gw) && end == 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:]))
}

}

Reproduceable steps:

Create multiple goroutines that (a) write to GinkgoWriter, (b) emit timeline entries, and (c) complete in an order different from creation—this can yield tl.Offset < cursor for a later-emitted entry.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions