runtime, testing: support Goexit, SkipNow, FailNow

Keep a pending Goexit separate from the active panic state so recovered
deferred panics do not cancel the original Goexit unwind. Goexit should
also ignore -panic=trap, since it is not a panic.

Wire testing.FailNow and SkipNow through runtime.Goexit, and run tests
and benchmarks in goroutines. This lets Goexit terminate the user
function without skipping test bookkeeping. Add Goexit/recover coverage
and enable crypto/ecdh in the Linux stdlib test list.
This commit is contained in:
Jake Bailey
2026-07-03 16:30:32 -07:00
committed by Ron Evans
parent fb1bf2e6bf
commit 16eefc59eb
15 changed files with 201 additions and 23 deletions
+10
View File
@@ -0,0 +1,10 @@
package main
import "runtime"
func main() {
defer func() {
panic("panic after Goexit")
}()
runtime.Goexit()
}
+65
View File
@@ -158,6 +158,71 @@ func runtimeGoexit() {
runtime.Goexit()
}()
wg.Wait()
wg.Add(1)
go func() {
defer wg.Done()
runtimeGoexitRecoveredPanic()
println("unreachable after Goexit helper")
}()
wg.Wait()
wg.Add(1)
go func() {
defer wg.Done()
runtimeGoexitNestedRecoveredPanic()
println("unreachable after nested Goexit helper")
}()
wg.Wait()
wg.Add(1)
go func() {
defer wg.Done()
runtimeGoexitLoopDefers()
println("unreachable after loop Goexit helper")
}()
wg.Wait()
}
func runtimeGoexitRecoveredPanic() {
defer func() {
if r := recover(); r != nil {
printitf("Goexit recovered deferred panic:", r)
}
}()
defer func() {
panic("panic during Goexit")
}()
runtime.Goexit()
}
func runtimeGoexitNestedRecoveredPanic() {
defer func() {
defer func() {
if r := recover(); r != nil {
printitf("Goexit nested recovered deferred panic:", r)
}
}()
panic("nested panic during Goexit")
}()
runtime.Goexit()
}
func runtimeGoexitLoopDefers() {
for i := 0; i < 2; i++ {
defer func() {
println("Goexit loop defer")
}()
}
defer func() {
if r := recover(); r != nil {
printitf("Goexit loop recovered deferred panic:", r)
}
}()
defer func() {
panic("loop panic during Goexit")
}()
runtime.Goexit()
}
// Test that a repanic inside a deferred function propagates correctly
+5
View File
@@ -33,6 +33,11 @@ indirect recover returned: indirect panic
# runtime.Goexit
Goexit deferred function, recover is nil: true
Goexit recovered deferred panic: panic during Goexit
Goexit nested recovered deferred panic: nested panic during Goexit
Goexit loop recovered deferred panic: loop panic during Goexit
Goexit loop defer
Goexit loop defer
# repanic
inner, repanicking
+21
View File
@@ -0,0 +1,21 @@
--- FAIL: TestBar (0.00s)
log Bar
log g
h
i
--- FAIL: TestBar/Bar2 (0.00s)
log Bar2
a
b
c
failed
after failed
log Bar end
--- FAIL: TestAllLowercase (0.00s)
--- FAIL: TestAllLowercase/BETA (0.00s)
expected lowercase name, got BETA
--- FAIL: TestAllLowercase/BELTA (0.00s)
expected lowercase name, got BELTA
FAIL
exitcode: 1
+12 -1
View File
@@ -6,6 +6,7 @@ import (
"errors"
"flag"
"io"
"runtime"
"strings"
"testing"
)
@@ -25,11 +26,21 @@ func TestBar(t *testing.T) {
t.Log("after failed")
})
t.Run("Bar3", func(t *testing.T) {})
if runtime.GOARCH != "wasm" {
t.Run("Bar4", func(t *testing.T) {
t.Fatal("fatal")
t.Log("after fatal")
})
t.Run("Bar5", func(t *testing.T) {
t.SkipNow()
t.Error("after skip")
})
}
t.Log("log Bar end")
}
func TestAllLowercase(t *testing.T) {
names := []string {
names := []string{
"alpha",
"BETA",
"gamma",
+2
View File
@@ -11,6 +11,8 @@
c
failed
after failed
--- FAIL: TestBar/Bar4 (0.00s)
fatal
log Bar end
--- FAIL: TestAllLowercase (0.00s)
--- FAIL: TestAllLowercase/BETA (0.00s)