Skip to content

Commit bc5de81

Browse files
fraenkelodeke-em
authored andcommitted
testing: remove data races so that parallel benchmarks can safely call .Fatal* and .Skip*
Protects the usages of (*common).finished with locks to prevent data races, thus allowing benchmarks to safely invoke .Fatal* and .Skip* concurrently. Fixes #45526 Change-Id: I2b4846f525c426d6c7d3418f8f6c86446adbf986 Reviewed-on: https://go-review.googlesource.com/c/go/+/309572 Reviewed-by: Emmanuel Odeke <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]> Run-TryBot: Emmanuel Odeke <[email protected]> TryBot-Result: Go Bot <[email protected]> Trust: Tobias Klauser <[email protected]>
1 parent e97d8eb commit bc5de81

File tree

3 files changed

+45
-12
lines changed

3 files changed

+45
-12
lines changed

src/testing/benchmark.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,12 +238,15 @@ func (b *B) run1() bool {
238238
}
239239
// Only print the output if we know we are not going to proceed.
240240
// Otherwise it is printed in processBench.
241-
if atomic.LoadInt32(&b.hasSub) != 0 || b.finished {
241+
b.mu.RLock()
242+
finished := b.finished
243+
b.mu.RUnlock()
244+
if atomic.LoadInt32(&b.hasSub) != 0 || finished {
242245
tag := "BENCH"
243246
if b.skipped {
244247
tag = "SKIP"
245248
}
246-
if b.chatty != nil && (len(b.output) > 0 || b.finished) {
249+
if b.chatty != nil && (len(b.output) > 0 || finished) {
247250
b.trimOutput()
248251
fmt.Fprintf(b.w, "--- %s: %s\n%s", tag, b.name, b.output)
249252
}

src/testing/benchmark_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,30 @@ func TestRunParallelFail(t *testing.T) {
102102
})
103103
}
104104

105+
func TestRunParallelFatal(t *testing.T) {
106+
testing.Benchmark(func(b *testing.B) {
107+
b.RunParallel(func(pb *testing.PB) {
108+
for pb.Next() {
109+
if b.N > 1 {
110+
b.Fatal("error")
111+
}
112+
}
113+
})
114+
})
115+
}
116+
117+
func TestRunParallelSkipNow(t *testing.T) {
118+
testing.Benchmark(func(b *testing.B) {
119+
b.RunParallel(func(pb *testing.PB) {
120+
for pb.Next() {
121+
if b.N > 1 {
122+
b.SkipNow()
123+
}
124+
}
125+
})
126+
})
127+
}
128+
105129
func ExampleB_RunParallel() {
106130
// Parallel benchmark for text/template.Template.Execute on a single object.
107131
testing.Benchmark(func(b *testing.B) {

src/testing/testing.go

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -395,10 +395,10 @@ type common struct {
395395
cleanups []func() // optional functions to be called at the end of the test
396396
cleanupName string // Name of the cleanup function.
397397
cleanupPc []uintptr // The stack trace at the point where Cleanup was called.
398+
finished bool // Test function has completed.
398399

399400
chatty *chattyPrinter // A copy of chattyPrinter, if the chatty flag is set.
400401
bench bool // Whether the current test is a benchmark.
401-
finished bool // Test function has completed.
402402
hasSub int32 // Written atomically.
403403
raceErrors int // Number of races detected during test.
404404
runner string // Function name of tRunner running the test.
@@ -738,7 +738,9 @@ func (c *common) FailNow() {
738738
// it would run on a test failure. Because we send on c.signal during
739739
// a top-of-stack deferred function now, we know that the send
740740
// only happens after any other stacked defers have completed.
741+
c.mu.Lock()
741742
c.finished = true
743+
c.mu.Unlock()
742744
runtime.Goexit()
743745
}
744746

@@ -837,15 +839,11 @@ func (c *common) Skipf(format string, args ...interface{}) {
837839
// other goroutines created during the test. Calling SkipNow does not stop
838840
// those other goroutines.
839841
func (c *common) SkipNow() {
840-
c.skip()
841-
c.finished = true
842-
runtime.Goexit()
843-
}
844-
845-
func (c *common) skip() {
846842
c.mu.Lock()
847-
defer c.mu.Unlock()
848843
c.skipped = true
844+
c.finished = true
845+
c.mu.Unlock()
846+
runtime.Goexit()
849847
}
850848

851849
// Skipped reports whether the test was skipped.
@@ -1138,10 +1136,16 @@ func tRunner(t *T, fn func(t *T)) {
11381136
err := recover()
11391137
signal := true
11401138

1141-
if !t.finished && err == nil {
1139+
t.mu.RLock()
1140+
finished := t.finished
1141+
t.mu.RUnlock()
1142+
if !finished && err == nil {
11421143
err = errNilPanicOrGoexit
11431144
for p := t.parent; p != nil; p = p.parent {
1144-
if p.finished {
1145+
p.mu.RLock()
1146+
finished = p.finished
1147+
p.mu.RUnlock()
1148+
if finished {
11451149
t.Errorf("%v: subtest may have called FailNow on a parent test", err)
11461150
err = nil
11471151
signal = false
@@ -1235,7 +1239,9 @@ func tRunner(t *T, fn func(t *T)) {
12351239
fn(t)
12361240

12371241
// code beyond here will not be executed when FailNow is invoked
1242+
t.mu.Lock()
12381243
t.finished = true
1244+
t.mu.Unlock()
12391245
}
12401246

12411247
// Run runs f as a subtest of t called name. It runs f in a separate goroutine

0 commit comments

Comments
 (0)