1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
|
//+private
//+build windows, linux, darwin, freebsd, openbsd, netbsd, haiku
package testing
import "base:intrinsics"
import "core:c/libc"
import "core:encoding/ansi"
import "core:sync"
import "core:os"
@require import "core:sys/unix"
@(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")
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="file")
stop_test_callback :: proc "c" (sig: libc.int) {
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()
when ODIN_OS != .Windows {
// NOTE(Feoramund): Some UNIX-like platforms may require this.
//
// During testing, I found that NetBSD 10.0 refused to
// terminate a task thread, even when its thread had been
// properly set to PTHREAD_CANCEL_ASYNCHRONOUS.
//
// The runner would stall after returning from `pthread_cancel`.
unix.pthread_testcancel()
}
}
}
}
_setup_signal_handler :: proc() {
local_test_index = -1
// 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_task_signal_handler :: proc(test_index: int) {
local_test_index = cast(libc.sig_atomic_t)test_index
}
_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
}
ok = true
}
return
}
|