diff --git a/main.go b/main.go index c4cbf86e..fe8cda25 100644 --- a/main.go +++ b/main.go @@ -128,22 +128,50 @@ func Test(pkgName string, options *compileopts.Options) error { } return builder.Build(pkgName, ".elf", config, func(result builder.BuildResult) error { - cmd := exec.Command(result.Binary) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - cmd.Dir = result.MainDir - err := cmd.Run() - if err != nil { - // Propagate the exit code - if err, ok := err.(*exec.ExitError); ok { - if status, ok := err.Sys().(syscall.WaitStatus); ok { - os.Exit(status.ExitStatus()) + if len(config.Target.Emulator) == 0 { + // Run directly. + cmd := exec.Command(result.Binary) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Dir = result.MainDir + err := cmd.Run() + if err != nil { + // Propagate the exit code + if err, ok := err.(*exec.ExitError); ok { + if status, ok := err.Sys().(syscall.WaitStatus); ok { + os.Exit(status.ExitStatus()) + } + os.Exit(1) } - os.Exit(1) + return &commandError{"failed to run compiled binary", result.Binary, err} + } + return nil + } else { + // Run in an emulator. + args := append(config.Target.Emulator[1:], result.Binary) + cmd := exec.Command(config.Target.Emulator[0], args...) + buf := &bytes.Buffer{} + w := io.MultiWriter(os.Stdout, buf) + cmd.Stdout = w + cmd.Stderr = os.Stderr + err := cmd.Run() + if err != nil { + if err, ok := err.(*exec.ExitError); !ok || !err.Exited() { + // Workaround for QEMU which always exits with an error. + return &commandError{"failed to run emulator with", result.Binary, err} + } + } + testOutput := string(buf.Bytes()) + if testOutput == "PASS\n" || strings.HasSuffix(testOutput, "\nPASS\n") { + // Test passed. + return nil + } else { + // Test failed, either by ending with the word "FAIL" or with a + // panic of some sort. + os.Exit(1) + return nil // unreachable } - return &commandError{"failed to run compiled binary", result.Binary, err} } - return nil }) } diff --git a/src/runtime/baremetal.go b/src/runtime/baremetal.go index e85628dd..f51cad2c 100644 --- a/src/runtime/baremetal.go +++ b/src/runtime/baremetal.go @@ -39,4 +39,9 @@ func libc_free(ptr unsafe.Pointer) { free(ptr) } +//go:linkname syscall_Exit syscall.Exit +func syscall_Exit(code int) { + abort() +} + const baremetal = true diff --git a/src/runtime/runtime_cortexm_abort.go b/src/runtime/runtime_cortexm_abort.go index 7eda1e5d..d1a19bd9 100644 --- a/src/runtime/runtime_cortexm_abort.go +++ b/src/runtime/runtime_cortexm_abort.go @@ -1,4 +1,4 @@ -// +build cortexm,!nxp +// +build cortexm,!nxp,!qemu package runtime diff --git a/src/runtime/runtime_cortexm_qemu.go b/src/runtime/runtime_cortexm_qemu.go index 20e9df9a..bc4fc2d1 100644 --- a/src/runtime/runtime_cortexm_qemu.go +++ b/src/runtime/runtime_cortexm_qemu.go @@ -21,6 +21,8 @@ func postinit() {} func main() { preinit() run() + + // Signal successful exit. arm.SemihostingCall(arm.SemihostingReportException, arm.SemihostingApplicationExit) abort() } @@ -54,3 +56,13 @@ func putchar(c byte) { func waitForEvents() { arm.Asm("wfe") } + +func abort() { + // Signal an abnormal exit. + arm.SemihostingCall(arm.SemihostingReportException, arm.SemihostingRunTimeErrorUnknown) + + // Lock up forever (should be unreachable). + for { + arm.Asm("wfi") + } +} diff --git a/src/testing/testing.go b/src/testing/testing.go index b1e33de8..e9b50b0c 100644 --- a/src/testing/testing.go +++ b/src/testing/testing.go @@ -195,6 +195,8 @@ func (m *M) Run() int { if failures > 0 { fmt.Printf("exit status %d\n", failures) fmt.Println("FAIL") + } else { + fmt.Println("PASS") } return failures } diff --git a/targets/wasm_exec.js b/targets/wasm_exec.js index 4877cb1a..746b4c91 100644 --- a/targets/wasm_exec.js +++ b/targets/wasm_exec.js @@ -289,6 +289,17 @@ setTimeout(this._inst.exports.go_scheduler, timeout); }, + // func Exit(code int) + "syscall.Exit": (code) => { + if (global.process) { + // Node.js + process.exit(code); + } else { + // Can't exit in a browser. + throw 'trying to exit with code ' + code; + } + }, + // func finalizeRef(v ref) "syscall/js.finalizeRef": (sp) => { // Note: TinyGo does not support finalizers so this should never be