Fix core:testing traps, signals, and expectations

This commit is contained in:
gingerBill committed 2026-11-01 12:00:02 +00:00
1 parent 6aad2bc029
commit 73d725e45f
4 files changed
+70 -42

No files matched your search

+8 -2
View File
@@ -163,6 +163,11 @@ run_test_task :: proc(task: thread.Task) {
data.it.p(&data.t)
if local_test_expected_failures.signal != 0 ||
local_test_expected_failures.message_count + local_test_expected_failures.location_count > 0 {
log.errorf("`%s.%s` returned without the assertion or signal it expected.", data.it.pkg, data.it.name)
}
end_t(&data.t)
when TRACKING_MEMORY && FAIL_ON_BAD_MEMORY {
@@ -807,8 +812,6 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
if test_index, reason, ok := should_stop_test(); ok {
passed := reason == .Successful_Stop
#no_bounds_check report.all_test_states[test_index] = .Successful if passed else .Failed
#no_bounds_check it := internal_tests[test_index]
#no_bounds_check pkg := report.packages_by_name[it.pkg]
pkg.frame_ready = false
@@ -829,6 +832,9 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
fmt.assertf(task_data != nil, "A signal (%v) was raised to stop test #%i %s.%s, but its task data is missing.",
reason, test_index, it.pkg, it.name)
passed := reason == .Successful_Stop && !failed(&task_data.t)
#no_bounds_check report.all_test_states[test_index] = .Successful if passed else .Failed
if !passed && !task_data.t._fail_now_called {
if test_index not_in failed_test_reason_map {
// We only write a new error message here if there wasn't one
+4 -1
View File
@@ -39,7 +39,8 @@ Stop_Reason :: enum {
}
test_assertion_failure_proc :: proc(prefix, message: string, loc: runtime.Source_Code_Location) -> ! {
if local_test_expected_failures.message_count + local_test_expected_failures.location_count > 0 {
if local_test_expected_failures.signal != 0 ||
local_test_expected_failures.message_count + local_test_expected_failures.location_count > 0 {
local_test_assertion_raised = { message, loc }
log.debugf("%s\n\tmessage: %q\n\tlocation: %w", prefix, message, loc)
} else {
@@ -53,6 +54,8 @@ setup_signal_handler :: proc() {
}
setup_task_signal_handler :: proc(test_index: int) {
local_test_expected_failures = {}
local_test_assertion_raised = {}
_setup_task_signal_handler(test_index)
}
+54 -36
View File
@@ -117,49 +117,62 @@ This is a dire bug and should be reported to the Odin developers.
win32.TerminateProcess(win32.GetCurrentProcess(), 1)
}
if sync.mutex_guard(&stop_test_gate) {
intrinsics.atomic_store(&stop_test_index, local_test_index)
intrinsics.atomic_store(&stop_test_signal, code)
passed: bool
check_passing: {
if location := local_test_assertion_raised.location; location != {} {
for i in 0..<local_test_expected_failures.location_count {
if local_test_expected_failures.locations[i] == location {
passed = true
break check_passing
}
}
}
if message := local_test_assertion_raised.message; message != "" {
for i in 0..<local_test_expected_failures.message_count {
if local_test_expected_failures.messages[i] == message {
passed = true
break check_passing
}
}
}
signal := local_test_expected_failures.signal
switch signal {
case libc.SIGILL: passed = code == win32.EXCEPTION_ILLEGAL_INSTRUCTION || code == win32.EXCEPTION_ARRAY_BOUNDS_EXCEEDED
case libc.SIGSEGV: passed = code == win32.EXCEPTION_ACCESS_VIOLATION
case libc.SIGFPE:
switch code {
case win32.EXCEPTION_FLT_DENORMAL_OPERAND ..= win32.EXCEPTION_INT_OVERFLOW:
stop_test(code)
}
@(private="file")
stop_test_signal_callback :: proc "c" (sig: libc.int) {
context = runtime.default_context()
switch sig {
case libc.SIGILL: stop_test(win32.EXCEPTION_ILLEGAL_INSTRUCTION)
case libc.SIGFPE: stop_test(win32.EXCEPTION_INT_DIVIDE_BY_ZERO)
case libc.SIGSEGV: stop_test(win32.EXCEPTION_ACCESS_VIOLATION)
}
}
@(private="file")
stop_test :: proc(code: win32.DWORD) -> ! {
sync.mutex_lock(&stop_test_gate)
intrinsics.atomic_store(&stop_test_index, local_test_index)
intrinsics.atomic_store(&stop_test_signal, code)
passed: bool
check_passing: {
if location := local_test_assertion_raised.location; location != {} {
for i in 0..<local_test_expected_failures.location_count {
if local_test_expected_failures.locations[i] == location {
passed = true
break check_passing
}
}
}
intrinsics.atomic_store(&stop_test_passed, passed)
intrinsics.atomic_store(&stop_test_alert, 1)
for {
// Idle until the runner terminates this thread.
// Passing the exception on would end the whole process.
win32.Sleep(win32.INFINITE)
if message := local_test_assertion_raised.message; message != "" {
for i in 0..<local_test_expected_failures.message_count {
if local_test_expected_failures.messages[i] == message {
passed = true
break check_passing
}
}
}
signal := local_test_expected_failures.signal
switch signal {
case libc.SIGILL: passed = code == win32.EXCEPTION_ILLEGAL_INSTRUCTION || code == win32.EXCEPTION_ARRAY_BOUNDS_EXCEEDED
case libc.SIGSEGV: passed = code == win32.EXCEPTION_ACCESS_VIOLATION
case libc.SIGFPE:
switch code {
case win32.EXCEPTION_FLT_DENORMAL_OPERAND ..= win32.EXCEPTION_INT_OVERFLOW:
passed = true
}
}
}
intrinsics.atomic_store(&stop_test_passed, passed)
intrinsics.atomic_store(&stop_test_alert, 1)
return win32.EXCEPTION_CONTINUE_SEARCH
for {
// Idle until the runner terminates this thread.
win32.Sleep(win32.INFINITE)
}
}
_setup_signal_handler :: proc() {
@@ -181,6 +194,11 @@ _setup_signal_handler :: proc() {
_setup_task_signal_handler :: proc(test_index: int) {
local_test_index = test_index
local_test_index_set = true
// Catch `libc.raise`. The C runtime keeps these handlers per thread.
libc.signal(libc.SIGILL, stop_test_signal_callback)
libc.signal(libc.SIGFPE, stop_test_signal_callback)
libc.signal(libc.SIGSEGV, stop_test_signal_callback)
}
_should_stop_runner :: proc() -> bool {
+4 -3
View File
@@ -160,7 +160,8 @@ set_fail_timeout :: proc(t: ^T, duration: time.Duration, loc := #caller_location
/*
Let the test runner know that it should expect an assertion failure from a
specific location in the source code for this test.
specific location in the source code for this test. The test fails if it
returns without one.
In the event that an assertion fails, a debug message will be logged with its
exact message and location in a copyable format to make it convenient to write
@@ -183,7 +184,7 @@ expect_assert_from :: proc(t: ^T, expected_place: runtime.Source_Code_Location,
/*
Let the test runner know that it should expect an assertion failure with a
specific message for this test.
specific message for this test. The test fails if it returns without one.
In the event that an assertion fails, a debug message will be logged with its
exact message and location in a copyable format to make it convenient to write
@@ -211,7 +212,7 @@ expect_assert :: proc {
/*
Let the test runner know that it should expect a signal to be raised within
this test.
this test. The test fails if it returns without one.
This API is for advanced users, as arbitrary signals will not be caught; only
the ones already handled by the test runner, such as