diff --git a/core/testing/runner_windows.odin b/core/testing/runner_windows.odin index 4f3af2495..513f1a077 100644 --- a/core/testing/runner_windows.odin +++ b/core/testing/runner_windows.odin @@ -6,6 +6,9 @@ import win32 "core:sys/windows" import "core:runtime" import "core:intrinsics" import "core:time" +import "core:fmt" + +_ :: fmt Sema :: struct { count: i32, @@ -18,12 +21,7 @@ sema_wait :: proc "contextless" (s: ^Sema) { for { original_count := s.count for original_count == 0 { - win32.WaitOnAddress( - &s.count, - &original_count, - size_of(original_count), - win32.INFINITE, - ) + win32.WaitOnAddress(&s.count, &original_count, size_of(original_count), win32.INFINITE) original_count = s.count } if original_count == intrinsics.atomic_cxchg(&s.count, original_count-1, original_count) { @@ -31,6 +29,31 @@ sema_wait :: proc "contextless" (s: ^Sema) { } } } +sema_wait_with_timeout :: proc "contextless" (s: ^Sema, duration: time.Duration) -> bool { + if duration <= 0 { + return false + } + for { + + original_count := intrinsics.atomic_load(&s.count) + for start := time.tick_now(); original_count == 0; /**/ { + if intrinsics.atomic_load(&s.count) != original_count { + remaining := duration - time.tick_since(start) + if remaining < 0 { + return false + } + ms := u32(remaining/time.Millisecond) + if !win32.WaitOnAddress(&s.count, &original_count, size_of(original_count), ms) { + return false + } + } + original_count = s.count + } + if original_count == intrinsics.atomic_cxchg(&s.count, original_count-1, original_count) { + return true + } + } +} sema_post :: proc "contextless" (s: ^Sema, count := 1) { intrinsics.atomic_add(&s.count, i32(count)) @@ -42,6 +65,7 @@ sema_post :: proc "contextless" (s: ^Sema, count := 1) { } + Thread_Proc :: #type proc(^Thread) MAX_USER_ARGUMENTS :: 8 @@ -127,19 +151,21 @@ thread_terminate :: proc "contextless" (thread: ^Thread, exit_code: int) { _fail_timeout :: proc(t: ^T, duration: time.Duration, loc := #caller_location) { thread := thread_create(proc(thread: ^Thread) { t := thread.t - time.sleep(thread.internal_fail_timeout) - if !intrinsics.atomic_load(&t._is_done) { + timeout := thread.internal_fail_timeout + if !sema_wait_with_timeout(&global_fail_timeout_semaphore, timeout) { fail_now(t, "TIMEOUT", thread.internal_fail_timeout_loc) } - // NOTE(bill): Complete hack and probably not a good idea - thread_join_and_destroy(thread) }) thread.internal_fail_timeout = duration thread.internal_fail_timeout_loc = loc thread.t = t + global_fail_timeout_thread = thread thread_start(thread) } +global_fail_timeout_thread: ^Thread +global_fail_timeout_semaphore: Sema + global_threaded_runner_semaphore: Sema global_exception_handler: rawptr global_current_thread: ^Thread @@ -164,7 +190,7 @@ run_internal_test :: proc(t: ^T, it: Internal_Test) { return win32.EXCEPTION_CONTINUE_SEARCH } global_exception_handler = win32.AddVectoredExceptionHandler(0, exception_handler_proc) - + context.assertion_failure_proc = proc(prefix, message: string, loc: runtime.Source_Code_Location) -> ! { errorf(t=global_current_t, format="%s %s", args={prefix, message}, loc=loc) intrinsics.trap() @@ -172,11 +198,14 @@ run_internal_test :: proc(t: ^T, it: Internal_Test) { t := thread.t - t._fail_timeout_set = false - intrinsics.atomic_store(&t._is_done, false) + global_fail_timeout_thread = nil + sema_reset(&global_fail_timeout_semaphore) + thread.it.p(t) - intrinsics.atomic_store(&t._is_done, true) - + + sema_post(&global_fail_timeout_semaphore) + thread_join_and_destroy(global_fail_timeout_thread) + thread.success = true sema_post(&global_threaded_runner_semaphore) }) diff --git a/core/testing/testing.odin b/core/testing/testing.odin index 0f91c7020..63df26c1f 100644 --- a/core/testing/testing.odin +++ b/core/testing/testing.odin @@ -3,6 +3,7 @@ package testing import "core:fmt" import "core:io" import "core:time" +import "core:intrinsics" // IMPORTANT NOTE: Compiler requires this layout Test_Signature :: proc(^T) @@ -28,8 +29,6 @@ T :: struct { cleanups: [dynamic]Internal_Cleanup, _fail_now: proc() -> !, - _is_done: bool, - _fail_timeout_set: bool, } @@ -89,10 +88,17 @@ expect :: proc(t: ^T, ok: bool, msg: string = "", loc := #caller_location) -> bo } return ok } +expect_value :: proc(t: ^T, value, expected: $T, loc := #caller_location) -> bool where intrinsics.type_is_comparable(T) { + ok := value == expected + if !ok { + errorf(t=t, format="expected %v, got %v", args={expected, value}, loc=loc) + } + return ok +} + set_fail_timeout :: proc(t: ^T, duration: time.Duration, loc := #caller_location) { - assert(t._fail_timeout_set == false, "set_fail_timeout previously called", loc) - t._fail_timeout_set = true + assert(global_fail_timeout_thread == nil, "set_fail_timeout previously called", loc) _fail_timeout(t, duration, loc) } \ No newline at end of file