From 76582c725475b7441a0f0ef4ccac1f18b0ac3910 Mon Sep 17 00:00:00 2001 From: Jordan Olshevski Date: Tue, 12 Jul 2016 18:30:02 -0700 Subject: [PATCH] Allow units and precision in benchmark A RecordValueWithPrecision method is added to the Benchmarker interface. The method behaves similarly to RecordValue, but allows the caller to additionally provide a measurement unit and float truncate precision. The stenographer is also updated to facilitate variable precision. --- ginkgo_dsl.go | 3 ++ internal/leafnodes/benchmarker.go | 14 ++++++-- internal/leafnodes/measure_node_test.go | 45 +++++++++++++++++++++++++ internal/spec/spec_test.go | 10 +++++- internal/suite/suite_test.go | 3 ++ reporters/stenographer/stenographer.go | 16 ++++----- types/types.go | 16 ++++++++- types/types_test.go | 10 ++++++ 8 files changed, 104 insertions(+), 13 deletions(-) diff --git a/ginkgo_dsl.go b/ginkgo_dsl.go index 1d8e59305..0220549f7 100644 --- a/ginkgo_dsl.go +++ b/ginkgo_dsl.go @@ -168,6 +168,8 @@ func CurrentGinkgoTestDescription() GinkgoTestDescription { // //You use the Time() function to time how long the passed in body function takes to run //You use the RecordValue() function to track arbitrary numerical measurements. +//The RecordValueWithPrecision() function can be used alternatively to provide the unit +//and resolution of the numeric measurement. //The optional info argument is passed to the test reporter and can be used to // provide the measurement data to a custom reporter with context. // @@ -175,6 +177,7 @@ func CurrentGinkgoTestDescription() GinkgoTestDescription { type Benchmarker interface { Time(name string, body func(), info ...interface{}) (elapsedTime time.Duration) RecordValue(name string, value float64, info ...interface{}) + RecordValueWithPrecision(name string, value float64, units string, precision int, info ...interface{}) } //RunSpecs is the entry point for the Ginkgo test runner. diff --git a/internal/leafnodes/benchmarker.go b/internal/leafnodes/benchmarker.go index bc0dd1a62..9c3eed2b6 100644 --- a/internal/leafnodes/benchmarker.go +++ b/internal/leafnodes/benchmarker.go @@ -28,20 +28,27 @@ func (b *benchmarker) Time(name string, body func(), info ...interface{}) (elaps b.mu.Lock() defer b.mu.Unlock() - measurement := b.getMeasurement(name, "Fastest Time", "Slowest Time", "Average Time", "s", info...) + measurement := b.getMeasurement(name, "Fastest Time", "Slowest Time", "Average Time", "s", 3, info...) measurement.Results = append(measurement.Results, elapsedTime.Seconds()) return } func (b *benchmarker) RecordValue(name string, value float64, info ...interface{}) { - measurement := b.getMeasurement(name, "Smallest", " Largest", " Average", "", info...) + measurement := b.getMeasurement(name, "Smallest", " Largest", " Average", "", 3, info...) b.mu.Lock() defer b.mu.Unlock() measurement.Results = append(measurement.Results, value) } -func (b *benchmarker) getMeasurement(name string, smallestLabel string, largestLabel string, averageLabel string, units string, info ...interface{}) *types.SpecMeasurement { +func (b *benchmarker) RecordValueWithPrecision(name string, value float64, units string, precision int, info ...interface{}) { + measurement := b.getMeasurement(name, "Smallest", " Largest", " Average", units, precision, info...) + b.mu.Lock() + defer b.mu.Unlock() + measurement.Results = append(measurement.Results, value) +} + +func (b *benchmarker) getMeasurement(name string, smallestLabel string, largestLabel string, averageLabel string, units string, precision int, info ...interface{}) *types.SpecMeasurement { measurement, ok := b.measurements[name] if !ok { var computedInfo interface{} @@ -57,6 +64,7 @@ func (b *benchmarker) getMeasurement(name string, smallestLabel string, largestL LargestLabel: largestLabel, AverageLabel: averageLabel, Units: units, + Precision: precision, Results: make([]float64, 0), } b.measurements[name] = measurement diff --git a/internal/leafnodes/measure_node_test.go b/internal/leafnodes/measure_node_test.go index 4dcd00bb3..252065b87 100644 --- a/internal/leafnodes/measure_node_test.go +++ b/internal/leafnodes/measure_node_test.go @@ -70,6 +70,51 @@ var _ = Describe("Measure Nodes", func() { }) }) + Describe("Value with precision", func() { + BeforeEach(func() { + measure = NewMeasureNode("the measurement", func(b Benchmarker) { + b.RecordValueWithPrecision("foo", 7, "ms", 7, "info!") + b.RecordValueWithPrecision("foo", 2, "ms", 6) + b.RecordValueWithPrecision("foo", 3, "ms", 5) + b.RecordValueWithPrecision("bar", 0.3, "ns", 4) + b.RecordValueWithPrecision("bar", 0.1, "ns", 3) + b.RecordValueWithPrecision("bar", 0.5, "ns", 2) + b.RecordValueWithPrecision("bar", 0.7, "ns", 1) + }, types.FlagTypeFocused, codelocation.New(0), 1, Failer.New(), 3) + Ω(measure.Run()).Should(Equal(types.SpecStatePassed)) + }) + + It("records passed in values and reports on them", func() { + report := measure.MeasurementsReport() + Ω(report).Should(HaveLen(2)) + Ω(report["foo"].Name).Should(Equal("foo")) + Ω(report["foo"].Info).Should(Equal("info!")) + Ω(report["foo"].Order).Should(Equal(0)) + Ω(report["foo"].SmallestLabel).Should(Equal("Smallest")) + Ω(report["foo"].LargestLabel).Should(Equal(" Largest")) + Ω(report["foo"].AverageLabel).Should(Equal(" Average")) + Ω(report["foo"].Units).Should(Equal("ms")) + Ω(report["foo"].Results).Should(Equal([]float64{7, 2, 3})) + Ω(report["foo"].Smallest).Should(BeNumerically("==", 2)) + Ω(report["foo"].Largest).Should(BeNumerically("==", 7)) + Ω(report["foo"].Average).Should(BeNumerically("==", 4)) + Ω(report["foo"].StdDeviation).Should(BeNumerically("~", 2.16, 0.01)) + + Ω(report["bar"].Name).Should(Equal("bar")) + Ω(report["bar"].Info).Should(BeNil()) + Ω(report["bar"].SmallestLabel).Should(Equal("Smallest")) + Ω(report["bar"].Order).Should(Equal(1)) + Ω(report["bar"].LargestLabel).Should(Equal(" Largest")) + Ω(report["bar"].AverageLabel).Should(Equal(" Average")) + Ω(report["bar"].Units).Should(Equal("ns")) + Ω(report["bar"].Results).Should(Equal([]float64{0.3, 0.1, 0.5, 0.7})) + Ω(report["bar"].Smallest).Should(BeNumerically("==", 0.1)) + Ω(report["bar"].Largest).Should(BeNumerically("==", 0.7)) + Ω(report["bar"].Average).Should(BeNumerically("==", 0.4)) + Ω(report["bar"].StdDeviation).Should(BeNumerically("~", 0.22, 0.01)) + }) + }) + Describe("Time", func() { BeforeEach(func() { measure = NewMeasureNode("the measurement", func(b Benchmarker) { diff --git a/internal/spec/spec_test.go b/internal/spec/spec_test.go index 6d0f58cb0..c199f4ae9 100644 --- a/internal/spec/spec_test.go +++ b/internal/spec/spec_test.go @@ -558,6 +558,7 @@ var _ = Describe("Spec", func() { BeforeEach(func() { spec = New(leafnodes.NewMeasureNode("measure node", func(b Benchmarker) { b.RecordValue("a value", 7, "some info") + b.RecordValueWithPrecision("another value", 8, "ns", 5, "more info") }, noneFlag, codeLocation, 4, failer, 0), containers(), false) spec.Run(buffer) Ω(spec.Passed()).Should(BeTrue()) @@ -574,11 +575,18 @@ var _ = Describe("Spec", func() { It("should have the measurements report", func() { Ω(summary.Measurements).Should(HaveKey("a value")) - report := summary.Measurements["a value"] Ω(report.Name).Should(Equal("a value")) Ω(report.Info).Should(Equal("some info")) Ω(report.Results).Should(Equal([]float64{7, 7, 7, 7})) + + Ω(summary.Measurements).Should(HaveKey("another value")) + report = summary.Measurements["another value"] + Ω(report.Name).Should(Equal("another value")) + Ω(report.Info).Should(Equal("more info")) + Ω(report.Results).Should(Equal([]float64{8, 8, 8, 8})) + Ω(report.Units).Should(Equal("ns")) + Ω(report.Precision).Should(Equal(5)) }) }) diff --git a/internal/suite/suite_test.go b/internal/suite/suite_test.go index e975720f9..6e663e2f5 100644 --- a/internal/suite/suite_test.go +++ b/internal/suite/suite_test.go @@ -137,6 +137,9 @@ var _ = Describe("Suite", func() { b.RecordValue("random value", randomValue) Ω(randomValue).Should(BeNumerically("<=", 10.0)) Ω(randomValue).Should(BeNumerically(">=", 0.0)) + + b.RecordValueWithPrecision("specific value", 123.4567, "ms", 2) + b.RecordValueWithPrecision("specific value", 234.5678, "ms", 2) }, 10) It("creates a node hierarchy, converts it to a spec collection, and runs it", func() { diff --git a/reporters/stenographer/stenographer.go b/reporters/stenographer/stenographer.go index 5b5d905da..b58892475 100644 --- a/reporters/stenographer/stenographer.go +++ b/reporters/stenographer/stenographer.go @@ -506,15 +506,15 @@ func (s *consoleStenographer) measurementReport(spec *types.SpecSummary, succinc 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, "%.3f", measurement.Smallest), + s.colorize(greenColor, measurement.PrecisionFmt(), measurement.Smallest), measurement.Units, measurement.AverageLabel, - s.colorize(cyanColor, "%.3f", measurement.Average), + s.colorize(cyanColor, measurement.PrecisionFmt(), measurement.Average), measurement.Units, - s.colorize(cyanColor, "%.3f", measurement.StdDeviation), + s.colorize(cyanColor, measurement.PrecisionFmt(), measurement.StdDeviation), measurement.Units, measurement.LargestLabel, - s.colorize(redColor, "%.3f", measurement.Largest), + s.colorize(redColor, measurement.PrecisionFmt(), measurement.Largest), measurement.Units, )) } @@ -531,15 +531,15 @@ func (s *consoleStenographer) measurementReport(spec *types.SpecSummary, succinc s.colorize(boldStyle, "%s", measurement.Name), info, measurement.SmallestLabel, - s.colorize(greenColor, "%.3f", measurement.Smallest), + s.colorize(greenColor, measurement.PrecisionFmt(), measurement.Smallest), measurement.Units, measurement.LargestLabel, - s.colorize(redColor, "%.3f", measurement.Largest), + s.colorize(redColor, measurement.PrecisionFmt(), measurement.Largest), measurement.Units, measurement.AverageLabel, - s.colorize(cyanColor, "%.3f", measurement.Average), + s.colorize(cyanColor, measurement.PrecisionFmt(), measurement.Average), measurement.Units, - s.colorize(cyanColor, "%.3f", measurement.StdDeviation), + s.colorize(cyanColor, measurement.PrecisionFmt(), measurement.StdDeviation), measurement.Units, )) } diff --git a/types/types.go b/types/types.go index 889612e0a..ef695775c 100644 --- a/types/types.go +++ b/types/types.go @@ -1,6 +1,9 @@ package types -import "time" +import ( + "strconv" + "time" +) const GINKGO_FOCUS_EXIT_CODE = 197 @@ -100,6 +103,17 @@ type SpecMeasurement struct { LargestLabel string AverageLabel string Units string + Precision int +} + +func (s SpecMeasurement) PrecisionFmt() string { + if s.Precision == 0 { + return "%f" + } + + str := strconv.Itoa(s.Precision) + + return "%." + str + "f" } type SpecState uint diff --git a/types/types_test.go b/types/types_test.go index 5e3b1af9d..a0e161c88 100644 --- a/types/types_test.go +++ b/types/types_test.go @@ -86,4 +86,14 @@ var _ = Describe("Types", func() { }, SpecStateSkipped) }) }) + + Describe("SpecMeasurement", func() { + It("knows how to format values when the precision is 0", func() { + Ω(SpecMeasurement{}.PrecisionFmt()).Should(Equal("%f")) + }) + + It("knows how to format the values when the precision is 3", func() { + Ω(SpecMeasurement{Precision: 3}.PrecisionFmt()).Should(Equal("%.3f")) + }) + }) })