mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-04 11:07:46 +00:00
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:
@@ -5,7 +5,7 @@ const GOARCH = "386"
|
||||
// The bitness of the CPU (e.g. 8, 32, 64).
|
||||
const TargetBits = 32
|
||||
|
||||
const deferExtraRegs = 0
|
||||
const deferExtraRegs = 4
|
||||
|
||||
const callInstSize = 5 // "call someFunction" is 5 bytes
|
||||
|
||||
|
||||
@@ -43,6 +43,10 @@ tinygo_longjmp:
|
||||
// Note: the code we jump to assumes eax is set to a non-zero value if we
|
||||
// jump from here.
|
||||
movl 4(%esp), %eax
|
||||
movl 8(%eax), %ebx
|
||||
movl 12(%eax), %esi
|
||||
movl 16(%eax), %edi
|
||||
movl 20(%eax), %ebp
|
||||
movl 0(%eax), %esp // jumpSP
|
||||
movl 4(%eax), %eax // jumpPC (stash in volatile register)
|
||||
jmpl *%eax
|
||||
|
||||
+11
-1
@@ -41,6 +41,7 @@ type deferFrame struct {
|
||||
Panicking panicState // not panicking, panicking, or in Goexit
|
||||
PanicValue interface{} // panic value, might be nil for panic(nil) for example
|
||||
DeferPtr unsafe.Pointer // head of the stack-allocated defer list
|
||||
Goexit bool // whether Goexit is still pending after a recovered deferred panic
|
||||
}
|
||||
|
||||
type panicState uint8
|
||||
@@ -57,7 +58,7 @@ func _panic(message interface{}) {
|
||||
}
|
||||
|
||||
func panicOrGoexit(message interface{}, panicking panicState) {
|
||||
if panicStrategy() == tinygo.PanicStrategyTrap {
|
||||
if panicking != panicGoexit && panicStrategy() == tinygo.PanicStrategyTrap {
|
||||
trap()
|
||||
}
|
||||
// Note: recover is not supported inside interrupts.
|
||||
@@ -65,6 +66,9 @@ func panicOrGoexit(message interface{}, panicking panicState) {
|
||||
if supportsRecover() && !interrupt.In() {
|
||||
frame := (*deferFrame)(task.Current().DeferFrame)
|
||||
if frame != nil {
|
||||
if panicking == panicGoexit {
|
||||
frame.Goexit = true
|
||||
}
|
||||
frame.PanicValue = message
|
||||
frame.Panicking = panicking
|
||||
tinygo_longjmp(frame)
|
||||
@@ -138,6 +142,7 @@ func setupDeferFrame(frame *deferFrame, jumpSP unsafe.Pointer) {
|
||||
frame.JumpSP = jumpSP
|
||||
frame.Panicking = panicFalse
|
||||
frame.DeferPtr = nil
|
||||
frame.Goexit = false
|
||||
currentTask.DeferFrame = unsafe.Pointer(frame)
|
||||
}
|
||||
|
||||
@@ -154,6 +159,11 @@ func destroyDeferFrame(frame *deferFrame) {
|
||||
// Re-raise the panic now.
|
||||
panicOrGoexit(frame.PanicValue, frame.Panicking)
|
||||
}
|
||||
if frame.Goexit {
|
||||
// A deferred function panicked during Goexit, and that panic was
|
||||
// recovered. Continue the original Goexit instead of returning.
|
||||
panicOrGoexit(nil, panicGoexit)
|
||||
}
|
||||
}
|
||||
|
||||
// _recover is the built-in recover() function. It tries to recover a currently
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user