Browse Source

Add `testing.expect_value`; Improve `testing.set_fail_timeout`

gingerBill 3 years ago
parent
commit
b67e0002c1
2 changed files with 54 additions and 19 deletions
  1. 44 15
      core/testing/runner_windows.odin
  2. 10 4
      core/testing/testing.odin

+ 44 - 15
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)
 	})

+ 10 - 4
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)
 }