From 0659a11a1a42679af56e988bb63166b483a99c60 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sat, 1 May 2021 18:24:31 +0200 Subject: PNG: Fix tRNS handling. --- core/testing/runner.odin | 1 - 1 file changed, 1 deletion(-) (limited to 'core/testing') diff --git a/core/testing/runner.odin b/core/testing/runner.odin index efeaa04f6..7691ef11e 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -3,7 +3,6 @@ package testing import "core:io" import "core:os" -import "core:strings" import "core:slice" reset_t :: proc(t: ^T) { -- cgit v1.2.3 From 52d38ae42b15311102719d25b6f285c43af701bb Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 1 May 2021 22:54:27 +0100 Subject: Make the core:testing runner on windows run in a separate thread to handle crashes in more safe manner --- core/testing/runner.odin | 6 +- core/testing/runner_other.odin | 7 ++ core/testing/runner_windows.odin | 188 +++++++++++++++++++++++++++++++++++++++ core/testing/testing.odin | 7 +- src/main.cpp | 7 +- 5 files changed, 208 insertions(+), 7 deletions(-) create mode 100644 core/testing/runner_other.odin create mode 100644 core/testing/runner_windows.odin (limited to 'core/testing') 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, ""); } -- cgit v1.2.3 From 364e6c9573f35cb32be50cabb898ceb2afa57b88 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 1 May 2021 22:58:13 +0100 Subject: Move comment --- core/testing/runner.odin | 1 - core/testing/runner_other.odin | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) (limited to 'core/testing') diff --git a/core/testing/runner.odin b/core/testing/runner.odin index 1552cbc12..e3286988c 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -54,7 +54,6 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { logf(t, "[Test: %s]", it.name); - // TODO(bill): Catch panics run_internal_test(t, it); if failed(t) { diff --git a/core/testing/runner_other.odin b/core/testing/runner_other.odin index 84d830512..0bd95e10a 100644 --- a/core/testing/runner_other.odin +++ b/core/testing/runner_other.odin @@ -3,5 +3,6 @@ package testing run_internal_test :: proc(t: ^T, it: Internal_Test) { + // TODO(bill): Catch panics on other platforms it.p(t); } -- cgit v1.2.3 From cf0bf1a7cb395cb334cc456d30af3224fa607ca0 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 1 May 2021 23:06:14 +0100 Subject: Add `testing.fail_now` --- core/testing/runner_windows.odin | 6 +++++- core/testing/testing.odin | 9 +++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) (limited to 'core/testing') diff --git a/core/testing/runner_windows.odin b/core/testing/runner_windows.odin index 4ab24f39c..4b600218a 100644 --- a/core/testing/runner_windows.odin +++ b/core/testing/runner_windows.odin @@ -156,7 +156,7 @@ run_internal_test :: proc(t: ^T, it: Internal_Test) { 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(); + intrinsics.trap(); }; thread.it.p(thread.t); @@ -168,6 +168,10 @@ run_internal_test :: proc(t: ^T, it: Internal_Test) { sema_reset(&global_threaded_runner_semaphore); global_current_t = t; + t._fail_now = proc() -> ! { + intrinsics.trap(); + }; + thread.t = t; thread.it = it; thread.success = false; diff --git a/core/testing/testing.odin b/core/testing/testing.odin index 8a32ce7c8..ec47ca4d4 100644 --- a/core/testing/testing.odin +++ b/core/testing/testing.odin @@ -25,6 +25,8 @@ T :: struct { w: io.Writer, cleanups: [dynamic]Internal_Cleanup, + + _fail_now: proc() -> !, } @@ -46,6 +48,13 @@ fail :: proc(t: ^T) { t.error_count += 1; } +fail_now :: proc(t: ^T) { + fail(t); + if t._fail_now != nil { + t._fail_now(); + } +} + failed :: proc(t: ^T) -> bool { return t.error_count != 0; } -- cgit v1.2.3 From 9854dbe8897b3722e571a23b699c234cfc0baa83 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 1 May 2021 23:14:14 +0100 Subject: Remove unused import --- core/testing/runner_windows.odin | 1 - 1 file changed, 1 deletion(-) (limited to 'core/testing') diff --git a/core/testing/runner_windows.odin b/core/testing/runner_windows.odin index 4b600218a..d8633f703 100644 --- a/core/testing/runner_windows.odin +++ b/core/testing/runner_windows.odin @@ -4,7 +4,6 @@ package testing import win32 "core:sys/windows" import "core:runtime" -import "core:fmt" import "intrinsics" -- cgit v1.2.3