~sircmpwn/hare-dev

This thread contains a patchset. You're looking at the original emails, but you may wish to use the patch review UI. Review patch
3 3

[PATCH hare] Report the backtraces on tests failures

Details
Message ID
<20240802181647.28716-2-contact@willowbarraco.fr>
DKIM signature
pass
Download raw message
Patch: +35 -72
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] build failed

builds.sr.ht <builds@sr.ht>
Details
Message ID
<D35MVR6IYA05.151SFZAGXBDWI@fra01>
In-Reply-To
<20240802181647.28716-2-contact@willowbarraco.fr> (view parent)
DKIM signature
missing
Download raw message
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]: 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
Details
Message ID
<D3ADAQAYQZT3.24BFPEIXHVCSL@cmpwn.com>
In-Reply-To
<20240802181647.28716-2-contact@willowbarraco.fr> (view parent)
DKIM signature
pass
Download raw message
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.
Details
Message ID
<D3T41DD2VQOA.3281HA8Z6WQJQ@willowbarraco.fr>
In-Reply-To
<D3ADAQAYQZT3.24BFPEIXHVCSL@cmpwn.com> (view parent)
DKIM signature
pass
Download raw message
On Thu Aug 8, 2024 at 9:55 AM CEST, Drew DeVault wrote:
> 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.

I've sent a v2, moving the debug handler in test:: itself. I'm not sure
about what you meant with storing the backtrace from here. You meant not
stderr it, and handle this more specifically, with the tests summaries?
Reply to thread Export thread (mbox)