Skip to content

Commit a408139

Browse files
changkunianlancetaylor
authored andcommitted
testing: fix panicking tests hang if Cleanup calls FailNow
Previously, it was impossible to call FailNow in a Cleanup. Because it can terminate a panicking goroutine and cause its parent hangs on t.signal channel. This CL sends the signal in a deferred call to prevent the hang. Fixes #41355 Change-Id: I4552d3a7ea763ef86817bf9b50c0e37fb34bf20f Reviewed-on: https://go-review.googlesource.com/c/go/+/254637 Reviewed-by: Ian Lance Taylor <[email protected]> Reviewed-by: Emmanuel Odeke <[email protected]> Run-TryBot: Ian Lance Taylor <[email protected]> TryBot-Result: Go Bot <[email protected]> Trust: Emmanuel Odeke <[email protected]>
1 parent 5764653 commit a408139

File tree

2 files changed

+44
-1
lines changed

2 files changed

+44
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# For issue 41355
2+
[short] skip
3+
4+
! go test -v cleanup_failnow/panic_nocleanup_test.go
5+
stdout '(?s)panic: die \[recovered\].*panic: die'
6+
! stdout '(?s)panic: die \[recovered\].*panic: die.*panic: die'
7+
8+
! go test -v cleanup_failnow/panic_withcleanup_test.go
9+
stdout '(?s)panic: die \[recovered\].*panic: die'
10+
! stdout '(?s)panic: die \[recovered\].*panic: die.*panic: die'
11+
12+
-- cleanup_failnow/panic_nocleanup_test.go --
13+
package panic_nocleanup_test
14+
import "testing"
15+
func TestX(t *testing.T) {
16+
t.Run("x", func(t *testing.T) {
17+
panic("die")
18+
})
19+
}
20+
21+
-- cleanup_failnow/panic_withcleanup_test.go --
22+
package panic_withcleanup_test
23+
import "testing"
24+
func TestCleanupWithFailNow(t *testing.T) {
25+
t.Cleanup(func() {
26+
t.FailNow()
27+
})
28+
t.Run("x", func(t *testing.T) {
29+
t.Run("y", func(t *testing.T) {
30+
panic("die")
31+
})
32+
})
33+
}

src/testing/testing.go

+11-1
Original file line numberDiff line numberDiff line change
@@ -1075,6 +1075,7 @@ func tRunner(t *T, fn func(t *T)) {
10751075
// If the test panicked, print any test output before dying.
10761076
err := recover()
10771077
signal := true
1078+
10781079
if !t.finished && err == nil {
10791080
err = errNilPanicOrGoexit
10801081
for p := t.parent; p != nil; p = p.parent {
@@ -1086,6 +1087,15 @@ func tRunner(t *T, fn func(t *T)) {
10861087
}
10871088
}
10881089
}
1090+
// Use a deferred call to ensure that we report that the test is
1091+
// complete even if a cleanup function calls t.FailNow. See issue 41355.
1092+
didPanic := false
1093+
defer func() {
1094+
t.signal <- signal
1095+
if err != nil && !didPanic {
1096+
panic(err)
1097+
}
1098+
}()
10891099

10901100
doPanic := func(err interface{}) {
10911101
t.Fail()
@@ -1103,6 +1113,7 @@ func tRunner(t *T, fn func(t *T)) {
11031113
fmt.Fprintf(root.parent.w, "cleanup panicked with %v", r)
11041114
}
11051115
}
1116+
didPanic = true
11061117
panic(err)
11071118
}
11081119
if err != nil {
@@ -1144,7 +1155,6 @@ func tRunner(t *T, fn func(t *T)) {
11441155
if t.parent != nil && atomic.LoadInt32(&t.hasSub) == 0 {
11451156
t.setRan()
11461157
}
1147-
t.signal <- signal
11481158
}()
11491159
defer func() {
11501160
if len(t.sub) == 0 {

0 commit comments

Comments
 (0)