mirror of
https://github.com/odin-lang/Odin.git
synced 2026-11-05 10:13:11 -05:00
Fix core:testing traps, signals, and expectations
This commit is contained in:
1 parent
6aad2bc029
commit
73d725e45f
4 files changed
+70
-42
No files matched your search
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user