File size: 5,151 Bytes
e36aeda | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 | // Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package testing
import (
"bytes"
"strings"
"time"
)
// See also TestBenchmarkBLoop* in other files.
func TestBenchmarkBLoop(t *T) {
var initialStart highPrecisionTime
var firstStart highPrecisionTime
var scaledStart highPrecisionTime
var runningEnd bool
runs := 0
iters := 0
firstBN := 0
restBN := 0
finalBN := 0
bRet := Benchmark(func(b *B) {
initialStart = b.start
runs++
for b.Loop() {
if iters == 0 {
firstStart = b.start
firstBN = b.N
} else {
restBN = max(restBN, b.N)
}
if iters == 1 {
scaledStart = b.start
}
iters++
}
finalBN = b.N
runningEnd = b.timerOn
})
// Verify that a b.Loop benchmark is invoked just once.
if runs != 1 {
t.Errorf("want runs == 1, got %d", runs)
}
// Verify that at least one iteration ran.
if iters == 0 {
t.Fatalf("no iterations ran")
}
// Verify that b.N, bRet.N, and the b.Loop() iteration count match.
if finalBN != iters || bRet.N != iters {
t.Errorf("benchmark iterations mismatch: %d loop iterations, final b.N=%d, bRet.N=%d", iters, finalBN, bRet.N)
}
// Verify that b.N was 0 inside the loop
if firstBN != 0 {
t.Errorf("want b.N == 0 on first iteration, got %d", firstBN)
}
if restBN != 0 {
t.Errorf("want b.N == 0 on subsequent iterations, got %d", restBN)
}
// Make sure the benchmark ran for an appropriate amount of time.
if bRet.T < benchTime.d {
t.Fatalf("benchmark ran for %s, want >= %s", bRet.T, benchTime.d)
}
// Verify that the timer is reset on the first loop, and then left alone.
if firstStart == initialStart {
t.Errorf("b.Loop did not reset the timer")
}
if scaledStart != firstStart {
t.Errorf("b.Loop stops and restarts the timer during iteration")
}
// Verify that it stopped the timer after the last loop.
if runningEnd {
t.Errorf("timer was still running after last iteration")
}
}
func TestBenchmarkBLoopCheapEarlyTerminate(t *T) {
if Short() {
t.Skip("B.Loop test needs to run for > 1s to saturate 1e9 iterations")
}
runCnt := 0
// Set the benchmark time high enough that we're likely to hit the 1B
// iteration limit even on very slow hardware.
// (on an AMD Ryzen 5900X, this benchmark runs in just over a second)
//
// Notably, the assertions below shouldn't fail if a test-run is slow
// enough that it doesn't saturate the limit.
const maxBenchTime = time.Second * 30
res := Benchmark(func(b *B) {
// Set the benchmark time _much_ higher than required to hit 1e9 iterations.
b.benchTime.d = maxBenchTime
for b.Loop() {
runCnt++
}
})
if runCnt > maxBenchPredictIters {
t.Errorf("loop body ran more than max (%d) times: %d", maxBenchPredictIters, runCnt)
if res.T >= maxBenchTime {
t.Logf("cheap benchmark exhausted time budget: %s; ran for %s", maxBenchTime, res.T)
}
}
if res.N != runCnt {
t.Errorf("disagreeing loop counts: res.N reported %d, while b.Loop() iterated %d times", res.N, runCnt)
}
if res.N > maxBenchPredictIters {
t.Errorf("benchmark result claims more runs than max (%d) times: %d", maxBenchPredictIters, res.N)
}
}
func TestBenchmarkBLoopBreak(t *T) {
var bState *B
var bLog bytes.Buffer
bRet := Benchmark(func(b *B) {
// The Benchmark function provides no access to the failure state and
// discards the log, so capture the B and save its log.
bState = b
b.common.w = &bLog
for i := 0; b.Loop(); i++ {
if i == 2 {
break
}
}
})
if !bState.failed {
t.Errorf("benchmark should have failed")
}
const wantLog = "benchmark function returned without B.Loop"
if log := bLog.String(); !strings.Contains(log, wantLog) {
t.Errorf("missing error %q in output:\n%s", wantLog, log)
}
// A benchmark that exits early should not report its target iteration count
// because it's not meaningful.
if bRet.N != 0 {
t.Errorf("want N == 0, got %d", bRet.N)
}
}
func TestBenchmarkBLoopError(t *T) {
// Test that a benchmark that exits early because of an error doesn't *also*
// complain that the benchmark exited early.
var bState *B
var bLog bytes.Buffer
bRet := Benchmark(func(b *B) {
bState = b
b.common.w = &bLog
for i := 0; b.Loop(); i++ {
b.Error("error")
return
}
})
if !bState.failed {
t.Errorf("benchmark should have failed")
}
const noWantLog = "benchmark function returned without B.Loop"
if log := bLog.String(); strings.Contains(log, noWantLog) {
t.Errorf("unexpected error %q in output:\n%s", noWantLog, log)
}
if bRet.N != 0 {
t.Errorf("want N == 0, got %d", bRet.N)
}
}
func TestBenchmarkBLoopStop(t *T) {
var bState *B
var bLog bytes.Buffer
bRet := Benchmark(func(b *B) {
bState = b
b.common.w = &bLog
for i := 0; b.Loop(); i++ {
b.StopTimer()
}
})
if !bState.failed {
t.Errorf("benchmark should have failed")
}
const wantLog = "B.Loop called with timer stopped"
if log := bLog.String(); !strings.Contains(log, wantLog) {
t.Errorf("missing error %q in output:\n%s", wantLog, log)
}
if bRet.N != 0 {
t.Errorf("want N == 0, got %d", bRet.N)
}
}
|