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
+12 -3
View File
@@ -179,7 +179,12 @@ func (b *B) run1() bool {
ctx.maxLen = n + 8 // Add additional slack to avoid too many jumps in size.
}
}
b.runN(1)
done := make(chan struct{})
go func() {
defer close(done)
b.runN(1)
}()
<-done
return !b.hasSub
}
@@ -195,8 +200,12 @@ func (b *B) run() {
}
func (b *B) doBench() BenchmarkResult {
// in upstream, this uses a goroutine
b.launch()
done := make(chan struct{})
go func() {
defer close(done)
b.launch()
}()
<-done
return b.result
}
+15 -11
View File
@@ -214,9 +214,8 @@ func (c *common) Failed() bool {
// current goroutine).
func (c *common) FailNow() {
c.Fail()
c.finished = true
c.Error("FailNow is incomplete, requires runtime.Goexit()")
runtime.Goexit()
}
// log generates the output.
@@ -288,7 +287,7 @@ func (c *common) Skipf(format string, args ...any) {
func (c *common) SkipNow() {
c.skip()
c.finished = true
c.Error("SkipNow is incomplete, requires runtime.Goexit()")
runtime.Goexit()
}
func (c *common) skip() {
@@ -480,19 +479,19 @@ type InternalTest struct {
}
func tRunner(t *T, fn func(t *T)) {
t.start = time.Now()
defer func() {
t.duration += time.Since(t.start) // TODO: capture cleanup time, too.
t.runCleanup()
t.report() // Report after all subtests have finished.
if t.parent != nil && !t.hasSub {
t.setRan()
}
}()
// Run the test.
t.start = time.Now()
fn(t)
t.duration += time.Since(t.start) // TODO: capture cleanup time, too.
t.report() // Report after all subtests have finished.
if t.parent != nil && !t.hasSub {
t.setRan()
}
t.finished = true
}
// Run runs f as a subtest of t called name. It waits until the subtest is finished
@@ -524,7 +523,12 @@ func (t *T) Run(name string, f func(t *T)) bool {
fmt.Fprintf(t.output, "=== RUN %s\n", sub.name)
}
tRunner(&sub, f)
done := make(chan struct{})
go func() {
defer close(done)
tRunner(&sub, f)
}()
<-done
return !sub.failed
}