[PATCH hare] Report the backtraces on tests failures
Export this patch
This is a minimum patch to stderr the backtrace while running the tests.
We basically backport the global management from rt/abort+test to
debug/abort.
debug is now always included while building for tests, even when tests
are run with the release mode. But I think it is completely ok.
We could iterate over this, if we want to report the backtrace in a
dedicated output part by example. This would require more globals, and
some other minimal changes.
Signed-off-by: Willow Barraco <contact@willowbarraco.fr>
---
debug/abort.ha | 24 ++++++++++++++++++ -
rt/abort+test.ha | 61 -----------------------------------------------
test/+test.ha | 15 ++++++ ------
test/util+test.ha | 7 +++ ---
4 files changed, 35 insertions(+), 72 deletions(-)
delete mode 100644 rt/abort+test.ha
diff --git a/debug/abort.ha b/debug/abort.ha
index 3b52f260..c4d005d5 100644
--- a/debug/abort.ha
+++ b/debug/abort.ha
@@ -5,6 +5,16 @@ use debug::image;
use fmt;
use rt;
+ export type abort_reason = struct {
+ path: nullable *str,
+ line: u64,
+ col: u64,
+ msg: str,
+ };
+
+ export let jmp: nullable *rt::jmpbuf = null;
+ export let reason: abort_reason = abort_reason { ... };
+
@init fn init_abort() void = {
rt::onabort(&debug_abort);
};
@@ -36,7 +46,19 @@ fn debug_abort(
};
backtrace(&self, frame);
- halt();
+
+ match (jmp) {
+ case let j: *rt::jmpbuf =>
+ reason = abort_reason {
+ path = path,
+ line = line,
+ col = col,
+ msg = msg,
+ };
+ rt::longjmp(j, 1); // test::status::ABORT
+ case null =>
+ halt();
+ };
};
fn halt() never = {
diff --git a/rt/abort+test.ha b/rt/abort+test.ha
deleted file mode 100644
index c38f88f8..00000000
--- a/rt/abort+test.ha
@@ -1,61 +0,0 @@
- // SPDX-License-Identifier: MPL-2.0
- // (c) Hare authors <https://harelang.org>
-
- // Signature for abort handler function.
- export type abort_handler = fn(
- path: *str,
- line: u64,
- col: u64,
- msg: str,
- ) never;
-
- // Sets a new global runtime abort handler.
- export fn onabort(handler: *abort_handler) void = {
- return; // no-op on +test (XXX: Do something here?)
- };
-
- export type abort_reason = struct {
- path: nullable *str,
- line: u64,
- col: u64,
- msg: str,
- };
-
- export let jmp: nullable *jmpbuf = null;
- export let reason: abort_reason = abort_reason { ... };
-
- export @symbol("rt.abort") fn _abort(
- path: *str,
- line: u64,
- col: u64,
- msg: str,
- ) void = {
- match (jmp) {
- case let j: *jmpbuf =>
- reason = abort_reason {
- path = path,
- line = line,
- col = col,
- msg = msg,
- };
- longjmp(j, 1); // test::status::ABORT
- case null =>
- platform_abort(path, line, col, msg);
- };
- };
-
- // See harec:include/gen.h
- const reasons: [_]str = [
- "slice or array access out of bounds", // 0
- "type assertion failed", // 1
- "out of memory", // 2
- "static insert/append exceeds slice capacity", // 3
- "execution reached unreachable code (compiler bug)", // 4
- "slice allocation capacity smaller than initializer", // 5
- "assertion failed", // 6
- "error occurred", // 7
- ];
-
- export fn abort_fixed(path: *str, line: u64, col: u64, i: u64) void = {
- _abort(path, line, col, reasons[i]);
- };
diff --git a/test/+test.ha b/test/+test.ha
index 6863f6ba..ef10f613 100644
--- a/test/+test.ha
+++ b/test/+test.ha
@@ -15,6 +15,7 @@ use strings;
use time;
use unix::signal;
use unix::tty;
+ use debug;
type test = struct {
name: str,
@@ -31,7 +32,7 @@ type status = enum {
type failure = struct {
test: str,
- reason: rt::abort_reason,
+ reason: debug::abort_reason,
};
type skipped = struct {
@@ -242,10 +243,10 @@ fn run_test(ctx: *context, test: test) status = {
let orig_stderr = os::stderr;
os::stdout = &ctx.stdout;
os::stderr = &ctx.stderr;
- defer rt::jmp = null;
+ defer debug::jmp = null;
const n = rt::setjmp(&jmpbuf): status;
if (n == status::RETURN) {
- rt::jmp = &jmpbuf;
+ debug::jmp = &jmpbuf;
test.func();
};
@@ -289,7 +290,7 @@ fn interpret_status(ctx: *context, test: str, status: status) bool = {
styled_print(91, "FAIL");
append(ctx.failures, failure {
test = test,
- reason = rt::abort_reason {
+ reason = debug::abort_reason {
msg = "Expected test to abort",
...
},
@@ -307,7 +308,7 @@ fn interpret_status(ctx: *context, test: str, status: status) bool = {
styled_print(91, "FAIL");
append(ctx.failures, failure {
test = test,
- reason = rt::reason,
+ reason = debug::reason,
});
return true;
};
@@ -315,14 +316,14 @@ fn interpret_status(ctx: *context, test: str, status: status) bool = {
styled_print(37, "SKIP");
append(ctx.skipped, skipped {
test = test,
- reason = rt::reason.msg,
+ reason = debug::reason.msg,
});
return false;
case status::SEGV =>
styled_print(91, "FAIL");
append(ctx.failures, failure {
test = test,
- reason = rt::abort_reason {
+ reason = debug::abort_reason {
msg = "Segmentation fault",
...
},
diff --git a/test/util+test.ha b/test/util+test.ha
index 353de281..6177d9fe 100644
--- a/test/util+test.ha
+++ b/test/util+test.ha
@@ -5,13 +5,14 @@ use fmt;
use os;
use rt;
use strings;
+ use debug;
let want_abort = false;
// Expect the currently running test to abort. The test will fail if it doesn't
// abort.
export fn expectabort() void = {
- if (rt::jmp == null) {
+ if (debug::jmp == null) {
abort("Attempted to call test::expectabort outside of @test function");
};
want_abort = true;
@@ -19,10 +20,10 @@ export fn expectabort() void = {
// Skip the currently running test.
export fn skip(reason: str) never = {
- if (rt::jmp == null) {
+ if (debug::jmp == null) {
abort("Attempted to call test::skip outside of @test function");
};
- rt::reason = rt::abort_reason {
+ debug::reason = debug::abort_reason {
msg = reason,
...
};
--
2.46.0
hare/patches: FAILED in 5m35s
[Report the backtraces on tests failures][0] from [Willow Barraco][1]
[0]: https://lists.sr.ht/~sircmpwn/hare-dev/patches/54258
[1]: mailto:contact@willowbarraco.fr
✗ #1292255 FAILED hare/patches/openbsd.yml https://builds.sr.ht/~sircmpwn/job/1292255
✗ #1292254 FAILED hare/patches/netbsd.yml https://builds.sr.ht/~sircmpwn/job/1292254
✓ #1292252 SUCCESS hare/patches/alpine.yml https://builds.sr.ht/~sircmpwn/job/1292252
✗ #1292253 FAILED hare/patches/freebsd.yml https://builds.sr.ht/~sircmpwn/job/1292253
I think I would rather take an approach where we drop rt+test and
instead have test:: register its own abort handler which does the
longjmp and taps debug:: to retrieve a backtrace, storing it to be
printed with the failed test summary at the end.