Skip to content

Commit d10eddc

Browse files
rscdsnet
authored andcommitted
testing: make parallel t.Run safe again
Fixes #18603. Change-Id: I5760c0a9f862200b7e943058a672eb559ac1b9d9 Reviewed-on: https://go-review.googlesource.com/35354 Run-TryBot: Russ Cox <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Joe Tsai <[email protected]> Reviewed-by: Brad Fitzpatrick <[email protected]>
1 parent 2c8b70e commit d10eddc

File tree

3 files changed

+30
-6
lines changed

3 files changed

+30
-6
lines changed

src/testing/benchmark.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ func (b *B) run1() bool {
219219
}
220220
// Only print the output if we know we are not going to proceed.
221221
// Otherwise it is printed in processBench.
222-
if b.hasSub || b.finished {
222+
if atomic.LoadInt32(&b.hasSub) != 0 || b.finished {
223223
tag := "BENCH"
224224
if b.skipped {
225225
tag = "SKIP"
@@ -460,10 +460,13 @@ func (ctx *benchContext) processBench(b *B) {
460460
//
461461
// A subbenchmark is like any other benchmark. A benchmark that calls Run at
462462
// least once will not be measured itself and will be called once with N=1.
463+
//
464+
// Run may be called simultaneously from multiple goroutines, but all such
465+
// calls must happen before the outer benchmark function for b returns.
463466
func (b *B) Run(name string, f func(b *B)) bool {
464467
// Since b has subbenchmarks, we will no longer run it as a benchmark itself.
465468
// Release the lock and acquire it on exit to ensure locks stay paired.
466-
b.hasSub = true
469+
atomic.StoreInt32(&b.hasSub, 1)
467470
benchmarkLock.Unlock()
468471
defer benchmarkLock.Lock()
469472

src/testing/sub_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package testing
66

77
import (
88
"bytes"
9+
"fmt"
910
"regexp"
1011
"strings"
1112
"sync/atomic"
@@ -515,3 +516,19 @@ func TestBenchmarkOutput(t *T) {
515516
Benchmark(func(b *B) { b.Error("do not print this output") })
516517
Benchmark(func(b *B) {})
517518
}
519+
520+
func TestParallelSub(t *T) {
521+
c := make(chan int)
522+
block := make(chan int)
523+
for i := 0; i < 10; i++ {
524+
go func(i int) {
525+
<-block
526+
t.Run(fmt.Sprint(i), func(t *T) {})
527+
c <- 1
528+
}(i)
529+
}
530+
close(block)
531+
for i := 0; i < 10; i++ {
532+
<-c
533+
}
534+
}

src/testing/testing.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ import (
216216
"strconv"
217217
"strings"
218218
"sync"
219+
"sync/atomic"
219220
"time"
220221
)
221222

@@ -267,8 +268,8 @@ type common struct {
267268
skipped bool // Test of benchmark has been skipped.
268269
finished bool // Test function has completed.
269270
done bool // Test is finished and all subtests have completed.
270-
hasSub bool
271-
raceErrors int // number of races detected during test
271+
hasSub int32 // written atomically
272+
raceErrors int // number of races detected during test
272273

273274
parent *common
274275
level int // Nesting depth of test or benchmark.
@@ -645,7 +646,7 @@ func tRunner(t *T, fn func(t *T)) {
645646
// Do not lock t.done to allow race detector to detect race in case
646647
// the user does not appropriately synchronizes a goroutine.
647648
t.done = true
648-
if t.parent != nil && !t.hasSub {
649+
if t.parent != nil && atomic.LoadInt32(&t.hasSub) == 0 {
649650
t.setRan()
650651
}
651652
t.signal <- true
@@ -659,8 +660,11 @@ func tRunner(t *T, fn func(t *T)) {
659660

660661
// Run runs f as a subtest of t called name. It reports whether f succeeded.
661662
// Run will block until all its parallel subtests have completed.
663+
//
664+
// Run may be called simultaneously from multiple goroutines, but all such
665+
// calls must happen before the outer test function for t returns.
662666
func (t *T) Run(name string, f func(t *T)) bool {
663-
t.hasSub = true
667+
atomic.StoreInt32(&t.hasSub, 1)
664668
testName, ok := t.context.match.fullName(&t.common, name)
665669
if !ok {
666670
return true

0 commit comments

Comments
 (0)