| /* |
| The stenographer is used by Ginkgo's reporters to generate output. |
| |
| Move along, nothing to see here. |
| */ |
| |
| package stenographer |
| |
| import ( |
| "fmt" |
| "io" |
| "runtime" |
| "strings" |
| |
| "github.com/onsi/ginkgo/reporters/stenographer/support/go-colorable" |
| "github.com/onsi/ginkgo/types" |
| ) |
| |
| const defaultStyle = "\x1b[0m" |
| const boldStyle = "\x1b[1m" |
| const redColor = "\x1b[91m" |
| const greenColor = "\x1b[32m" |
| const yellowColor = "\x1b[33m" |
| const cyanColor = "\x1b[36m" |
| const grayColor = "\x1b[90m" |
| const lightGrayColor = "\x1b[37m" |
| |
| type cursorStateType int |
| |
| const ( |
| cursorStateTop cursorStateType = iota |
| cursorStateStreaming |
| cursorStateMidBlock |
| cursorStateEndBlock |
| ) |
| |
| type Stenographer interface { |
| AnnounceSuite(description string, randomSeed int64, randomizingAll bool, succinct bool) |
| AnnounceAggregatedParallelRun(nodes int, succinct bool) |
| AnnounceParallelRun(node int, nodes int, succinct bool) |
| AnnounceTotalNumberOfSpecs(total int, succinct bool) |
| AnnounceNumberOfSpecs(specsToRun int, total int, succinct bool) |
| AnnounceSpecRunCompletion(summary *types.SuiteSummary, succinct bool) |
| |
| AnnounceSpecWillRun(spec *types.SpecSummary) |
| AnnounceBeforeSuiteFailure(summary *types.SetupSummary, succinct bool, fullTrace bool) |
| AnnounceAfterSuiteFailure(summary *types.SetupSummary, succinct bool, fullTrace bool) |
| |
| AnnounceCapturedOutput(output string) |
| |
| AnnounceSuccesfulSpec(spec *types.SpecSummary) |
| AnnounceSuccesfulSlowSpec(spec *types.SpecSummary, succinct bool) |
| AnnounceSuccesfulMeasurement(spec *types.SpecSummary, succinct bool) |
| |
| AnnouncePendingSpec(spec *types.SpecSummary, noisy bool) |
| AnnounceSkippedSpec(spec *types.SpecSummary, succinct bool, fullTrace bool) |
| |
| AnnounceSpecTimedOut(spec *types.SpecSummary, succinct bool, fullTrace bool) |
| AnnounceSpecPanicked(spec *types.SpecSummary, succinct bool, fullTrace bool) |
| AnnounceSpecFailed(spec *types.SpecSummary, succinct bool, fullTrace bool) |
| |
| SummarizeFailures(summaries []*types.SpecSummary) |
| } |
| |
| func New(color bool, enableFlakes bool) Stenographer { |
| denoter := "•" |
| if runtime.GOOS == "windows" { |
| denoter = "+" |
| } |
| return &consoleStenographer{ |
| color: color, |
| denoter: denoter, |
| cursorState: cursorStateTop, |
| enableFlakes: enableFlakes, |
| w: colorable.NewColorableStdout(), |
| } |
| } |
| |
| type consoleStenographer struct { |
| color bool |
| denoter string |
| cursorState cursorStateType |
| enableFlakes bool |
| w io.Writer |
| } |
| |
| var alternatingColors = []string{defaultStyle, grayColor} |
| |
| func (s *consoleStenographer) AnnounceSuite(description string, randomSeed int64, randomizingAll bool, succinct bool) { |
| if succinct { |
| s.print(0, "[%d] %s ", randomSeed, s.colorize(boldStyle, description)) |
| return |
| } |
| s.printBanner(fmt.Sprintf("Running Suite: %s", description), "=") |
| s.print(0, "Random Seed: %s", s.colorize(boldStyle, "%d", randomSeed)) |
| if randomizingAll { |
| s.print(0, " - Will randomize all specs") |
| } |
| s.printNewLine() |
| } |
| |
| func (s *consoleStenographer) AnnounceParallelRun(node int, nodes int, succinct bool) { |
| if succinct { |
| s.print(0, "- node #%d ", node) |
| return |
| } |
| s.println(0, |
| "Parallel test node %s/%s.", |
| s.colorize(boldStyle, "%d", node), |
| s.colorize(boldStyle, "%d", nodes), |
| ) |
| s.printNewLine() |
| } |
| |
| func (s *consoleStenographer) AnnounceAggregatedParallelRun(nodes int, succinct bool) { |
| if succinct { |
| s.print(0, "- %d nodes ", nodes) |
| return |
| } |
| s.println(0, |
| "Running in parallel across %s nodes", |
| s.colorize(boldStyle, "%d", nodes), |
| ) |
| s.printNewLine() |
| } |
| |
| func (s *consoleStenographer) AnnounceNumberOfSpecs(specsToRun int, total int, succinct bool) { |
| if succinct { |
| s.print(0, "- %d/%d specs ", specsToRun, total) |
| s.stream() |
| return |
| } |
| s.println(0, |
| "Will run %s of %s specs", |
| s.colorize(boldStyle, "%d", specsToRun), |
| s.colorize(boldStyle, "%d", total), |
| ) |
| |
| s.printNewLine() |
| } |
| |
| func (s *consoleStenographer) AnnounceTotalNumberOfSpecs(total int, succinct bool) { |
| if succinct { |
| s.print(0, "- %d specs ", total) |
| s.stream() |
| return |
| } |
| s.println(0, |
| "Will run %s specs", |
| s.colorize(boldStyle, "%d", total), |
| ) |
| |
| s.printNewLine() |
| } |
| |
| func (s *consoleStenographer) AnnounceSpecRunCompletion(summary *types.SuiteSummary, succinct bool) { |
| if succinct && summary.SuiteSucceeded { |
| s.print(0, " %s %s ", s.colorize(greenColor, "SUCCESS!"), summary.RunTime) |
| return |
| } |
| s.printNewLine() |
| color := greenColor |
| if !summary.SuiteSucceeded { |
| color = redColor |
| } |
| s.println(0, s.colorize(boldStyle+color, "Ran %d of %d Specs in %.3f seconds", summary.NumberOfSpecsThatWillBeRun, summary.NumberOfTotalSpecs, summary.RunTime.Seconds())) |
| |
| status := "" |
| if summary.SuiteSucceeded { |
| status = s.colorize(boldStyle+greenColor, "SUCCESS!") |
| } else { |
| status = s.colorize(boldStyle+redColor, "FAIL!") |
| } |
| |
| flakes := "" |
| if s.enableFlakes { |
| flakes = " | " + s.colorize(yellowColor+boldStyle, "%d Flaked", summary.NumberOfFlakedSpecs) |
| } |
| |
| s.print(0, |
| "%s -- %s | %s | %s | %s\n", |
| status, |
| s.colorize(greenColor+boldStyle, "%d Passed", summary.NumberOfPassedSpecs), |
| s.colorize(redColor+boldStyle, "%d Failed", summary.NumberOfFailedSpecs)+flakes, |
| s.colorize(yellowColor+boldStyle, "%d Pending", summary.NumberOfPendingSpecs), |
| s.colorize(cyanColor+boldStyle, "%d Skipped", summary.NumberOfSkippedSpecs), |
| ) |
| } |
| |
| func (s *consoleStenographer) AnnounceSpecWillRun(spec *types.SpecSummary) { |
| s.startBlock() |
| for i, text := range spec.ComponentTexts[1 : len(spec.ComponentTexts)-1] { |
| s.print(0, s.colorize(alternatingColors[i%2], text)+" ") |
| } |
| |
| indentation := 0 |
| if len(spec.ComponentTexts) > 2 { |
| indentation = 1 |
| s.printNewLine() |
| } |
| index := len(spec.ComponentTexts) - 1 |
| s.print(indentation, s.colorize(boldStyle, spec.ComponentTexts[index])) |
| s.printNewLine() |
| s.print(indentation, s.colorize(lightGrayColor, spec.ComponentCodeLocations[index].String())) |
| s.printNewLine() |
| s.midBlock() |
| } |
| |
| func (s *consoleStenographer) AnnounceBeforeSuiteFailure(summary *types.SetupSummary, succinct bool, fullTrace bool) { |
| s.announceSetupFailure("BeforeSuite", summary, succinct, fullTrace) |
| } |
| |
| func (s *consoleStenographer) AnnounceAfterSuiteFailure(summary *types.SetupSummary, succinct bool, fullTrace bool) { |
| s.announceSetupFailure("AfterSuite", summary, succinct, fullTrace) |
| } |
| |
| func (s *consoleStenographer) announceSetupFailure(name string, summary *types.SetupSummary, succinct bool, fullTrace bool) { |
| s.startBlock() |
| var message string |
| switch summary.State { |
| case types.SpecStateFailed: |
| message = "Failure" |
| case types.SpecStatePanicked: |
| message = "Panic" |
| case types.SpecStateTimedOut: |
| message = "Timeout" |
| } |
| |
| s.println(0, s.colorize(redColor+boldStyle, "%s [%.3f seconds]", message, summary.RunTime.Seconds())) |
| |
| indentation := s.printCodeLocationBlock([]string{name}, []types.CodeLocation{summary.CodeLocation}, summary.ComponentType, 0, summary.State, true) |
| |
| s.printNewLine() |
| s.printFailure(indentation, summary.State, summary.Failure, fullTrace) |
| |
| s.endBlock() |
| } |
| |
| func (s *consoleStenographer) AnnounceCapturedOutput(output string) { |
| if output == "" { |
| return |
| } |
| |
| s.startBlock() |
| s.println(0, output) |
| s.midBlock() |
| } |
| |
| func (s *consoleStenographer) AnnounceSuccesfulSpec(spec *types.SpecSummary) { |
| s.print(0, s.colorize(greenColor, s.denoter)) |
| s.stream() |
| } |
| |
| func (s *consoleStenographer) AnnounceSuccesfulSlowSpec(spec *types.SpecSummary, succinct bool) { |
| s.printBlockWithMessage( |
| s.colorize(greenColor, "%s [SLOW TEST:%.3f seconds]", s.denoter, spec.RunTime.Seconds()), |
| "", |
| spec, |
| succinct, |
| ) |
| } |
| |
| func (s *consoleStenographer) AnnounceSuccesfulMeasurement(spec *types.SpecSummary, succinct bool) { |
| s.printBlockWithMessage( |
| s.colorize(greenColor, "%s [MEASUREMENT]", s.denoter), |
| s.measurementReport(spec, succinct), |
| spec, |
| succinct, |
| ) |
| } |
| |
| func (s *consoleStenographer) AnnouncePendingSpec(spec *types.SpecSummary, noisy bool) { |
| if noisy { |
| s.printBlockWithMessage( |
| s.colorize(yellowColor, "P [PENDING]"), |
| "", |
| spec, |
| false, |
| ) |
| } else { |
| s.print(0, s.colorize(yellowColor, "P")) |
| s.stream() |
| } |
| } |
| |
| func (s *consoleStenographer) AnnounceSkippedSpec(spec *types.SpecSummary, succinct bool, fullTrace bool) { |
| // Skips at runtime will have a non-empty spec.Failure. All others should be succinct. |
| if succinct || spec.Failure == (types.SpecFailure{}) { |
| s.print(0, s.colorize(cyanColor, "S")) |
| s.stream() |
| } else { |
| s.startBlock() |
| s.println(0, s.colorize(cyanColor+boldStyle, "S [SKIPPING]%s [%.3f seconds]", s.failureContext(spec.Failure.ComponentType), spec.RunTime.Seconds())) |
| |
| indentation := s.printCodeLocationBlock(spec.ComponentTexts, spec.ComponentCodeLocations, spec.Failure.ComponentType, spec.Failure.ComponentIndex, spec.State, succinct) |
| |
| s.printNewLine() |
| s.printSkip(indentation, spec.Failure) |
| s.endBlock() |
| } |
| } |
| |
| func (s *consoleStenographer) AnnounceSpecTimedOut(spec *types.SpecSummary, succinct bool, fullTrace bool) { |
| s.printSpecFailure(fmt.Sprintf("%s... Timeout", s.denoter), spec, succinct, fullTrace) |
| } |
| |
| func (s *consoleStenographer) AnnounceSpecPanicked(spec *types.SpecSummary, succinct bool, fullTrace bool) { |
| s.printSpecFailure(fmt.Sprintf("%s! Panic", s.denoter), spec, succinct, fullTrace) |
| } |
| |
| func (s *consoleStenographer) AnnounceSpecFailed(spec *types.SpecSummary, succinct bool, fullTrace bool) { |
| s.printSpecFailure(fmt.Sprintf("%s Failure", s.denoter), spec, succinct, fullTrace) |
| } |
| |
| func (s *consoleStenographer) SummarizeFailures(summaries []*types.SpecSummary) { |
| failingSpecs := []*types.SpecSummary{} |
| |
| for _, summary := range summaries { |
| if summary.HasFailureState() { |
| failingSpecs = append(failingSpecs, summary) |
| } |
| } |
| |
| if len(failingSpecs) == 0 { |
| return |
| } |
| |
| s.printNewLine() |
| s.printNewLine() |
| plural := "s" |
| if len(failingSpecs) == 1 { |
| plural = "" |
| } |
| s.println(0, s.colorize(redColor+boldStyle, "Summarizing %d Failure%s:", len(failingSpecs), plural)) |
| for _, summary := range failingSpecs { |
| s.printNewLine() |
| if summary.HasFailureState() { |
| if summary.TimedOut() { |
| s.print(0, s.colorize(redColor+boldStyle, "[Timeout...] ")) |
| } else if summary.Panicked() { |
| s.print(0, s.colorize(redColor+boldStyle, "[Panic!] ")) |
| } else if summary.Failed() { |
| s.print(0, s.colorize(redColor+boldStyle, "[Fail] ")) |
| } |
| s.printSpecContext(summary.ComponentTexts, summary.ComponentCodeLocations, summary.Failure.ComponentType, summary.Failure.ComponentIndex, summary.State, true) |
| s.printNewLine() |
| s.println(0, s.colorize(lightGrayColor, summary.Failure.Location.String())) |
| } |
| } |
| } |
| |
| func (s *consoleStenographer) startBlock() { |
| if s.cursorState == cursorStateStreaming { |
| s.printNewLine() |
| s.printDelimiter() |
| } else if s.cursorState == cursorStateMidBlock { |
| s.printNewLine() |
| } |
| } |
| |
| func (s *consoleStenographer) midBlock() { |
| s.cursorState = cursorStateMidBlock |
| } |
| |
| func (s *consoleStenographer) endBlock() { |
| s.printDelimiter() |
| s.cursorState = cursorStateEndBlock |
| } |
| |
| func (s *consoleStenographer) stream() { |
| s.cursorState = cursorStateStreaming |
| } |
| |
| func (s *consoleStenographer) printBlockWithMessage(header string, message string, spec *types.SpecSummary, succinct bool) { |
| s.startBlock() |
| s.println(0, header) |
| |
| indentation := s.printCodeLocationBlock(spec.ComponentTexts, spec.ComponentCodeLocations, types.SpecComponentTypeInvalid, 0, spec.State, succinct) |
| |
| if message != "" { |
| s.printNewLine() |
| s.println(indentation, message) |
| } |
| |
| s.endBlock() |
| } |
| |
| func (s *consoleStenographer) printSpecFailure(message string, spec *types.SpecSummary, succinct bool, fullTrace bool) { |
| s.startBlock() |
| s.println(0, s.colorize(redColor+boldStyle, "%s%s [%.3f seconds]", message, s.failureContext(spec.Failure.ComponentType), spec.RunTime.Seconds())) |
| |
| indentation := s.printCodeLocationBlock(spec.ComponentTexts, spec.ComponentCodeLocations, spec.Failure.ComponentType, spec.Failure.ComponentIndex, spec.State, succinct) |
| |
| s.printNewLine() |
| s.printFailure(indentation, spec.State, spec.Failure, fullTrace) |
| s.endBlock() |
| } |
| |
| func (s *consoleStenographer) failureContext(failedComponentType types.SpecComponentType) string { |
| switch failedComponentType { |
| case types.SpecComponentTypeBeforeSuite: |
| return " in Suite Setup (BeforeSuite)" |
| case types.SpecComponentTypeAfterSuite: |
| return " in Suite Teardown (AfterSuite)" |
| case types.SpecComponentTypeBeforeEach: |
| return " in Spec Setup (BeforeEach)" |
| case types.SpecComponentTypeJustBeforeEach: |
| return " in Spec Setup (JustBeforeEach)" |
| case types.SpecComponentTypeAfterEach: |
| return " in Spec Teardown (AfterEach)" |
| } |
| |
| return "" |
| } |
| |
| func (s *consoleStenographer) printSkip(indentation int, spec types.SpecFailure) { |
| s.println(indentation, s.colorize(cyanColor, spec.Message)) |
| s.printNewLine() |
| s.println(indentation, spec.Location.String()) |
| } |
| |
| func (s *consoleStenographer) printFailure(indentation int, state types.SpecState, failure types.SpecFailure, fullTrace bool) { |
| if state == types.SpecStatePanicked { |
| s.println(indentation, s.colorize(redColor+boldStyle, failure.Message)) |
| s.println(indentation, s.colorize(redColor, failure.ForwardedPanic)) |
| s.println(indentation, failure.Location.String()) |
| s.printNewLine() |
| s.println(indentation, s.colorize(redColor, "Full Stack Trace")) |
| s.println(indentation, failure.Location.FullStackTrace) |
| } else { |
| s.println(indentation, s.colorize(redColor, failure.Message)) |
| s.printNewLine() |
| s.println(indentation, failure.Location.String()) |
| if fullTrace { |
| s.printNewLine() |
| s.println(indentation, s.colorize(redColor, "Full Stack Trace")) |
| s.println(indentation, failure.Location.FullStackTrace) |
| } |
| } |
| } |
| |
| func (s *consoleStenographer) printSpecContext(componentTexts []string, componentCodeLocations []types.CodeLocation, failedComponentType types.SpecComponentType, failedComponentIndex int, state types.SpecState, succinct bool) int { |
| startIndex := 1 |
| indentation := 0 |
| |
| if len(componentTexts) == 1 { |
| startIndex = 0 |
| } |
| |
| for i := startIndex; i < len(componentTexts); i++ { |
| if (state.IsFailure() || state == types.SpecStateSkipped) && i == failedComponentIndex { |
| color := redColor |
| if state == types.SpecStateSkipped { |
| color = cyanColor |
| } |
| blockType := "" |
| switch failedComponentType { |
| case types.SpecComponentTypeBeforeSuite: |
| blockType = "BeforeSuite" |
| case types.SpecComponentTypeAfterSuite: |
| blockType = "AfterSuite" |
| case types.SpecComponentTypeBeforeEach: |
| blockType = "BeforeEach" |
| case types.SpecComponentTypeJustBeforeEach: |
| blockType = "JustBeforeEach" |
| case types.SpecComponentTypeAfterEach: |
| blockType = "AfterEach" |
| case types.SpecComponentTypeIt: |
| blockType = "It" |
| case types.SpecComponentTypeMeasure: |
| blockType = "Measurement" |
| } |
| if succinct { |
| s.print(0, s.colorize(color+boldStyle, "[%s] %s ", blockType, componentTexts[i])) |
| } else { |
| s.println(indentation, s.colorize(color+boldStyle, "%s [%s]", componentTexts[i], blockType)) |
| s.println(indentation, s.colorize(grayColor, "%s", componentCodeLocations[i])) |
| } |
| } else { |
| if succinct { |
| s.print(0, s.colorize(alternatingColors[i%2], "%s ", componentTexts[i])) |
| } else { |
| s.println(indentation, componentTexts[i]) |
| s.println(indentation, s.colorize(grayColor, "%s", componentCodeLocations[i])) |
| } |
| } |
| indentation++ |
| } |
| |
| return indentation |
| } |
| |
| func (s *consoleStenographer) printCodeLocationBlock(componentTexts []string, componentCodeLocations []types.CodeLocation, failedComponentType types.SpecComponentType, failedComponentIndex int, state types.SpecState, succinct bool) int { |
| indentation := s.printSpecContext(componentTexts, componentCodeLocations, failedComponentType, failedComponentIndex, state, succinct) |
| |
| if succinct { |
| if len(componentTexts) > 0 { |
| s.printNewLine() |
| s.print(0, s.colorize(lightGrayColor, "%s", componentCodeLocations[len(componentCodeLocations)-1])) |
| } |
| s.printNewLine() |
| indentation = 1 |
| } else { |
| indentation-- |
| } |
| |
| return indentation |
| } |
| |
| func (s *consoleStenographer) orderedMeasurementKeys(measurements map[string]*types.SpecMeasurement) []string { |
| orderedKeys := make([]string, len(measurements)) |
| for key, measurement := range measurements { |
| orderedKeys[measurement.Order] = key |
| } |
| return orderedKeys |
| } |
| |
| func (s *consoleStenographer) measurementReport(spec *types.SpecSummary, succinct bool) string { |
| if len(spec.Measurements) == 0 { |
| return "Found no measurements" |
| } |
| |
| message := []string{} |
| orderedKeys := s.orderedMeasurementKeys(spec.Measurements) |
| |
| if succinct { |
| message = append(message, fmt.Sprintf("%s samples:", s.colorize(boldStyle, "%d", spec.NumberOfSamples))) |
| for _, key := range orderedKeys { |
| measurement := spec.Measurements[key] |
| message = append(message, fmt.Sprintf(" %s - %s: %s%s, %s: %s%s ± %s%s, %s: %s%s", |
| s.colorize(boldStyle, "%s", measurement.Name), |
| measurement.SmallestLabel, |
| s.colorize(greenColor, measurement.PrecisionFmt(), measurement.Smallest), |
| measurement.Units, |
| measurement.AverageLabel, |
| s.colorize(cyanColor, measurement.PrecisionFmt(), measurement.Average), |
| measurement.Units, |
| s.colorize(cyanColor, measurement.PrecisionFmt(), measurement.StdDeviation), |
| measurement.Units, |
| measurement.LargestLabel, |
| s.colorize(redColor, measurement.PrecisionFmt(), measurement.Largest), |
| measurement.Units, |
| )) |
| } |
| } else { |
| message = append(message, fmt.Sprintf("Ran %s samples:", s.colorize(boldStyle, "%d", spec.NumberOfSamples))) |
| for _, key := range orderedKeys { |
| measurement := spec.Measurements[key] |
| info := "" |
| if measurement.Info != nil { |
| message = append(message, fmt.Sprintf("%v", measurement.Info)) |
| } |
| |
| message = append(message, fmt.Sprintf("%s:\n%s %s: %s%s\n %s: %s%s\n %s: %s%s ± %s%s", |
| s.colorize(boldStyle, "%s", measurement.Name), |
| info, |
| measurement.SmallestLabel, |
| s.colorize(greenColor, measurement.PrecisionFmt(), measurement.Smallest), |
| measurement.Units, |
| measurement.LargestLabel, |
| s.colorize(redColor, measurement.PrecisionFmt(), measurement.Largest), |
| measurement.Units, |
| measurement.AverageLabel, |
| s.colorize(cyanColor, measurement.PrecisionFmt(), measurement.Average), |
| measurement.Units, |
| s.colorize(cyanColor, measurement.PrecisionFmt(), measurement.StdDeviation), |
| measurement.Units, |
| )) |
| } |
| } |
| |
| return strings.Join(message, "\n") |
| } |