mirror of
https://github.com/golang/go
synced 2024-11-22 20:24:47 -07:00
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 <emmanuel@orijtech.com> Reviewed-by: Ian Lance Taylor <iant@golang.org> Run-TryBot: Emmanuel Odeke <emmanuel@orijtech.com> TryBot-Result: Go Bot <gobot@golang.org> Trust: Tobias Klauser <tobias.klauser@gmail.com>
This commit is contained in:
parent
e97d8eb027
commit
bc5de81e70
@ -238,12 +238,15 @@ func (b *B) run1() bool {
|
|||||||
}
|
}
|
||||||
// Only print the output if we know we are not going to proceed.
|
// Only print the output if we know we are not going to proceed.
|
||||||
// Otherwise it is printed in processBench.
|
// Otherwise it is printed in processBench.
|
||||||
if atomic.LoadInt32(&b.hasSub) != 0 || b.finished {
|
b.mu.RLock()
|
||||||
|
finished := b.finished
|
||||||
|
b.mu.RUnlock()
|
||||||
|
if atomic.LoadInt32(&b.hasSub) != 0 || finished {
|
||||||
tag := "BENCH"
|
tag := "BENCH"
|
||||||
if b.skipped {
|
if b.skipped {
|
||||||
tag = "SKIP"
|
tag = "SKIP"
|
||||||
}
|
}
|
||||||
if b.chatty != nil && (len(b.output) > 0 || b.finished) {
|
if b.chatty != nil && (len(b.output) > 0 || finished) {
|
||||||
b.trimOutput()
|
b.trimOutput()
|
||||||
fmt.Fprintf(b.w, "--- %s: %s\n%s", tag, b.name, b.output)
|
fmt.Fprintf(b.w, "--- %s: %s\n%s", tag, b.name, b.output)
|
||||||
}
|
}
|
||||||
|
@ -102,6 +102,30 @@ func TestRunParallelFail(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRunParallelFatal(t *testing.T) {
|
||||||
|
testing.Benchmark(func(b *testing.B) {
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
if b.N > 1 {
|
||||||
|
b.Fatal("error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunParallelSkipNow(t *testing.T) {
|
||||||
|
testing.Benchmark(func(b *testing.B) {
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
if b.N > 1 {
|
||||||
|
b.SkipNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func ExampleB_RunParallel() {
|
func ExampleB_RunParallel() {
|
||||||
// Parallel benchmark for text/template.Template.Execute on a single object.
|
// Parallel benchmark for text/template.Template.Execute on a single object.
|
||||||
testing.Benchmark(func(b *testing.B) {
|
testing.Benchmark(func(b *testing.B) {
|
||||||
|
@ -395,10 +395,10 @@ type common struct {
|
|||||||
cleanups []func() // optional functions to be called at the end of the test
|
cleanups []func() // optional functions to be called at the end of the test
|
||||||
cleanupName string // Name of the cleanup function.
|
cleanupName string // Name of the cleanup function.
|
||||||
cleanupPc []uintptr // The stack trace at the point where Cleanup was called.
|
cleanupPc []uintptr // The stack trace at the point where Cleanup was called.
|
||||||
|
finished bool // Test function has completed.
|
||||||
|
|
||||||
chatty *chattyPrinter // A copy of chattyPrinter, if the chatty flag is set.
|
chatty *chattyPrinter // A copy of chattyPrinter, if the chatty flag is set.
|
||||||
bench bool // Whether the current test is a benchmark.
|
bench bool // Whether the current test is a benchmark.
|
||||||
finished bool // Test function has completed.
|
|
||||||
hasSub int32 // Written atomically.
|
hasSub int32 // Written atomically.
|
||||||
raceErrors int // Number of races detected during test.
|
raceErrors int // Number of races detected during test.
|
||||||
runner string // Function name of tRunner running the test.
|
runner string // Function name of tRunner running the test.
|
||||||
@ -738,7 +738,9 @@ func (c *common) FailNow() {
|
|||||||
// it would run on a test failure. Because we send on c.signal during
|
// it would run on a test failure. Because we send on c.signal during
|
||||||
// a top-of-stack deferred function now, we know that the send
|
// a top-of-stack deferred function now, we know that the send
|
||||||
// only happens after any other stacked defers have completed.
|
// only happens after any other stacked defers have completed.
|
||||||
|
c.mu.Lock()
|
||||||
c.finished = true
|
c.finished = true
|
||||||
|
c.mu.Unlock()
|
||||||
runtime.Goexit()
|
runtime.Goexit()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -837,15 +839,11 @@ func (c *common) Skipf(format string, args ...interface{}) {
|
|||||||
// other goroutines created during the test. Calling SkipNow does not stop
|
// other goroutines created during the test. Calling SkipNow does not stop
|
||||||
// those other goroutines.
|
// those other goroutines.
|
||||||
func (c *common) SkipNow() {
|
func (c *common) SkipNow() {
|
||||||
c.skip()
|
|
||||||
c.finished = true
|
|
||||||
runtime.Goexit()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *common) skip() {
|
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
|
||||||
c.skipped = true
|
c.skipped = true
|
||||||
|
c.finished = true
|
||||||
|
c.mu.Unlock()
|
||||||
|
runtime.Goexit()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skipped reports whether the test was skipped.
|
// Skipped reports whether the test was skipped.
|
||||||
@ -1138,10 +1136,16 @@ func tRunner(t *T, fn func(t *T)) {
|
|||||||
err := recover()
|
err := recover()
|
||||||
signal := true
|
signal := true
|
||||||
|
|
||||||
if !t.finished && err == nil {
|
t.mu.RLock()
|
||||||
|
finished := t.finished
|
||||||
|
t.mu.RUnlock()
|
||||||
|
if !finished && err == nil {
|
||||||
err = errNilPanicOrGoexit
|
err = errNilPanicOrGoexit
|
||||||
for p := t.parent; p != nil; p = p.parent {
|
for p := t.parent; p != nil; p = p.parent {
|
||||||
if p.finished {
|
p.mu.RLock()
|
||||||
|
finished = p.finished
|
||||||
|
p.mu.RUnlock()
|
||||||
|
if finished {
|
||||||
t.Errorf("%v: subtest may have called FailNow on a parent test", err)
|
t.Errorf("%v: subtest may have called FailNow on a parent test", err)
|
||||||
err = nil
|
err = nil
|
||||||
signal = false
|
signal = false
|
||||||
@ -1235,7 +1239,9 @@ func tRunner(t *T, fn func(t *T)) {
|
|||||||
fn(t)
|
fn(t)
|
||||||
|
|
||||||
// code beyond here will not be executed when FailNow is invoked
|
// code beyond here will not be executed when FailNow is invoked
|
||||||
|
t.mu.Lock()
|
||||||
t.finished = true
|
t.finished = true
|
||||||
|
t.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run runs f as a subtest of t called name. It runs f in a separate goroutine
|
// Run runs f as a subtest of t called name. It runs f in a separate goroutine
|
||||||
|
Loading…
Reference in New Issue
Block a user