mirror of
https://github.com/golang/go
synced 2024-11-21 16:44:43 -07:00
testing: fix racy t.done usage by adding a separate race-canary field
This commit is contained in:
parent
eaa7d9ff86
commit
e6ce5de4a4
@ -710,6 +710,7 @@ func fRunner(f *F, fn func(*F)) {
|
|||||||
|
|
||||||
// Report after all subtests have finished.
|
// Report after all subtests have finished.
|
||||||
f.report()
|
f.report()
|
||||||
|
f.doneRaceCanary = true
|
||||||
f.done = true
|
f.done = true
|
||||||
f.setRan()
|
f.setRan()
|
||||||
}()
|
}()
|
||||||
|
@ -593,20 +593,21 @@ const maxStackLen = 50
|
|||||||
// common holds the elements common between T and B and
|
// common holds the elements common between T and B and
|
||||||
// captures common methods such as Errorf.
|
// captures common methods such as Errorf.
|
||||||
type common struct {
|
type common struct {
|
||||||
mu sync.RWMutex // guards this group of fields
|
mu sync.RWMutex // guards this group of fields
|
||||||
output []byte // Output generated by test or benchmark.
|
output []byte // Output generated by test or benchmark.
|
||||||
w io.Writer // For flushToParent.
|
w io.Writer // For flushToParent.
|
||||||
ran bool // Test or benchmark (or one of its subtests) was executed.
|
ran bool // Test or benchmark (or one of its subtests) was executed.
|
||||||
failed bool // Test or benchmark has failed.
|
failed bool // Test or benchmark has failed.
|
||||||
skipped bool // Test or benchmark has been skipped.
|
skipped bool // Test or benchmark has been skipped.
|
||||||
done bool // Test is finished and all subtests have completed.
|
done bool // Test is finished and all subtests have completed.
|
||||||
helperPCs map[uintptr]struct{} // functions to be skipped when writing file/line info
|
doneRaceCanary bool // Shadows all interactions with done except test-completion to trigger a race on misbehavior.
|
||||||
helperNames map[string]struct{} // helperPCs converted to function names
|
helperPCs map[uintptr]struct{} // functions to be skipped when writing file/line info
|
||||||
cleanups []func() // optional functions to be called at the end of the test
|
helperNames map[string]struct{} // helperPCs converted to function names
|
||||||
cleanupName string // Name of the cleanup function.
|
cleanups []func() // optional functions to be called at the end of the test
|
||||||
cleanupPc []uintptr // The stack trace at the point where Cleanup was called.
|
cleanupName string // Name of the cleanup function.
|
||||||
finished bool // Test function has completed.
|
cleanupPc []uintptr // The stack trace at the point where Cleanup was called.
|
||||||
inFuzzFn bool // Whether the fuzz target, if this is one, is running.
|
finished bool // Test function has completed.
|
||||||
|
inFuzzFn bool // Whether the fuzz target, if this is one, is running.
|
||||||
|
|
||||||
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.
|
||||||
@ -949,6 +950,7 @@ func (c *common) Fail() {
|
|||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
// c.done needs to be locked to synchronize checks to c.done in parent tests.
|
// c.done needs to be locked to synchronize checks to c.done in parent tests.
|
||||||
|
_ = c.doneRaceCanary
|
||||||
if c.done {
|
if c.done {
|
||||||
panic("Fail in goroutine after " + c.name + " has completed")
|
panic("Fail in goroutine after " + c.name + " has completed")
|
||||||
}
|
}
|
||||||
@ -960,6 +962,7 @@ func (c *common) Failed() bool {
|
|||||||
c.mu.RLock()
|
c.mu.RLock()
|
||||||
defer c.mu.RUnlock()
|
defer c.mu.RUnlock()
|
||||||
|
|
||||||
|
_ = c.doneRaceCanary
|
||||||
if !c.done && int64(race.Errors()) > c.lastRaceErrors.Load() {
|
if !c.done && int64(race.Errors()) > c.lastRaceErrors.Load() {
|
||||||
c.mu.RUnlock()
|
c.mu.RUnlock()
|
||||||
c.checkRaces()
|
c.checkRaces()
|
||||||
@ -1015,12 +1018,14 @@ func (c *common) log(s string) {
|
|||||||
func (c *common) logDepth(s string, depth int) {
|
func (c *common) logDepth(s string, depth int) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
|
_ = c.doneRaceCanary
|
||||||
if c.done {
|
if c.done {
|
||||||
// This test has already finished. Try and log this message
|
// This test has already finished. Try and log this message
|
||||||
// with our parent. If we don't have a parent, panic.
|
// with our parent. If we don't have a parent, panic.
|
||||||
for parent := c.parent; parent != nil; parent = parent.parent {
|
for parent := c.parent; parent != nil; parent = parent.parent {
|
||||||
parent.mu.Lock()
|
parent.mu.Lock()
|
||||||
defer parent.mu.Unlock()
|
defer parent.mu.Unlock()
|
||||||
|
_ = parent.doneRaceCanary
|
||||||
if !parent.done {
|
if !parent.done {
|
||||||
parent.output = append(parent.output, parent.decorate(s, depth+1)...)
|
parent.output = append(parent.output, parent.decorate(s, depth+1)...)
|
||||||
return
|
return
|
||||||
@ -1672,9 +1677,13 @@ func tRunner(t *T, fn func(t *T)) {
|
|||||||
}
|
}
|
||||||
t.report() // Report after all subtests have finished.
|
t.report() // Report after all subtests have finished.
|
||||||
|
|
||||||
// Do not lock t.done to allow race detector to detect race in case
|
// Do not lock around t.done's race canary to allow race detector to detect
|
||||||
// the user does not appropriately synchronize a goroutine.
|
// cases where the user does not appropriately synchronize a goroutine...
|
||||||
|
t.doneRaceCanary = true
|
||||||
|
t.mu.Lock()
|
||||||
|
// ...but do lock around t.done so after-done behavior is consistent.
|
||||||
t.done = true
|
t.done = true
|
||||||
|
t.mu.Unlock()
|
||||||
if t.parent != nil && !t.hasSub.Load() {
|
if t.parent != nil && !t.hasSub.Load() {
|
||||||
t.setRan()
|
t.setRan()
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user