@@ -593,20 +593,21 @@ const maxStackLen = 50
593
593
// common holds the elements common between T and B and
594
594
// captures common methods such as Errorf.
595
595
type common struct {
596
- mu sync.RWMutex // guards this group of fields
597
- output []byte // Output generated by test or benchmark.
598
- w io.Writer // For flushToParent.
599
- ran bool // Test or benchmark (or one of its subtests) was executed.
600
- failed bool // Test or benchmark has failed.
601
- skipped bool // Test or benchmark has been skipped.
602
- done bool // Test is finished and all subtests have completed.
603
- helperPCs map [uintptr ]struct {} // functions to be skipped when writing file/line info
604
- helperNames map [string ]struct {} // helperPCs converted to function names
605
- cleanups []func () // optional functions to be called at the end of the test
606
- cleanupName string // Name of the cleanup function.
607
- cleanupPc []uintptr // The stack trace at the point where Cleanup was called.
608
- finished bool // Test function has completed.
609
- inFuzzFn bool // Whether the fuzz target, if this is one, is running.
596
+ mu sync.RWMutex // guards this group of fields
597
+ output []byte // Output generated by test or benchmark.
598
+ w io.Writer // For flushToParent.
599
+ ran bool // Test or benchmark (or one of its subtests) was executed.
600
+ failed bool // Test or benchmark has failed.
601
+ skipped bool // Test or benchmark has been skipped.
602
+ done bool // Test is finished and all subtests have completed.
603
+ doneRaceCanary bool // Shadows all interactions with done except test-completion to trigger a race on misbehavior.
604
+ helperPCs map [uintptr ]struct {} // functions to be skipped when writing file/line info
605
+ helperNames map [string ]struct {} // helperPCs converted to function names
606
+ cleanups []func () // optional functions to be called at the end of the test
607
+ cleanupName string // Name of the cleanup function.
608
+ cleanupPc []uintptr // The stack trace at the point where Cleanup was called.
609
+ finished bool // Test function has completed.
610
+ inFuzzFn bool // Whether the fuzz target, if this is one, is running.
610
611
611
612
chatty * chattyPrinter // A copy of chattyPrinter, if the chatty flag is set.
612
613
bench bool // Whether the current test is a benchmark.
@@ -949,6 +950,7 @@ func (c *common) Fail() {
949
950
c .mu .Lock ()
950
951
defer c .mu .Unlock ()
951
952
// c.done needs to be locked to synchronize checks to c.done in parent tests.
953
+ _ = c .doneRaceCanary
952
954
if c .done {
953
955
panic ("Fail in goroutine after " + c .name + " has completed" )
954
956
}
@@ -960,6 +962,7 @@ func (c *common) Failed() bool {
960
962
c .mu .RLock ()
961
963
defer c .mu .RUnlock ()
962
964
965
+ _ = c .doneRaceCanary
963
966
if ! c .done && int64 (race .Errors ()) > c .lastRaceErrors .Load () {
964
967
c .mu .RUnlock ()
965
968
c .checkRaces ()
@@ -1015,12 +1018,14 @@ func (c *common) log(s string) {
1015
1018
func (c * common ) logDepth (s string , depth int ) {
1016
1019
c .mu .Lock ()
1017
1020
defer c .mu .Unlock ()
1021
+ _ = c .doneRaceCanary
1018
1022
if c .done {
1019
1023
// This test has already finished. Try and log this message
1020
1024
// with our parent. If we don't have a parent, panic.
1021
1025
for parent := c .parent ; parent != nil ; parent = parent .parent {
1022
1026
parent .mu .Lock ()
1023
1027
defer parent .mu .Unlock ()
1028
+ _ = parent .doneRaceCanary
1024
1029
if ! parent .done {
1025
1030
parent .output = append (parent .output , parent .decorate (s , depth + 1 )... )
1026
1031
return
@@ -1672,9 +1677,13 @@ func tRunner(t *T, fn func(t *T)) {
1672
1677
}
1673
1678
t .report () // Report after all subtests have finished.
1674
1679
1675
- // Do not lock t.done to allow race detector to detect race in case
1676
- // the user does not appropriately synchronize a goroutine.
1680
+ // Do not lock around t.done's race canary to allow race detector to detect
1681
+ // cases where the user does not appropriately synchronize a goroutine...
1682
+ t .doneRaceCanary = true
1683
+ t .mu .Lock ()
1684
+ // ...but do lock around t.done so after-done behavior is consistent.
1677
1685
t .done = true
1686
+ t .mu .Unlock ()
1678
1687
if t .parent != nil && ! t .hasSub .Load () {
1679
1688
t .setRan ()
1680
1689
}
0 commit comments