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)
	}
}