Repository navigation
testing: T.FailNow() in cleanup function causes test report to not be printed #76447
Description
Activity
It's this call to
t.runCleanupwhich never returns if one of the registered cleanup functions callsruntime.Goexit(which is called byT.FailNow).Printing the report in a separate deferred goroutine fixes the issue:
diff --git a/src/testing/testing.go b/src/testing/testing.go index 0d1d08ca89..42840ecfea 100644 --- a/src/testing/testing.go +++ b/src/testing/testing.go @@ -1873,6 +1873,13 @@ func tRunner(t *T, fn func(t *T)) { // returned normally or because a test failure triggered // a call to runtime.Goexit, record the duration and send // a signal saying that the test is done. + defer func() { + // Output buffered logs. + for root := &t.common; root.parent != nil; root = root.parent { + root.flushPartial() + } + t.report() // Report after all subtests have finished. + }() defer func() { t.checkRaces() @@ -2005,11 +2012,6 @@ func tRunner(t *T, fn func(t *T)) { // test. See comment in Run method. t.tstate.release() } - // Output buffered logs. - for root := &t.common; root.parent != nil; root = root.parent { - root.flushPartial() - } - t.report() // Report after all subtests have finished. // Do not lock t.done to allow race detector to detect race in case // the user does not appropriately synchronize a goroutine.
A proper fix would also run the remaining cleanup functions and the other code that's expected to be executed afterwards in the same goroutine.
Related Issues
- testing: t.FailNow() in a deferred function masks test panic #29207
- testing: failures in Cleanup functions do not trigger -failfast if the test includes a parallel subtest #61034
- testing: t.Failed() returns false during panic #49929
- testing: panic swallowed if t.Cleanup calls t.Fatal #51395 (closed)
- testing: Parallel subtests killed on panic #38749 (closed)
- testing: misleading error message when a parallel Cleanup function calls runtime.Goexit #48502 (closed)
- [dev.fuzz] testing: errors in (*testing.F).Cleanup functions do not always report the failure #46993 (closed)
- testing: reject calls to Run within Cleanup callbacks #48515 (closed)
- testing: API for deferring a cleanup step when subtests use Parallel #31651 (closed)
- testing: panic + Skip is not reported as a failure, though Error + Skip is #53259 (closed)
(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)
- addedBugReportIssues describing a possible bug in the Go implementation.Issues describing a possible bug in the Go implementation.
on Nov 24, 2025 A proper fix would also run the remaining cleanup functions and the other code that's expected to be executed afterwards in the same goroutine.
This seems to work:
diff --git a/src/testing/testing.go b/src/testing/testing.go index 0d1d08ca89..5d69aa3418 100644 --- a/src/testing/testing.go +++ b/src/testing/testing.go @@ -1664,7 +1664,12 @@ func (c *common) runCleanup(ph panicHandling) (panicVal any) { if cleanup == nil { return nil } - cleanup() + done := make(chan struct{}) + go func() { + defer close(done) // will run even if the function calls runtime.Goexit + cleanup() + }() + <-done } }
- addedNeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
on Nov 26, 2025 @seankhliao Are you sure that this is a duplicate of #29207? I thought so too at first, but after looking at it closer I found that it's triggered by different conditions and the result is also different.
#29207 is reproduced by:
func TestPanic(t *testing.T) { defer func() { t.FailNow() }() panic("This panic is not printed") }
The result is that the panic is not printed. This issue on the other hand is about the test report (the list of the test and subtests and whether they passed or failed) not being printed. Replacing the
panicfrom above with at.Errordoes not reproduce this issue:func TestError(t *testing.T) { defer func() { t.FailNow() }() t.Error("This error is printed") }
Results in the report being printed:
--- FAIL: TestError (0.00s) main_test.go:32: This error is printedI can only reproduce the issue with the conditions I mentioned in the description:
Calling
T.FailNow()in a function that is deferred viaT.Cleanupcauses the test report to not be printed if there are parallel subtests and the tests are run without-test.v.So these conditions are different from #29207:
T.FailNow()must be called in aT.Cleanup. Usingdeferdoes not trigger the issue.- There must be parallel subtests
Reacted by Jake Bailey
Go version
go version go1.25.3 linux/amd64
Output of
go envin your module/workspace:What did you do?
Calling
T.FailNow()in a function that is deferred viaT.Cleanupcauses the test report to not be printed if there are parallel subtests and the tests are run without-test.v.Minimal reproducer:
If a subtest fails, those test failures are also not reported:
What did you see happen?
The test fails without any error message:
What did you expect to see?
I expected the test to print the test report, as it does when there are no parallel subtests: