diff options
| -rw-r--r-- | core/testing/runner.odin | 6 | ||||
| -rw-r--r-- | core/testing/runner_other.odin | 7 | ||||
| -rw-r--r-- | core/testing/runner_windows.odin | 188 | ||||
| -rw-r--r-- | core/testing/testing.odin | 7 | ||||
| -rw-r--r-- | src/main.cpp | 7 |
5 files changed, 208 insertions, 7 deletions
diff --git a/core/testing/runner.odin b/core/testing/runner.odin index 7691ef11e..1552cbc12 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -55,11 +55,9 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { logf(t, "[Test: %s]", it.name); // TODO(bill): Catch panics - { - it.p(t); - } + run_internal_test(t, it); - if t.error_count != 0 { + if failed(t) { logf(t, "[%s : FAILURE]", it.name); } else { logf(t, "[%s : SUCCESS]", it.name); diff --git a/core/testing/runner_other.odin b/core/testing/runner_other.odin new file mode 100644 index 000000000..84d830512 --- /dev/null +++ b/core/testing/runner_other.odin @@ -0,0 +1,7 @@ +//+private +//+build !windows +package testing + +run_internal_test :: proc(t: ^T, it: Internal_Test) { + it.p(t); +} diff --git a/core/testing/runner_windows.odin b/core/testing/runner_windows.odin new file mode 100644 index 000000000..4ab24f39c --- /dev/null +++ b/core/testing/runner_windows.odin @@ -0,0 +1,188 @@ +//+private +//+build windows +package testing + +import win32 "core:sys/windows" +import "core:runtime" +import "core:fmt" +import "intrinsics" + + +Sema :: struct { + count: i32, +} + +sema_reset :: proc "contextless" (s: ^Sema) { + intrinsics.atomic_store(&s.count, 0); +} +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, + ); + original_count = s.count; + } + if original_count == intrinsics.atomic_cxchg(&s.count, original_count-1, original_count) { + return; + } + } +} + +sema_post :: proc "contextless" (s: ^Sema, count := 1) { + intrinsics.atomic_add(&s.count, i32(count)); + if count == 1 { + win32.WakeByAddressSingle(&s.count); + } else { + win32.WakeByAddressAll(&s.count); + } +} + + +Thread_Proc :: #type proc(^Thread); + +MAX_USER_ARGUMENTS :: 8; + +Thread :: struct { + using specific: Thread_Os_Specific, + procedure: Thread_Proc, + + t: ^T, + it: Internal_Test, + success: bool, + + init_context: Maybe(runtime.Context), + + creation_allocator: runtime.Allocator, +} + +Thread_Os_Specific :: struct { + win32_thread: win32.HANDLE, + win32_thread_id: win32.DWORD, + done: bool, // see note in `is_done` +} + +thread_create :: proc(procedure: Thread_Proc) -> ^Thread { + __windows_thread_entry_proc :: proc "stdcall" (t_: rawptr) -> win32.DWORD { + t := (^Thread)(t_); + context = runtime.default_context(); + c := context; + if ic, ok := t.init_context.?; ok { + c = ic; + } + context = c; + + t.procedure(t); + + if t.init_context == nil { + if context.temp_allocator.data == &runtime.global_default_temp_allocator_data { + runtime.default_temp_allocator_destroy(auto_cast context.temp_allocator.data); + } + } + + intrinsics.atomic_store(&t.done, true); + return 0; + } + + + thread := new(Thread); + if thread == nil { + return nil; + } + thread.creation_allocator = context.allocator; + + win32_thread_id: win32.DWORD; + win32_thread := win32.CreateThread(nil, 0, __windows_thread_entry_proc, thread, win32.CREATE_SUSPENDED, &win32_thread_id); + if win32_thread == nil { + free(thread, thread.creation_allocator); + return nil; + } + thread.procedure = procedure; + thread.win32_thread = win32_thread; + thread.win32_thread_id = win32_thread_id; + thread.init_context = context; + + return thread; +} + +thread_start :: proc "contextless" (thread: ^Thread) { + win32.ResumeThread(thread.win32_thread); +} + +thread_join_and_destroy :: proc(thread: ^Thread) { + if thread.win32_thread != win32.INVALID_HANDLE { + win32.WaitForSingleObject(thread.win32_thread, win32.INFINITE); + win32.CloseHandle(thread.win32_thread); + thread.win32_thread = win32.INVALID_HANDLE; + } + free(thread, thread.creation_allocator); +} + +thread_terminate :: proc "contextless" (thread: ^Thread, exit_code: int) { + win32.TerminateThread(thread.win32_thread, u32(exit_code)); +} + + + + +global_threaded_runner_semaphore: Sema; +global_exception_handler: rawptr; +global_current_thread: ^Thread; +global_current_t: ^T; + +run_internal_test :: proc(t: ^T, it: Internal_Test) { + thread := thread_create(proc(thread: ^Thread) { + exception_handler_proc :: proc "stdcall" (ExceptionInfo: ^win32.EXCEPTION_POINTERS) -> win32.LONG { + switch ExceptionInfo.ExceptionRecord.ExceptionCode { + case + win32.EXCEPTION_DATATYPE_MISALIGNMENT, + win32.EXCEPTION_BREAKPOINT, + win32.EXCEPTION_ACCESS_VIOLATION, + win32.EXCEPTION_ILLEGAL_INSTRUCTION, + win32.EXCEPTION_ARRAY_BOUNDS_EXCEEDED, + win32.EXCEPTION_STACK_OVERFLOW: + + sema_post(&global_threaded_runner_semaphore); + return win32.EXCEPTION_EXECUTE_HANDLER; + } + + 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.debug_trap(); + }; + + thread.it.p(thread.t); + + thread.success = true; + sema_post(&global_threaded_runner_semaphore); + }); + + sema_reset(&global_threaded_runner_semaphore); + global_current_t = t; + + thread.t = t; + thread.it = it; + thread.success = false; + + thread_start(thread); + + sema_wait(&global_threaded_runner_semaphore); + thread_terminate(thread, int(!thread.success)); + thread_join_and_destroy(thread); + + win32.RemoveVectoredExceptionHandler(global_exception_handler); + + if !thread.success && t.error_count == 0 { + t.error_count += 1; + } + + return; +} diff --git a/core/testing/testing.odin b/core/testing/testing.odin index a431d8575..8a32ce7c8 100644 --- a/core/testing/testing.odin +++ b/core/testing/testing.odin @@ -29,12 +29,15 @@ T :: struct { error :: proc(t: ^T, args: ..any, loc := #caller_location) { - log(t=t, args=args, loc=loc); + fmt.wprintf(t.w, "%v: ", loc); + fmt.wprintln(t.w, ..args); t.error_count += 1; } errorf :: proc(t: ^T, format: string, args: ..any, loc := #caller_location) { - logf(t=t, format=format, args=args, loc=loc); + fmt.wprintf(t.w, "%v: ", loc); + fmt.wprintf(t.w, format, ..args); + fmt.wprintln(t.w); t.error_count += 1; } diff --git a/src/main.cpp b/src/main.cpp index 117c4e549..abc90d627 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1554,7 +1554,7 @@ void print_show_help(String const arg0, String const &command) { } else if (command == "check") { print_usage_line(1, "check parse and type check .odin file"); } else if (command == "test") { - print_usage_line(1, "test build ands runs 'test_*' procedures in the initial package"); + print_usage_line(1, "test build ands runs procedures with the attribute @(test) in the initial package"); } else if (command == "query") { print_usage_line(1, "query [experimental] parse, type check, and output a .json file containing information about the program"); } else if (command == "doc") { @@ -1662,6 +1662,11 @@ void print_show_help(String const arg0, String const &command) { print_usage_line(3, "-build-mode:shared Build as a dynamically linked library"); print_usage_line(3, "-build-mode:obj Build as an object file"); print_usage_line(3, "-build-mode:object Build as an object file"); + print_usage_line(3, "-build-mode:assembly Build as an object file"); + print_usage_line(3, "-build-mode:assembler Build as an assembly file"); + print_usage_line(3, "-build-mode:asm Build as an assembly file"); + print_usage_line(3, "-build-mode:llvm-ir Build as an LLVM IR file"); + print_usage_line(3, "-build-mode:llvm Build as an LLVM IR file"); print_usage_line(0, ""); } |