Skip to content

Commit b7f8c2a

Browse files
aclementsgopherbot
authored andcommitted
testing: detect early return from B.Loop
Currently, if a benchmark function returns prior to B.Loop() returning false, we'll report a bogus result. While there was no way to detect this with b.N-style benchmarks, one way b.Loop()-style benchmarks are more robust is that we *can* detect it. This CL adds a flag to B that tracks if B.Loop() has finished and checks it after the benchmark completes. If there was an early exit (not caused by another error), it reports a B.Error. Fixes #72933. Updates #72971. Change-Id: I731c1350e6df938c0ffa08fcedc11dc147e78854 Reviewed-on: https://go-review.googlesource.com/c/go/+/659656 LUCI-TryBot-Result: Go LUCI <[email protected]> Auto-Submit: Austin Clements <[email protected]> Reviewed-by: Michael Pratt <[email protected]> Reviewed-by: Junyang Shao <[email protected]>
1 parent c72e274 commit b7f8c2a

File tree

2 files changed

+70
-1
lines changed

2 files changed

+70
-1
lines changed

src/testing/benchmark.go

+9
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ type B struct {
124124
// i is the current Loop iteration. It's strictly monotonically
125125
// increasing toward n.
126126
i int
127+
128+
done bool // set when B.Loop return false
127129
}
128130
}
129131

@@ -201,6 +203,7 @@ func (b *B) runN(n int) {
201203
b.N = n
202204
b.loop.n = 0
203205
b.loop.i = 0
206+
b.loop.done = false
204207
b.ctx = ctx
205208
b.cancelCtx = cancelCtx
206209

@@ -211,6 +214,10 @@ func (b *B) runN(n int) {
211214
b.StopTimer()
212215
b.previousN = n
213216
b.previousDuration = b.duration
217+
218+
if b.loop.n > 0 && !b.loop.done && !b.failed {
219+
b.Error("benchmark function returned without B.Loop() == false (break or return in loop?)")
220+
}
214221
}
215222

216223
// run1 runs the first iteration of benchFunc. It reports whether more
@@ -382,6 +389,7 @@ func (b *B) stopOrScaleBLoop() bool {
382389
b.StopTimer()
383390
// Commit iteration count
384391
b.N = b.loop.n
392+
b.loop.done = true
385393
return false
386394
}
387395
// Loop scaling
@@ -414,6 +422,7 @@ func (b *B) loopSlowPath() bool {
414422
b.StopTimer()
415423
// Commit iteration count
416424
b.N = b.loop.n
425+
b.loop.done = true
417426
return false
418427
}
419428
// Handles fixed time case

src/testing/loop_test.go

+61-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@
44

55
package testing
66

7+
import (
8+
"bytes"
9+
"strings"
10+
)
11+
12+
// See also TestBenchmarkBLoop* in other files.
13+
714
func TestBenchmarkBLoop(t *T) {
815
var initialStart highPrecisionTime
916
var firstStart highPrecisionTime
@@ -68,4 +75,57 @@ func TestBenchmarkBLoop(t *T) {
6875
}
6976
}
7077

71-
// See also TestBenchmarkBLoop* in other files.
78+
func TestBenchmarkBLoopBreak(t *T) {
79+
var bState *B
80+
var bLog bytes.Buffer
81+
bRet := Benchmark(func(b *B) {
82+
// The Benchmark function provides no access to the failure state and
83+
// discards the log, so capture the B and save its log.
84+
bState = b
85+
b.common.w = &bLog
86+
87+
for i := 0; b.Loop(); i++ {
88+
if i == 2 {
89+
break
90+
}
91+
}
92+
})
93+
if !bState.failed {
94+
t.Errorf("benchmark should have failed")
95+
}
96+
const wantLog = "benchmark function returned without B.Loop"
97+
if log := bLog.String(); !strings.Contains(log, wantLog) {
98+
t.Errorf("missing error %q in output:\n%s", wantLog, log)
99+
}
100+
// A benchmark that exits early should not report its target iteration count
101+
// because it's not meaningful.
102+
if bRet.N != 0 {
103+
t.Errorf("want N == 0, got %d", bRet.N)
104+
}
105+
}
106+
107+
func TestBenchmarkBLoopError(t *T) {
108+
// Test that a benchmark that exits early because of an error doesn't *also*
109+
// complain that the benchmark exited early.
110+
var bState *B
111+
var bLog bytes.Buffer
112+
bRet := Benchmark(func(b *B) {
113+
bState = b
114+
b.common.w = &bLog
115+
116+
for i := 0; b.Loop(); i++ {
117+
b.Error("error")
118+
return
119+
}
120+
})
121+
if !bState.failed {
122+
t.Errorf("benchmark should have failed")
123+
}
124+
const noWantLog = "benchmark function returned without B.Loop"
125+
if log := bLog.String(); strings.Contains(log, noWantLog) {
126+
t.Errorf("unexpected error %q in output:\n%s", noWantLog, log)
127+
}
128+
if bRet.N != 0 {
129+
t.Errorf("want N == 0, got %d", bRet.N)
130+
}
131+
}

0 commit comments

Comments
 (0)