573 lines
18 KiB
Go
573 lines
18 KiB
Go
/*
|
|
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/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)
|
|
|
|
AnnounceSuccessfulSpec(spec *types.SpecSummary)
|
|
AnnounceSuccessfulSlowSpec(spec *types.SpecSummary, succinct bool)
|
|
AnnounceSuccessfulMeasurement(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, writer io.Writer) Stenographer {
|
|
denoter := "•"
|
|
if runtime.GOOS == "windows" {
|
|
denoter = "+"
|
|
}
|
|
return &consoleStenographer{
|
|
color: color,
|
|
denoter: denoter,
|
|
cursorState: cursorStateTop,
|
|
enableFlakes: enableFlakes,
|
|
w: writer,
|
|
}
|
|
}
|
|
|
|
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) AnnounceSuccessfulSpec(spec *types.SpecSummary) {
|
|
s.print(0, s.colorize(greenColor, s.denoter))
|
|
s.stream()
|
|
}
|
|
|
|
func (s *consoleStenographer) AnnounceSuccessfulSlowSpec(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) AnnounceSuccessfulMeasurement(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")
|
|
}
|