2024-12-12 21:17:04 -05:00
|
|
|
// 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
|
|
|
|
|
2025-03-20 10:26:54 -04:00
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
// See also TestBenchmarkBLoop* in other files.
|
|
|
|
|
2024-12-12 21:17:04 -05:00
|
|
|
func TestBenchmarkBLoop(t *T) {
|
|
|
|
var initialStart highPrecisionTime
|
|
|
|
var firstStart highPrecisionTime
|
2025-03-18 21:13:23 +00:00
|
|
|
var scaledStart highPrecisionTime
|
2024-12-12 21:18:44 -05:00
|
|
|
var runningEnd bool
|
2024-12-12 21:17:04 -05:00
|
|
|
runs := 0
|
|
|
|
iters := 0
|
[release-branch.go1.24] testing: separate b.Loop counter from b.N
Currently, b.Loop uses b.N as the iteration count target. However,
since it updates the target as it goes, the behavior is quite
different from a b.N-style benchmark. To avoid user confusion, this CL
gives b.Loop a separate, unexported iteration count target. It ensures
b.N is 0 within the b.Loop loop to help catch misuses, and commits the
final iteration count to b.N only once the loop is done (as the
documentation states "After Loop returns false, b.N contains the total
number of iterations that ran, so the benchmark may use b.N to compute
other average metrics.")
Since there are now two variables used by b.Loop, we put them in an
unnamed struct. Also, we rename b.loopN to b.loop.i because this
variable tracks the current iteration index (conventionally "i"), not
the target (conventionally "n").
Unfortunately, a simple renaming causes B.Loop to be too large for the
inliner. Thus, we make one simplification to B.Loop to keep it under
the threshold. We're about to lean into that simplification anyway in
a follow-up CL, so this is just temporary.
For #72974.
Change-Id: Ide1c4f1b9ca37f300f3beb0e60ba6202331b183e
Reviewed-on: https://go-review.googlesource.com/c/go/+/659655
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Junyang Shao <shaojunyang@google.com>
Auto-Submit: Austin Clements <austin@google.com>
Reviewed-on: https://go-review.googlesource.com/c/go/+/660556
Auto-Submit: Dmitri Shuralyov <dmitshur@google.com>
2025-03-19 11:46:41 -04:00
|
|
|
firstBN := 0
|
|
|
|
restBN := 0
|
2024-12-12 21:17:04 -05:00
|
|
|
finalBN := 0
|
|
|
|
bRet := Benchmark(func(b *B) {
|
|
|
|
initialStart = b.start
|
|
|
|
runs++
|
|
|
|
for b.Loop() {
|
|
|
|
if iters == 0 {
|
|
|
|
firstStart = b.start
|
[release-branch.go1.24] testing: separate b.Loop counter from b.N
Currently, b.Loop uses b.N as the iteration count target. However,
since it updates the target as it goes, the behavior is quite
different from a b.N-style benchmark. To avoid user confusion, this CL
gives b.Loop a separate, unexported iteration count target. It ensures
b.N is 0 within the b.Loop loop to help catch misuses, and commits the
final iteration count to b.N only once the loop is done (as the
documentation states "After Loop returns false, b.N contains the total
number of iterations that ran, so the benchmark may use b.N to compute
other average metrics.")
Since there are now two variables used by b.Loop, we put them in an
unnamed struct. Also, we rename b.loopN to b.loop.i because this
variable tracks the current iteration index (conventionally "i"), not
the target (conventionally "n").
Unfortunately, a simple renaming causes B.Loop to be too large for the
inliner. Thus, we make one simplification to B.Loop to keep it under
the threshold. We're about to lean into that simplification anyway in
a follow-up CL, so this is just temporary.
For #72974.
Change-Id: Ide1c4f1b9ca37f300f3beb0e60ba6202331b183e
Reviewed-on: https://go-review.googlesource.com/c/go/+/659655
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Junyang Shao <shaojunyang@google.com>
Auto-Submit: Austin Clements <austin@google.com>
Reviewed-on: https://go-review.googlesource.com/c/go/+/660556
Auto-Submit: Dmitri Shuralyov <dmitshur@google.com>
2025-03-19 11:46:41 -04:00
|
|
|
firstBN = b.N
|
|
|
|
} else {
|
|
|
|
restBN = max(restBN, b.N)
|
2024-12-12 21:17:04 -05:00
|
|
|
}
|
2025-03-18 21:13:23 +00:00
|
|
|
if iters == 1 {
|
|
|
|
scaledStart = b.start
|
|
|
|
}
|
2024-12-12 21:17:04 -05:00
|
|
|
iters++
|
|
|
|
}
|
|
|
|
finalBN = b.N
|
2024-12-12 21:18:44 -05:00
|
|
|
runningEnd = b.timerOn
|
2024-12-12 21:17:04 -05:00
|
|
|
})
|
|
|
|
// 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)
|
|
|
|
}
|
[release-branch.go1.24] testing: separate b.Loop counter from b.N
Currently, b.Loop uses b.N as the iteration count target. However,
since it updates the target as it goes, the behavior is quite
different from a b.N-style benchmark. To avoid user confusion, this CL
gives b.Loop a separate, unexported iteration count target. It ensures
b.N is 0 within the b.Loop loop to help catch misuses, and commits the
final iteration count to b.N only once the loop is done (as the
documentation states "After Loop returns false, b.N contains the total
number of iterations that ran, so the benchmark may use b.N to compute
other average metrics.")
Since there are now two variables used by b.Loop, we put them in an
unnamed struct. Also, we rename b.loopN to b.loop.i because this
variable tracks the current iteration index (conventionally "i"), not
the target (conventionally "n").
Unfortunately, a simple renaming causes B.Loop to be too large for the
inliner. Thus, we make one simplification to B.Loop to keep it under
the threshold. We're about to lean into that simplification anyway in
a follow-up CL, so this is just temporary.
For #72974.
Change-Id: Ide1c4f1b9ca37f300f3beb0e60ba6202331b183e
Reviewed-on: https://go-review.googlesource.com/c/go/+/659655
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Junyang Shao <shaojunyang@google.com>
Auto-Submit: Austin Clements <austin@google.com>
Reviewed-on: https://go-review.googlesource.com/c/go/+/660556
Auto-Submit: Dmitri Shuralyov <dmitshur@google.com>
2025-03-19 11:46:41 -04:00
|
|
|
// 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)
|
|
|
|
}
|
2024-12-12 21:17:04 -05:00
|
|
|
// 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")
|
|
|
|
}
|
2025-03-18 21:13:23 +00:00
|
|
|
if scaledStart != firstStart {
|
|
|
|
t.Errorf("b.Loop stops and restarts the timer during iteration")
|
2024-12-12 21:17:04 -05:00
|
|
|
}
|
2024-12-12 21:18:44 -05:00
|
|
|
// Verify that it stopped the timer after the last loop.
|
|
|
|
if runningEnd {
|
|
|
|
t.Errorf("timer was still running after last iteration")
|
|
|
|
}
|
2024-12-12 21:17:04 -05:00
|
|
|
}
|
|
|
|
|
2025-03-20 10:26:54 -04:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
2025-03-20 12:16:17 -04:00
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|