mirror of
https://github.com/odin-lang/Odin.git
synced 2025-12-30 01:44:36 +00:00
This is needed for the docs generator to generate all the docs for the posix package, if it is imported like it was on Windows it would generate docs for the Windows version of the package which has much less symbols exposed.
166 lines
4.8 KiB
Odin
166 lines
4.8 KiB
Odin
#+private
|
|
#+build windows, linux, darwin, freebsd, openbsd, netbsd, haiku
|
|
package testing
|
|
|
|
/*
|
|
(c) Copyright 2024 Feoramund <rune@swevencraft.org>.
|
|
Made available under Odin's BSD-3 license.
|
|
|
|
List of contributors:
|
|
Feoramund: Total rewrite.
|
|
*/
|
|
|
|
import "base:intrinsics"
|
|
import "core:c/libc"
|
|
import "core:encoding/ansi"
|
|
import "core:sync"
|
|
import "core:os"
|
|
|
|
@(private="file") stop_runner_flag: libc.sig_atomic_t
|
|
|
|
@(private="file") stop_test_gate: sync.Mutex
|
|
@(private="file") stop_test_index: libc.sig_atomic_t
|
|
@(private="file") stop_test_reason: libc.sig_atomic_t
|
|
@(private="file") stop_test_alert: libc.sig_atomic_t
|
|
|
|
@(private="file", thread_local)
|
|
local_test_index: libc.sig_atomic_t
|
|
@(private="file", thread_local)
|
|
local_test_index_set: bool
|
|
|
|
// Windows does not appear to have a SIGTRAP, so this is defined here, instead
|
|
// of in the libc package, just so there's no confusion about it being
|
|
// available there.
|
|
SIGTRAP :: 5
|
|
|
|
@(private="file")
|
|
stop_runner_callback :: proc "c" (sig: libc.int) {
|
|
prev := intrinsics.atomic_add(&stop_runner_flag, 1)
|
|
|
|
// If the flag was already set (if this is the second signal sent for example),
|
|
// consider this a forced (not graceful) exit.
|
|
if prev > 0 {
|
|
os.exit(int(sig))
|
|
}
|
|
}
|
|
|
|
@(private)
|
|
stop_test_callback :: proc "c" (sig: libc.int) {
|
|
if !local_test_index_set {
|
|
// We're a thread created by a test thread.
|
|
//
|
|
// There's nothing we can do to inform the test runner about who
|
|
// signalled, so hopefully the test will handle their own sub-threads.
|
|
return
|
|
}
|
|
if local_test_index == -1 {
|
|
// We're the test runner, and we ourselves have caught a signal from
|
|
// which there is no recovery.
|
|
//
|
|
// The most we can do now is make sure the user's cursor is visible,
|
|
// nuke the entire processs, and hope a useful core dump survives.
|
|
|
|
// NOTE(Feoramund): Using these write calls in a signal handler is
|
|
// undefined behavior in C99 but possibly tolerated in POSIX 2008.
|
|
// Either way, we may as well try to salvage what we can.
|
|
show_cursor := ansi.CSI + ansi.DECTCEM_SHOW
|
|
libc.fwrite(raw_data(show_cursor), size_of(byte), len(show_cursor), libc.stdout)
|
|
libc.fflush(libc.stdout)
|
|
|
|
// This is an attempt at being compliant by avoiding printf.
|
|
sigbuf: [8]byte
|
|
sigstr: string
|
|
{
|
|
signum := cast(int)sig
|
|
i := len(sigbuf) - 2
|
|
for signum > 0 {
|
|
m := signum % 10
|
|
signum /= 10
|
|
sigbuf[i] = cast(u8)('0' + m)
|
|
i -= 1
|
|
}
|
|
sigstr = cast(string)sigbuf[1 + i:len(sigbuf) - 1]
|
|
}
|
|
|
|
advisory_a := `
|
|
The test runner's main thread has caught an unrecoverable error (signal `
|
|
advisory_b := `) and will now forcibly terminate.
|
|
This is a dire bug and should be reported to the Odin developers.
|
|
`
|
|
libc.fwrite(raw_data(advisory_a), size_of(byte), len(advisory_a), libc.stderr)
|
|
libc.fwrite(raw_data(sigstr), size_of(byte), len(sigstr), libc.stderr)
|
|
libc.fwrite(raw_data(advisory_b), size_of(byte), len(advisory_b), libc.stderr)
|
|
|
|
// Try to get a core dump.
|
|
libc.abort()
|
|
}
|
|
|
|
if sync.mutex_guard(&stop_test_gate) {
|
|
intrinsics.atomic_store(&stop_test_index, local_test_index)
|
|
intrinsics.atomic_store(&stop_test_reason, cast(libc.sig_atomic_t)sig)
|
|
intrinsics.atomic_store(&stop_test_alert, 1)
|
|
|
|
for {
|
|
// Idle until this thread is terminated by the runner,
|
|
// otherwise we may continue to generate signals.
|
|
intrinsics.cpu_relax()
|
|
|
|
_test_thread_cancel()
|
|
}
|
|
}
|
|
}
|
|
|
|
_setup_signal_handler :: proc() {
|
|
local_test_index = -1
|
|
local_test_index_set = true
|
|
|
|
// Catch user interrupt / CTRL-C.
|
|
libc.signal(libc.SIGINT, stop_runner_callback)
|
|
// Catch polite termination request.
|
|
libc.signal(libc.SIGTERM, stop_runner_callback)
|
|
|
|
// For tests:
|
|
// Catch asserts and panics.
|
|
libc.signal(libc.SIGILL, stop_test_callback)
|
|
// Catch arithmetic errors.
|
|
libc.signal(libc.SIGFPE, stop_test_callback)
|
|
// Catch segmentation faults (illegal memory access).
|
|
libc.signal(libc.SIGSEGV, stop_test_callback)
|
|
|
|
__setup_signal_handler()
|
|
}
|
|
|
|
_setup_task_signal_handler :: proc(test_index: int) {
|
|
local_test_index = cast(libc.sig_atomic_t)test_index
|
|
local_test_index_set = true
|
|
}
|
|
|
|
_should_stop_runner :: proc() -> bool {
|
|
return intrinsics.atomic_load(&stop_runner_flag) == 1
|
|
}
|
|
|
|
@(private="file")
|
|
unlock_stop_test_gate :: proc(_: int, _: Stop_Reason, ok: bool) {
|
|
if ok {
|
|
sync.mutex_unlock(&stop_test_gate)
|
|
}
|
|
}
|
|
|
|
@(deferred_out=unlock_stop_test_gate)
|
|
_should_stop_test :: proc() -> (test_index: int, reason: Stop_Reason, ok: bool) {
|
|
if intrinsics.atomic_load(&stop_test_alert) == 1 {
|
|
intrinsics.atomic_store(&stop_test_alert, 0)
|
|
|
|
test_index = cast(int)intrinsics.atomic_load(&stop_test_index)
|
|
switch intrinsics.atomic_load(&stop_test_reason) {
|
|
case libc.SIGFPE: reason = .Arithmetic_Error
|
|
case libc.SIGILL: reason = .Illegal_Instruction
|
|
case libc.SIGSEGV: reason = .Segmentation_Fault
|
|
case SIGTRAP: reason = .Unhandled_Trap
|
|
}
|
|
ok = true
|
|
}
|
|
|
|
return
|
|
}
|