runner_windows.odin 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. //+private
  2. //+build windows
  3. package testing
  4. import win32 "core:sys/windows"
  5. import "core:runtime"
  6. import "core:intrinsics"
  7. import "core:time"
  8. Sema :: struct {
  9. count: i32,
  10. }
  11. sema_reset :: proc "contextless" (s: ^Sema) {
  12. intrinsics.atomic_store(&s.count, 0)
  13. }
  14. sema_wait :: proc "contextless" (s: ^Sema) {
  15. for {
  16. original_count := s.count
  17. for original_count == 0 {
  18. win32.WaitOnAddress(&s.count, &original_count, size_of(original_count), win32.INFINITE)
  19. original_count = s.count
  20. }
  21. if original_count == intrinsics.atomic_compare_exchange_strong(&s.count, original_count-1, original_count) {
  22. return
  23. }
  24. }
  25. }
  26. sema_wait_with_timeout :: proc "contextless" (s: ^Sema, duration: time.Duration) -> bool {
  27. if duration <= 0 {
  28. return false
  29. }
  30. for {
  31. original_count := intrinsics.atomic_load(&s.count)
  32. for start := time.tick_now(); original_count == 0; /**/ {
  33. if intrinsics.atomic_load(&s.count) != original_count {
  34. remaining := duration - time.tick_since(start)
  35. if remaining < 0 {
  36. return false
  37. }
  38. ms := u32(remaining/time.Millisecond)
  39. if !win32.WaitOnAddress(&s.count, &original_count, size_of(original_count), ms) {
  40. return false
  41. }
  42. }
  43. original_count = s.count
  44. }
  45. if original_count == intrinsics.atomic_compare_exchange_strong(&s.count, original_count-1, original_count) {
  46. return true
  47. }
  48. }
  49. }
  50. sema_post :: proc "contextless" (s: ^Sema, count := 1) {
  51. intrinsics.atomic_add(&s.count, i32(count))
  52. if count == 1 {
  53. win32.WakeByAddressSingle(&s.count)
  54. } else {
  55. win32.WakeByAddressAll(&s.count)
  56. }
  57. }
  58. Thread_Proc :: #type proc(^Thread)
  59. MAX_USER_ARGUMENTS :: 8
  60. Thread :: struct {
  61. using specific: Thread_Os_Specific,
  62. procedure: Thread_Proc,
  63. t: ^T,
  64. it: Internal_Test,
  65. success: bool,
  66. init_context: Maybe(runtime.Context),
  67. creation_allocator: runtime.Allocator,
  68. internal_fail_timeout: time.Duration,
  69. internal_fail_timeout_loc: runtime.Source_Code_Location,
  70. }
  71. Thread_Os_Specific :: struct {
  72. win32_thread: win32.HANDLE,
  73. win32_thread_id: win32.DWORD,
  74. done: bool, // see note in `is_done`
  75. }
  76. thread_create :: proc(procedure: Thread_Proc) -> ^Thread {
  77. __windows_thread_entry_proc :: proc "stdcall" (t_: rawptr) -> win32.DWORD {
  78. t := (^Thread)(t_)
  79. context = t.init_context.? or_else runtime.default_context()
  80. t.procedure(t)
  81. if t.init_context == nil {
  82. if context.temp_allocator.data == &runtime.global_default_temp_allocator_data {
  83. runtime.default_temp_allocator_destroy(auto_cast context.temp_allocator.data)
  84. }
  85. }
  86. intrinsics.atomic_store(&t.done, true)
  87. return 0
  88. }
  89. thread := new(Thread)
  90. if thread == nil {
  91. return nil
  92. }
  93. thread.creation_allocator = context.allocator
  94. win32_thread_id: win32.DWORD
  95. win32_thread := win32.CreateThread(nil, 0, __windows_thread_entry_proc, thread, win32.CREATE_SUSPENDED, &win32_thread_id)
  96. if win32_thread == nil {
  97. free(thread, thread.creation_allocator)
  98. return nil
  99. }
  100. thread.procedure = procedure
  101. thread.win32_thread = win32_thread
  102. thread.win32_thread_id = win32_thread_id
  103. thread.init_context = context
  104. return thread
  105. }
  106. thread_start :: proc "contextless" (thread: ^Thread) {
  107. win32.ResumeThread(thread.win32_thread)
  108. }
  109. thread_join_and_destroy :: proc(thread: ^Thread) {
  110. if thread.win32_thread != win32.INVALID_HANDLE {
  111. win32.WaitForSingleObject(thread.win32_thread, win32.INFINITE)
  112. win32.CloseHandle(thread.win32_thread)
  113. thread.win32_thread = win32.INVALID_HANDLE
  114. }
  115. free(thread, thread.creation_allocator)
  116. }
  117. thread_terminate :: proc "contextless" (thread: ^Thread, exit_code: int) {
  118. win32.TerminateThread(thread.win32_thread, u32(exit_code))
  119. }
  120. _fail_timeout :: proc(t: ^T, duration: time.Duration, loc := #caller_location) {
  121. assert(global_fail_timeout_thread == nil, "set_fail_timeout previously called", loc)
  122. thread := thread_create(proc(thread: ^Thread) {
  123. t := thread.t
  124. timeout := thread.internal_fail_timeout
  125. if !sema_wait_with_timeout(&global_fail_timeout_semaphore, timeout) {
  126. fail_now(t, "TIMEOUT", thread.internal_fail_timeout_loc)
  127. }
  128. })
  129. thread.internal_fail_timeout = duration
  130. thread.internal_fail_timeout_loc = loc
  131. thread.t = t
  132. global_fail_timeout_thread = thread
  133. thread_start(thread)
  134. }
  135. global_fail_timeout_thread: ^Thread
  136. global_fail_timeout_semaphore: Sema
  137. global_threaded_runner_semaphore: Sema
  138. global_exception_handler: rawptr
  139. global_current_thread: ^Thread
  140. global_current_t: ^T
  141. run_internal_test :: proc(t: ^T, it: Internal_Test) {
  142. thread := thread_create(proc(thread: ^Thread) {
  143. exception_handler_proc :: proc "stdcall" (ExceptionInfo: ^win32.EXCEPTION_POINTERS) -> win32.LONG {
  144. switch ExceptionInfo.ExceptionRecord.ExceptionCode {
  145. case
  146. win32.EXCEPTION_DATATYPE_MISALIGNMENT,
  147. win32.EXCEPTION_BREAKPOINT,
  148. win32.EXCEPTION_ACCESS_VIOLATION,
  149. win32.EXCEPTION_ILLEGAL_INSTRUCTION,
  150. win32.EXCEPTION_ARRAY_BOUNDS_EXCEEDED,
  151. win32.EXCEPTION_STACK_OVERFLOW:
  152. sema_post(&global_threaded_runner_semaphore)
  153. return win32.EXCEPTION_EXECUTE_HANDLER
  154. }
  155. return win32.EXCEPTION_CONTINUE_SEARCH
  156. }
  157. global_exception_handler = win32.AddVectoredExceptionHandler(0, exception_handler_proc)
  158. context.assertion_failure_proc = proc(prefix, message: string, loc: runtime.Source_Code_Location) -> ! {
  159. errorf(t=global_current_t, format="%s %s", args={prefix, message}, loc=loc)
  160. intrinsics.trap()
  161. }
  162. t := thread.t
  163. global_fail_timeout_thread = nil
  164. sema_reset(&global_fail_timeout_semaphore)
  165. thread.it.p(t)
  166. sema_post(&global_fail_timeout_semaphore)
  167. thread_join_and_destroy(global_fail_timeout_thread)
  168. thread.success = true
  169. sema_post(&global_threaded_runner_semaphore)
  170. })
  171. sema_reset(&global_threaded_runner_semaphore)
  172. global_current_t = t
  173. t._fail_now = proc() -> ! {
  174. intrinsics.trap()
  175. }
  176. thread.t = t
  177. thread.it = it
  178. thread.success = false
  179. thread_start(thread)
  180. sema_wait(&global_threaded_runner_semaphore)
  181. thread_terminate(thread, int(!thread.success))
  182. thread_join_and_destroy(thread)
  183. win32.RemoveVectoredExceptionHandler(global_exception_handler)
  184. if !thread.success && t.error_count == 0 {
  185. t.error_count += 1
  186. }
  187. return
  188. }