@@ -611,7 +611,6 @@ type common struct {
611
611
bench bool // Whether the current test is a benchmark.
612
612
hasSub atomic.Bool // whether there are sub-benchmarks.
613
613
cleanupStarted atomic.Bool // Registered cleanup callbacks have started to execute
614
- raceErrors int // Number of races detected during test.
615
614
runner string // Function name of tRunner running the test.
616
615
isParallel bool // Whether the test is parallel.
617
616
@@ -625,6 +624,9 @@ type common struct {
625
624
signal chan bool // To signal a test is done.
626
625
sub []* T // Queue of subtests to be run in parallel.
627
626
627
+ lastRaceErrors atomic.Int64 // Max value of race.Errors seen during the test or its subtests.
628
+ raceErrorLogged atomic.Bool
629
+
628
630
tempDirMu sync.Mutex
629
631
tempDir string
630
632
tempDirErr error
@@ -955,9 +957,15 @@ func (c *common) Fail() {
955
957
// Failed reports whether the function has failed.
956
958
func (c * common ) Failed () bool {
957
959
c .mu .RLock ()
958
- failed := c .failed
959
- c .mu .RUnlock ()
960
- return failed || c .raceErrors + race .Errors () > 0
960
+ defer c .mu .RUnlock ()
961
+
962
+ if ! c .done && int64 (race .Errors ()) > c .lastRaceErrors .Load () {
963
+ c .mu .RUnlock ()
964
+ c .checkRaces ()
965
+ c .mu .RLock ()
966
+ }
967
+
968
+ return c .failed
961
969
}
962
970
963
971
// FailNow marks the function as having failed and stops its execution
@@ -1346,6 +1354,69 @@ func (c *common) runCleanup(ph panicHandling) (panicVal any) {
1346
1354
}
1347
1355
}
1348
1356
1357
+ // resetRaces updates c.parent's count of data race errors (or the global count,
1358
+ // if c has no parent), and updates c.lastRaceErrors to match.
1359
+ //
1360
+ // Any races that occurred prior to this call to resetRaces will
1361
+ // not be attributed to c.
1362
+ func (c * common ) resetRaces () {
1363
+ if c .parent == nil {
1364
+ c .lastRaceErrors .Store (int64 (race .Errors ()))
1365
+ } else {
1366
+ c .lastRaceErrors .Store (c .parent .checkRaces ())
1367
+ }
1368
+ }
1369
+
1370
+ // checkRaces checks whether the global count of data race errors has increased
1371
+ // since c's count was last reset.
1372
+ //
1373
+ // If so, it marks c as having failed due to those races (logging an error for
1374
+ // the first such race), and updates the race counts for the parents of c so
1375
+ // that if they are currently suspended (such as in a call to T.Run) they will
1376
+ // not log separate errors for the race(s).
1377
+ //
1378
+ // Note that multiple tests may be marked as failed due to the same race if they
1379
+ // are executing in parallel.
1380
+ func (c * common ) checkRaces () (raceErrors int64 ) {
1381
+ raceErrors = int64 (race .Errors ())
1382
+ for {
1383
+ last := c .lastRaceErrors .Load ()
1384
+ if raceErrors <= last {
1385
+ // All races have already been reported.
1386
+ return raceErrors
1387
+ }
1388
+ if c .lastRaceErrors .CompareAndSwap (last , raceErrors ) {
1389
+ break
1390
+ }
1391
+ }
1392
+
1393
+ if c .raceErrorLogged .CompareAndSwap (false , true ) {
1394
+ // This is the first race we've encountered for this test.
1395
+ // Mark the test as failed, and log the reason why only once.
1396
+ // (Note that the race detector itself will still write a goroutine
1397
+ // dump for any further races it detects.)
1398
+ c .Errorf ("race detected during execution of test" )
1399
+ }
1400
+
1401
+ // Update the parent(s) of this test so that they don't re-report the race.
1402
+ parent := c .parent
1403
+ for parent != nil {
1404
+ for {
1405
+ last := parent .lastRaceErrors .Load ()
1406
+ if raceErrors <= last {
1407
+ // This race was already reported by another (likely parallel) subtest.
1408
+ return raceErrors
1409
+ }
1410
+ if parent .lastRaceErrors .CompareAndSwap (last , raceErrors ) {
1411
+ break
1412
+ }
1413
+ }
1414
+ parent = parent .parent
1415
+ }
1416
+
1417
+ return raceErrors
1418
+ }
1419
+
1349
1420
// callerName gives the function name (qualified with a package path)
1350
1421
// for the caller after skip frames (where 0 means the current function).
1351
1422
func callerName (skip int ) string {
@@ -1390,7 +1461,18 @@ func (t *T) Parallel() {
1390
1461
1391
1462
// Add to the list of tests to be released by the parent.
1392
1463
t .parent .sub = append (t .parent .sub , t )
1393
- t .raceErrors += race .Errors ()
1464
+
1465
+ // Report any races during execution of this test up to this point.
1466
+ //
1467
+ // We will assume that any races that occur between here and the point where
1468
+ // we unblock are not caused by this subtest. That assumption usually holds,
1469
+ // although it can be wrong if the test spawns a goroutine that races in the
1470
+ // background while the rest of the test is blocked on the call to Parallel.
1471
+ // If that happens, we will misattribute the background race to some other
1472
+ // test, or to no test at all — but that false-negative is so unlikely that it
1473
+ // is not worth adding race-report noise for the common case where the test is
1474
+ // completely suspended during the call to Parallel.
1475
+ t .checkRaces ()
1394
1476
1395
1477
if t .chatty != nil {
1396
1478
t .chatty .Updatef (t .name , "=== PAUSE %s\n " , t .name )
@@ -1405,9 +1487,16 @@ func (t *T) Parallel() {
1405
1487
t .chatty .Updatef (t .name , "=== CONT %s\n " , t .name )
1406
1488
}
1407
1489
running .Store (t .name , time .Now ())
1408
-
1409
1490
t .start = time .Now ()
1410
- t .raceErrors += - race .Errors ()
1491
+
1492
+ // Reset the local race counter to ignore any races that happened while this
1493
+ // goroutine was blocked, such as in the parent test or in other parallel
1494
+ // subtests.
1495
+ //
1496
+ // (Note that we don't call parent.checkRaces here:
1497
+ // if other parallel subtests have already introduced races, we want to
1498
+ // let them report those races instead of attributing them to the parent.)
1499
+ t .lastRaceErrors .Store (int64 (race .Errors ()))
1411
1500
}
1412
1501
1413
1502
// Setenv calls os.Setenv(key, value) and uses Cleanup to
@@ -1455,14 +1544,13 @@ func tRunner(t *T, fn func(t *T)) {
1455
1544
// a call to runtime.Goexit, record the duration and send
1456
1545
// a signal saying that the test is done.
1457
1546
defer func () {
1547
+ t .checkRaces ()
1548
+
1549
+ // TODO(#61034): This is the wrong place for this check.
1458
1550
if t .Failed () {
1459
1551
numFailed .Add (1 )
1460
1552
}
1461
1553
1462
- if t .raceErrors + race .Errors () > 0 {
1463
- t .Errorf ("race detected during execution of test" )
1464
- }
1465
-
1466
1554
// Check if the test panicked or Goexited inappropriately.
1467
1555
//
1468
1556
// If this happens in a normal test, print output but continue panicking.
@@ -1564,6 +1652,7 @@ func tRunner(t *T, fn func(t *T)) {
1564
1652
if err != nil {
1565
1653
doPanic (err )
1566
1654
}
1655
+ t .checkRaces ()
1567
1656
if ! t .isParallel {
1568
1657
// Reacquire the count for sequential tests. See comment in Run.
1569
1658
t .context .waitParallel ()
@@ -1589,7 +1678,7 @@ func tRunner(t *T, fn func(t *T)) {
1589
1678
}()
1590
1679
1591
1680
t .start = time .Now ()
1592
- t .raceErrors = - race . Errors ()
1681
+ t .resetRaces ()
1593
1682
fn (t )
1594
1683
1595
1684
// code beyond here will not be executed when FailNow is invoked
@@ -1936,7 +2025,12 @@ func (m *M) Run() (code int) {
1936
2025
testOk = false
1937
2026
}
1938
2027
}
1939
- if ! testOk || ! exampleOk || ! fuzzTargetsOk || ! runBenchmarks (m .deps .ImportPath (), m .deps .MatchString , m .benchmarks ) || race .Errors () > 0 {
2028
+ anyFailed := ! testOk || ! exampleOk || ! fuzzTargetsOk || ! runBenchmarks (m .deps .ImportPath (), m .deps .MatchString , m .benchmarks )
2029
+ if ! anyFailed && race .Errors () > 0 {
2030
+ fmt .Print (chatty .prefix (), "testing: race detected outside of test execution\n " )
2031
+ anyFailed = true
2032
+ }
2033
+ if anyFailed {
1940
2034
fmt .Print (chatty .prefix (), "FAIL\n " )
1941
2035
m .exitCode = 1
1942
2036
return
0 commit comments