فهرست منبع

Merge branch 'master' into fix-fmt-compquat-sign

Jeroen van Rijn 1 سال پیش
والد
کامیت
f9d59ef6d4
100فایلهای تغییر یافته به همراه4888 افزوده شده و 4683 حذف شده
  1. 23 0
      .github/workflows/ci.yml
  2. 2 3
      ci/create_nightly_json.py
  3. 2 4
      ci/delete_old_binaries.py
  4. 1 1
      ci/upload_create_nightly.sh
  5. 2 2
      core/bufio/reader.odin
  6. 35 35
      core/bytes/buffer.odin
  7. 1 1
      core/c/libc/signal.odin
  8. 8 6
      core/c/libc/stdio.odin
  9. 137 0
      core/encoding/ansi/ansi.odin
  10. 20 0
      core/encoding/ansi/doc.odin
  11. 2 2
      core/encoding/cbor/cbor.odin
  12. 54 53
      core/encoding/cbor/coding.odin
  13. 8 8
      core/encoding/cbor/marshal.odin
  14. 52 50
      core/encoding/cbor/unmarshal.odin
  15. 5 6
      core/encoding/hex/hex.odin
  16. 16 15
      core/encoding/hxa/hxa.odin
  17. 20 22
      core/encoding/hxa/read.odin
  18. 2 2
      core/encoding/json/marshal.odin
  19. 33 32
      core/encoding/json/parser.odin
  20. 7 7
      core/encoding/json/types.odin
  21. 26 15
      core/log/file_console_logger.odin
  22. 341 0
      core/mem/rollback_stack_allocator.odin
  23. 14 0
      core/mem/tracking_allocator.odin
  24. 1 1
      core/os/os_darwin.odin
  25. 4 4
      core/strings/builder.odin
  26. 2 2
      core/sys/unix/pthread_freebsd.odin
  27. 2 2
      core/sys/unix/pthread_openbsd.odin
  28. 1 0
      core/sys/unix/pthread_unix.odin
  29. 48 0
      core/testing/events.odin
  30. 71 0
      core/testing/logging.odin
  31. 329 0
      core/testing/reporting.odin
  32. 783 36
      core/testing/runner.odin
  33. 0 14
      core/testing/runner_other.odin
  34. 0 235
      core/testing/runner_windows.odin
  35. 33 0
      core/testing/signal_handler.odin
  36. 142 0
      core/testing/signal_handler_libc.odin
  37. 19 0
      core/testing/signal_handler_other.odin
  38. 43 26
      core/testing/testing.odin
  39. 0 2
      core/text/i18n/qt_linguist.odin
  40. 118 15
      core/thread/thread_pool.odin
  41. 0 1
      src/build_settings.cpp
  42. 0 29
      src/checker.cpp
  43. 0 24
      src/main.cpp
  44. 14 0
      tests/benchmark/Makefile
  45. 13 0
      tests/benchmark/build.bat
  46. 356 0
      tests/benchmark/crypto/benchmark_crypto.odin
  47. 218 0
      tests/benchmark/hash/benchmark_hash.odin
  48. 0 81
      tests/common/common.odin
  49. 55 50
      tests/core/Makefile
  50. 45 36
      tests/core/build.bat
  51. 0 36
      tests/core/c/libc/test_core_libc.odin
  52. 7 8
      tests/core/c/libc/test_core_libc_complex_pow.odin
  53. 17 86
      tests/core/compress/test_core_compress.odin
  54. 37 36
      tests/core/container/test_core_avl.odin
  55. 0 26
      tests/core/container/test_core_container.odin
  56. 43 47
      tests/core/container/test_core_rbtree.odin
  57. 36 33
      tests/core/container/test_core_small_array.odin
  58. 28 68
      tests/core/crypto/test_core_crypto.odin
  59. 55 71
      tests/core/crypto/test_core_crypto_aes.odin
  60. 113 180
      tests/core/crypto/test_core_crypto_ecc25519.odin
  61. 39 59
      tests/core/crypto/test_core_crypto_hash.odin
  62. 18 36
      tests/core/crypto/test_core_crypto_kdf.odin
  63. 29 51
      tests/core/crypto/test_core_crypto_mac.odin
  64. 36 60
      tests/core/crypto/test_core_crypto_sha3_variants.odin
  65. 0 361
      tests/core/crypto/test_crypto_benchmark.odin
  66. 21 44
      tests/core/encoding/base64/base64.odin
  67. 132 209
      tests/core/encoding/cbor/test_core_cbor.odin
  68. 44 50
      tests/core/encoding/hex/test_core_hex.odin
  69. 87 115
      tests/core/encoding/hxa/test_core_hxa.odin
  70. 23 65
      tests/core/encoding/json/test_core_json.odin
  71. 24 60
      tests/core/encoding/varint/test_core_varint.odin
  72. 84 136
      tests/core/encoding/xml/test_core_xml.odin
  73. 20 41
      tests/core/fmt/test_core_fmt.odin
  74. 38 270
      tests/core/hash/test_core_hash.odin
  75. 2 4
      tests/core/hash/test_vectors_xxhash.odin
  76. 2 4
      tests/core/image/build.bat
  77. 112 191
      tests/core/image/test_core_image.odin
  78. 1 1
      tests/core/math/big/build.bat
  79. 3 17
      tests/core/math/linalg/glsl/test_linalg_glsl_math.odin
  80. 74 121
      tests/core/math/noise/test_core_math_noise.odin
  81. 93 165
      tests/core/math/test_core_math.odin
  82. 83 193
      tests/core/net/test_core_net.odin
  83. 12 41
      tests/core/odin/test_parser.odin
  84. 35 38
      tests/core/path/filepath/test_core_filepath.odin
  85. 33 54
      tests/core/reflect/test_core_reflect.odin
  86. 5 38
      tests/core/runtime/test_core_runtime.odin
  87. 39 97
      tests/core/slice/test_core_slice.odin
  88. 24 62
      tests/core/strings/test_core_strings.odin
  89. 66 101
      tests/core/text/i18n/test_core_text_i18n.odin
  90. 62 97
      tests/core/text/match/test_core_text_match.odin
  91. 12 44
      tests/core/thread/test_core_thread.odin
  92. 92 78
      tests/core/time/test_core_time.odin
  93. 8 9
      tests/internal/Makefile
  94. 7 8
      tests/internal/build.bat
  95. 8 42
      tests/internal/test_128.odin
  96. 5 40
      tests/internal/test_asan.odin
  97. 46 116
      tests/internal/test_map.odin
  98. 6 42
      tests/internal/test_pow.odin
  99. 7 62
      tests/internal/test_rtti.odin
  100. 12 48
      tests/internal/test_string_compare.odin

+ 23 - 0
.github/workflows/ci.yml

@@ -34,6 +34,7 @@ jobs:
           (cd tests/core; gmake all_bsd)
           (cd tests/internal; gmake all_bsd)
           (cd tests/issues; ./run.sh)
+          (cd tests/benchmark; gmake all)
   build_linux:
     name: Ubuntu Build, Check, and Test
     runs-on: ubuntu-latest
@@ -80,6 +81,11 @@ jobs:
           cd tests/internal
           make
         timeout-minutes: 10
+      - name: Odin core library benchmarks
+        run: |
+          cd tests/benchmark
+          make
+        timeout-minutes: 10
       - name: Odin check examples/all for Linux i386
         run: ./odin check examples/all -vet -strict-style -target:linux_i386
         timeout-minutes: 10
@@ -131,6 +137,11 @@ jobs:
           cd tests/internal
           make
         timeout-minutes: 10
+      - name: Odin core library benchmarks
+        run: |
+          cd tests/benchmark
+          make
+        timeout-minutes: 10
   build_macOS_arm:
     name: MacOS ARM Build, Check, and Test
     runs-on: macos-14 # This is an arm/m1 runner.
@@ -170,6 +181,11 @@ jobs:
           cd tests/internal
           make
         timeout-minutes: 10
+      - name: Odin core library benchmarks
+        run: |
+          cd tests/benchmark
+          make
+        timeout-minutes: 10
   build_windows:
     name: Windows Build, Check, and Test
     runs-on: windows-2022
@@ -217,6 +233,13 @@ jobs:
           cd tests\core
           call build.bat
         timeout-minutes: 10
+      - name: Core library benchmarks
+        shell: cmd
+        run: |
+          call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
+          cd tests\benchmark
+          call build.bat
+        timeout-minutes: 10
       - name: Vendor library tests
         shell: cmd
         run: |

+ 2 - 3
ci/create_nightly_json.py

@@ -13,7 +13,7 @@ def main():
     for x in files_lines:
         parts = x.split(" ", 1)
         if parts[0]:
-            json_str = execute_cli(f"b2 get-file-info {parts[0]}")
+            json_str = execute_cli(f"b2 file info {parts[0]}")
             data = json.loads(json_str)
             name = remove_prefix(data['fileName'], "nightly/")
             url = f"https://f001.backblazeb2.com/file/{bucket}/nightly/{urllib.parse.quote_plus(name)}"
@@ -47,5 +47,4 @@ def execute_cli(command):
     return sb.stdout.read().decode("utf-8");
 
 if __name__ == '__main__':
-    sys.exit(main())
-
+    sys.exit(main())

+ 2 - 4
ci/delete_old_binaries.py

@@ -12,7 +12,6 @@ def main():
     print(f"Looking for binaries to delete older than {days_to_keep} days")
 
     files_lines = execute_cli(f"b2 ls --long --versions b2://{bucket}/nightly/").split("\n")
-    print(files_lines)
     for x in files_lines:
         parts = [y for y in x.split(' ') if y]
 
@@ -23,7 +22,7 @@ def main():
 
             if delta.days > days_to_keep:
                 print(f'Deleting {parts[5]}')
-                execute_cli(f'b2 delete-file-version {parts[0]}')
+                execute_cli(f'b2 rm {parts[0]}')
 
 
 def execute_cli(command):
@@ -31,5 +30,4 @@ def execute_cli(command):
     return sb.stdout.read().decode("utf-8");
 
 if __name__ == '__main__':
-    sys.exit(main())
-
+    sys.exit(main())

+ 1 - 1
ci/upload_create_nightly.sh

@@ -22,4 +22,4 @@ else
 	7z a -bd "output/$filename" -r "$artifact"
 fi
 
-b2 upload-file "$bucket" "output/$filename" "nightly/$filename"
+b2 file upload "$bucket" "output/$filename" "nightly/$filename"

+ 2 - 2
core/bufio/reader.odin

@@ -29,12 +29,12 @@ MIN_READ_BUFFER_SIZE :: 16
 @(private)
 DEFAULT_MAX_CONSECUTIVE_EMPTY_READS :: 128
 
-reader_init :: proc(b: ^Reader, rd: io.Reader, size: int = DEFAULT_BUF_SIZE, allocator := context.allocator) {
+reader_init :: proc(b: ^Reader, rd: io.Reader, size: int = DEFAULT_BUF_SIZE, allocator := context.allocator, loc := #caller_location) {
 	size := size
 	size = max(size, MIN_READ_BUFFER_SIZE)
 	reader_reset(b, rd)
 	b.buf_allocator = allocator
-	b.buf = make([]byte, size, allocator)
+	b.buf = make([]byte, size, allocator, loc)
 }
 
 reader_init_with_buf :: proc(b: ^Reader, rd: io.Reader, buf: []byte) {

+ 35 - 35
core/bytes/buffer.odin

@@ -27,19 +27,19 @@ Read_Op :: enum i8 {
 }
 
 
-buffer_init :: proc(b: ^Buffer, buf: []byte) {
-	resize(&b.buf, len(buf))
+buffer_init :: proc(b: ^Buffer, buf: []byte, loc := #caller_location) {
+	resize(&b.buf, len(buf), loc=loc)
 	copy(b.buf[:], buf)
 }
 
-buffer_init_string :: proc(b: ^Buffer, s: string) {
-	resize(&b.buf, len(s))
+buffer_init_string :: proc(b: ^Buffer, s: string, loc := #caller_location) {
+	resize(&b.buf, len(s), loc=loc)
 	copy(b.buf[:], s)
 }
 
-buffer_init_allocator :: proc(b: ^Buffer, len, cap: int, allocator := context.allocator) {
+buffer_init_allocator :: proc(b: ^Buffer, len, cap: int, allocator := context.allocator, loc := #caller_location) {
 	if b.buf == nil {
-		b.buf = make([dynamic]byte, len, cap, allocator)
+		b.buf = make([dynamic]byte, len, cap, allocator, loc)
 		return
 	}
 
@@ -96,28 +96,28 @@ buffer_truncate :: proc(b: ^Buffer, n: int) {
 }
 
 @(private)
-_buffer_try_grow :: proc(b: ^Buffer, n: int) -> (int, bool) {
+_buffer_try_grow :: proc(b: ^Buffer, n: int, loc := #caller_location) -> (int, bool) {
 	if l := len(b.buf); n <= cap(b.buf)-l {
-		resize(&b.buf, l+n)
+		resize(&b.buf, l+n, loc=loc)
 		return l, true
 	}
 	return 0, false
 }
 
 @(private)
-_buffer_grow :: proc(b: ^Buffer, n: int) -> int {
+_buffer_grow :: proc(b: ^Buffer, n: int, loc := #caller_location) -> int {
 	m := buffer_length(b)
 	if m == 0 && b.off != 0 {
 		buffer_reset(b)
 	}
-	if i, ok := _buffer_try_grow(b, n); ok {
+	if i, ok := _buffer_try_grow(b, n, loc=loc); ok {
 		return i
 	}
 
 	if b.buf == nil && n <= SMALL_BUFFER_SIZE {
 		// Fixes #2756 by preserving allocator if already set on Buffer via init_buffer_allocator
-		reserve(&b.buf, SMALL_BUFFER_SIZE)
-		resize(&b.buf, n)
+		reserve(&b.buf, SMALL_BUFFER_SIZE, loc=loc)
+		resize(&b.buf, n, loc=loc)
 		return 0
 	}
 
@@ -127,31 +127,31 @@ _buffer_grow :: proc(b: ^Buffer, n: int) -> int {
 	} else if c > max(int) - c - n {
 		panic("bytes.Buffer: too large")
 	} else {
-		resize(&b.buf, 2*c + n)
+		resize(&b.buf, 2*c + n, loc=loc)
 		copy(b.buf[:], b.buf[b.off:])
 	}
 	b.off = 0
-	resize(&b.buf, m+n)
+	resize(&b.buf, m+n, loc=loc)
 	return m
 }
 
-buffer_grow :: proc(b: ^Buffer, n: int) {
+buffer_grow :: proc(b: ^Buffer, n: int, loc := #caller_location) {
 	if n < 0 {
 		panic("bytes.buffer_grow: negative count")
 	}
-	m := _buffer_grow(b, n)
-	resize(&b.buf, m)
+	m := _buffer_grow(b, n, loc=loc)
+	resize(&b.buf, m, loc=loc)
 }
 
-buffer_write_at :: proc(b: ^Buffer, p: []byte, offset: int) -> (n: int, err: io.Error) {
+buffer_write_at :: proc(b: ^Buffer, p: []byte, offset: int, loc := #caller_location) -> (n: int, err: io.Error) {
 	b.last_read = .Invalid
 	if offset < 0 {
 		err = .Invalid_Offset
 		return
 	}
-	_, ok := _buffer_try_grow(b, offset+len(p))
+	_, ok := _buffer_try_grow(b, offset+len(p), loc=loc)
 	if !ok {
-		_ = _buffer_grow(b, offset+len(p))
+		_ = _buffer_grow(b, offset+len(p), loc=loc)
 	}
 	if len(b.buf) <= offset {
 		return 0, .Short_Write
@@ -160,47 +160,47 @@ buffer_write_at :: proc(b: ^Buffer, p: []byte, offset: int) -> (n: int, err: io.
 }
 
 
-buffer_write :: proc(b: ^Buffer, p: []byte) -> (n: int, err: io.Error) {
+buffer_write :: proc(b: ^Buffer, p: []byte, loc := #caller_location) -> (n: int, err: io.Error) {
 	b.last_read = .Invalid
-	m, ok := _buffer_try_grow(b, len(p))
+	m, ok := _buffer_try_grow(b, len(p), loc=loc)
 	if !ok {
-		m = _buffer_grow(b, len(p))
+		m = _buffer_grow(b, len(p), loc=loc)
 	}
 	return copy(b.buf[m:], p), nil
 }
 
-buffer_write_ptr :: proc(b: ^Buffer, ptr: rawptr, size: int) -> (n: int, err: io.Error) {
-	return buffer_write(b, ([^]byte)(ptr)[:size])
+buffer_write_ptr :: proc(b: ^Buffer, ptr: rawptr, size: int, loc := #caller_location) -> (n: int, err: io.Error) {
+	return buffer_write(b, ([^]byte)(ptr)[:size], loc=loc)
 }
 
-buffer_write_string :: proc(b: ^Buffer, s: string) -> (n: int, err: io.Error) {
+buffer_write_string :: proc(b: ^Buffer, s: string, loc := #caller_location) -> (n: int, err: io.Error) {
 	b.last_read = .Invalid
-	m, ok := _buffer_try_grow(b, len(s))
+	m, ok := _buffer_try_grow(b, len(s), loc=loc)
 	if !ok {
-		m = _buffer_grow(b, len(s))
+		m = _buffer_grow(b, len(s), loc=loc)
 	}
 	return copy(b.buf[m:], s), nil
 }
 
-buffer_write_byte :: proc(b: ^Buffer, c: byte) -> io.Error {
+buffer_write_byte :: proc(b: ^Buffer, c: byte, loc := #caller_location) -> io.Error {
 	b.last_read = .Invalid
-	m, ok := _buffer_try_grow(b, 1)
+	m, ok := _buffer_try_grow(b, 1, loc=loc)
 	if !ok {
-		m = _buffer_grow(b, 1)
+		m = _buffer_grow(b, 1, loc=loc)
 	}
 	b.buf[m] = c
 	return nil
 }
 
-buffer_write_rune :: proc(b: ^Buffer, r: rune) -> (n: int, err: io.Error) {
+buffer_write_rune :: proc(b: ^Buffer, r: rune, loc := #caller_location) -> (n: int, err: io.Error) {
 	if r < utf8.RUNE_SELF {
-		buffer_write_byte(b, byte(r))
+		buffer_write_byte(b, byte(r), loc=loc)
 		return 1, nil
 	}
 	b.last_read = .Invalid
-	m, ok := _buffer_try_grow(b, utf8.UTF_MAX)
+	m, ok := _buffer_try_grow(b, utf8.UTF_MAX, loc=loc)
 	if !ok {
-		m = _buffer_grow(b, utf8.UTF_MAX)
+		m = _buffer_grow(b, utf8.UTF_MAX, loc=loc)
 	}
 	res: [4]byte
 	res, n = utf8.encode_rune(r)

+ 1 - 1
core/c/libc/signal.odin

@@ -34,7 +34,7 @@ when ODIN_OS == .Windows {
 	SIGTERM :: 15
 }
 
-when ODIN_OS == .Linux || ODIN_OS == .FreeBSD {
+when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Haiku || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD {
 	SIG_ERR  :: rawptr(~uintptr(0))
 	SIG_DFL  :: rawptr(uintptr(0))
 	SIG_IGN  :: rawptr(uintptr(1)) 

+ 8 - 6
core/c/libc/stdio.odin

@@ -102,10 +102,12 @@ when ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD {
 	SEEK_END :: 2
 
 	foreign libc {
-		stderr: ^FILE
-		stdin:  ^FILE
-		stdout: ^FILE
+		__sF: [3]FILE
 	}
+
+	stdin:  ^FILE = &__sF[0]
+	stdout: ^FILE = &__sF[1]
+	stderr: ^FILE = &__sF[2]
 }
 
 when ODIN_OS == .FreeBSD {
@@ -127,9 +129,9 @@ when ODIN_OS == .FreeBSD {
 	SEEK_END :: 2
 
 	foreign libc {
-		stderr: ^FILE
-		stdin:  ^FILE
-		stdout: ^FILE
+		@(link_name="__stderrp") stderr: ^FILE
+		@(link_name="__stdinp")  stdin:  ^FILE
+		@(link_name="__stdoutp") stdout: ^FILE
 	}
 }
 

+ 137 - 0
core/encoding/ansi/ansi.odin

@@ -0,0 +1,137 @@
+package ansi
+
+BEL     :: "\a" // Bell
+BS      :: "\b" // Backspace
+ESC     :: "\e" // Escape
+
+// Fe Escape sequences
+
+CSI     :: ESC + "["  // Control Sequence Introducer
+OSC     :: ESC + "]"  // Operating System Command
+ST      :: ESC + "\\" // String Terminator
+
+// CSI sequences
+
+CUU     :: "A"  // Cursor Up
+CUD     :: "B"  // Cursor Down
+CUF     :: "C"  // Cursor Forward
+CUB     :: "D"  // Cursor Back
+CNL     :: "E"  // Cursor Next Line
+CPL     :: "F"  // Cursor Previous Line
+CHA     :: "G"  // Cursor Horizontal Absolute
+CUP     :: "H"  // Cursor Position
+ED      :: "J"  // Erase in Display
+EL      :: "K"  // Erase in Line
+SU      :: "S"  // Scroll Up
+SD      :: "T"  // Scroll Down
+HVP     :: "f"  // Horizontal Vertical Position
+SGR     :: "m"  // Select Graphic Rendition
+AUX_ON  :: "5i" // AUX Port On
+AUX_OFF :: "4i" // AUX Port Off
+DSR     :: "6n" // Device Status Report
+
+// CSI: private sequences
+
+SCP          :: "s"    // Save Current Cursor Position
+RCP          :: "u"    // Restore Saved Cursor Position
+DECAWM_ON    :: "?7h"  // Auto Wrap Mode (Enabled)
+DECAWM_OFF   :: "?7l"  // Auto Wrap Mode (Disabled)
+DECTCEM_SHOW :: "?25h" // Text Cursor Enable Mode (Visible)
+DECTCEM_HIDE :: "?25l" // Text Cursor Enable Mode (Invisible)
+
+// SGR sequences
+
+RESET                   :: "0"
+BOLD                    :: "1"
+FAINT                   :: "2"
+ITALIC                  :: "3" // Not widely supported.
+UNDERLINE               :: "4"
+BLINK_SLOW              :: "5"
+BLINK_RAPID             :: "6" // Not widely supported.
+INVERT                  :: "7" // Also known as reverse video.
+HIDE                    :: "8" // Not widely supported.
+STRIKE                  :: "9"
+FONT_PRIMARY            :: "10"
+FONT_ALT1               :: "11"
+FONT_ALT2               :: "12"
+FONT_ALT3               :: "13"
+FONT_ALT4               :: "14"
+FONT_ALT5               :: "15"
+FONT_ALT6               :: "16"
+FONT_ALT7               :: "17"
+FONT_ALT8               :: "18"
+FONT_ALT9               :: "19"
+FONT_FRAKTUR            :: "20" // Rarely supported.
+UNDERLINE_DOUBLE        :: "21" // May be interpreted as "disable bold."
+NO_BOLD_FAINT           :: "22"
+NO_ITALIC_BLACKLETTER   :: "23"
+NO_UNDERLINE            :: "24"
+NO_BLINK                :: "25"
+PROPORTIONAL_SPACING    :: "26"
+NO_REVERSE              :: "27"
+NO_HIDE                 :: "28"
+NO_STRIKE               :: "29"
+
+FG_BLACK                :: "30"
+FG_RED                  :: "31"
+FG_GREEN                :: "32"
+FG_YELLOW               :: "33"
+FG_BLUE                 :: "34"
+FG_MAGENTA              :: "35"
+FG_CYAN                 :: "36"
+FG_WHITE                :: "37"
+FG_COLOR                :: "38"
+FG_COLOR_8_BIT          :: "38;5" // Followed by ";n" where n is in 0..=255
+FG_COLOR_24_BIT         :: "38;2" // Followed by ";r;g;b" where r,g,b are in 0..=255
+FG_DEFAULT              :: "39"
+
+BG_BLACK                :: "40"
+BG_RED                  :: "41"
+BG_GREEN                :: "42"
+BG_YELLOW               :: "43"
+BG_BLUE                 :: "44"
+BG_MAGENTA              :: "45"
+BG_CYAN                 :: "46"
+BG_WHITE                :: "47"
+BG_COLOR                :: "48"
+BG_COLOR_8_BIT          :: "48;5" // Followed by ";n" where n is in 0..=255
+BG_COLOR_24_BIT         :: "48;2" // Followed by ";r;g;b" where r,g,b are in 0..=255
+BG_DEFAULT              :: "49"
+
+NO_PROPORTIONAL_SPACING :: "50"
+FRAMED                  :: "51"
+ENCIRCLED               :: "52"
+OVERLINED               :: "53"
+NO_FRAME_ENCIRCLE       :: "54"
+NO_OVERLINE             :: "55"
+
+// SGR: non-standard bright colors
+
+FG_BRIGHT_BLACK         :: "90" // Also known as grey.
+FG_BRIGHT_RED           :: "91"
+FG_BRIGHT_GREEN         :: "92"
+FG_BRIGHT_YELLOW        :: "93"
+FG_BRIGHT_BLUE          :: "94"
+FG_BRIGHT_MAGENTA       :: "95"
+FG_BRIGHT_CYAN          :: "96"
+FG_BRIGHT_WHITE         :: "97"
+
+BG_BRIGHT_BLACK         :: "100" // Also known as grey.
+BG_BRIGHT_RED           :: "101"
+BG_BRIGHT_GREEN         :: "102"
+BG_BRIGHT_YELLOW        :: "103"
+BG_BRIGHT_BLUE          :: "104"
+BG_BRIGHT_MAGENTA       :: "105"
+BG_BRIGHT_CYAN          :: "106"
+BG_BRIGHT_WHITE         :: "107"
+
+// Fp Escape sequences
+
+DECSC :: ESC + "7" // DEC Save Cursor
+DECRC :: ESC + "8" // DEC Restore Cursor
+
+// OSC sequences
+
+WINDOW_TITLE :: "2"  // Followed by ";<text>" ST.
+HYPERLINK    :: "8"  // Followed by ";[params];<URI>" ST. Closed by OSC HYPERLINK ";;" ST.
+CLIPBOARD    :: "52" // Followed by ";c;<Base64-encoded string>" ST.

+ 20 - 0
core/encoding/ansi/doc.odin

@@ -0,0 +1,20 @@
+/*
+package ansi implements constant references to many widely-supported ANSI
+escape codes, primarily used in terminal emulators for enhanced graphics, such
+as colors, text styling, and animated displays.
+
+For example, you can print out a line of cyan text like this:
+	fmt.println(ansi.CSI + ansi.FG_CYAN + ansi.SGR + "Hellope!" + ansi.CSI + ansi.RESET + ansi.SGR)
+
+Multiple SGR (Select Graphic Rendition) codes can be joined by semicolons:
+	fmt.println(ansi.CSI + ansi.BOLD + ";" + ansi.FG_BLUE + ansi.SGR + "Hellope!" + ansi.CSI + ansi.RESET + ansi.SGR)
+
+If your terminal supports 24-bit true color mode, you can also do this:
+	fmt.println(ansi.CSI + ansi.FG_COLOR_24_BIT + ";0;255;255" + ansi.SGR + "Hellope!" + ansi.CSI + ansi.RESET + ansi.SGR)
+
+For more information, see:
+	1. https://en.wikipedia.org/wiki/ANSI_escape_code
+	2. https://www.vt100.net/docs/vt102-ug/chapter5.html
+	3. https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
+*/
+package ansi

+ 2 - 2
core/encoding/cbor/cbor.odin

@@ -320,8 +320,8 @@ to_diagnostic_format :: proc {
 
 // Turns the given CBOR value into a human-readable string.
 // See docs on the proc group `diagnose` for more info.
-to_diagnostic_format_string :: proc(val: Value, padding := 0, allocator := context.allocator) -> (string, mem.Allocator_Error) #optional_allocator_error {
-	b := strings.builder_make(allocator)
+to_diagnostic_format_string :: proc(val: Value, padding := 0, allocator := context.allocator, loc := #caller_location) -> (string, mem.Allocator_Error) #optional_allocator_error {
+	b := strings.builder_make(allocator, loc)
 	w := strings.to_stream(&b)
 	err := to_diagnostic_format_writer(w, val, padding)
 	if err == .EOF {

+ 54 - 53
core/encoding/cbor/coding.odin

@@ -95,24 +95,25 @@ decode :: decode_from
 
 // Decodes the given string as CBOR.
 // See docs on the proc group `decode` for more information.
-decode_from_string :: proc(s: string, flags: Decoder_Flags = {}, allocator := context.allocator) -> (v: Value, err: Decode_Error) {
+decode_from_string :: proc(s: string, flags: Decoder_Flags = {}, allocator := context.allocator, loc := #caller_location) -> (v: Value, err: Decode_Error) {
 	r: strings.Reader
 	strings.reader_init(&r, s)
-	return decode_from_reader(strings.reader_to_stream(&r), flags, allocator)
+	return decode_from_reader(strings.reader_to_stream(&r), flags, allocator, loc)
 }
 
 // Reads a CBOR value from the given reader.
 // See docs on the proc group `decode` for more information.
-decode_from_reader :: proc(r: io.Reader, flags: Decoder_Flags = {}, allocator := context.allocator) -> (v: Value, err: Decode_Error) {
+decode_from_reader :: proc(r: io.Reader, flags: Decoder_Flags = {}, allocator := context.allocator, loc := #caller_location) -> (v: Value, err: Decode_Error) {
 	return decode_from_decoder(
 		Decoder{ DEFAULT_MAX_PRE_ALLOC, flags, r },
 		allocator=allocator,
+		loc = loc,
 	)
 }
 
 // Reads a CBOR value from the given decoder.
 // See docs on the proc group `decode` for more information.
-decode_from_decoder :: proc(d: Decoder, allocator := context.allocator) -> (v: Value, err: Decode_Error) {
+decode_from_decoder :: proc(d: Decoder, allocator := context.allocator, loc := #caller_location) -> (v: Value, err: Decode_Error) {
 	context.allocator = allocator
 	
 	d := d
@@ -121,13 +122,13 @@ decode_from_decoder :: proc(d: Decoder, allocator := context.allocator) -> (v: V
 		d.max_pre_alloc = DEFAULT_MAX_PRE_ALLOC
 	}
 
-	v, err = _decode_from_decoder(d)
+	v, err = _decode_from_decoder(d, {}, allocator, loc)
 	// Normal EOF does not exist here, we try to read the exact amount that is said to be provided.
 	if err == .EOF { err = .Unexpected_EOF }
 	return
 }
 
-_decode_from_decoder :: proc(d: Decoder, hdr: Header = Header(0)) -> (v: Value, err: Decode_Error) {
+_decode_from_decoder :: proc(d: Decoder, hdr: Header = Header(0), allocator := context.allocator, loc := #caller_location) -> (v: Value, err: Decode_Error) {
 	hdr := hdr
 	r := d.reader
 	if hdr == Header(0) { hdr = _decode_header(r) or_return }
@@ -161,11 +162,11 @@ _decode_from_decoder :: proc(d: Decoder, hdr: Header = Header(0)) -> (v: Value,
 	switch maj {
 	case .Unsigned: return _decode_tiny_u8(add)
 	case .Negative: return Negative_U8(_decode_tiny_u8(add) or_return), nil
-	case .Bytes:    return _decode_bytes_ptr(d, add)
-	case .Text:     return _decode_text_ptr(d, add)
-	case .Array:    return _decode_array_ptr(d, add)
-	case .Map:      return _decode_map_ptr(d, add)
-	case .Tag:      return _decode_tag_ptr(d, add)
+	case .Bytes:    return _decode_bytes_ptr(d, add, .Bytes, allocator, loc)
+	case .Text:     return _decode_text_ptr(d, add, allocator, loc)
+	case .Array:    return _decode_array_ptr(d, add, allocator, loc)
+	case .Map:      return _decode_map_ptr(d, add, allocator, loc)
+	case .Tag:      return _decode_tag_ptr(d, add, allocator, loc)
 	case .Other:    return _decode_tiny_simple(add)
 	case:           return nil, .Bad_Major
 	}
@@ -203,27 +204,27 @@ encode :: encode_into
 
 // Encodes the CBOR value into binary CBOR allocated on the given allocator.
 // See the docs on the proc group `encode_into` for more info.
-encode_into_bytes :: proc(v: Value, flags := ENCODE_SMALL, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (data: []byte, err: Encode_Error) {
-	b := strings.builder_make(allocator) or_return
+encode_into_bytes :: proc(v: Value, flags := ENCODE_SMALL, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (data: []byte, err: Encode_Error) {
+	b := strings.builder_make(allocator, loc) or_return
 	encode_into_builder(&b, v, flags, temp_allocator) or_return
 	return b.buf[:], nil
 }
 
 // Encodes the CBOR value into binary CBOR written to the given builder.
 // See the docs on the proc group `encode_into` for more info.
-encode_into_builder :: proc(b: ^strings.Builder, v: Value, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator) -> Encode_Error {
-	return encode_into_writer(strings.to_stream(b), v, flags, temp_allocator)
+encode_into_builder :: proc(b: ^strings.Builder, v: Value, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator, loc := #caller_location) -> Encode_Error {
+	return encode_into_writer(strings.to_stream(b), v, flags, temp_allocator, loc=loc)
 }
 
 // Encodes the CBOR value into binary CBOR written to the given writer.
 // See the docs on the proc group `encode_into` for more info.
-encode_into_writer :: proc(w: io.Writer, v: Value, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator) -> Encode_Error {
-	return encode_into_encoder(Encoder{flags, w, temp_allocator}, v)
+encode_into_writer :: proc(w: io.Writer, v: Value, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator, loc := #caller_location) -> Encode_Error {
+	return encode_into_encoder(Encoder{flags, w, temp_allocator}, v, loc=loc)
 }
 
 // Encodes the CBOR value into binary CBOR written to the given encoder.
 // See the docs on the proc group `encode_into` for more info.
-encode_into_encoder :: proc(e: Encoder, v: Value) -> Encode_Error {
+encode_into_encoder :: proc(e: Encoder, v: Value, loc := #caller_location) -> Encode_Error {
 	e := e
 
 	if e.temp_allocator.procedure == nil {
@@ -366,21 +367,21 @@ _encode_u64_exact :: proc(w: io.Writer, v: u64, major: Major = .Unsigned) -> (er
 	return
 }
 
-_decode_bytes_ptr :: proc(d: Decoder, add: Add, type: Major = .Bytes) -> (v: ^Bytes, err: Decode_Error) {
-	v = new(Bytes) or_return
-	defer if err != nil { free(v) }
+_decode_bytes_ptr :: proc(d: Decoder, add: Add, type: Major = .Bytes, allocator := context.allocator, loc := #caller_location) -> (v: ^Bytes, err: Decode_Error) {
+	v = new(Bytes, allocator, loc) or_return
+	defer if err != nil { free(v, allocator, loc) }
 
-	v^ = _decode_bytes(d, add, type) or_return
+	v^ = _decode_bytes(d, add, type, allocator, loc) or_return
 	return
 }
 
-_decode_bytes :: proc(d: Decoder, add: Add, type: Major = .Bytes, allocator := context.allocator) -> (v: Bytes, err: Decode_Error) {
+_decode_bytes :: proc(d: Decoder, add: Add, type: Major = .Bytes, allocator := context.allocator, loc := #caller_location) -> (v: Bytes, err: Decode_Error) {
 	context.allocator = allocator
 
 	add := add
 	n, scap := _decode_len_str(d, add) or_return
 	
-	buf := strings.builder_make(0, scap) or_return
+	buf := strings.builder_make(0, scap, allocator, loc) or_return
 	defer if err != nil { strings.builder_destroy(&buf) }
 	buf_stream := strings.to_stream(&buf)
 
@@ -426,40 +427,40 @@ _encode_bytes :: proc(e: Encoder, val: Bytes, major: Major = .Bytes) -> (err: En
 	return
 }
 
-_decode_text_ptr :: proc(d: Decoder, add: Add) -> (v: ^Text, err: Decode_Error) {
-	v = new(Text) or_return
+_decode_text_ptr :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: ^Text, err: Decode_Error) {
+	v = new(Text, allocator, loc) or_return
 	defer if err != nil { free(v) }
 
-	v^ = _decode_text(d, add) or_return
+	v^ = _decode_text(d, add, allocator, loc) or_return
 	return
 }
 
-_decode_text :: proc(d: Decoder, add: Add, allocator := context.allocator) -> (v: Text, err: Decode_Error) {
-	return (Text)(_decode_bytes(d, add, .Text, allocator) or_return), nil
+_decode_text :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: Text, err: Decode_Error) {
+	return (Text)(_decode_bytes(d, add, .Text, allocator, loc) or_return), nil
 }
 
 _encode_text :: proc(e: Encoder, val: Text) -> Encode_Error {
     return _encode_bytes(e, transmute([]byte)val, .Text)
 }
 
-_decode_array_ptr :: proc(d: Decoder, add: Add) -> (v: ^Array, err: Decode_Error) {
-	v = new(Array) or_return
+_decode_array_ptr :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: ^Array, err: Decode_Error) {
+	v = new(Array, allocator, loc) or_return
 	defer if err != nil { free(v) }
 
-	v^ = _decode_array(d, add) or_return
+	v^ = _decode_array(d, add, allocator, loc) or_return
 	return
 }
 
-_decode_array :: proc(d: Decoder, add: Add) -> (v: Array, err: Decode_Error) {
+_decode_array :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: Array, err: Decode_Error) {
 	n, scap := _decode_len_container(d, add) or_return
-	array := make([dynamic]Value, 0, scap) or_return
+	array := make([dynamic]Value, 0, scap, allocator, loc) or_return
 	defer if err != nil {
-		for entry in array { destroy(entry) }
-		delete(array)
+		for entry in array { destroy(entry, allocator) }
+		delete(array, loc)
 	}
 	
 	for i := 0; n == -1 || i < n; i += 1 {
-		val, verr := _decode_from_decoder(d)
+		val, verr := _decode_from_decoder(d, {}, allocator, loc)
 		if n == -1 && verr == .Break {
 			break
 		} else if verr != nil {
@@ -485,39 +486,39 @@ _encode_array :: proc(e: Encoder, arr: Array) -> Encode_Error {
     return nil
 }
 
-_decode_map_ptr :: proc(d: Decoder, add: Add) -> (v: ^Map, err: Decode_Error) {
-	v = new(Map) or_return
+_decode_map_ptr :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: ^Map, err: Decode_Error) {
+	v = new(Map, allocator, loc) or_return
 	defer if err != nil { free(v) }
 
-	v^ = _decode_map(d, add) or_return
+	v^ = _decode_map(d, add, allocator, loc) or_return
 	return
 }
 
-_decode_map :: proc(d: Decoder, add: Add) -> (v: Map, err: Decode_Error) {
+_decode_map :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: Map, err: Decode_Error) {
 	n, scap := _decode_len_container(d, add) or_return
-	items := make([dynamic]Map_Entry, 0, scap) or_return
+	items := make([dynamic]Map_Entry, 0, scap, allocator, loc) or_return
 	defer if err != nil { 
 		for entry in items {
 			destroy(entry.key)
 			destroy(entry.value)
 		}
-		delete(items)
+		delete(items, loc)
 	}
 
 	for i := 0; n == -1 || i < n; i += 1 {
-		key, kerr := _decode_from_decoder(d)
+		key, kerr := _decode_from_decoder(d, {}, allocator, loc)
 		if n == -1 && kerr == .Break {
 			break
 		} else if kerr != nil {
 			return nil, kerr
 		} 
 
-		value := _decode_from_decoder(d) or_return
+		value := _decode_from_decoder(d, {}, allocator, loc) or_return
 
 		append(&items, Map_Entry{
 			key   = key,
 			value = value,
-		}) or_return
+		}, loc) or_return
 	}
 
 	if .Shrink_Excess in d.flags { shrink(&items) }
@@ -578,20 +579,20 @@ _encode_map :: proc(e: Encoder, m: Map) -> (err: Encode_Error) {
     return nil
 }
 
-_decode_tag_ptr :: proc(d: Decoder, add: Add) -> (v: Value, err: Decode_Error) {
-	tag := _decode_tag(d, add) or_return
+_decode_tag_ptr :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: Value, err: Decode_Error) {
+	tag := _decode_tag(d, add, allocator, loc) or_return
 	if t, ok := tag.?; ok {
 		defer if err != nil { destroy(t.value) }
-		tp := new(Tag) or_return
+		tp := new(Tag, allocator, loc) or_return
 		tp^ = t
 		return tp, nil
 	}
 
 	// no error, no tag, this was the self described CBOR tag, skip it.
-	return _decode_from_decoder(d)
+	return _decode_from_decoder(d, {}, allocator, loc)
 }
 
-_decode_tag :: proc(d: Decoder, add: Add) -> (v: Maybe(Tag), err: Decode_Error) {
+_decode_tag :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: Maybe(Tag), err: Decode_Error) {
 	num := _decode_uint_as_u64(d.reader, add) or_return
 
 	// CBOR can be wrapped in a tag that decoders can use to see/check if the binary data is CBOR.
@@ -602,7 +603,7 @@ _decode_tag :: proc(d: Decoder, add: Add) -> (v: Maybe(Tag), err: Decode_Error)
 
 	t := Tag{
 		number = num,
-		value = _decode_from_decoder(d) or_return,
+		value = _decode_from_decoder(d, {}, allocator, loc) or_return,
 	}
 
 	if nested, ok := t.value.(^Tag); ok {
@@ -883,4 +884,4 @@ _encode_deterministic_f64 :: proc(w: io.Writer, v: f64) -> io.Error {
 	}
 
 	return _encode_f64_exact(w, v)
-}
+}

+ 8 - 8
core/encoding/cbor/marshal.odin

@@ -45,8 +45,8 @@ marshal :: marshal_into
 
 // Marshals the given value into a CBOR byte stream (allocated using the given allocator).
 // See docs on the `marshal_into` proc group for more info.
-marshal_into_bytes :: proc(v: any, flags := ENCODE_SMALL, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (bytes: []byte, err: Marshal_Error) {
-	b, alloc_err := strings.builder_make(allocator)
+marshal_into_bytes :: proc(v: any, flags := ENCODE_SMALL, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (bytes: []byte, err: Marshal_Error) {
+	b, alloc_err := strings.builder_make(allocator, loc=loc)
  	// The builder as a stream also returns .EOF if it ran out of memory so this is consistent.
 	if alloc_err != nil {
 		return nil, .EOF
@@ -54,7 +54,7 @@ marshal_into_bytes :: proc(v: any, flags := ENCODE_SMALL, allocator := context.a
 
 	defer if err != nil { strings.builder_destroy(&b) }
 
-	if err = marshal_into_builder(&b, v, flags, temp_allocator); err != nil {
+	if err = marshal_into_builder(&b, v, flags, temp_allocator, loc=loc); err != nil {
 		return
 	}
 
@@ -63,20 +63,20 @@ marshal_into_bytes :: proc(v: any, flags := ENCODE_SMALL, allocator := context.a
 
 // Marshals the given value into a CBOR byte stream written to the given builder.
 // See docs on the `marshal_into` proc group for more info.
-marshal_into_builder :: proc(b: ^strings.Builder, v: any, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator) -> Marshal_Error {
-	return marshal_into_writer(strings.to_writer(b), v, flags, temp_allocator)
+marshal_into_builder :: proc(b: ^strings.Builder, v: any, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator, loc := #caller_location) -> Marshal_Error {
+	return marshal_into_writer(strings.to_writer(b), v, flags, temp_allocator, loc=loc)
 }
 
 // Marshals the given value into a CBOR byte stream written to the given writer.
 // See docs on the `marshal_into` proc group for more info.
-marshal_into_writer :: proc(w: io.Writer, v: any, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator) -> Marshal_Error {
+marshal_into_writer :: proc(w: io.Writer, v: any, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator, loc := #caller_location) -> Marshal_Error {
 	encoder := Encoder{flags, w, temp_allocator}
-	return marshal_into_encoder(encoder, v)
+	return marshal_into_encoder(encoder, v, loc=loc)
 }
 
 // Marshals the given value into a CBOR byte stream written to the given encoder.
 // See docs on the `marshal_into` proc group for more info.
-marshal_into_encoder :: proc(e: Encoder, v: any) -> (err: Marshal_Error) {
+marshal_into_encoder :: proc(e: Encoder, v: any, loc :=  #caller_location) -> (err: Marshal_Error) {
 	e := e
 
 	if e.temp_allocator.procedure == nil {

+ 52 - 50
core/encoding/cbor/unmarshal.odin

@@ -31,8 +31,8 @@ unmarshal :: proc {
 	unmarshal_from_string,
 }
 
-unmarshal_from_reader :: proc(r: io.Reader, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (err: Unmarshal_Error) {
-	err = unmarshal_from_decoder(Decoder{ DEFAULT_MAX_PRE_ALLOC, flags, r }, ptr, allocator, temp_allocator)
+unmarshal_from_reader :: proc(r: io.Reader, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) {
+	err = unmarshal_from_decoder(Decoder{ DEFAULT_MAX_PRE_ALLOC, flags, r }, ptr, allocator, temp_allocator, loc)
 
 	// Normal EOF does not exist here, we try to read the exact amount that is said to be provided.
 	if err == .EOF { err = .Unexpected_EOF }
@@ -40,21 +40,21 @@ unmarshal_from_reader :: proc(r: io.Reader, ptr: ^$T, flags := Decoder_Flags{},
 }
 
 // Unmarshals from a string, see docs on the proc group `Unmarshal` for more info.
-unmarshal_from_string :: proc(s: string, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (err: Unmarshal_Error) {
+unmarshal_from_string :: proc(s: string, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) {
 	sr: strings.Reader
 	r := strings.to_reader(&sr, s)
 
-	err = unmarshal_from_reader(r, ptr, flags, allocator, temp_allocator)
+	err = unmarshal_from_reader(r, ptr, flags, allocator, temp_allocator, loc)
 
 	// Normal EOF does not exist here, we try to read the exact amount that is said to be provided.
 	if err == .EOF { err = .Unexpected_EOF }
 	return
 }
 
-unmarshal_from_decoder :: proc(d: Decoder, ptr: ^$T, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (err: Unmarshal_Error) {
+unmarshal_from_decoder :: proc(d: Decoder, ptr: ^$T, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) {
 	d := d
 
-	err = _unmarshal_any_ptr(d, ptr, nil, allocator, temp_allocator)
+	err = _unmarshal_any_ptr(d, ptr, nil, allocator, temp_allocator, loc)
 
 	// Normal EOF does not exist here, we try to read the exact amount that is said to be provided.
 	if err == .EOF { err = .Unexpected_EOF }
@@ -62,7 +62,7 @@ unmarshal_from_decoder :: proc(d: Decoder, ptr: ^$T, allocator := context.alloca
 
 }
 
-_unmarshal_any_ptr :: proc(d: Decoder, v: any, hdr: Maybe(Header) = nil, allocator := context.allocator, temp_allocator := context.temp_allocator) -> Unmarshal_Error {
+_unmarshal_any_ptr :: proc(d: Decoder, v: any, hdr: Maybe(Header) = nil, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> Unmarshal_Error {
 	context.allocator = allocator
 	context.temp_allocator = temp_allocator
 	v := v
@@ -78,10 +78,10 @@ _unmarshal_any_ptr :: proc(d: Decoder, v: any, hdr: Maybe(Header) = nil, allocat
 	}
 	
 	data := any{(^rawptr)(v.data)^, ti.variant.(reflect.Type_Info_Pointer).elem.id}	
-	return _unmarshal_value(d, data, hdr.? or_else (_decode_header(d.reader) or_return))
+	return _unmarshal_value(d, data, hdr.? or_else (_decode_header(d.reader) or_return), allocator, temp_allocator, loc)
 }
 
-_unmarshal_value :: proc(d: Decoder, v: any, hdr: Header) -> (err: Unmarshal_Error) {
+_unmarshal_value :: proc(d: Decoder, v: any, hdr: Header, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) {
 	v := v
 	ti := reflect.type_info_base(type_info_of(v.id))
 	r := d.reader
@@ -104,7 +104,7 @@ _unmarshal_value :: proc(d: Decoder, v: any, hdr: Header) -> (err: Unmarshal_Err
 	// Allow generic unmarshal by doing it into a `Value`.
 	switch &dst in v {
 	case Value:
-		dst = err_conv(_decode_from_decoder(d, hdr)) or_return
+		dst = err_conv(_decode_from_decoder(d, hdr, allocator, loc)) or_return
 		return
 	}
 
@@ -308,7 +308,7 @@ _unmarshal_value :: proc(d: Decoder, v: any, hdr: Header) -> (err: Unmarshal_Err
 		if impl, ok := _tag_implementations_nr[nr]; ok {
 			return impl->unmarshal(d, nr, v)
 		} else if nr == TAG_OBJECT_TYPE {
-			return _unmarshal_union(d, v, ti, hdr)
+			return _unmarshal_union(d, v, ti, hdr, loc=loc)
 		} else {
 			// Discard the tag info and unmarshal as its value.
 			return _unmarshal_value(d, v, _decode_header(r) or_return)
@@ -316,19 +316,19 @@ _unmarshal_value :: proc(d: Decoder, v: any, hdr: Header) -> (err: Unmarshal_Err
 
 		return _unsupported(v, hdr, add)
 
-	case .Bytes: return _unmarshal_bytes(d, v, ti, hdr, add)
-	case .Text:  return _unmarshal_string(d, v, ti, hdr, add)
-	case .Array: return _unmarshal_array(d, v, ti, hdr, add)
-	case .Map:   return _unmarshal_map(d, v, ti, hdr, add)
+	case .Bytes: return _unmarshal_bytes(d, v, ti, hdr, add, allocator=allocator, loc=loc)
+	case .Text:  return _unmarshal_string(d, v, ti, hdr, add, allocator=allocator, loc=loc)
+	case .Array: return _unmarshal_array(d, v, ti, hdr, add, allocator=allocator, loc=loc)
+	case .Map:   return _unmarshal_map(d, v, ti, hdr, add, allocator=allocator, loc=loc)
 
 	case:        return .Bad_Major
 	}
 }
 
-_unmarshal_bytes :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add) -> (err: Unmarshal_Error) {
+_unmarshal_bytes :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add, allocator := context.allocator, loc := #caller_location) -> (err: Unmarshal_Error) {
 	#partial switch t in ti.variant {
 	case reflect.Type_Info_String:
-		bytes := err_conv(_decode_bytes(d, add)) or_return
+		bytes := err_conv(_decode_bytes(d, add, allocator=allocator, loc=loc)) or_return
 
 		if t.is_cstring {
 			raw  := (^cstring)(v.data)
@@ -347,7 +347,7 @@ _unmarshal_bytes :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
 
 		if elem_base.id != byte { return _unsupported(v, hdr) }
 
-		bytes := err_conv(_decode_bytes(d, add)) or_return
+		bytes := err_conv(_decode_bytes(d, add, allocator=allocator, loc=loc)) or_return
 		raw   := (^mem.Raw_Slice)(v.data)
 		raw^   = transmute(mem.Raw_Slice)bytes
 		return
@@ -357,12 +357,12 @@ _unmarshal_bytes :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
 
 		if elem_base.id != byte { return _unsupported(v, hdr) }
 		
-		bytes         := err_conv(_decode_bytes(d, add)) or_return
+		bytes         := err_conv(_decode_bytes(d, add, allocator=allocator, loc=loc)) or_return
 		raw           := (^mem.Raw_Dynamic_Array)(v.data)
 		raw.data       = raw_data(bytes)
 		raw.len        = len(bytes)
 		raw.cap        = len(bytes)
-		raw.allocator  = context.allocator
+		raw.allocator  = allocator
 		return
 
 	case reflect.Type_Info_Array:
@@ -385,10 +385,10 @@ _unmarshal_bytes :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
 	return _unsupported(v, hdr)
 }
 
-_unmarshal_string :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add) -> (err: Unmarshal_Error) {
+_unmarshal_string :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) {
 	#partial switch t in ti.variant {
 	case reflect.Type_Info_String:
-		text := err_conv(_decode_text(d, add)) or_return
+		text := err_conv(_decode_text(d, add, allocator, loc)) or_return
 
 		if t.is_cstring {
 			raw := (^cstring)(v.data)
@@ -403,8 +403,8 @@ _unmarshal_string :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Heade
 
 	// Enum by its variant name.
 	case reflect.Type_Info_Enum:
-		text := err_conv(_decode_text(d, add, allocator=context.temp_allocator)) or_return
-		defer delete(text, context.temp_allocator)
+		text := err_conv(_decode_text(d, add, allocator=temp_allocator, loc=loc)) or_return
+		defer delete(text, temp_allocator, loc)
 
 		for name, i in t.names {
 			if name == text {
@@ -414,8 +414,8 @@ _unmarshal_string :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Heade
 		}
 	
 	case reflect.Type_Info_Rune:
-		text := err_conv(_decode_text(d, add, allocator=context.temp_allocator)) or_return
-		defer delete(text, context.temp_allocator)
+		text := err_conv(_decode_text(d, add, allocator=temp_allocator, loc=loc)) or_return
+		defer delete(text, temp_allocator, loc)
 
 		r := (^rune)(v.data)
 		dr, n := utf8.decode_rune(text)
@@ -430,13 +430,15 @@ _unmarshal_string :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Heade
 	return _unsupported(v, hdr)
 }
 
-_unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add) -> (err: Unmarshal_Error) {
+_unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add, allocator := context.allocator, loc := #caller_location) -> (err: Unmarshal_Error) {
 	assign_array :: proc(
 		d: Decoder,
 		da: ^mem.Raw_Dynamic_Array,
 		elemt: ^reflect.Type_Info,
 		length: int,
 		growable := true,
+		allocator := context.allocator,
+		loc := #caller_location,
 	) -> (out_of_space: bool, err: Unmarshal_Error) {
 		for idx: uintptr = 0; length == -1 || idx < uintptr(length); idx += 1 {
 			elem_ptr := rawptr(uintptr(da.data) + idx*uintptr(elemt.size))
@@ -450,13 +452,13 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
 				if !growable { return true, .Out_Of_Memory }
 
 				cap := 2 * da.cap
-				ok := runtime.__dynamic_array_reserve(da, elemt.size, elemt.align, cap)
+				ok := runtime.__dynamic_array_reserve(da, elemt.size, elemt.align, cap, loc)
  				
 				// NOTE: Might be lying here, but it is at least an allocator error.
 				if !ok { return false, .Out_Of_Memory }
 			}
 			
-			err = _unmarshal_value(d, elem, hdr)
+			err = _unmarshal_value(d, elem, hdr, allocator=allocator, loc=loc)
 			if length == -1 && err == .Break { break }
 			if err != nil { return }
 
@@ -469,10 +471,10 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
 	// Allow generically storing the values array.
 	switch &dst in v {
 	case ^Array:
-		dst = err_conv(_decode_array_ptr(d, add)) or_return
+		dst = err_conv(_decode_array_ptr(d, add, allocator=allocator, loc=loc)) or_return
 		return
 	case Array:
-		dst = err_conv(_decode_array(d, add)) or_return
+		dst = err_conv(_decode_array(d, add, allocator=allocator, loc=loc)) or_return
 		return
 	}
 
@@ -480,8 +482,8 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
 	case reflect.Type_Info_Slice:
 		length, scap := err_conv(_decode_len_container(d, add)) or_return
 
-		data := mem.alloc_bytes_non_zeroed(t.elem.size * scap, t.elem.align) or_return
-		defer if err != nil { mem.free_bytes(data) }
+		data := mem.alloc_bytes_non_zeroed(t.elem.size * scap, t.elem.align, allocator=allocator, loc=loc) or_return
+		defer if err != nil { mem.free_bytes(data, allocator=allocator, loc=loc) }
 
 		da := mem.Raw_Dynamic_Array{raw_data(data), 0, length, context.allocator }
 
@@ -489,7 +491,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
 
 		if .Shrink_Excess in d.flags {
 			// Ignoring an error here, but this is not critical to succeed.
-			_ = runtime.__dynamic_array_shrink(&da, t.elem.size, t.elem.align, da.len)
+			_ = runtime.__dynamic_array_shrink(&da, t.elem.size, t.elem.align, da.len, loc=loc)
 		}
 
 		raw      := (^mem.Raw_Slice)(v.data)
@@ -500,8 +502,8 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
 	case reflect.Type_Info_Dynamic_Array:
 		length, scap := err_conv(_decode_len_container(d, add)) or_return
 
-		data := mem.alloc_bytes_non_zeroed(t.elem.size * scap, t.elem.align) or_return
-		defer if err != nil { mem.free_bytes(data) }
+		data := mem.alloc_bytes_non_zeroed(t.elem.size * scap, t.elem.align, loc=loc) or_return
+		defer if err != nil { mem.free_bytes(data, allocator=allocator, loc=loc) }
 
 		raw           := (^mem.Raw_Dynamic_Array)(v.data)
 		raw.data       = raw_data(data) 
@@ -513,7 +515,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
 
 		if .Shrink_Excess in d.flags {
 			// Ignoring an error here, but this is not critical to succeed.
-			_ = runtime.__dynamic_array_shrink(raw, t.elem.size, t.elem.align, raw.len)
+			_ = runtime.__dynamic_array_shrink(raw, t.elem.size, t.elem.align, raw.len, loc=loc)
 		}
 		return
 
@@ -525,7 +527,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
 			return _unsupported(v, hdr)
 		}
 
-		da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, length, context.allocator }
+		da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, length, allocator }
 
 		out_of_space := assign_array(d, &da, t.elem, length, growable=false) or_return
 		if out_of_space { return _unsupported(v, hdr) }
@@ -539,7 +541,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
 			return _unsupported(v, hdr)
 		}
 
-		da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, length, context.allocator }
+		da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, length, allocator }
 
 		out_of_space := assign_array(d, &da, t.elem, length, growable=false) or_return
 		if out_of_space { return _unsupported(v, hdr) }
@@ -553,7 +555,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
 			return _unsupported(v, hdr)
 		}
 
-		da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, 2, context.allocator }
+		da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, 2, allocator }
 
 		info: ^runtime.Type_Info
 		switch ti.id {
@@ -575,7 +577,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
 			return _unsupported(v, hdr)
 		}
 
-		da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, 4, context.allocator }
+		da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, 4, allocator }
 
 		info: ^runtime.Type_Info
 		switch ti.id {
@@ -593,17 +595,17 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
 	}
 }
 
-_unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add) -> (err: Unmarshal_Error) {
+_unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add, allocator := context.allocator, loc := #caller_location) -> (err: Unmarshal_Error) {
 	r := d.reader
-	decode_key :: proc(d: Decoder, v: any, allocator := context.allocator) -> (k: string, err: Unmarshal_Error) {
+	decode_key :: proc(d: Decoder, v: any, allocator := context.allocator, loc := #caller_location) -> (k: string, err: Unmarshal_Error) {
 		entry_hdr := _decode_header(d.reader) or_return
 		entry_maj, entry_add := _header_split(entry_hdr)
 		#partial switch entry_maj {
 		case .Text:
-			k = err_conv(_decode_text(d, entry_add, allocator)) or_return
+			k = err_conv(_decode_text(d, entry_add, allocator=allocator, loc=loc)) or_return
 			return
 		case .Bytes:
-			bytes := err_conv(_decode_bytes(d, entry_add, allocator=allocator)) or_return
+			bytes := err_conv(_decode_bytes(d, entry_add, allocator=allocator, loc=loc)) or_return
 			k = string(bytes)
 			return
 		case:
@@ -615,10 +617,10 @@ _unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header,
 	// Allow generically storing the map array.
 	switch &dst in v {
 	case ^Map:
-		dst = err_conv(_decode_map_ptr(d, add)) or_return
+		dst = err_conv(_decode_map_ptr(d, add, allocator=allocator, loc=loc)) or_return
 		return
 	case Map:
-		dst = err_conv(_decode_map(d, add)) or_return
+		dst = err_conv(_decode_map(d, add, allocator=allocator, loc=loc)) or_return
 		return
 	}
 
@@ -754,7 +756,7 @@ _unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header,
 // Unmarshal into a union, based on the `TAG_OBJECT_TYPE` tag of the spec, it denotes a tag which
 // contains an array of exactly two elements, the first is a textual representation of the following
 // CBOR value's type.
-_unmarshal_union :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header) -> (err: Unmarshal_Error) {
+_unmarshal_union :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, loc := #caller_location) -> (err: Unmarshal_Error) {
 	r := d.reader
 	#partial switch t in ti.variant {
 	case reflect.Type_Info_Union:
@@ -792,7 +794,7 @@ _unmarshal_union :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
 			case reflect.Type_Info_Named:
 				if vti.name == target_name {
 					reflect.set_union_variant_raw_tag(v, tag)
-					return _unmarshal_value(d, any{v.data, variant.id}, _decode_header(r) or_return)
+					return _unmarshal_value(d, any{v.data, variant.id}, _decode_header(r) or_return, loc=loc)
 				}
 
 			case:
@@ -804,7 +806,7 @@ _unmarshal_union :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
 				
 				if variant_name == target_name {
 					reflect.set_union_variant_raw_tag(v, tag)
-					return _unmarshal_value(d, any{v.data, variant.id}, _decode_header(r) or_return)
+					return _unmarshal_value(d, any{v.data, variant.id}, _decode_header(r) or_return, loc=loc)
 				}
 			}
 		}

+ 5 - 6
core/encoding/hex/hex.odin

@@ -2,8 +2,8 @@ package encoding_hex
 
 import "core:strings"
 
-encode :: proc(src: []byte, allocator := context.allocator) -> []byte #no_bounds_check {
-	dst := make([]byte, len(src) * 2, allocator)
+encode :: proc(src: []byte, allocator := context.allocator, loc := #caller_location) -> []byte #no_bounds_check {
+	dst := make([]byte, len(src) * 2, allocator, loc)
 	for i, j := 0, 0; i < len(src); i += 1 {
 		v := src[i]
 		dst[j]   = HEXTABLE[v>>4]
@@ -15,12 +15,12 @@ encode :: proc(src: []byte, allocator := context.allocator) -> []byte #no_bounds
 }
 
 
-decode :: proc(src: []byte, allocator := context.allocator) -> (dst: []byte, ok: bool) #no_bounds_check {
+decode :: proc(src: []byte, allocator := context.allocator, loc := #caller_location) -> (dst: []byte, ok: bool) #no_bounds_check {
 	if len(src) % 2 == 1 {
 		return
 	}
 
-	dst = make([]byte, len(src) / 2, allocator)
+	dst = make([]byte, len(src) / 2, allocator, loc)
 	for i, j := 0, 1; j < len(src); j += 2 {
 		p := src[j-1]
 		q := src[j]
@@ -69,5 +69,4 @@ hex_digit :: proc(char: byte) -> (u8, bool) {
 	case 'A' ..= 'F': return char - 'A' + 10, true
 	case:             return 0, false
 	}
-}
-
+}

+ 16 - 15
core/encoding/hxa/hxa.odin

@@ -160,34 +160,35 @@ CONVENTION_SOFT_TRANSFORM :: "transform"
 
 /* destroy procedures */
 
-meta_destroy :: proc(meta: Meta, allocator := context.allocator) {
+meta_destroy :: proc(meta: Meta, allocator := context.allocator, loc := #caller_location) {
 	if nested, ok := meta.value.([]Meta); ok {
 		for m in nested {
-			meta_destroy(m)
+			meta_destroy(m, loc=loc)
 		}
-		delete(nested, allocator)
+		delete(nested, allocator, loc=loc)
 	}
 }
-nodes_destroy :: proc(nodes: []Node, allocator := context.allocator) {
+nodes_destroy :: proc(nodes: []Node, allocator := context.allocator, loc := #caller_location) {
 	for node in nodes {
 		for meta in node.meta_data {
-			meta_destroy(meta)
+			meta_destroy(meta, loc=loc)
 		}
-		delete(node.meta_data, allocator)
+		delete(node.meta_data, allocator, loc=loc)
 
 		switch n in node.content {
 		case Node_Geometry:
-			delete(n.corner_stack, allocator)
-			delete(n.edge_stack, allocator)
-			delete(n.face_stack, allocator)
+			delete(n.corner_stack, allocator, loc=loc)
+			delete(n.vertex_stack, allocator, loc=loc)
+			delete(n.edge_stack,   allocator, loc=loc)
+			delete(n.face_stack,   allocator, loc=loc)
 		case Node_Image:
-			delete(n.image_stack, allocator)
+			delete(n.image_stack,  allocator, loc=loc)
 		}
 	}
-	delete(nodes, allocator)
+	delete(nodes, allocator, loc=loc)
 }
 
-file_destroy :: proc(file: File) {
-	nodes_destroy(file.nodes, file.allocator)
-	delete(file.backing, file.allocator)
-}
+file_destroy :: proc(file: File, loc := #caller_location) {
+	nodes_destroy(file.nodes, file.allocator, loc=loc)
+	delete(file.backing, file.allocator, loc=loc)
+}

+ 20 - 22
core/encoding/hxa/read.odin

@@ -11,24 +11,21 @@ Read_Error :: enum {
 	Unable_To_Read_File,
 }
 
-read_from_file :: proc(filename: string, print_error := false, allocator := context.allocator) -> (file: File, err: Read_Error) {
+read_from_file :: proc(filename: string, print_error := false, allocator := context.allocator, loc := #caller_location) -> (file: File, err: Read_Error) {
 	context.allocator = allocator
 
-	data, ok := os.read_entire_file(filename)
+	data, ok := os.read_entire_file(filename, allocator, loc)
 	if !ok {
 		err = .Unable_To_Read_File
+		delete(data, allocator, loc)
 		return
 	}
-	defer if !ok {
-		delete(data)
-	} else {
-		file.backing = data
-	}
-	file, err = read(data, filename, print_error, allocator)
+	file, err = read(data, filename, print_error, allocator, loc)
+	file.backing   = data
 	return
 }
 
-read :: proc(data: []byte, filename := "<input>", print_error := false, allocator := context.allocator) -> (file: File, err: Read_Error) {
+read :: proc(data: []byte, filename := "<input>", print_error := false, allocator := context.allocator, loc := #caller_location) -> (file: File, err: Read_Error) {
 	Reader :: struct {
 		filename:    string,
 		data:        []byte,
@@ -79,8 +76,8 @@ read :: proc(data: []byte, filename := "<input>", print_error := false, allocato
 		return string(data[:len]), nil
 	}
 
-	read_meta :: proc(r: ^Reader, capacity: u32le) -> (meta_data: []Meta, err: Read_Error) {
-		meta_data = make([]Meta, int(capacity))
+	read_meta :: proc(r: ^Reader, capacity: u32le, allocator := context.allocator, loc := #caller_location) -> (meta_data: []Meta, err: Read_Error) {
+		meta_data = make([]Meta, int(capacity), allocator=allocator)
 		count := 0
 		defer meta_data = meta_data[:count]
 		for &m in meta_data {
@@ -111,10 +108,10 @@ read :: proc(data: []byte, filename := "<input>", print_error := false, allocato
 		return
 	}
 
-	read_layer_stack :: proc(r: ^Reader, capacity: u32le) -> (layers: Layer_Stack, err: Read_Error) {
+	read_layer_stack :: proc(r: ^Reader, capacity: u32le, allocator := context.allocator, loc := #caller_location) -> (layers: Layer_Stack, err: Read_Error) {
 		stack_count := read_value(r, u32le) or_return
 		layer_count := 0
-		layers = make(Layer_Stack, stack_count)
+		layers = make(Layer_Stack, stack_count, allocator=allocator, loc=loc)
 		defer layers = layers[:layer_count]
 		for &layer in layers {
 			layer.name = read_name(r) or_return
@@ -170,7 +167,8 @@ read :: proc(data: []byte, filename := "<input>", print_error := false, allocato
 
 	node_count := 0
 	file.header = header^
-	file.nodes = make([]Node, header.internal_node_count)
+	file.nodes = make([]Node, header.internal_node_count, allocator=allocator, loc=loc)
+	file.allocator = allocator
 	defer if err != nil {
 		nodes_destroy(file.nodes)
 		file.nodes = nil
@@ -198,15 +196,15 @@ read :: proc(data: []byte, filename := "<input>", print_error := false, allocato
 		case .Geometry:
 			g: Node_Geometry
 
-			g.vertex_count      = read_value(r, u32le)                     or_return
-			g.vertex_stack      = read_layer_stack(r, g.vertex_count)      or_return
-			g.edge_corner_count = read_value(r, u32le)                     or_return
-			g.corner_stack      = read_layer_stack(r, g.edge_corner_count) or_return
+			g.vertex_count      = read_value(r, u32le)                               or_return
+			g.vertex_stack      = read_layer_stack(r, g.vertex_count, loc=loc)       or_return
+			g.edge_corner_count = read_value(r, u32le)                               or_return
+			g.corner_stack      = read_layer_stack(r, g.edge_corner_count, loc=loc)  or_return
 			if header.version > 2 {
-				g.edge_stack = read_layer_stack(r, g.edge_corner_count) or_return
+				g.edge_stack = read_layer_stack(r, g.edge_corner_count, loc=loc) or_return
 			}
-			g.face_count = read_value(r, u32le)              or_return
-			g.face_stack = read_layer_stack(r, g.face_count) or_return
+			g.face_count = read_value(r, u32le)                       or_return
+			g.face_stack = read_layer_stack(r, g.face_count, loc=loc) or_return
 
 			node.content = g
 
@@ -233,4 +231,4 @@ read :: proc(data: []byte, filename := "<input>", print_error := false, allocato
 	}
 
 	return
-}
+}

+ 2 - 2
core/encoding/json/marshal.odin

@@ -62,8 +62,8 @@ Marshal_Options :: struct {
 	mjson_skipped_first_braces_end: bool,
 }
 
-marshal :: proc(v: any, opt: Marshal_Options = {}, allocator := context.allocator) -> (data: []byte, err: Marshal_Error) {
-	b := strings.builder_make(allocator)
+marshal :: proc(v: any, opt: Marshal_Options = {}, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: Marshal_Error) {
+	b := strings.builder_make(allocator, loc)
 	defer if err != nil {
 		strings.builder_destroy(&b)
 	}

+ 33 - 32
core/encoding/json/parser.odin

@@ -28,27 +28,27 @@ make_parser_from_string :: proc(data: string, spec := DEFAULT_SPECIFICATION, par
 }
 
 
-parse :: proc(data: []byte, spec := DEFAULT_SPECIFICATION, parse_integers := false, allocator := context.allocator) -> (Value, Error) {
-	return parse_string(string(data), spec, parse_integers, allocator)
+parse :: proc(data: []byte, spec := DEFAULT_SPECIFICATION, parse_integers := false, allocator := context.allocator, loc := #caller_location) -> (Value, Error) {
+	return parse_string(string(data), spec, parse_integers, allocator, loc)
 }
 
-parse_string :: proc(data: string, spec := DEFAULT_SPECIFICATION, parse_integers := false, allocator := context.allocator) -> (Value, Error) {
+parse_string :: proc(data: string, spec := DEFAULT_SPECIFICATION, parse_integers := false, allocator := context.allocator, loc := #caller_location) -> (Value, Error) {
 	context.allocator = allocator
 	p := make_parser_from_string(data, spec, parse_integers, allocator)
 
 	switch p.spec {
 	case .JSON:
-		return parse_object(&p)
+		return parse_object(&p, loc)
 	case .JSON5:
-		return parse_value(&p)
+		return parse_value(&p, loc)
 	case .SJSON:
 		#partial switch p.curr_token.kind {
 		case .Ident, .String:
-			return parse_object_body(&p, .EOF)
+			return parse_object_body(&p, .EOF, loc)
 		}
-		return parse_value(&p)
+		return parse_value(&p, loc)
 	}
-	return parse_object(&p)
+	return parse_object(&p, loc)
 }
 
 token_end_pos :: proc(tok: Token) -> Pos {
@@ -106,7 +106,7 @@ parse_comma :: proc(p: ^Parser) -> (do_break: bool) {
 	return false
 }
 
-parse_value :: proc(p: ^Parser) -> (value: Value, err: Error) {
+parse_value :: proc(p: ^Parser, loc := #caller_location) -> (value: Value, err: Error) {
 	err = .None
 	token := p.curr_token
 	#partial switch token.kind {
@@ -142,13 +142,13 @@ parse_value :: proc(p: ^Parser) -> (value: Value, err: Error) {
 		
 	case .String:
 		advance_token(p)
-		return unquote_string(token, p.spec, p.allocator)
+		return unquote_string(token, p.spec, p.allocator, loc)
 
 	case .Open_Brace:
-		return parse_object(p)
+		return parse_object(p, loc)
 
 	case .Open_Bracket:
-		return parse_array(p)
+		return parse_array(p, loc)
 
 	case:
 		if p.spec != .JSON {
@@ -176,7 +176,7 @@ parse_value :: proc(p: ^Parser) -> (value: Value, err: Error) {
 	return
 }
 
-parse_array :: proc(p: ^Parser) -> (value: Value, err: Error) {
+parse_array :: proc(p: ^Parser, loc := #caller_location) -> (value: Value, err: Error) {
 	err = .None
 	expect_token(p, .Open_Bracket) or_return
 
@@ -184,14 +184,14 @@ parse_array :: proc(p: ^Parser) -> (value: Value, err: Error) {
 	array.allocator = p.allocator
 	defer if err != nil {
 		for elem in array {
-			destroy_value(elem)
+			destroy_value(elem, loc=loc)
 		}
-		delete(array)
+		delete(array, loc)
 	}
 
 	for p.curr_token.kind != .Close_Bracket {
-		elem := parse_value(p) or_return
-		append(&array, elem)
+		elem := parse_value(p, loc) or_return
+		append(&array, elem, loc)
 		
 		if parse_comma(p) {
 			break
@@ -228,38 +228,39 @@ clone_string :: proc(s: string, allocator: mem.Allocator, loc := #caller_locatio
 	return
 }
 
-parse_object_key :: proc(p: ^Parser, key_allocator: mem.Allocator) -> (key: string, err: Error) {
+parse_object_key :: proc(p: ^Parser, key_allocator: mem.Allocator, loc := #caller_location) -> (key: string, err: Error) {
 	tok := p.curr_token
 	if p.spec != .JSON {
 		if allow_token(p, .Ident) {
-			return clone_string(tok.text, key_allocator)
+			return clone_string(tok.text, key_allocator, loc)
 		}
 	}
 	if tok_err := expect_token(p, .String); tok_err != nil {
 		err = .Expected_String_For_Object_Key
 		return
 	}
-	return unquote_string(tok, p.spec, key_allocator)
+	return unquote_string(tok, p.spec, key_allocator, loc)
 }
 
-parse_object_body :: proc(p: ^Parser, end_token: Token_Kind) -> (obj: Object, err: Error) {
-	obj.allocator = p.allocator
+parse_object_body :: proc(p: ^Parser, end_token: Token_Kind, loc := #caller_location) -> (obj: Object, err: Error) {
+	obj = make(Object, allocator=p.allocator, loc=loc)
+
 	defer if err != nil {
 		for key, elem in obj {
-			delete(key, p.allocator)
-			destroy_value(elem)
+			delete(key, p.allocator, loc)
+			destroy_value(elem, loc=loc)
 		}
-		delete(obj)
+		delete(obj, loc)
 	}
 
 	for p.curr_token.kind != end_token {
-		key := parse_object_key(p, p.allocator) or_return
+		key := parse_object_key(p, p.allocator, loc) or_return
 		parse_colon(p) or_return
-		elem := parse_value(p) or_return
+		elem := parse_value(p, loc) or_return
 
 		if key in obj {
 			err = .Duplicate_Object_Key
-			delete(key, p.allocator)
+			delete(key, p.allocator, loc)
 			return
 		}
 
@@ -267,7 +268,7 @@ parse_object_body :: proc(p: ^Parser, end_token: Token_Kind) -> (obj: Object, er
 		// inserting empty key/values into the object and for those we do not
 		// want to allocate anything
 		if key != "" {
-			reserve_error := reserve(&obj, len(obj) + 1)
+			reserve_error := reserve(&obj, len(obj) + 1, loc)
 			if reserve_error == mem.Allocator_Error.Out_Of_Memory {
 				return nil, .Out_Of_Memory
 			}
@@ -281,9 +282,9 @@ parse_object_body :: proc(p: ^Parser, end_token: Token_Kind) -> (obj: Object, er
 	return obj, .None
 }
 
-parse_object :: proc(p: ^Parser) -> (value: Value, err: Error) {
+parse_object :: proc(p: ^Parser, loc := #caller_location) -> (value: Value, err: Error) {
 	expect_token(p, .Open_Brace) or_return
-	obj := parse_object_body(p, .Close_Brace) or_return
+	obj := parse_object_body(p, .Close_Brace, loc) or_return
 	expect_token(p, .Close_Brace) or_return
 	return obj, .None
 }
@@ -480,4 +481,4 @@ unquote_string :: proc(token: Token, spec: Specification, allocator := context.a
 	}
 
 	return string(b[:w]), nil
-}
+}

+ 7 - 7
core/encoding/json/types.odin

@@ -89,22 +89,22 @@ Error :: enum {
 
 
 
-destroy_value :: proc(value: Value, allocator := context.allocator) {
+destroy_value :: proc(value: Value, allocator := context.allocator, loc := #caller_location) {
 	context.allocator = allocator
 	#partial switch v in value {
 	case Object:
 		for key, elem in v {
-			delete(key)
-			destroy_value(elem)
+			delete(key, loc=loc)
+			destroy_value(elem, loc=loc)
 		}
-		delete(v)
+		delete(v, loc=loc)
 	case Array:
 		for elem in v {
-			destroy_value(elem)
+			destroy_value(elem, loc=loc)
 		}
-		delete(v)
+		delete(v, loc=loc)
 	case String:
-		delete(v)
+		delete(v, loc=loc)
 	}
 }
 

+ 26 - 15
core/log/file_console_logger.odin

@@ -1,6 +1,7 @@
 //+build !freestanding
 package log
 
+import "core:encoding/ansi"
 import "core:fmt"
 import "core:strings"
 import "core:os"
@@ -70,18 +71,10 @@ file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string
 	backing: [1024]byte //NOTE(Hoej): 1024 might be too much for a header backing, unless somebody has really long paths.
 	buf := strings.builder_from_bytes(backing[:])
 
-	do_level_header(options, level, &buf)
+	do_level_header(options, &buf, level)
 
 	when time.IS_SUPPORTED {
-		if Full_Timestamp_Opts & options != nil {
-			fmt.sbprint(&buf, "[")
-			t := time.now()
-			y, m, d := time.date(t)
-			h, min, s := time.clock(t)
-			if .Date in options { fmt.sbprintf(&buf, "%d-%02d-%02d ", y, m, d)    }
-			if .Time in options { fmt.sbprintf(&buf, "%02d:%02d:%02d", h, min, s) }
-			fmt.sbprint(&buf, "] ")
-		}
+		do_time_header(options, &buf, time.now())
 	}
 
 	do_location_header(options, &buf, location)
@@ -99,12 +92,12 @@ file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string
 	fmt.fprintf(h, "%s%s\n", strings.to_string(buf), text)
 }
 
-do_level_header :: proc(opts: Options, level: Level, str: ^strings.Builder) {
+do_level_header :: proc(opts: Options, str: ^strings.Builder, level: Level) {
 
-	RESET     :: "\x1b[0m"
-	RED       :: "\x1b[31m"
-	YELLOW    :: "\x1b[33m"
-	DARK_GREY :: "\x1b[90m"
+	RESET     :: ansi.CSI + ansi.RESET           + ansi.SGR
+	RED       :: ansi.CSI + ansi.FG_RED          + ansi.SGR
+	YELLOW    :: ansi.CSI + ansi.FG_YELLOW       + ansi.SGR
+	DARK_GREY :: ansi.CSI + ansi.FG_BRIGHT_BLACK + ansi.SGR
 
 	col := RESET
 	switch level {
@@ -125,6 +118,24 @@ do_level_header :: proc(opts: Options, level: Level, str: ^strings.Builder) {
 	}
 }
 
+do_time_header :: proc(opts: Options, buf: ^strings.Builder, t: time.Time) {
+	when time.IS_SUPPORTED {
+		if Full_Timestamp_Opts & opts != nil {
+			fmt.sbprint(buf, "[")
+			y, m, d := time.date(t)
+			h, min, s := time.clock(t)
+			if .Date in opts {
+				fmt.sbprintf(buf, "%d-%02d-%02d", y, m, d)
+				if .Time in opts {
+					fmt.sbprint(buf, " ")
+				}
+			}
+			if .Time in opts { fmt.sbprintf(buf, "%02d:%02d:%02d", h, min, s) }
+			fmt.sbprint(buf, "] ")
+		}
+	}
+}
+
 do_location_header :: proc(opts: Options, buf: ^strings.Builder, location := #caller_location) {
 	if Location_Header_Opts & opts == nil {
 		return

+ 341 - 0
core/mem/rollback_stack_allocator.odin

@@ -0,0 +1,341 @@
+package mem
+
+// The Rollback Stack Allocator was designed for the test runner to be fast,
+// able to grow, and respect the Tracking Allocator's requirement for
+// individual frees. It is not overly concerned with fragmentation, however.
+//
+// It has support for expansion when configured with a block allocator and
+// limited support for out-of-order frees.
+//
+// Allocation has constant-time best and usual case performance.
+// At worst, it is linear according to the number of memory blocks.
+//
+// Allocation follows a first-fit strategy when there are multiple memory
+// blocks.
+//
+// Freeing has constant-time best and usual case performance.
+// At worst, it is linear according to the number of memory blocks and number
+// of freed items preceding the last item in a block.
+//
+// Resizing has constant-time performance, if it's the last item in a block, or
+// the new size is smaller. Naturally, this becomes linear-time if there are
+// multiple blocks to search for the pointer's owning block. Otherwise, the
+// allocator defaults to a combined alloc & free operation internally.
+//
+// Out-of-order freeing is accomplished by collapsing a run of freed items
+// from the last allocation backwards.
+//
+// Each allocation has an overhead of 8 bytes and any extra bytes to satisfy
+// the requested alignment.
+
+import "base:runtime"
+
+ROLLBACK_STACK_DEFAULT_BLOCK_SIZE :: 4 * Megabyte
+
+// This limitation is due to the size of `prev_ptr`, but it is only for the
+// head block; any allocation in excess of the allocator's `block_size` is
+// valid, so long as the block allocator can handle it.
+//
+// This is because allocations over the block size are not split up if the item
+// within is freed; they are immediately returned to the block allocator.
+ROLLBACK_STACK_MAX_HEAD_BLOCK_SIZE :: 2 * Gigabyte
+
+
+Rollback_Stack_Header :: bit_field u64 {
+	prev_offset:  uintptr | 32,
+	is_free:         bool |  1,
+	prev_ptr:     uintptr | 31,
+}
+
+Rollback_Stack_Block :: struct {
+	next_block: ^Rollback_Stack_Block,
+	last_alloc: rawptr,
+	offset: uintptr,
+	buffer: []byte,
+}
+
+Rollback_Stack :: struct {
+	head: ^Rollback_Stack_Block,
+	block_size: int,
+	block_allocator: Allocator,
+}
+
+
+@(private="file", require_results)
+rb_ptr_in_bounds :: proc(block: ^Rollback_Stack_Block, ptr: rawptr) -> bool {
+	start := raw_data(block.buffer)
+	end   := start[block.offset:]
+	return start < ptr && ptr <= end
+}
+
+@(private="file", require_results)
+rb_find_ptr :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> (
+	parent: ^Rollback_Stack_Block,
+	block:  ^Rollback_Stack_Block,
+	header: ^Rollback_Stack_Header,
+	err: Allocator_Error,
+) {
+	for block = stack.head; block != nil; block = block.next_block {
+		if rb_ptr_in_bounds(block, ptr) {
+			header = cast(^Rollback_Stack_Header)(cast(uintptr)ptr - size_of(Rollback_Stack_Header))
+			return
+		}
+		parent = block
+	}
+	return nil, nil, nil, .Invalid_Pointer
+}
+
+@(private="file", require_results)
+rb_find_last_alloc :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> (
+	block: ^Rollback_Stack_Block,
+	header: ^Rollback_Stack_Header,
+	ok: bool,
+) {
+	for block = stack.head; block != nil; block = block.next_block {
+		if block.last_alloc == ptr {
+			header = cast(^Rollback_Stack_Header)(cast(uintptr)ptr - size_of(Rollback_Stack_Header))
+			return block, header, true
+		}
+	}
+	return nil, nil, false
+}
+
+@(private="file")
+rb_rollback_block :: proc(block: ^Rollback_Stack_Block, header: ^Rollback_Stack_Header) {
+	header := header
+	for block.offset > 0 && header.is_free {
+		block.offset = header.prev_offset
+		block.last_alloc = raw_data(block.buffer)[header.prev_ptr:]
+		header = cast(^Rollback_Stack_Header)(raw_data(block.buffer)[header.prev_ptr - size_of(Rollback_Stack_Header):])
+	}
+}
+
+@(private="file", require_results)
+rb_free :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> Allocator_Error {
+	parent, block, header := rb_find_ptr(stack, ptr) or_return
+	if header.is_free {
+		return .Invalid_Pointer
+	}
+	header.is_free = true
+	if block.last_alloc == ptr {
+		block.offset = header.prev_offset
+		rb_rollback_block(block, header)
+	}
+	if parent != nil && block.offset == 0 {
+		parent.next_block = block.next_block
+		runtime.mem_free_with_size(block, size_of(Rollback_Stack_Block) + len(block.buffer), stack.block_allocator)
+	}
+	return nil
+}
+
+@(private="file")
+rb_free_all :: proc(stack: ^Rollback_Stack) {
+	for block := stack.head.next_block; block != nil; /**/ {
+		next_block := block.next_block
+		runtime.mem_free_with_size(block, size_of(Rollback_Stack_Block) + len(block.buffer), stack.block_allocator)
+		block = next_block
+	}
+
+	stack.head.next_block = nil
+	stack.head.last_alloc = nil
+	stack.head.offset = 0
+}
+
+@(private="file", require_results)
+rb_resize :: proc(stack: ^Rollback_Stack, ptr: rawptr, old_size, size, alignment: int) -> (result: []byte, err: Allocator_Error) {
+	if ptr != nil {
+		if block, _, ok := rb_find_last_alloc(stack, ptr); ok {
+			// `block.offset` should never underflow because it is contingent
+			// on `old_size` in the first place, assuming sane arguments.
+			assert(block.offset >= cast(uintptr)old_size, "Rollback Stack Allocator received invalid `old_size`.")
+
+			if block.offset + cast(uintptr)size - cast(uintptr)old_size < cast(uintptr)len(block.buffer) {
+				// Prevent singleton allocations from fragmenting by forbidding
+				// them to shrink, removing the possibility of overflow bugs.
+				if len(block.buffer) <= stack.block_size {
+					block.offset += cast(uintptr)size - cast(uintptr)old_size
+				}
+				#no_bounds_check return (cast([^]byte)ptr)[:size], nil
+			}
+		}
+	}
+
+	result = rb_alloc(stack, size, alignment) or_return
+	runtime.mem_copy_non_overlapping(raw_data(result), ptr, old_size)
+	err = rb_free(stack, ptr)
+
+	return
+}
+
+@(private="file", require_results)
+rb_alloc :: proc(stack: ^Rollback_Stack, size, alignment: int) -> (result: []byte, err: Allocator_Error) {
+	parent: ^Rollback_Stack_Block
+	for block := stack.head; /**/; block = block.next_block {
+		when !ODIN_DISABLE_ASSERT {
+			allocated_new_block: bool
+		}
+
+		if block == nil {
+			if stack.block_allocator.procedure == nil {
+				return nil, .Out_Of_Memory
+			}
+
+			minimum_size_required := size_of(Rollback_Stack_Header) + size + alignment - 1
+			new_block_size := max(minimum_size_required, stack.block_size)
+			block = rb_make_block(new_block_size, stack.block_allocator) or_return
+			parent.next_block = block
+			when !ODIN_DISABLE_ASSERT {
+				allocated_new_block = true
+			}
+		}
+
+		start := raw_data(block.buffer)[block.offset:]
+		padding := cast(uintptr)calc_padding_with_header(cast(uintptr)start, cast(uintptr)alignment, size_of(Rollback_Stack_Header))
+
+		if block.offset + padding + cast(uintptr)size > cast(uintptr)len(block.buffer) {
+			when !ODIN_DISABLE_ASSERT {
+				if allocated_new_block {
+					panic("Rollback Stack Allocator allocated a new block but did not use it.")
+				}
+			}
+			parent = block
+			continue
+		}
+
+		header := cast(^Rollback_Stack_Header)(start[padding - size_of(Rollback_Stack_Header):])
+		ptr := start[padding:]
+
+		header^ = {
+			prev_offset = block.offset,
+			prev_ptr = uintptr(0) if block.last_alloc == nil else cast(uintptr)block.last_alloc - cast(uintptr)raw_data(block.buffer),
+			is_free = false,
+		}
+
+		block.last_alloc = ptr
+		block.offset += padding + cast(uintptr)size
+
+		if len(block.buffer) > stack.block_size {
+			// This block exceeds the allocator's standard block size and is considered a singleton.
+			// Prevent any further allocations on it.
+			block.offset = cast(uintptr)len(block.buffer)
+		}
+		
+		#no_bounds_check return ptr[:size], nil
+	}
+
+	return nil, .Out_Of_Memory
+}
+
+@(private="file", require_results)
+rb_make_block :: proc(size: int, allocator: Allocator) -> (block: ^Rollback_Stack_Block, err: Allocator_Error) {
+	buffer := runtime.mem_alloc(size_of(Rollback_Stack_Block) + size, align_of(Rollback_Stack_Block), allocator) or_return
+
+	block = cast(^Rollback_Stack_Block)raw_data(buffer)
+	#no_bounds_check block.buffer = buffer[size_of(Rollback_Stack_Block):]
+	return
+}
+
+
+rollback_stack_init_buffered :: proc(stack: ^Rollback_Stack, buffer: []byte, location := #caller_location) {
+	MIN_SIZE :: size_of(Rollback_Stack_Block) + size_of(Rollback_Stack_Header) + size_of(rawptr)
+	assert(len(buffer) >= MIN_SIZE, "User-provided buffer to Rollback Stack Allocator is too small.", location)
+
+	block := cast(^Rollback_Stack_Block)raw_data(buffer)
+	block^ = {}
+	#no_bounds_check block.buffer = buffer[size_of(Rollback_Stack_Block):]
+
+	stack^ = {}
+	stack.head = block
+	stack.block_size = len(block.buffer)
+}
+
+rollback_stack_init_dynamic :: proc(
+	stack: ^Rollback_Stack,
+	block_size : int = ROLLBACK_STACK_DEFAULT_BLOCK_SIZE,
+	block_allocator := context.allocator,
+	location := #caller_location,
+) -> Allocator_Error {
+	assert(block_size >= size_of(Rollback_Stack_Header) + size_of(rawptr), "Rollback Stack Allocator block size is too small.", location)
+	when size_of(int) > 4 {
+		// It's impossible to specify an argument in excess when your integer
+		// size is insufficient; check only on platforms with big enough ints.
+		assert(block_size <= ROLLBACK_STACK_MAX_HEAD_BLOCK_SIZE, "Rollback Stack Allocators cannot support head blocks larger than 2 gigabytes.", location)
+	}
+
+	block := rb_make_block(block_size, block_allocator) or_return
+
+	stack^ = {}
+	stack.head = block
+	stack.block_size = block_size
+	stack.block_allocator = block_allocator
+
+	return nil
+}
+
+rollback_stack_init :: proc {
+	rollback_stack_init_buffered,
+	rollback_stack_init_dynamic,
+}
+
+rollback_stack_destroy :: proc(stack: ^Rollback_Stack) {
+	if stack.block_allocator.procedure != nil {
+		rb_free_all(stack)
+		free(stack.head, stack.block_allocator)
+	}
+	stack^ = {}
+}
+
+@(require_results)
+rollback_stack_allocator :: proc(stack: ^Rollback_Stack) -> Allocator {
+	return Allocator {
+		data = stack,
+		procedure = rollback_stack_allocator_proc,
+	}
+}
+
+@(require_results)
+rollback_stack_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
+                                      size, alignment: int,
+                                      old_memory: rawptr, old_size: int, location := #caller_location,
+) -> (result: []byte, err: Allocator_Error) {
+	stack := cast(^Rollback_Stack)allocator_data
+
+	switch mode {
+	case .Alloc, .Alloc_Non_Zeroed:
+		assert(size >= 0, "Size must be positive or zero.", location)
+		assert(is_power_of_two(cast(uintptr)alignment), "Alignment must be a power of two.", location)
+		result = rb_alloc(stack, size, alignment) or_return
+
+		if mode == .Alloc {
+			zero_slice(result)
+		}
+
+	case .Free:
+		err = rb_free(stack, old_memory)
+
+	case .Free_All:
+		rb_free_all(stack)
+
+	case .Resize, .Resize_Non_Zeroed:
+		assert(size >= 0, "Size must be positive or zero.", location)
+		assert(old_size >= 0, "Old size must be positive or zero.", location)
+		assert(is_power_of_two(cast(uintptr)alignment), "Alignment must be a power of two.", location)
+		result = rb_resize(stack, old_memory, old_size, size, alignment) or_return
+
+		#no_bounds_check if mode == .Resize && size > old_size {
+			zero_slice(result[old_size:])
+		}
+
+	case .Query_Features:
+		set := (^Allocator_Mode_Set)(old_memory)
+		if set != nil {
+			set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Free_All, .Resize, .Resize_Non_Zeroed}
+		}
+		return nil, nil
+
+	case .Query_Info:
+		return nil, .Mode_Not_Implemented
+	}
+
+	return
+}

+ 14 - 0
core/mem/tracking_allocator.odin

@@ -47,6 +47,7 @@ tracking_allocator_destroy :: proc(t: ^Tracking_Allocator) {
 }
 
 
+// Clear only the current allocation data while keeping the totals intact.
 tracking_allocator_clear :: proc(t: ^Tracking_Allocator) {
 	sync.mutex_lock(&t.mutex)
 	clear(&t.allocation_map)
@@ -55,6 +56,19 @@ tracking_allocator_clear :: proc(t: ^Tracking_Allocator) {
 	sync.mutex_unlock(&t.mutex)
 }
 
+// Reset all of a Tracking Allocator's allocation data back to zero.
+tracking_allocator_reset :: proc(t: ^Tracking_Allocator) {
+	sync.mutex_lock(&t.mutex)
+	clear(&t.allocation_map)
+	clear(&t.bad_free_array)
+	t.total_memory_allocated = 0
+	t.total_allocation_count = 0
+	t.total_memory_freed = 0
+	t.total_free_count = 0
+	t.peak_memory_allocated = 0
+	t.current_memory_allocated = 0
+	sync.mutex_unlock(&t.mutex)
+}
 
 @(require_results)
 tracking_allocator :: proc(data: ^Tracking_Allocator) -> Allocator {

+ 1 - 1
core/os/os_darwin.odin

@@ -442,7 +442,7 @@ F_GETPATH :: 50 // return the full path of the fd
 foreign libc {
 	@(link_name="__error") __error :: proc() -> ^c.int ---
 
-	@(link_name="open")             _unix_open          :: proc(path: cstring, flags: i32, mode: u16) -> Handle ---
+	@(link_name="open")             _unix_open          :: proc(path: cstring, flags: i32, #c_vararg args: ..any) -> Handle ---
 	@(link_name="close")            _unix_close         :: proc(handle: Handle) -> c.int ---
 	@(link_name="read")             _unix_read          :: proc(handle: Handle, buffer: rawptr, count: c.size_t) -> int ---
 	@(link_name="write")            _unix_write         :: proc(handle: Handle, buffer: rawptr, count: c.size_t) -> int ---

+ 4 - 4
core/strings/builder.odin

@@ -350,9 +350,9 @@ Output:
 	ab
 
 */
-write_byte :: proc(b: ^Builder, x: byte) -> (n: int) {
+write_byte :: proc(b: ^Builder, x: byte, loc := #caller_location) -> (n: int) {
 	n0 := len(b.buf)
-	append(&b.buf, x)
+	append(&b.buf, x, loc)
 	n1 := len(b.buf)
 	return n1-n0
 }
@@ -380,9 +380,9 @@ NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n`
 Returns:
 - n: The number of bytes appended
 */
-write_bytes :: proc(b: ^Builder, x: []byte) -> (n: int) {
+write_bytes :: proc(b: ^Builder, x: []byte, loc := #caller_location) -> (n: int) {
 	n0 := len(b.buf)
-	append(&b.buf, ..x)
+	append(&b.buf, ..x, loc=loc)
 	n1 := len(b.buf)
 	return n1-n0
 }

+ 2 - 2
core/sys/unix/pthread_freebsd.odin

@@ -95,7 +95,7 @@ sem_t :: struct {
 PTHREAD_CANCEL_ENABLE       :: 0
 PTHREAD_CANCEL_DISABLE      :: 1
 PTHREAD_CANCEL_DEFERRED     :: 0
-PTHREAD_CANCEL_ASYNCHRONOUS :: 1
+PTHREAD_CANCEL_ASYNCHRONOUS :: 2
 
 foreign import "system:pthread"
 
@@ -119,4 +119,4 @@ foreign pthread {
 	pthread_setcancelstate :: proc (state: c.int, old_state: ^c.int) -> c.int ---
 	pthread_setcanceltype  :: proc (type:  c.int, old_type:  ^c.int) -> c.int ---
 	pthread_cancel         :: proc (thread: pthread_t) -> c.int ---
-}
+}

+ 2 - 2
core/sys/unix/pthread_openbsd.odin

@@ -49,7 +49,7 @@ sem_t :: distinct rawptr
 PTHREAD_CANCEL_ENABLE       :: 0
 PTHREAD_CANCEL_DISABLE      :: 1
 PTHREAD_CANCEL_DEFERRED     :: 0
-PTHREAD_CANCEL_ASYNCHRONOUS :: 1
+PTHREAD_CANCEL_ASYNCHRONOUS :: 2
 
 foreign import libc "system:c"
 
@@ -71,4 +71,4 @@ foreign libc {
 	pthread_setcancelstate :: proc (state: c.int, old_state: ^c.int) -> c.int ---
 	pthread_setcanceltype  :: proc (type:  c.int, old_type:  ^c.int) -> c.int ---
 	pthread_cancel         :: proc (thread: pthread_t) -> c.int ---
-}
+}

+ 1 - 0
core/sys/unix/pthread_unix.odin

@@ -116,4 +116,5 @@ foreign pthread {
 	pthread_mutexattr_setpshared :: proc(attrs: ^pthread_mutexattr_t, value: c.int) -> c.int ---
 	pthread_mutexattr_getpshared :: proc(attrs: ^pthread_mutexattr_t, result: ^c.int) -> c.int ---
 
+	pthread_testcancel :: proc () ---
 }

+ 48 - 0
core/testing/events.odin

@@ -0,0 +1,48 @@
+//+private
+package testing
+
+import "base:runtime"
+import "core:sync/chan"
+import "core:time"
+
+Test_State :: enum {
+	Ready,
+	Running,
+	Successful,
+	Failed,
+}
+
+Update_Channel :: chan.Chan(Channel_Event)
+Update_Channel_Sender :: chan.Chan(Channel_Event, .Send)
+
+Task_Channel :: struct {
+	channel: Update_Channel,
+	test_index: int,
+}
+
+Event_New_Test :: struct {
+	test_index: int,
+}
+
+Event_State_Change :: struct {
+	new_state: Test_State,
+}
+
+Event_Set_Fail_Timeout :: struct {
+	at_time: time.Time,
+	location: runtime.Source_Code_Location,
+}
+
+Event_Log_Message :: struct {
+	level: runtime.Logger_Level,
+	text: string,
+	time: time.Time,
+	formatted_text: string,
+}
+
+Channel_Event :: union {
+	Event_New_Test,
+	Event_State_Change,
+	Event_Set_Fail_Timeout,
+	Event_Log_Message,
+}

+ 71 - 0
core/testing/logging.odin

@@ -0,0 +1,71 @@
+//+private
+package testing
+
+import "base:runtime"
+import "core:fmt"
+import pkg_log "core:log"
+import "core:strings"
+import "core:sync/chan"
+import "core:time"
+
+Default_Test_Logger_Opts :: runtime.Logger_Options {
+	.Level,
+	.Terminal_Color,
+	.Short_File_Path,
+	.Line,
+	.Procedure,
+	.Date, .Time,
+}
+
+Log_Message :: struct {
+	level: runtime.Logger_Level,
+	text: string,
+	time: time.Time,
+	// `text` may be allocated differently, depending on where a log message
+	// originates from.
+	allocator: runtime.Allocator,
+}
+
+test_logger_proc :: proc(logger_data: rawptr, level: runtime.Logger_Level, text: string, options: runtime.Logger_Options, location := #caller_location) {
+	t := cast(^T)logger_data
+
+	if level >= .Error {
+		t.error_count += 1
+	}
+
+	cloned_text, clone_error := strings.clone(text, t._log_allocator)
+	assert(clone_error == nil, "Error while cloning string in test thread logger proc.")
+
+	now := time.now()
+
+	chan.send(t.channel, Event_Log_Message {
+		level = level,
+		text = cloned_text,
+		time = now,
+		formatted_text = format_log_text(level, text, options, location, now, t._log_allocator),
+	})
+}
+
+runner_logger_proc :: proc(logger_data: rawptr, level: runtime.Logger_Level, text: string, options: runtime.Logger_Options, location := #caller_location) {
+	log_messages := cast(^[dynamic]Log_Message)logger_data
+
+	now := time.now()
+
+	append(log_messages, Log_Message {
+		level = level,
+		text = format_log_text(level, text, options, location, now),
+		time = now,
+		allocator = context.allocator,
+	})
+}
+
+format_log_text :: proc(level: runtime.Logger_Level, text: string, options: runtime.Logger_Options, location: runtime.Source_Code_Location, at_time: time.Time, allocator := context.allocator) -> string{
+	backing: [1024]byte
+	buf := strings.builder_from_bytes(backing[:])
+
+	pkg_log.do_level_header(options, &buf, level)
+	pkg_log.do_time_header(options, &buf, at_time)
+	pkg_log.do_location_header(options, &buf, location)
+
+	return fmt.aprintf("%s%s", strings.to_string(buf), text, allocator = allocator)
+}

+ 329 - 0
core/testing/reporting.odin

@@ -0,0 +1,329 @@
+//+private
+package testing
+
+import "base:runtime"
+import "core:encoding/ansi"
+import "core:fmt"
+import "core:io"
+import "core:mem"
+import "core:path/filepath"
+import "core:strings"
+
+// Definitions of colors for use in the test runner.
+SGR_RESET   :: ansi.CSI + ansi.RESET           + ansi.SGR
+SGR_READY   :: ansi.CSI + ansi.FG_BRIGHT_BLACK + ansi.SGR
+SGR_RUNNING :: ansi.CSI + ansi.FG_YELLOW       + ansi.SGR
+SGR_SUCCESS :: ansi.CSI + ansi.FG_GREEN        + ansi.SGR
+SGR_FAILED  :: ansi.CSI + ansi.FG_RED          + ansi.SGR
+
+MAX_PROGRESS_WIDTH :: 100
+
+// More than enough bytes to cover long package names, long test names, dozens
+// of ANSI codes, et cetera.
+LINE_BUFFER_SIZE :: (MAX_PROGRESS_WIDTH * 8 + 224) * runtime.Byte
+
+PROGRESS_COLUMN_SPACING :: 2
+
+Package_Run :: struct {
+	name: string,
+	header: string,
+
+	frame_ready: bool,
+
+	redraw_buffer: [LINE_BUFFER_SIZE]byte,
+	redraw_string: string,
+
+	last_change_state: Test_State,
+	last_change_name: string,
+
+	tests: []Internal_Test,
+	test_states: []Test_State,
+}
+
+Report :: struct {
+	packages: []Package_Run,
+	packages_by_name: map[string]^Package_Run,
+
+	pkg_column_len: int,
+	test_column_len: int,
+	progress_width: int,
+
+	all_tests: []Internal_Test,
+	all_test_states: []Test_State,
+}
+
+// Organize all tests by package and sort out test state data.
+make_report :: proc(internal_tests: []Internal_Test) -> (report: Report, error: runtime.Allocator_Error) {
+	assert(len(internal_tests) > 0, "make_report called with no tests")
+
+	packages: [dynamic]Package_Run
+
+	report.all_tests = internal_tests
+	report.all_test_states = make([]Test_State, len(internal_tests)) or_return
+
+	// First, figure out what belongs where.
+	#no_bounds_check cur_pkg := internal_tests[0].pkg
+	pkg_start: int
+
+	// This loop assumes the tests are sorted by package already.
+	for it, index in internal_tests {
+		if cur_pkg != it.pkg {
+			#no_bounds_check { 
+				append(&packages, Package_Run {
+					name = cur_pkg,
+					tests = report.all_tests[pkg_start:index],
+					test_states = report.all_test_states[pkg_start:index],
+				}) or_return
+			}
+
+			when PROGRESS_WIDTH == 0 {
+				report.progress_width = max(report.progress_width, index - pkg_start)
+			}
+
+			pkg_start = index
+			report.pkg_column_len = max(report.pkg_column_len, len(cur_pkg))
+			cur_pkg = it.pkg
+		}
+		report.test_column_len = max(report.test_column_len, len(it.name))
+	}
+
+	// Handle the last (or only) package.
+	#no_bounds_check {
+		append(&packages, Package_Run {
+			name = cur_pkg,
+			header = cur_pkg,
+			tests = report.all_tests[pkg_start:],
+			test_states = report.all_test_states[pkg_start:],
+		}) or_return
+	}
+	when PROGRESS_WIDTH == 0 {
+		report.progress_width = max(report.progress_width, len(internal_tests) - pkg_start)
+	} else {
+		report.progress_width = PROGRESS_WIDTH
+	}
+	report.progress_width = min(report.progress_width, MAX_PROGRESS_WIDTH)
+
+	report.pkg_column_len = PROGRESS_COLUMN_SPACING + max(report.pkg_column_len, len(cur_pkg))
+
+	shrink(&packages) or_return
+
+	for &pkg in packages {
+		pkg.header = fmt.aprintf("%- *[1]s[", pkg.name, report.pkg_column_len)
+		assert(len(pkg.header) > 0, "Error allocating package header string.")
+
+		// This is safe because the array is done resizing, and it has the same
+		// lifetime as the map.
+		report.packages_by_name[pkg.name] = &pkg
+	}
+
+	// It's okay to discard the dynamic array's allocator information here,
+	// because its capacity has been shrunk to its length, it was allocated by
+	// the caller's context allocator, and it will be deallocated by the same.
+	//
+	// `delete_slice` is equivalent to `delete_dynamic_array` in this case.
+	report.packages = packages[:]
+
+	return
+}
+
+destroy_report :: proc(report: ^Report) {
+	for pkg in report.packages {
+		delete(pkg.header)
+	}
+
+	delete(report.packages)
+	delete(report.packages_by_name)
+	delete(report.all_test_states)
+}
+
+redraw_package :: proc(w: io.Writer, report: Report, pkg: ^Package_Run) {
+	if pkg.frame_ready {
+		io.write_string(w, pkg.redraw_string)
+		return
+	}
+
+	// Write the output line here so we can cache it.
+	line_builder := strings.builder_from_bytes(pkg.redraw_buffer[:])
+	line_writer  := strings.to_writer(&line_builder)
+
+	highest_run_index: int
+	failed_count: int
+	done_count: int
+	#no_bounds_check for i := 0; i < len(pkg.test_states); i += 1 {
+		switch pkg.test_states[i] {
+		case .Ready:
+			continue
+		case .Running:
+			highest_run_index = max(highest_run_index, i)
+		case .Successful:
+			done_count += 1
+		case .Failed:
+			failed_count += 1
+			done_count += 1
+		}
+	}
+
+	start := max(0, highest_run_index - (report.progress_width - 1))
+	end   := min(start + report.progress_width, len(pkg.test_states))
+
+	// This variable is to keep track of the last ANSI code emitted, in
+	// order to avoid repeating the same code over in a sequence.
+	//
+	// This should help reduce screen flicker.
+	last_state := Test_State(-1)
+
+	io.write_string(line_writer, pkg.header)
+
+	#no_bounds_check for state in pkg.test_states[start:end] {
+		switch state {
+		case .Ready:
+			if last_state != state {
+				io.write_string(line_writer, SGR_READY)
+				last_state = state
+			}
+		case .Running:
+			if last_state != state {
+				io.write_string(line_writer, SGR_RUNNING)
+				last_state = state
+			}
+		case .Successful:
+			if last_state != state {
+				io.write_string(line_writer, SGR_SUCCESS)
+				last_state = state
+			}
+		case .Failed:
+			if last_state != state {
+				io.write_string(line_writer, SGR_FAILED)
+				last_state = state
+			}
+		}
+		io.write_byte(line_writer, '|')
+	}
+
+	for _ in 0 ..< report.progress_width - (end - start) {
+		io.write_byte(line_writer, ' ')
+	}
+
+	io.write_string(line_writer, SGR_RESET + "] ")
+
+	ticker: string
+	if done_count == len(pkg.test_states) {
+		ticker = "[package done]"
+		if failed_count > 0 {
+			ticker = fmt.tprintf("%s (" + SGR_FAILED + "%i" + SGR_RESET + " failed)", ticker, failed_count)
+		}
+	} else {
+		if len(pkg.last_change_name) == 0 {
+			#no_bounds_check pkg.last_change_name = pkg.tests[0].name
+		}
+
+		switch pkg.last_change_state {
+		case .Ready:
+			ticker = fmt.tprintf(SGR_READY + "%s" + SGR_RESET, pkg.last_change_name)
+		case .Running:
+			ticker = fmt.tprintf(SGR_RUNNING + "%s" + SGR_RESET, pkg.last_change_name)
+		case .Failed:
+			ticker = fmt.tprintf(SGR_FAILED + "%s" + SGR_RESET, pkg.last_change_name)
+		case .Successful:
+			ticker = fmt.tprintf(SGR_SUCCESS + "%s" + SGR_RESET, pkg.last_change_name)
+		}
+	}
+
+	if done_count == len(pkg.test_states) {
+		fmt.wprintfln(line_writer, "     % 4i :: %s",
+			len(pkg.test_states),
+			ticker,
+		)
+	} else {
+		fmt.wprintfln(line_writer, "% 4i/% 4i :: %s",
+			done_count,
+			len(pkg.test_states),
+			ticker,
+		)
+	}
+
+	pkg.redraw_string = strings.to_string(line_builder)
+	pkg.frame_ready = true
+	io.write_string(w, pkg.redraw_string)
+}
+
+redraw_report :: proc(w: io.Writer, report: Report) {
+	// If we print a line longer than the user's terminal can handle, it may
+	// wrap around, shifting the progress report out of alignment.
+	//
+	// There are ways to get the current terminal width, and that would be the
+	// ideal way to handle this, but it would require system-specific code such
+	// as setting STDIN to be non-blocking in order to read the response from
+	// the ANSI DSR escape code, or reading environment variables.
+	//
+	// The DECAWM escape codes control whether or not the terminal will wrap
+	// long lines or overwrite the last visible character.
+	// This should be fine for now.
+	//
+	// Note that we only do this for the animated summary; log messages are
+	// still perfectly fine to wrap, as they're printed in their own batch,
+	// whereas the animation depends on each package being only on one line.
+	//
+	// Of course, if you resize your terminal while it's printing, things can
+	// still break...
+	fmt.wprint(w, ansi.CSI + ansi.DECAWM_OFF)
+	for &pkg in report.packages {
+		redraw_package(w, report, &pkg)
+	}
+	fmt.wprint(w, ansi.CSI + ansi.DECAWM_ON)
+}
+
+needs_to_redraw :: proc(report: Report) -> bool {
+	for pkg in report.packages {
+		if !pkg.frame_ready {
+			return true
+		}
+	}
+
+	return false
+}
+
+draw_status_bar :: proc(w: io.Writer, threads_string: string, total_done_count, total_test_count: int) {
+	if total_done_count == total_test_count {
+		// All tests are done; print a blank line to maintain the same height
+		// of the progress report.
+		fmt.wprintln(w)
+	} else {
+		fmt.wprintfln(w,
+			"%s % 4i/% 4i :: total",
+			threads_string,
+			total_done_count,
+			total_test_count)
+	}
+}
+
+write_memory_report :: proc(w: io.Writer, tracker: ^mem.Tracking_Allocator, pkg, name: string) {
+	fmt.wprintf(w,
+		"<% 10M/% 10M> <% 10M> (% 5i/% 5i) :: %s.%s",
+		tracker.current_memory_allocated,
+		tracker.total_memory_allocated,
+		tracker.peak_memory_allocated,
+		tracker.total_free_count,
+		tracker.total_allocation_count,
+		pkg,
+		name)
+
+	for ptr, entry in tracker.allocation_map {
+		fmt.wprintf(w,
+			"\n        +++ leak % 10M @ %p [%s:%i:%s()]",
+			entry.size,
+			ptr,
+			filepath.base(entry.location.file_path),
+			entry.location.line,
+			entry.location.procedure)
+		}
+
+	for entry in tracker.bad_free_array {
+		fmt.wprintf(w,
+			"\n        +++ bad free        @ %p [%s:%i:%s()]",
+			entry.memory,
+			filepath.base(entry.location.file_path),
+			entry.location.line,
+			entry.location.procedure)
+	}
+}

+ 783 - 36
core/testing/runner.odin

@@ -1,73 +1,820 @@
 //+private
 package testing
 
+import "base:intrinsics"
+import "base:runtime"
+import "core:bytes"
+import "core:encoding/ansi"
+@require import "core:encoding/base64"
+import "core:fmt"
 import "core:io"
+@require import pkg_log "core:log"
+import "core:mem"
 import "core:os"
 import "core:slice"
+@require import "core:strings"
+import "core:sync/chan"
+import "core:thread"
+import "core:time"
 
-reset_t :: proc(t: ^T) {
-	clear(&t.cleanups)
-	t.error_count = 0
+// Specify how many threads to use when running tests.
+TEST_THREADS          : int    : #config(ODIN_TEST_THREADS, 0)
+// Track the memory used by each test.
+TRACKING_MEMORY       : bool   : #config(ODIN_TEST_TRACK_MEMORY, true)
+// Always report how much memory is used, even when there are no leaks or bad frees.
+ALWAYS_REPORT_MEMORY  : bool   : #config(ODIN_TEST_ALWAYS_REPORT_MEMORY, false)
+// Specify how much memory each thread allocator starts with.
+PER_THREAD_MEMORY     : int    : #config(ODIN_TEST_THREAD_MEMORY, mem.ROLLBACK_STACK_DEFAULT_BLOCK_SIZE)
+// Select a specific set of tests to run by name.
+// Each test is separated by a comma and may optionally include the package name.
+// This may be useful when running tests on multiple packages with `-all-packages`.
+// The format is: `package.test_name,test_name_only,...`
+TEST_NAMES            : string : #config(ODIN_TEST_NAMES, "")
+// Show the fancy animated progress report.
+FANCY_OUTPUT          : bool   : #config(ODIN_TEST_FANCY, true)
+// Copy failed tests to the clipboard when done.
+USE_CLIPBOARD         : bool   : #config(ODIN_TEST_CLIPBOARD, false)
+// How many test results to show at a time per package.
+PROGRESS_WIDTH        : int    : #config(ODIN_TEST_PROGRESS_WIDTH, 24)
+// This is the random seed that will be sent to each test.
+// If it is unspecified, it will be set to the system cycle counter at startup.
+SHARED_RANDOM_SEED    : u64    : #config(ODIN_TEST_RANDOM_SEED, 0)
+// Set the lowest log level for this test run.
+LOG_LEVEL             : string : #config(ODIN_TEST_LOG_LEVEL, "info")
+
+
+get_log_level :: #force_inline proc() -> runtime.Logger_Level {
+	when ODIN_DEBUG {
+		// Always use .Debug in `-debug` mode.
+		return .Debug
+	} else {
+		when LOG_LEVEL == "debug"        { return .Debug   }
+		else when LOG_LEVEL == "info"    { return .Info    }
+		else when LOG_LEVEL == "warning" { return .Warning }
+		else when LOG_LEVEL == "error"   { return .Error   }
+		else when LOG_LEVEL == "fatal"   { return .Fatal   }
+	}
 }
+
 end_t :: proc(t: ^T) {
 	for i := len(t.cleanups)-1; i >= 0; i -= 1 {
-		c := t.cleanups[i]
+		#no_bounds_check c := t.cleanups[i]
+		context = c.ctx
 		c.procedure(c.user_data)
 	}
+
+	delete(t.cleanups)
+	t.cleanups = {}
+}
+
+Task_Data :: struct {
+	it: Internal_Test,
+	t: T,
+	allocator_index: int,
+}
+
+Task_Timeout :: struct {
+	test_index: int,
+	at_time: time.Time,
+	location: runtime.Source_Code_Location,
+}
+
+run_test_task :: proc(task: thread.Task) {
+	data := cast(^Task_Data)(task.data)
+
+	setup_task_signal_handler(task.user_index)
+
+	chan.send(data.t.channel, Event_New_Test {
+		test_index = task.user_index,
+	})
+
+	chan.send(data.t.channel, Event_State_Change {
+		new_state = .Running,
+	})
+	
+	context.assertion_failure_proc = test_assertion_failure_proc
+
+	context.logger = {
+		procedure = test_logger_proc,
+		data = &data.t,
+		lowest_level = get_log_level(),
+		options = Default_Test_Logger_Opts,
+	}
+
+	free_all(context.temp_allocator)
+
+	data.it.p(&data.t)
+
+	end_t(&data.t)
+
+	new_state : Test_State = .Failed if failed(&data.t) else .Successful
+
+	chan.send(data.t.channel, Event_State_Change {
+		new_state = new_state,
+	})
 }
 
 runner :: proc(internal_tests: []Internal_Test) -> bool {
-	stream := os.stream_from_handle(os.stdout)
-	w := io.to_writer(stream)
+	BATCH_BUFFER_SIZE     :: 32 * mem.Kilobyte
+	POOL_BLOCK_SIZE       :: 16 * mem.Kilobyte
+	CLIPBOARD_BUFFER_SIZE :: 16 * mem.Kilobyte
+
+	BUFFERED_EVENTS_PER_CHANNEL :: 16
+	RESERVED_LOG_MESSAGES       :: 64
+	RESERVED_TEST_FAILURES      :: 64
+
+	ERROR_STRING_TIMEOUT : string : "Test timed out."
+	ERROR_STRING_UNKNOWN : string : "Test failed for unknown reasons."
+	OSC_WINDOW_TITLE     : string : ansi.OSC + ansi.WINDOW_TITLE + ";Odin test runner (%i/%i)" + ansi.ST
+
+	safe_delete_string :: proc(s: string, allocator := context.allocator) {
+		// Guard against bad frees on static strings.
+		switch raw_data(s) {
+		case raw_data(ERROR_STRING_TIMEOUT), raw_data(ERROR_STRING_UNKNOWN):
+			return
+		case:
+			delete(s, allocator)
+		}
+	}
+
+	stdout := io.to_writer(os.stream_from_handle(os.stdout))
+	stderr := io.to_writer(os.stream_from_handle(os.stderr))
+
+	// -- Prepare test data.
+
+	alloc_error: mem.Allocator_Error
+
+	when TEST_NAMES != "" {
+		select_internal_tests: [dynamic]Internal_Test
+		defer delete(select_internal_tests)
+
+		{
+			index_list := TEST_NAMES
+			for selector in strings.split_iterator(&index_list, ",") {
+				// Temp allocator is fine since we just need to identify which test it's referring to.
+				split_selector := strings.split(selector, ".", context.temp_allocator)
+
+				found := false
+				switch len(split_selector) {
+				case 1:
+					// Only the test name?
+					#no_bounds_check name := split_selector[0]
+					find_test_by_name: for it in internal_tests {
+						if it.name == name {
+							found = true
+							_, alloc_error = append(&select_internal_tests, it)
+							fmt.assertf(alloc_error == nil, "Error appending to select internal tests: %v", alloc_error)
+							break find_test_by_name
+						}
+					}
+				case 2:
+					#no_bounds_check pkg  := split_selector[0]
+					#no_bounds_check name := split_selector[1]
+					find_test_by_pkg_and_name: for it in internal_tests {
+						if it.pkg == pkg && it.name == name {
+							found = true
+							_, alloc_error = append(&select_internal_tests, it)
+							fmt.assertf(alloc_error == nil, "Error appending to select internal tests: %v", alloc_error)
+							break find_test_by_pkg_and_name
+						}
+					}
+				}
+				if !found {
+					fmt.wprintfln(stderr, "No test found for the name: %q", selector)
+				}
+			}
+		}
 
-	t := &T{}
-	t.w = w
-	reserve(&t.cleanups, 1024)
-	defer delete(t.cleanups)
+		// Intentional shadow with user-specified tests.
+		internal_tests := select_internal_tests[:]
+	}
 
+	total_failure_count := 0
 	total_success_count := 0
-	total_test_count := len(internal_tests)
+	total_done_count    := 0
+	total_test_count    := len(internal_tests)
+
+	when !FANCY_OUTPUT {
+		// This is strictly for updating the window title when the progress
+		// report is disabled. We're otherwise able to depend on the call to
+		// `needs_to_redraw`.
+		last_done_count := -1
+	}
+
+	if total_test_count == 0 {
+		// Exit early.
+		fmt.wprintln(stdout, "No tests to run.")
+		return true
+	}
+
+	for it in internal_tests {
+		// NOTE(Feoramund): The old test runner skipped over tests with nil
+		// procedures, but I couldn't find any case where they occurred.
+		// This assert stands to prevent any oversight on my part.
+		fmt.assertf(it.p != nil, "Test %s.%s has <nil> procedure.", it.pkg, it.name)
+	}
 
 	slice.sort_by(internal_tests, proc(a, b: Internal_Test) -> bool {
-		if a.pkg < b.pkg {
-			return true
+		if a.pkg == b.pkg {
+			return a.name < b.name
+		} else {
+			return a.pkg < b.pkg
 		}
-		return a.name < b.name
 	})
 
-	prev_pkg := ""
+	// -- Set thread count.
 
-	for it in internal_tests {
-		if it.p == nil {
-			total_test_count -= 1
-			continue
+	when TEST_THREADS == 0 {
+		thread_count := os.processor_core_count()
+	} else {
+		thread_count := max(1, TEST_THREADS)
+	}
+
+	thread_count = min(thread_count, total_test_count)
+
+	// -- Allocate.
+
+	pool_stack: mem.Rollback_Stack
+	alloc_error = mem.rollback_stack_init(&pool_stack, POOL_BLOCK_SIZE)
+	fmt.assertf(alloc_error == nil, "Error allocating memory for thread pool: %v", alloc_error)
+	defer mem.rollback_stack_destroy(&pool_stack)
+
+	pool: thread.Pool
+	thread.pool_init(&pool, mem.rollback_stack_allocator(&pool_stack), thread_count)
+	defer thread.pool_destroy(&pool)
+
+	task_channels: []Task_Channel = ---
+	task_channels, alloc_error = make([]Task_Channel, thread_count)
+	fmt.assertf(alloc_error == nil, "Error allocating memory for update channels: %v", alloc_error)
+	defer delete(task_channels)
+
+	for &task_channel, index in task_channels {
+		task_channel.channel, alloc_error = chan.create_buffered(Update_Channel, BUFFERED_EVENTS_PER_CHANNEL, context.allocator)
+		fmt.assertf(alloc_error == nil, "Error allocating memory for update channel #%i: %v", index, alloc_error)
+	}
+	defer for &task_channel in task_channels {
+		chan.destroy(&task_channel.channel)
+	}
+
+	// This buffer is used to batch writes to STDOUT or STDERR, to help reduce
+	// screen flickering.
+	batch_buffer: bytes.Buffer
+	bytes.buffer_init_allocator(&batch_buffer, 0, BATCH_BUFFER_SIZE)
+	batch_writer := io.to_writer(bytes.buffer_to_stream(&batch_buffer))
+	defer bytes.buffer_destroy(&batch_buffer)
+
+	report: Report = ---
+	report, alloc_error = make_report(internal_tests)
+	fmt.assertf(alloc_error == nil, "Error allocating memory for test report: %v", alloc_error)
+	defer destroy_report(&report)
+
+	when FANCY_OUTPUT {
+		// We cannot make use of the ANSI save/restore cursor codes, because they
+		// work by absolute screen coordinates. This will cause unnecessary
+		// scrollback if we print at the bottom of someone's terminal.
+		ansi_redraw_string := fmt.aprintf(
+			// ANSI for "go up N lines then erase the screen from the cursor forward."
+			ansi.CSI + "%i" + ansi.CPL + ansi.CSI + ansi.ED +
+			// We'll combine this with the window title format string, since it
+			// can be printed at the same time.
+			"%s",
+			// 1 extra line for the status bar.
+			1 + len(report.packages), OSC_WINDOW_TITLE)
+		assert(len(ansi_redraw_string) > 0, "Error allocating ANSI redraw string.")
+		defer delete(ansi_redraw_string)
+
+		thread_count_status_string: string = ---
+		{
+			PADDING :: PROGRESS_COLUMN_SPACING + PROGRESS_WIDTH
+
+			unpadded := fmt.tprintf("%i thread%s", thread_count, "" if thread_count == 1 else "s")
+			thread_count_status_string = fmt.aprintf("%- *[1]s", unpadded, report.pkg_column_len + PADDING)
+			assert(len(thread_count_status_string) > 0, "Error allocating thread count status string.")
 		}
+		defer delete(thread_count_status_string)
+	}
+
+	task_data_slots: []Task_Data = ---
+	task_data_slots, alloc_error = make([]Task_Data, thread_count)
+	fmt.assertf(alloc_error == nil, "Error allocating memory for task data slots: %v", alloc_error)
+	defer delete(task_data_slots)
+
+	// Tests rotate through these allocators as they finish.
+	task_allocators: []mem.Rollback_Stack = ---
+	task_allocators, alloc_error = make([]mem.Rollback_Stack, thread_count)
+	fmt.assertf(alloc_error == nil, "Error allocating memory for task allocators: %v", alloc_error)
+	defer delete(task_allocators)
 
-		free_all(context.temp_allocator)
-		reset_t(t)
-		defer end_t(t)
+	when TRACKING_MEMORY {
+		task_memory_trackers: []mem.Tracking_Allocator = ---
+		task_memory_trackers, alloc_error = make([]mem.Tracking_Allocator, thread_count)
+		fmt.assertf(alloc_error == nil, "Error allocating memory for memory trackers: %v", alloc_error)
+		defer delete(task_memory_trackers)
+	}
+
+	#no_bounds_check for i in 0 ..< thread_count {
+		alloc_error = mem.rollback_stack_init(&task_allocators[i], PER_THREAD_MEMORY)
+		fmt.assertf(alloc_error == nil, "Error allocating memory for task allocator #%i: %v", i, alloc_error)
+		when TRACKING_MEMORY {
+			mem.tracking_allocator_init(&task_memory_trackers[i], mem.rollback_stack_allocator(&task_allocators[i]))
+		}
+	}
 
-		if prev_pkg != it.pkg {
-			prev_pkg = it.pkg
-			logf(t, "[Package: %s]", it.pkg)
+	defer #no_bounds_check for i in 0 ..< thread_count {
+		when TRACKING_MEMORY {
+			mem.tracking_allocator_destroy(&task_memory_trackers[i])
 		}
+		mem.rollback_stack_destroy(&task_allocators[i])
+	}
 
-		logf(t, "[Test: %s]", it.name)
+	task_timeouts: [dynamic]Task_Timeout = ---
+	task_timeouts, alloc_error = make([dynamic]Task_Timeout, 0, thread_count)
+	fmt.assertf(alloc_error == nil, "Error allocating memory for task timeouts: %v", alloc_error)
+	defer delete(task_timeouts)
 
-		run_internal_test(t, it)
+	failed_test_reason_map: map[int]string = ---
+	failed_test_reason_map, alloc_error = make(map[int]string, RESERVED_TEST_FAILURES)
+	fmt.assertf(alloc_error == nil, "Error allocating memory for failed test reasons: %v", alloc_error)
+	defer delete(failed_test_reason_map)
 
-		if failed(t) {
-			logf(t, "[%s : FAILURE]", it.name)
-		} else {
-			logf(t, "[%s : SUCCESS]", it.name)
-			total_success_count += 1
+	log_messages: [dynamic]Log_Message = ---
+	log_messages, alloc_error = make([dynamic]Log_Message, 0, RESERVED_LOG_MESSAGES)
+	fmt.assertf(alloc_error == nil, "Error allocating memory for log message queue: %v", alloc_error)
+	defer delete(log_messages)
+
+	sorted_failed_test_reasons: [dynamic]int = ---
+	sorted_failed_test_reasons, alloc_error = make([dynamic]int, 0, RESERVED_TEST_FAILURES)
+	fmt.assertf(alloc_error == nil, "Error allocating memory for sorted failed test reasons: %v", alloc_error)
+	defer delete(sorted_failed_test_reasons)
+
+	when USE_CLIPBOARD {
+		clipboard_buffer: bytes.Buffer
+		bytes.buffer_init_allocator(&clipboard_buffer, 0, CLIPBOARD_BUFFER_SIZE)
+		defer bytes.buffer_destroy(&clipboard_buffer)
+	}
+
+	when SHARED_RANDOM_SEED == 0 {
+		shared_random_seed := cast(u64)intrinsics.read_cycle_counter()
+	} else {
+		shared_random_seed := SHARED_RANDOM_SEED
+	}
+
+	// -- Setup initial tasks.
+
+	// NOTE(Feoramund): This is the allocator that will be used by threads to
+	// persist log messages past their lifetimes. It has its own variable name
+	// in the event it needs to be changed from `context.allocator` without
+	// digging through the source to divine everywhere it is used for that.
+	shared_log_allocator := context.allocator
+
+	context.logger = {
+		procedure = runner_logger_proc,
+		data = &log_messages,
+		lowest_level = get_log_level(),
+		options = Default_Test_Logger_Opts - {.Short_File_Path, .Line, .Procedure},
+	}
+
+	run_index: int
+
+	setup_tasks: for &data, task_index in task_data_slots {
+		setup_next_test: for run_index < total_test_count {
+			#no_bounds_check it := internal_tests[run_index]
+			defer run_index += 1
+
+			data.it = it
+			data.t.seed = shared_random_seed
+			#no_bounds_check data.t.channel = chan.as_send(task_channels[task_index].channel)
+			data.t._log_allocator = shared_log_allocator
+			data.allocator_index = task_index
+
+			#no_bounds_check when TRACKING_MEMORY {
+				task_allocator := mem.tracking_allocator(&task_memory_trackers[task_index])
+			} else {
+				task_allocator := mem.rollback_stack_allocator(&task_allocators[task_index])
+			}
+
+			thread.pool_add_task(&pool, task_allocator, run_test_task, &data, run_index)
+
+			continue setup_tasks
 		}
 	}
-	logf(t, "----------------------------------------")
-	if total_test_count == 0 {
-		log(t, "NO TESTS RAN")
+
+	// -- Run tests.
+
+	setup_signal_handler()
+
+	fmt.wprint(stdout, ansi.CSI + ansi.DECTCEM_HIDE)
+
+	when FANCY_OUTPUT {
+		signals_were_raised := false
+
+		redraw_report(stdout, report)
+		draw_status_bar(stdout, thread_count_status_string, total_done_count, total_test_count)
+	}
+
+	when TEST_THREADS == 0 {
+		pkg_log.infof("Starting test runner with %i thread%s. Set with -define:ODIN_TEST_THREADS=n.",
+			thread_count,
+			"" if thread_count == 1 else "s")
 	} else {
-		logf(t, "%d/%d SUCCESSFUL", total_success_count, total_test_count)
+		pkg_log.infof("Starting test runner with %i thread%s.",
+			thread_count,
+			"" if thread_count == 1 else "s")
 	}
+
+	when SHARED_RANDOM_SEED == 0 {
+		pkg_log.infof("The random seed sent to every test is: %v. Set with -define:ODIN_TEST_RANDOM_SEED=n.", shared_random_seed)
+	} else {
+		pkg_log.infof("The random seed sent to every test is: %v.", shared_random_seed)
+	}
+
+	when TRACKING_MEMORY {
+		when ALWAYS_REPORT_MEMORY {
+			pkg_log.info("Memory tracking is enabled. Tests will log their memory usage when complete.")
+		} else {
+			pkg_log.info("Memory tracking is enabled. Tests will log their memory usage if there's an issue.")
+		}
+		pkg_log.info("< Final Mem/ Total Mem> <  Peak Mem> (#Free/Alloc) :: [package.test_name]")
+	} else when ALWAYS_REPORT_MEMORY {
+		pkg_log.warn("ODIN_TEST_ALWAYS_REPORT_MEMORY is true, but ODIN_TRACK_MEMORY is false.")
+	}
+
+	start_time := time.now()
+
+	thread.pool_start(&pool)
+	main_loop: for !thread.pool_is_empty(&pool) {
+		{
+			events_pending := thread.pool_num_done(&pool) > 0
+
+			if !events_pending {
+				poll_tasks: for &task_channel in task_channels {
+					if chan.len(task_channel.channel) > 0 {
+						events_pending = true
+						break poll_tasks
+					}
+				}
+			}
+
+			if !events_pending {
+				// Keep the main thread from pegging a core at 100% usage.
+				time.sleep(1 * time.Microsecond)
+			}
+		}
+
+		cycle_pool: for task in thread.pool_pop_done(&pool) {
+			data := cast(^Task_Data)(task.data)
+
+			when TRACKING_MEMORY {
+				#no_bounds_check tracker := &task_memory_trackers[data.allocator_index]
+
+				memory_is_in_bad_state := len(tracker.allocation_map) + len(tracker.bad_free_array) > 0
+
+				when ALWAYS_REPORT_MEMORY {
+					should_report := true
+				} else {
+					should_report := memory_is_in_bad_state
+				}
+
+				if should_report {
+					write_memory_report(batch_writer, tracker, data.it.pkg, data.it.name)
+
+					pkg_log.log(.Warning if memory_is_in_bad_state else .Info, bytes.buffer_to_string(&batch_buffer))
+					bytes.buffer_reset(&batch_buffer)
+				}
+
+				mem.tracking_allocator_reset(tracker)
+			}
+
+			free_all(task.allocator)
+
+			if run_index < total_test_count {
+				#no_bounds_check it := internal_tests[run_index]
+				defer run_index += 1
+
+				data.it = it
+				data.t.seed = shared_random_seed
+				data.t.error_count = 0
+
+				thread.pool_add_task(&pool, task.allocator, run_test_task, data, run_index)
+			}
+		}
+
+		handle_events: for &task_channel in task_channels {
+			for ev in chan.try_recv(task_channel.channel) {
+				switch event in ev {
+				case Event_New_Test:
+					task_channel.test_index = event.test_index
+
+				case Event_State_Change:
+					#no_bounds_check report.all_test_states[task_channel.test_index] = event.new_state
+
+					#no_bounds_check it := internal_tests[task_channel.test_index]
+					#no_bounds_check pkg := report.packages_by_name[it.pkg]
+
+					#partial switch event.new_state {
+					case .Failed:
+						if task_channel.test_index not_in failed_test_reason_map {
+							failed_test_reason_map[task_channel.test_index] = ERROR_STRING_UNKNOWN
+						}
+						total_failure_count += 1
+						total_done_count += 1
+					case .Successful:
+						total_success_count += 1
+						total_done_count += 1
+					}
+
+					when ODIN_DEBUG {
+						pkg_log.debugf("Test #%i %s.%s changed state to %v.", task_channel.test_index, it.pkg, it.name, event.new_state)
+					}
+
+					pkg.last_change_state = event.new_state
+					pkg.last_change_name = it.name
+					pkg.frame_ready = false
+
+				case Event_Set_Fail_Timeout:
+					_, alloc_error = append(&task_timeouts, Task_Timeout {
+						test_index = task_channel.test_index,
+						at_time = event.at_time,
+						location = event.location,
+					})
+					fmt.assertf(alloc_error == nil, "Error appending to task timeouts: %v", alloc_error)
+
+				case Event_Log_Message:
+					_, alloc_error = append(&log_messages, Log_Message {
+						level = event.level,
+						text = event.formatted_text,
+						time = event.time,
+						allocator = shared_log_allocator,
+					})
+					fmt.assertf(alloc_error == nil, "Error appending to log messages: %v", alloc_error)
+
+					if event.level >= .Error {
+						// Save the message for the final summary.
+						if old_error, ok := failed_test_reason_map[task_channel.test_index]; ok {
+							safe_delete_string(old_error, shared_log_allocator)
+						}
+						failed_test_reason_map[task_channel.test_index] = event.text
+					} else {
+						delete(event.text, shared_log_allocator)
+					}
+				}
+			}
+		}
+
+		check_timeouts: for i := len(task_timeouts) - 1; i >= 0; i -= 1 {
+			#no_bounds_check timeout := &task_timeouts[i]
+
+			if time.since(timeout.at_time) < 0 {
+				continue check_timeouts
+			}
+
+			defer unordered_remove(&task_timeouts, i)
+
+			#no_bounds_check if report.all_test_states[timeout.test_index] > .Running {
+				continue check_timeouts
+			}
+
+			if !thread.pool_stop_task(&pool, timeout.test_index) {
+				// The task may have stopped a split second after we started
+				// checking, but we haven't handled the new state yet.
+				continue check_timeouts
+			}
+
+			#no_bounds_check report.all_test_states[timeout.test_index] = .Failed
+			#no_bounds_check it := internal_tests[timeout.test_index]
+			#no_bounds_check pkg := report.packages_by_name[it.pkg]
+			pkg.frame_ready = false
+
+			if old_error, ok := failed_test_reason_map[timeout.test_index]; ok {
+				safe_delete_string(old_error, shared_log_allocator)
+			}
+			failed_test_reason_map[timeout.test_index] = ERROR_STRING_TIMEOUT
+			total_failure_count += 1
+			total_done_count += 1
+
+			now := time.now()
+			_, alloc_error = append(&log_messages, Log_Message {
+				level = .Error,
+				text = format_log_text(.Error, ERROR_STRING_TIMEOUT, Default_Test_Logger_Opts, timeout.location, now),
+				time = now,
+				allocator = context.allocator,
+			})
+			fmt.assertf(alloc_error == nil, "Error appending to log messages: %v", alloc_error)
+
+			find_task_data: for &data in task_data_slots {
+				if data.it.pkg == it.pkg && data.it.name == it.name {
+					end_t(&data.t)
+					break find_task_data
+				}
+			}
+		}
+
+		if should_stop_runner() {
+			fmt.wprintln(stderr, "\nCaught interrupt signal. Stopping all tests.")
+			thread.pool_shutdown(&pool)
+			break main_loop
+		}
+
+		when FANCY_OUTPUT {
+			// Because the bounds checking procs send directly to STDERR with
+			// no way to redirect or handle them, we need to at least try to
+			// let the user see those messages when using the animated progress
+			// report. This flag may be set by the block of code below if a
+			// signal is raised.
+			//
+			// It'll be purely by luck if the output is interleaved properly,
+			// given the nature of non-thread-safe printing.
+			//
+			// At worst, if Odin did not print any error for this signal, we'll
+			// just re-display the progress report. The fatal log error message
+			// should be enough to clue the user in that something dire has
+			// occurred.
+			bypass_progress_overwrite := false
+		}
+
+		if test_index, reason, ok := should_stop_test(); ok {
+			#no_bounds_check report.all_test_states[test_index] = .Failed
+			#no_bounds_check it := internal_tests[test_index]
+			#no_bounds_check pkg := report.packages_by_name[it.pkg]
+			pkg.frame_ready = false
+
+			fmt.assertf(thread.pool_stop_task(&pool, test_index),
+				"A signal (%v) was raised to stop test #%i %s.%s, but it was unable to be found.",
+				reason, test_index, it.pkg, it.name)
+
+			if test_index not_in failed_test_reason_map {
+				// We only write a new error message here if there wasn't one
+				// already, because the message we can provide based only on
+				// the signal won't be very useful, whereas asserts and panics
+				// will provide a user-written error message.
+				failed_test_reason_map[test_index] = fmt.aprintf("Signal caught: %v", reason, allocator = shared_log_allocator)
+				pkg_log.fatalf("Caught signal to stop test #%i %s.%s for: %v.", test_index, it.pkg, it.name, reason)
+
+			}
+
+			when FANCY_OUTPUT {
+				bypass_progress_overwrite = true
+				signals_were_raised = true
+			}
+
+			total_failure_count += 1
+			total_done_count += 1
+		}
+
+		// -- Redraw.
+
+		when FANCY_OUTPUT {
+			if len(log_messages) == 0 && !needs_to_redraw(report) {
+				continue main_loop
+			}
+
+			if !bypass_progress_overwrite {
+				fmt.wprintf(stdout, ansi_redraw_string, total_done_count, total_test_count)
+			}
+		} else {
+			if total_done_count != last_done_count {
+				fmt.wprintf(stdout, OSC_WINDOW_TITLE, total_done_count, total_test_count)
+				last_done_count = total_done_count
+			}
+
+			if len(log_messages) == 0 {
+				continue main_loop
+			}
+		}
+
+		// Because each thread has its own messenger channel, log messages
+		// arrive in chunks that are in-order, but when they're merged with the
+		// logs from other threads, they become out-of-order.
+		slice.stable_sort_by(log_messages[:], proc(a, b: Log_Message) -> bool {
+			return time.diff(a.time, b.time) > 0
+		})
+
+		for message in log_messages {
+			fmt.wprintln(batch_writer, message.text)
+			delete(message.text, message.allocator)
+		}
+
+		fmt.wprint(stderr, bytes.buffer_to_string(&batch_buffer))
+		clear(&log_messages)
+		bytes.buffer_reset(&batch_buffer)
+
+		when FANCY_OUTPUT {
+			redraw_report(batch_writer, report)
+			draw_status_bar(batch_writer, thread_count_status_string, total_done_count, total_test_count)
+			fmt.wprint(stdout, bytes.buffer_to_string(&batch_buffer))
+			bytes.buffer_reset(&batch_buffer)
+		}
+	}
+
+	// -- All tests are complete, or the runner has been interrupted.
+
+	// NOTE(Feoramund): If you've arrived here after receiving signal 11 or
+	// SIGSEGV on the main runner thread, while using a UNIX-like platform,
+	// there is the possibility that you may have encountered a rare edge case
+	// involving the joining of threads.
+	//
+	// At the time of writing, the thread library is undergoing a rewrite that
+	// should solve this problem; it is not an issue with the test runner itself.
+	thread.pool_join(&pool)
+
+	finished_in := time.since(start_time)
+
+	when !FANCY_OUTPUT {
+		// One line to space out the results, since we don't have the status
+		// bar in plain mode.
+		fmt.wprintln(batch_writer)
+	}
+
+	fmt.wprintf(batch_writer,
+		"Finished %i test%s in %v.",
+		total_done_count,
+		"" if total_done_count == 1 else "s",
+		finished_in)
+	
+	if total_done_count != total_test_count {
+		not_run_count := total_test_count - total_done_count
+		fmt.wprintf(batch_writer,
+			" " + SGR_READY + "%i" + SGR_RESET + " %s left undone.",
+			not_run_count,
+			"test was" if not_run_count == 1 else "tests were")
+	}
+
+	if total_success_count == total_test_count {
+		fmt.wprintfln(batch_writer,
+			" %s " + SGR_SUCCESS + "successful." + SGR_RESET,
+			"The test was" if total_test_count == 1 else "All tests were")
+	} else if total_failure_count > 0 {
+		if total_failure_count == total_test_count {
+			fmt.wprintfln(batch_writer,
+				" %s " + SGR_FAILED + "failed." + SGR_RESET,
+				"The test" if total_test_count == 1 else "All tests")
+		} else {
+			fmt.wprintfln(batch_writer,
+				" " + SGR_FAILED + "%i" + SGR_RESET + " test%s failed.",
+				total_failure_count,
+				"" if total_failure_count == 1 else "s")
+		}
+
+		for test_index in failed_test_reason_map {
+			_, alloc_error = append(&sorted_failed_test_reasons, test_index)
+			fmt.assertf(alloc_error == nil, "Error appending to sorted failed test reasons: %v", alloc_error)
+		}
+
+		slice.sort(sorted_failed_test_reasons[:])
+
+		for test_index in sorted_failed_test_reasons {
+			#no_bounds_check last_error := failed_test_reason_map[test_index]
+			#no_bounds_check it := internal_tests[test_index]
+			pkg_and_name := fmt.tprintf("%s.%s", it.pkg, it.name)
+			fmt.wprintfln(batch_writer, " - %- *[1]s\t%s",
+				pkg_and_name,
+				report.pkg_column_len + report.test_column_len,
+				last_error)
+			safe_delete_string(last_error, shared_log_allocator)
+		}
+
+		if total_success_count > 0 {
+			when USE_CLIPBOARD {
+				clipboard_writer := io.to_writer(bytes.buffer_to_stream(&clipboard_buffer))
+				fmt.wprint(clipboard_writer, "-define:ODIN_TEST_NAMES=")
+				for test_index in sorted_failed_test_reasons {
+					#no_bounds_check it := internal_tests[test_index]
+					fmt.wprintf(clipboard_writer, "%s.%s,", it.pkg, it.name)
+				}
+
+				encoded_names := base64.encode(bytes.buffer_to_bytes(&clipboard_buffer), allocator = context.temp_allocator)
+
+				fmt.wprintf(batch_writer,
+					ansi.OSC + ansi.CLIPBOARD + ";c;%s" + ansi.ST + 
+					"\nThe name%s of the failed test%s been copied to your clipboard.",
+					encoded_names,
+					"" if total_failure_count == 1 else "s",
+					" has" if total_failure_count == 1 else "s have")
+			} else {
+				fmt.wprintf(batch_writer, "\nTo run only the failed test%s, use:\n\t-define:ODIN_TEST_NAMES=",
+					"" if total_failure_count == 1 else "s")
+				for test_index in sorted_failed_test_reasons {
+					#no_bounds_check it := internal_tests[test_index]
+					fmt.wprintf(batch_writer, "%s.%s,", it.pkg, it.name)
+				}
+				fmt.wprint(batch_writer, "\n\nIf your terminal supports OSC 52, you may use -define:ODIN_TEST_CLIPBOARD to have this copied directly to your clipboard.")
+			}
+
+			fmt.wprintln(batch_writer)
+		}
+	}
+
+	fmt.wprint(stdout, ansi.CSI + ansi.DECTCEM_SHOW)
+
+	when FANCY_OUTPUT {
+		if signals_were_raised {
+			fmt.wprintln(batch_writer, `
+Signals were raised during this test run. Log messages are likely to have collided with each other.
+To partly mitigate this, redirect STDERR to a file or use the -define:ODIN_TEST_FANCY=false option.`)
+		}
+	}
+
+	fmt.wprintln(stderr, bytes.buffer_to_string(&batch_buffer))
+
 	return total_success_count == total_test_count
 }

+ 0 - 14
core/testing/runner_other.odin

@@ -1,14 +0,0 @@
-//+private
-//+build !windows
-package testing
-
-import "core:time"
-
-run_internal_test :: proc(t: ^T, it: Internal_Test) {
-	// TODO(bill): Catch panics on other platforms
-	it.p(t)
-}
-
-_fail_timeout :: proc(t: ^T, duration: time.Duration, loc := #caller_location) {
-
-}

+ 0 - 235
core/testing/runner_windows.odin

@@ -1,235 +0,0 @@
-//+private
-//+build windows
-package testing
-
-import win32 "core:sys/windows"
-import "base:runtime"
-import "base:intrinsics"
-import "core:time"
-
-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_compare_exchange_strong(&s.count, original_count-1, original_count) {
-			return
-		}
-	}
-}
-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_compare_exchange_strong(&s.count, original_count-1, original_count) {
-			return true
-		}
-	}
-}
-
-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,
-	
-	internal_fail_timeout: time.Duration,
-	internal_fail_timeout_loc: runtime.Source_Code_Location,
-}
-
-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 "system" (t_: rawptr) -> win32.DWORD {
-		t := (^Thread)(t_)
-		context = t.init_context.? or_else runtime.default_context()
-
-		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))
-}
-
-
-_fail_timeout :: proc(t: ^T, duration: time.Duration, loc := #caller_location) {
-	assert(global_fail_timeout_thread == nil, "set_fail_timeout previously called", loc)
-	
-	thread := thread_create(proc(thread: ^Thread) {
-		t := thread.t
-		timeout := thread.internal_fail_timeout
-		if !sema_wait_with_timeout(&global_fail_timeout_semaphore, timeout) {
-			fail_now(t, "TIMEOUT", thread.internal_fail_timeout_loc)
-		}
-	})
-	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
-global_current_t: ^T
-
-run_internal_test :: proc(t: ^T, it: Internal_Test) {
-	thread := thread_create(proc(thread: ^Thread) {
-		exception_handler_proc :: proc "system" (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(global_current_t, "%s %s", prefix, message, loc=loc)
-			intrinsics.trap()
-		}
-		
-		t := thread.t
-
-		global_fail_timeout_thread = nil
-		sema_reset(&global_fail_timeout_semaphore)
-		
-		thread.it.p(t)
-		
-		sema_post(&global_fail_timeout_semaphore)
-		if global_fail_timeout_thread != nil do thread_join_and_destroy(global_fail_timeout_thread)
-		
-		thread.success = true
-		sema_post(&global_threaded_runner_semaphore)
-	})
-
-	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
-	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
-}

+ 33 - 0
core/testing/signal_handler.odin

@@ -0,0 +1,33 @@
+//+private
+package testing
+
+import "base:runtime"
+import pkg_log "core:log"
+
+Stop_Reason :: enum {
+	Unknown,
+	Illegal_Instruction,
+	Arithmetic_Error,
+	Segmentation_Fault,
+}
+
+test_assertion_failure_proc :: proc(prefix, message: string, loc: runtime.Source_Code_Location) -> ! {
+	pkg_log.fatalf("%s: %s", prefix, message, location = loc)
+	runtime.trap()
+}
+
+setup_signal_handler :: proc() {
+	_setup_signal_handler()
+}
+
+setup_task_signal_handler :: proc(test_index: int) {
+	_setup_task_signal_handler(test_index)
+}
+
+should_stop_runner :: proc() -> bool {
+	return _should_stop_runner()
+}
+
+should_stop_test :: proc() -> (test_index: int, reason: Stop_Reason, ok: bool) {
+	return _should_stop_test()
+}

+ 142 - 0
core/testing/signal_handler_libc.odin

@@ -0,0 +1,142 @@
+//+private
+//+build windows, linux, darwin, freebsd, openbsd, netbsd, haiku
+package testing
+
+import "base:intrinsics"
+import "core:c/libc"
+import "core:encoding/ansi"
+import "core:sync"
+@require import "core:sys/unix"
+
+@(private="file") stop_runner_flag: libc.sig_atomic_t
+
+@(private="file") stop_test_gate:   sync.Mutex
+@(private="file") stop_test_index:  libc.sig_atomic_t
+@(private="file") stop_test_reason: libc.sig_atomic_t
+@(private="file") stop_test_alert:  libc.sig_atomic_t
+
+@(private="file", thread_local)
+local_test_index: libc.sig_atomic_t
+
+@(private="file")
+stop_runner_callback :: proc "c" (sig: libc.int) {
+	intrinsics.atomic_store(&stop_runner_flag, 1)
+}
+
+@(private="file")
+stop_test_callback :: proc "c" (sig: libc.int) {
+	if local_test_index == -1 {
+		// We're the test runner, and we ourselves have caught a signal from
+		// which there is no recovery.
+		//
+		// The most we can do now is make sure the user's cursor is visible,
+		// nuke the entire processs, and hope a useful core dump survives.
+
+		// NOTE(Feoramund): Using these write calls in a signal handler is
+		// undefined behavior in C99 but possibly tolerated in POSIX 2008.
+		// Either way, we may as well try to salvage what we can.
+		show_cursor := ansi.CSI + ansi.DECTCEM_SHOW
+		libc.fwrite(raw_data(show_cursor), size_of(byte), len(show_cursor), libc.stdout)
+		libc.fflush(libc.stdout)
+
+		// This is an attempt at being compliant by avoiding printf.
+		sigbuf: [8]byte
+		sigstr: string
+		{
+			signum := cast(int)sig
+			i := len(sigbuf) - 2
+			for signum > 0 {
+				m := signum % 10
+				signum /= 10
+				sigbuf[i] = cast(u8)('0' + m)
+				i -= 1
+			}
+			sigstr = cast(string)sigbuf[1 + i:len(sigbuf) - 1]
+		}
+
+		advisory_a := `
+The test runner's main thread has caught an unrecoverable error (signal `
+		advisory_b := `) and will now forcibly terminate.
+This is a dire bug and should be reported to the Odin developers.
+`
+		libc.fwrite(raw_data(advisory_a), size_of(byte), len(advisory_a), libc.stderr)
+		libc.fwrite(raw_data(sigstr), size_of(byte), len(sigstr), libc.stderr)
+		libc.fwrite(raw_data(advisory_b), size_of(byte), len(advisory_b), libc.stderr)
+
+		// Try to get a core dump.
+		libc.abort()
+	}
+
+	if sync.mutex_guard(&stop_test_gate) {
+		intrinsics.atomic_store(&stop_test_index, local_test_index)
+		intrinsics.atomic_store(&stop_test_reason, cast(libc.sig_atomic_t)sig)
+		intrinsics.atomic_store(&stop_test_alert, 1)
+
+		for {
+			// Idle until this thread is terminated by the runner,
+			// otherwise we may continue to generate signals.
+			intrinsics.cpu_relax()
+
+			when ODIN_OS != .Windows {
+				// NOTE(Feoramund): Some UNIX-like platforms may require this.
+				//
+				// During testing, I found that NetBSD 10.0 refused to
+				// terminate a task thread, even when its thread had been
+				// properly set to PTHREAD_CANCEL_ASYNCHRONOUS.
+				//
+				// The runner would stall after returning from `pthread_cancel`.
+			
+				unix.pthread_testcancel()
+			}
+		}
+	}
+}
+
+_setup_signal_handler :: proc() {
+	local_test_index = -1
+
+	// Catch user interrupt / CTRL-C.
+	libc.signal(libc.SIGINT, stop_runner_callback)
+	// Catch polite termination request.
+	libc.signal(libc.SIGTERM, stop_runner_callback)
+
+	// For tests:
+	// Catch asserts and panics.
+	libc.signal(libc.SIGILL, stop_test_callback)
+	// Catch arithmetic errors.
+	libc.signal(libc.SIGFPE, stop_test_callback)
+	// Catch segmentation faults (illegal memory access).
+	libc.signal(libc.SIGSEGV, stop_test_callback)
+}
+
+_setup_task_signal_handler :: proc(test_index: int) {
+	local_test_index = cast(libc.sig_atomic_t)test_index
+}
+
+_should_stop_runner :: proc() -> bool {
+	return intrinsics.atomic_load(&stop_runner_flag) == 1
+}
+
+@(private="file")
+unlock_stop_test_gate :: proc(_: int, _: Stop_Reason, ok: bool) {
+	if ok {
+		sync.mutex_unlock(&stop_test_gate)
+	}
+}
+
+@(deferred_out=unlock_stop_test_gate)
+_should_stop_test :: proc() -> (test_index: int, reason: Stop_Reason, ok: bool) {
+	if intrinsics.atomic_load(&stop_test_alert) == 1 {
+		intrinsics.atomic_store(&stop_test_alert, 0)
+
+		test_index = cast(int)intrinsics.atomic_load(&stop_test_index)
+		switch intrinsics.atomic_load(&stop_test_reason) {
+		case libc.SIGFPE: reason = .Arithmetic_Error
+		case libc.SIGILL: reason = .Illegal_Instruction
+		case libc.SIGSEGV: reason = .Segmentation_Fault
+		}
+		ok = true
+	}
+
+	return
+}

+ 19 - 0
core/testing/signal_handler_other.odin

@@ -0,0 +1,19 @@
+//+private
+//+build !windows !linux !darwin !freebsd !openbsd !netbsd !haiku
+package testing
+
+_setup_signal_handler :: proc() {
+	// Do nothing.
+}
+
+_setup_task_signal_handler :: proc(test_index: int) {
+	// Do nothing.
+}
+
+_should_stop_runner :: proc() -> bool {
+	return false
+}
+
+_should_stop_test :: proc() -> (test_index: int, reason: Stop_Reason, ok: bool) {
+	return 0, {}, false
+}

+ 43 - 26
core/testing/testing.odin

@@ -1,10 +1,11 @@
 package testing
 
-import "core:fmt"
-import "core:io"
-import "core:time"
 import "base:intrinsics"
+import "base:runtime"
+import pkg_log "core:log"
 import "core:reflect"
+import "core:sync/chan"
+import "core:time"
 
 _ :: reflect // alias reflect to nothing to force visibility for -vet
 
@@ -22,44 +23,55 @@ Internal_Test :: struct {
 Internal_Cleanup :: struct {
 	procedure: proc(rawptr),
 	user_data: rawptr,
+	ctx: runtime.Context,
 }
 
 T :: struct {
 	error_count: int,
 
-	w: io.Writer,
+	// If your test needs to perform random operations, it's advised to use
+	// this value to seed a local random number generator rather than relying
+	// on the non-thread-safe global one.
+	//
+	// This way, your results will be deterministic.
+	//
+	// This value is chosen at startup of the test runner, logged, and may be
+	// specified by the user. It is the same for all tests of a single run.
+	seed: u64,
+
+	channel: Update_Channel_Sender,
 
 	cleanups: [dynamic]Internal_Cleanup,
 
+	// This allocator is shared between the test runner and its threads for
+	// cloning log strings, so they can outlive the lifetime of individual
+	// tests during channel transmission.
+	_log_allocator: runtime.Allocator,
+
 	_fail_now: proc() -> !,
 }
 
 
+@(deprecated="prefer `log.error`")
 error :: proc(t: ^T, args: ..any, loc := #caller_location) {
-	fmt.wprintf(t.w, "%v: ", loc)
-	fmt.wprintln(t.w, ..args)
-	t.error_count += 1
+	pkg_log.error(..args, location = loc)
 }
 
+@(deprecated="prefer `log.errorf`")
 errorf :: proc(t: ^T, format: string, args: ..any, loc := #caller_location) {
-	fmt.wprintf(t.w, "%v: ", loc)
-	fmt.wprintf(t.w, format, ..args)
-	fmt.wprintln(t.w)
-	t.error_count += 1
+	pkg_log.errorf(format, ..args, location = loc)
 }
 
 fail :: proc(t: ^T, loc := #caller_location) {
-	error(t, "FAIL", loc=loc)
-	t.error_count += 1
+	pkg_log.error("FAIL", location=loc)
 }
 
 fail_now :: proc(t: ^T, msg := "", loc := #caller_location) {
 	if msg != "" {
-		error(t, "FAIL:", msg, loc=loc)
+		pkg_log.error("FAIL:", msg, location=loc)
 	} else {
-		error(t, "FAIL", loc=loc)
+		pkg_log.error("FAIL", location=loc)
 	}
-	t.error_count += 1
 	if t._fail_now != nil {
 		t._fail_now()
 	}
@@ -69,32 +81,34 @@ failed :: proc(t: ^T) -> bool {
 	return t.error_count != 0
 }
 
+@(deprecated="prefer `log.info`")
 log :: proc(t: ^T, args: ..any, loc := #caller_location) {
-	fmt.wprintln(t.w, ..args)
+	pkg_log.info(..args, location = loc)
 }
 
+@(deprecated="prefer `log.infof`")
 logf :: proc(t: ^T, format: string, args: ..any, loc := #caller_location) {
-	fmt.wprintf(t.w, format, ..args)
-	fmt.wprintln(t.w)
+	pkg_log.infof(format, ..args, location = loc)
 }
 
 
-// cleanup registers a procedure and user_data, which will be called when the test, and all its subtests, complete
-// cleanup procedures will be called in LIFO (last added, first called) order.
+// cleanup registers a procedure and user_data, which will be called when the test, and all its subtests, complete.
+// Cleanup procedures will be called in LIFO (last added, first called) order.
+// Each procedure will use a copy of the context at the time of registering.
 cleanup :: proc(t: ^T, procedure: proc(rawptr), user_data: rawptr) {
-	append(&t.cleanups, Internal_Cleanup{procedure, user_data})
+	append(&t.cleanups, Internal_Cleanup{procedure, user_data, context})
 }
 
 expect :: proc(t: ^T, ok: bool, msg: string = "", loc := #caller_location) -> bool {
 	if !ok {
-		error(t, msg, loc=loc)
+		pkg_log.error(msg, location=loc)
 	}
 	return ok
 }
 
 expectf :: proc(t: ^T, ok: bool, format: string, args: ..any, loc := #caller_location) -> bool {
 	if !ok {
-		errorf(t, format, ..args, loc=loc)
+		pkg_log.errorf(format, ..args, location=loc)
 	}
 	return ok
 }
@@ -102,12 +116,15 @@ expectf :: proc(t: ^T, ok: bool, format: string, args: ..any, loc := #caller_loc
 expect_value :: proc(t: ^T, value, expected: $T, loc := #caller_location) -> bool where intrinsics.type_is_comparable(T) {
 	ok := value == expected || reflect.is_nil(value) && reflect.is_nil(expected)
 	if !ok {
-		errorf(t, "expected %v, got %v", expected, value, loc=loc)
+		pkg_log.errorf("expected %v, got %v", expected, value, location=loc)
 	}
 	return ok
 }
 
 
 set_fail_timeout :: proc(t: ^T, duration: time.Duration, loc := #caller_location) {
-	_fail_timeout(t, duration, loc)
+	chan.send(t.channel, Event_Set_Fail_Timeout {
+		at_time = time.time_add(time.now(), duration),
+		location = loc,
+	})
 }

+ 0 - 2
core/text/i18n/qt_linguist.odin

@@ -162,8 +162,6 @@ parse_qt_linguist_file :: proc(filename: string, options := DEFAULT_PARSE_OPTION
 	context.allocator = allocator
 
 	data, data_ok := os.read_entire_file(filename)
-	defer delete(data)
-
 	if !data_ok { return {}, .File_Error }
 
 	return parse_qt_linguist_from_bytes(data, options, pluralizer, allocator)

+ 118 - 15
core/thread/thread_pool.odin

@@ -44,6 +44,29 @@ Pool :: struct {
 	tasks_done: [dynamic]Task,
 }
 
+Pool_Thread_Data :: struct {
+	pool: ^Pool,
+	task: Task,
+}
+
+@(private="file")
+pool_thread_runner :: proc(t: ^Thread) {
+	data := cast(^Pool_Thread_Data)t.data
+	pool := data.pool
+
+	for intrinsics.atomic_load(&pool.is_running) {
+		sync.wait(&pool.sem_available)
+
+		if task, ok := pool_pop_waiting(pool); ok {
+			data.task = task
+			pool_do_work(pool, task)
+			data.task = {}
+		}
+	}
+
+	sync.post(&pool.sem_available, 1)
+}
+
 // Once initialized, the pool's memory address is not allowed to change until
 // it is destroyed. 
 //
@@ -58,21 +81,11 @@ pool_init :: proc(pool: ^Pool, allocator: mem.Allocator, thread_count: int) {
 	pool.is_running = true
 
 	for _, i in pool.threads {
-		t := create(proc(t: ^Thread) {
-			pool := (^Pool)(t.data)
-
-			for intrinsics.atomic_load(&pool.is_running) {
-				sync.wait(&pool.sem_available)
-
-				if task, ok := pool_pop_waiting(pool); ok {
-					pool_do_work(pool, task)
-				}
-			}
-
-			sync.post(&pool.sem_available, 1)
-		})
+		t := create(pool_thread_runner)
+		data := new(Pool_Thread_Data)
+		data.pool = pool
 		t.user_index = i
-		t.data = pool
+		t.data = data
 		pool.threads[i] = t
 	}
 }
@@ -82,6 +95,8 @@ pool_destroy :: proc(pool: ^Pool) {
 	delete(pool.tasks_done)
 
 	for &t in pool.threads {
+		data := cast(^Pool_Thread_Data)t.data
+		free(data, pool.allocator)
 		destroy(t)
 	}
 
@@ -103,7 +118,7 @@ pool_join :: proc(pool: ^Pool) {
 
 	yield()
 
-started_count: int
+	started_count: int
 	for started_count < len(pool.threads) {
 		started_count = 0
 		for t in pool.threads {
@@ -138,6 +153,94 @@ pool_add_task :: proc(pool: ^Pool, allocator: mem.Allocator, procedure: Task_Pro
 	sync.post(&pool.sem_available, 1)
 }
 
+// Forcibly stop a running task by its user index.
+//
+// This will terminate the underlying thread. Ideally, you should use some
+// means of communication to stop a task, as thread termination may leave
+// resources unclaimed.
+//
+// The thread will be restarted to accept new tasks.
+//
+// Returns true if the task was found and terminated.
+pool_stop_task :: proc(pool: ^Pool, user_index: int, exit_code: int = 1) -> bool {
+	sync.guard(&pool.mutex)
+
+	for t, i in pool.threads {
+		data := cast(^Pool_Thread_Data)t.data
+		if data.task.user_index == user_index && data.task.procedure != nil {
+			terminate(t, exit_code)
+
+			append(&pool.tasks_done, data.task)
+			intrinsics.atomic_add(&pool.num_done, 1)
+			intrinsics.atomic_sub(&pool.num_outstanding, 1)
+			intrinsics.atomic_sub(&pool.num_in_processing, 1)
+
+			destroy(t)
+
+			replacement := create(pool_thread_runner)
+			replacement.user_index = t.user_index
+			replacement.data = data
+			data.task = {}
+			pool.threads[i] = replacement
+
+			start(replacement)
+			return true
+		}
+	}
+
+	return false
+}
+
+// Forcibly stop all running tasks.
+//
+// The same notes from `pool_stop_task` apply here.
+pool_stop_all_tasks :: proc(pool: ^Pool, exit_code: int = 1) {
+	sync.guard(&pool.mutex)
+
+	for t, i in pool.threads {
+		data := cast(^Pool_Thread_Data)t.data
+		if data.task.procedure != nil {
+			terminate(t, exit_code)
+
+			append(&pool.tasks_done, data.task)
+			intrinsics.atomic_add(&pool.num_done, 1)
+			intrinsics.atomic_sub(&pool.num_outstanding, 1)
+			intrinsics.atomic_sub(&pool.num_in_processing, 1)
+
+			destroy(t)
+
+			replacement := create(pool_thread_runner)
+			replacement.user_index = t.user_index
+			replacement.data = data
+			data.task = {}
+			pool.threads[i] = replacement
+
+			start(replacement)
+		}
+	}
+}
+
+// Force the pool to stop all of its threads and put it into a state where
+// it will no longer run any more tasks.
+//
+// The pool must still be destroyed after this.
+pool_shutdown :: proc(pool: ^Pool, exit_code: int = 1) {
+	intrinsics.atomic_store(&pool.is_running, false)
+	sync.guard(&pool.mutex)
+
+	for t in pool.threads {
+		terminate(t, exit_code)
+
+		data := cast(^Pool_Thread_Data)t.data
+		if data.task.procedure != nil {
+			append(&pool.tasks_done, data.task)
+			intrinsics.atomic_add(&pool.num_done, 1)
+			intrinsics.atomic_sub(&pool.num_outstanding, 1)
+			intrinsics.atomic_sub(&pool.num_in_processing, 1)
+		}
+	}
+}
+
 // Number of tasks waiting to be processed. Only informational, mostly for
 // debugging. Don't rely on this value being consistent with other num_*
 // values.

+ 0 - 1
src/build_settings.cpp

@@ -893,7 +893,6 @@ struct BuildContext {
 	u32 cmd_doc_flags;
 	Array<String> extra_packages;
 
-	StringSet test_names;
 	bool      test_all_packages;
 
 	gbAffinity affinity;

+ 0 - 29
src/checker.cpp

@@ -5852,35 +5852,6 @@ gb_internal void remove_neighbouring_duplicate_entires_from_sorted_array(Array<E
 gb_internal void check_test_procedures(Checker *c) {
 	array_sort(c->info.testing_procedures, init_procedures_cmp);
 	remove_neighbouring_duplicate_entires_from_sorted_array(&c->info.testing_procedures);
-
-	if (build_context.test_names.entries.count == 0) {
-		return;
-	}
-
-	AstPackage *pkg = c->info.init_package;
-	Scope *s = pkg->scope;
-
-	for (String const &name : build_context.test_names) {
-		Entity *e = scope_lookup(s, name);
-		if (e == nullptr) {
-			Token tok = {};
-			if (pkg->files.count != 0) {
-				tok = pkg->files[0]->tokens[0];
-			}
-			error(tok, "Unable to find the test '%.*s' in 'package %.*s' ", LIT(name), LIT(pkg->name));
-		}
-	}
-
-	for (isize i = 0; i < c->info.testing_procedures.count; /**/) {
-		Entity *e = c->info.testing_procedures[i];
-		String name = e->token.string;
-		if (!string_set_exists(&build_context.test_names, name)) {
-			array_ordered_remove(&c->info.testing_procedures, i);
-		} else {
-			i += 1;
-		}
-	}
-
 }
 
 

+ 0 - 24
src/main.cpp

@@ -276,8 +276,6 @@ enum BuildFlagKind {
 	BuildFlag_RelocMode,
 	BuildFlag_DisableRedZone,
 
-	BuildFlag_TestName,
-
 	BuildFlag_DisallowDo,
 	BuildFlag_DefaultToNilAllocator,
 	BuildFlag_DefaultToPanicAllocator,
@@ -471,8 +469,6 @@ gb_internal bool parse_build_flags(Array<String> args) {
 	add_flag(&build_flags, BuildFlag_RelocMode,               str_lit("reloc-mode"),                BuildFlagParam_String,  Command__does_build);
 	add_flag(&build_flags, BuildFlag_DisableRedZone,          str_lit("disable-red-zone"),          BuildFlagParam_None,    Command__does_build);
 
-	add_flag(&build_flags, BuildFlag_TestName,                str_lit("test-name"),                 BuildFlagParam_String,  Command_test);
-
 	add_flag(&build_flags, BuildFlag_DisallowDo,              str_lit("disallow-do"),               BuildFlagParam_None,    Command__does_check);
 	add_flag(&build_flags, BuildFlag_DefaultToNilAllocator,   str_lit("default-to-nil-allocator"),  BuildFlagParam_None,    Command__does_check);
 	add_flag(&build_flags, BuildFlag_DefaultToPanicAllocator, str_lit("default-to-panic-allocator"),BuildFlagParam_None,    Command__does_check);
@@ -1119,21 +1115,6 @@ gb_internal bool parse_build_flags(Array<String> args) {
 						case BuildFlag_DisableRedZone:
 							build_context.disable_red_zone = true;
 							break;
-						case BuildFlag_TestName: {
-							GB_ASSERT(value.kind == ExactValue_String);
-							{
-								String name = value.value_string;
-								if (!string_is_valid_identifier(name)) {
-									gb_printf_err("Test name '%.*s' must be a valid identifier\n", LIT(name));
-									bad_flags = true;
-									break;
-								}
-								string_set_add(&build_context.test_names, name);
-
-								// NOTE(bill): Allow for multiple -test-name
-								continue;
-							}
-						}
 						case BuildFlag_DisallowDo:
 							build_context.disallow_do = true;
 							break;
@@ -1962,10 +1943,6 @@ gb_internal void print_show_help(String const arg0, String const &command) {
 	}
 
 	if (test_only) {
-		print_usage_line(1, "-test-name:<string>");
-		print_usage_line(2, "Runs specific test only by name.");
-		print_usage_line(0, "");
-
 		print_usage_line(1, "-all-packages");
 		print_usage_line(2, "Tests all packages imported into the given initial package.");
 		print_usage_line(0, "");
@@ -2489,7 +2466,6 @@ int main(int arg_count, char const **arg_ptr) {
 	TIME_SECTION("init args");
 	map_init(&build_context.defined_values);
 	build_context.extra_packages.allocator = heap_allocator();
-	string_set_init(&build_context.test_names);
 
 	Array<String> args = setup_args(arg_count, arg_ptr);
 

+ 14 - 0
tests/benchmark/Makefile

@@ -0,0 +1,14 @@
+ODIN=../../odin
+COMMON=-no-bounds-check -vet -strict-style -define:ODIN_TEST_FANCY=false
+
+all: crypto_bench \
+     hash_bench
+
+crypto_bench:
+	$(ODIN) test crypto $(COMMON) -o:speed -out:bench_crypto
+
+hash_bench:
+	$(ODIN) test hash $(COMMON) -o:speed -out:bench_hash
+
+clean:
+	rm bench_*

+ 13 - 0
tests/benchmark/build.bat

@@ -0,0 +1,13 @@
+@echo off
+set COMMON=-no-bounds-check -vet -strict-style -define:ODIN_TEST_FANCY=false
+set PATH_TO_ODIN==..\..\odin
+
+echo ---
+echo Running core:crypto benchmarks
+echo ---
+%PATH_TO_ODIN% test crypto %COMMON% -o:speed -out:bench_crypto.exe || exit /b
+
+echo ---
+echo Running core:hash benchmarks
+echo ---
+%PATH_TO_ODIN% test hash %COMMON% -o:speed -out:bench_hash.exe || exit /b

+ 356 - 0
tests/benchmark/crypto/benchmark_crypto.odin

@@ -0,0 +1,356 @@
+package benchmark_core_crypto
+
+import "base:runtime"
+import "core:encoding/hex"
+import "core:fmt"
+import "core:log"
+import "core:strings"
+import "core:testing"
+import "core:time"
+
+import "core:crypto/aes"
+import "core:crypto/chacha20"
+import "core:crypto/chacha20poly1305"
+import "core:crypto/ed25519"
+import "core:crypto/poly1305"
+import "core:crypto/x25519"
+
+// Cryptographic primitive benchmarks.
+
+@(test)
+benchmark_crypto :: proc(t: ^testing.T) {
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
+
+	str: strings.Builder
+	strings.builder_init(&str, context.allocator)
+	defer {
+		log.info(strings.to_string(str))
+		strings.builder_destroy(&str)
+	}
+
+	{
+		name := "ChaCha20 64 bytes"
+		options := &time.Benchmark_Options {
+			rounds = 1_000,
+			bytes = 64,
+			setup = _setup_sized_buf,
+			bench = _benchmark_chacha20,
+			teardown = _teardown_sized_buf,
+		}
+
+		err := time.benchmark(options, context.allocator)
+		testing.expect(t, err == nil, name)
+		benchmark_print(&str, name, options)
+
+		name = "ChaCha20 1024 bytes"
+		options.bytes = 1024
+		err = time.benchmark(options, context.allocator)
+		testing.expect(t, err == nil, name)
+		benchmark_print(&str, name, options)
+
+		name = "ChaCha20 65536 bytes"
+		options.bytes = 65536
+		err = time.benchmark(options, context.allocator)
+		testing.expect(t, err == nil, name)
+		benchmark_print(&str, name, options)
+	}
+	{
+		name := "Poly1305 64 zero bytes"
+		options := &time.Benchmark_Options {
+			rounds = 1_000,
+			bytes = 64,
+			setup = _setup_sized_buf,
+			bench = _benchmark_poly1305,
+			teardown = _teardown_sized_buf,
+		}
+
+		err := time.benchmark(options, context.allocator)
+		testing.expect(t, err == nil, name)
+		benchmark_print(&str, name, options)
+
+		name = "Poly1305 1024 zero bytes"
+		options.bytes = 1024
+		err = time.benchmark(options, context.allocator)
+		testing.expect(t, err == nil, name)
+		benchmark_print(&str, name, options)
+	}
+	{
+		name := "chacha20poly1305 64 bytes"
+		options := &time.Benchmark_Options {
+			rounds = 1_000,
+			bytes = 64,
+			setup = _setup_sized_buf,
+			bench = _benchmark_chacha20poly1305,
+			teardown = _teardown_sized_buf,
+		}
+
+		err := time.benchmark(options, context.allocator)
+		testing.expect(t, err == nil, name)
+		benchmark_print(&str, name, options)
+
+		name = "chacha20poly1305 1024 bytes"
+		options.bytes = 1024
+		err = time.benchmark(options, context.allocator)
+		testing.expect(t, err == nil, name)
+		benchmark_print(&str, name, options)
+
+		name = "chacha20poly1305 65536 bytes"
+		options.bytes = 65536
+		err = time.benchmark(options, context.allocator)
+		testing.expect(t, err == nil, name)
+		benchmark_print(&str, name, options)
+	}
+	{
+		name := "AES256-GCM 64 bytes"
+		options := &time.Benchmark_Options {
+			rounds = 1_000,
+			bytes = 64,
+			setup = _setup_sized_buf,
+			bench = _benchmark_aes256_gcm,
+			teardown = _teardown_sized_buf,
+		}
+
+		key := [aes.KEY_SIZE_256]byte {
+			0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
+			0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
+			0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
+			0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
+		}
+		ctx: aes.Context_GCM
+		aes.init_gcm(&ctx, key[:])
+
+		context.user_ptr = &ctx
+
+		err := time.benchmark(options, context.allocator)
+		testing.expect(t, err == nil, name)
+		benchmark_print(&str, name, options)
+
+		name = "AES256-GCM 1024 bytes"
+		options.bytes = 1024
+		err = time.benchmark(options, context.allocator)
+		testing.expect(t, err == nil, name)
+		benchmark_print(&str, name, options)
+
+		name = "AES256-GCM 65536 bytes"
+		options.bytes = 65536
+		err = time.benchmark(options, context.allocator)
+		testing.expect(t, err == nil, name)
+		benchmark_print(&str, name, options)
+	}
+	{
+		iters :: 10000
+
+		priv_str := "cafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe"
+		priv_bytes, _ := hex.decode(transmute([]byte)(priv_str), context.temp_allocator)
+		priv_key: ed25519.Private_Key
+		start := time.now()
+		for i := 0; i < iters; i = i + 1 {
+			ok := ed25519.private_key_set_bytes(&priv_key, priv_bytes)
+			assert(ok, "private key should deserialize")
+		}
+		elapsed := time.since(start)
+		fmt.sbprintfln(&str,
+			"ed25519.private_key_set_bytes: ~%f us/op",
+			time.duration_microseconds(elapsed) / iters,
+		)
+
+		pub_bytes := priv_key._pub_key._b[:] // "I know what I am doing"
+		pub_key: ed25519.Public_Key
+		start = time.now()
+		for i := 0; i < iters; i = i + 1 {
+			ok := ed25519.public_key_set_bytes(&pub_key, pub_bytes[:])
+			assert(ok, "public key should deserialize")
+		}
+		elapsed = time.since(start)
+		fmt.sbprintfln(&str,
+			"ed25519.public_key_set_bytes: ~%f us/op",
+			time.duration_microseconds(elapsed) / iters,
+		)
+
+		msg := "Got a job for you, 621."
+		sig_bytes: [ed25519.SIGNATURE_SIZE]byte
+		msg_bytes := transmute([]byte)(msg)
+		start = time.now()
+		for i := 0; i < iters; i = i + 1 {
+			ed25519.sign(&priv_key, msg_bytes, sig_bytes[:])
+		}
+		elapsed = time.since(start)
+		fmt.sbprintfln(&str,
+		    "ed25519.sign: ~%f us/op",
+		    time.duration_microseconds(elapsed) / iters,
+		)
+
+		start = time.now()
+		for i := 0; i < iters; i = i + 1 {
+			ok := ed25519.verify(&pub_key, msg_bytes, sig_bytes[:])
+			assert(ok, "signature should validate")
+		}
+		elapsed = time.since(start)
+		fmt.sbprintfln(&str,
+			"ed25519.verify: ~%f us/op",
+			time.duration_microseconds(elapsed) / iters,
+		)
+	}
+	{
+		point_str := "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"
+		scalar_str := "cafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe"
+
+		point, _ := hex.decode(transmute([]byte)(point_str), context.temp_allocator)
+		scalar, _ := hex.decode(transmute([]byte)(scalar_str), context.temp_allocator)
+		out: [x25519.POINT_SIZE]byte = ---
+
+		iters :: 10000
+		start := time.now()
+		for i := 0; i < iters; i = i + 1 {
+			x25519.scalarmult(out[:], scalar[:], point[:])
+		}
+		elapsed := time.since(start)
+
+		fmt.sbprintfln(&str,
+			"x25519.scalarmult: ~%f us/op",
+			time.duration_microseconds(elapsed) / iters,
+		)
+	}
+}
+
+@(private)
+_setup_sized_buf :: proc(
+	options: ^time.Benchmark_Options,
+	allocator := context.allocator,
+) -> (
+	err: time.Benchmark_Error,
+) {
+	assert(options != nil)
+
+	options.input = make([]u8, options.bytes, allocator)
+	return nil if len(options.input) == options.bytes else .Allocation_Error
+}
+
+@(private)
+_teardown_sized_buf :: proc(
+	options: ^time.Benchmark_Options,
+	allocator := context.allocator,
+) -> (
+	err: time.Benchmark_Error,
+) {
+	assert(options != nil)
+
+	delete(options.input)
+	return nil
+}
+
+@(private)
+_benchmark_chacha20 :: proc(
+	options: ^time.Benchmark_Options,
+	allocator := context.allocator,
+) -> (
+	err: time.Benchmark_Error,
+) {
+	buf := options.input
+	key := [chacha20.KEY_SIZE]byte {
+		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
+		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
+		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
+		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
+	}
+	nonce := [chacha20.NONCE_SIZE]byte {
+		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		0x00, 0x00, 0x00, 0x00,
+	}
+
+	ctx: chacha20.Context = ---
+	chacha20.init(&ctx, key[:], nonce[:])
+
+	for _ in 0 ..= options.rounds {
+		chacha20.xor_bytes(&ctx, buf, buf)
+	}
+	options.count = options.rounds
+	options.processed = options.rounds * options.bytes
+	return nil
+}
+
+@(private)
+_benchmark_poly1305 :: proc(
+	options: ^time.Benchmark_Options,
+	allocator := context.allocator,
+) -> (
+	err: time.Benchmark_Error,
+) {
+	buf := options.input
+	key := [poly1305.KEY_SIZE]byte {
+		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
+		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
+		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
+		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
+	}
+
+	tag: [poly1305.TAG_SIZE]byte = ---
+	for _ in 0 ..= options.rounds {
+		poly1305.sum(tag[:], buf, key[:])
+	}
+	options.count = options.rounds
+	options.processed = options.rounds * options.bytes
+	//options.hash      = u128(h)
+	return nil
+}
+
+@(private)
+_benchmark_chacha20poly1305 :: proc(
+	options: ^time.Benchmark_Options,
+	allocator := context.allocator,
+) -> (
+	err: time.Benchmark_Error,
+) {
+	buf := options.input
+	key := [chacha20.KEY_SIZE]byte {
+		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
+		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
+		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
+		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
+	}
+	nonce := [chacha20.NONCE_SIZE]byte {
+		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		0x00, 0x00, 0x00, 0x00,
+	}
+
+	tag: [chacha20poly1305.TAG_SIZE]byte = ---
+
+	for _ in 0 ..= options.rounds {
+		chacha20poly1305.encrypt(buf, tag[:], key[:], nonce[:], nil, buf)
+	}
+	options.count = options.rounds
+	options.processed = options.rounds * options.bytes
+	return nil
+}
+
+_benchmark_aes256_gcm :: proc(
+	options: ^time.Benchmark_Options,
+	allocator := context.allocator,
+) -> (
+	err: time.Benchmark_Error,
+) {
+	buf := options.input
+	nonce: [aes.GCM_NONCE_SIZE]byte
+	tag: [aes.GCM_TAG_SIZE]byte = ---
+
+	ctx := transmute(^aes.Context_GCM)context.user_ptr
+
+	for _ in 0 ..= options.rounds {
+		aes.seal_gcm(ctx, buf, tag[:], nonce[:], nil, buf)
+	}
+	options.count = options.rounds
+	options.processed = options.rounds * options.bytes
+	return nil
+}
+
+@(private)
+benchmark_print :: proc(str: ^strings.Builder, name: string, options: ^time.Benchmark_Options, loc := #caller_location) {
+	fmt.sbprintfln(str, "[%v] %v rounds, %v bytes processed in %v ns\n\t\t%5.3f rounds/s, %5.3f MiB/s\n",
+		name,
+		options.rounds,
+		options.processed,
+		time.duration_nanoseconds(options.duration),
+		options.rounds_per_second,
+		options.megabytes_per_second,
+	)
+}

+ 218 - 0
tests/benchmark/hash/benchmark_hash.odin

@@ -0,0 +1,218 @@
+package benchmark_core_hash
+
+import "core:fmt"
+import "core:hash/xxhash"
+import "base:intrinsics"
+import "core:strings"
+import "core:testing"
+import "core:time"
+
+@(test)
+benchmark_hash :: proc(t: ^testing.T) {
+	str: strings.Builder
+	strings.builder_init(&str, context.allocator)
+	defer {
+		fmt.println(strings.to_string(str))
+		strings.builder_destroy(&str)
+	}
+
+	{
+		name := "XXH32 100 zero bytes"
+		options := &time.Benchmark_Options{
+			rounds   = 1_000,
+			bytes    = 100,
+			setup    = setup_xxhash,
+			bench    = benchmark_xxh32,
+			teardown = teardown_xxhash,
+		}
+		err := time.benchmark(options, context.allocator)
+		testing.expectf(t, err == nil, "%s failed with err %v", name, err)
+		hash := u128(0x85f6413c)
+		testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash)
+		benchmark_print(&str, name, options)
+	}
+	{
+		name := "XXH32 1 MiB zero bytes"
+		options := &time.Benchmark_Options{
+			rounds   = 1_000,
+			bytes    = 1_048_576,
+			setup    = setup_xxhash,
+			bench    = benchmark_xxh32,
+			teardown = teardown_xxhash,
+		}
+		err := time.benchmark(options, context.allocator)
+		testing.expectf(t, err == nil, "%s failed with err %v", name, err)
+		hash := u128(0x9430f97f)
+		testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash)
+		benchmark_print(&str, name, options)
+	}
+	{
+		name := "XXH64 100 zero bytes"
+		options := &time.Benchmark_Options{
+			rounds   = 1_000,
+			bytes    = 100,
+			setup    = setup_xxhash,
+			bench    = benchmark_xxh64,
+			teardown = teardown_xxhash,
+		}
+		err := time.benchmark(options, context.allocator)
+		testing.expectf(t, err == nil, "%s failed with err %v", name, err)
+		hash := u128(0x17bb1103c92c502f)
+		testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash)
+		benchmark_print(&str, name, options)
+	}
+	{
+		name := "XXH64 1 MiB zero bytes"
+		options := &time.Benchmark_Options{
+			rounds   = 1_000,
+			bytes    = 1_048_576,
+			setup    = setup_xxhash,
+			bench    = benchmark_xxh64,
+			teardown = teardown_xxhash,
+		}
+		err := time.benchmark(options, context.allocator)
+		testing.expectf(t, err == nil, "%s failed with err %v", name, err)
+		hash := u128(0x87d2a1b6e1163ef1)
+		testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash)
+		benchmark_print(&str, name, options)
+	}
+	{
+		name := "XXH3_64 100 zero bytes"
+		options := &time.Benchmark_Options{
+			rounds   = 1_000,
+			bytes    = 100,
+			setup    = setup_xxhash,
+			bench    = benchmark_xxh3_64,
+			teardown = teardown_xxhash,
+		}
+		err := time.benchmark(options, context.allocator)
+		testing.expectf(t, err == nil, "%s failed with err %v", name, err)
+		hash := u128(0x801fedc74ccd608c)
+		testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash)
+		benchmark_print(&str, name, options)
+	}
+	{
+		name := "XXH3_64 1 MiB zero bytes"
+		options := &time.Benchmark_Options{
+			rounds   = 1_000,
+			bytes    = 1_048_576,
+			setup    = setup_xxhash,
+			bench    = benchmark_xxh3_64,
+			teardown = teardown_xxhash,
+		}
+		err := time.benchmark(options, context.allocator)
+		testing.expectf(t, err == nil, "%s failed with err %v", name, err)
+		hash := u128(0x918780b90550bf34)
+		testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash)
+		benchmark_print(&str, name, options)
+	}
+	{
+		name := "XXH3_128 100 zero bytes"
+		options := &time.Benchmark_Options{
+			rounds   = 1_000,
+			bytes    = 100,
+			setup    = setup_xxhash,
+			bench    = benchmark_xxh3_128,
+			teardown = teardown_xxhash,
+		}
+		err := time.benchmark(options, context.allocator)
+		testing.expectf(t, err == nil, "%s failed with err %v", name, err)
+		hash := u128(0x6ba30a4e9dffe1ff801fedc74ccd608c)
+		testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash)
+		benchmark_print(&str, name, options)
+	}
+	{
+		name := "XXH3_128 1 MiB zero bytes"
+		options := &time.Benchmark_Options{
+			rounds   = 1_000,
+			bytes    = 1_048_576,
+			setup    = setup_xxhash,
+			bench    = benchmark_xxh3_128,
+			teardown = teardown_xxhash,
+		}
+		err := time.benchmark(options, context.allocator)
+		testing.expectf(t, err == nil, "%s failed with err %v", name, err)
+		hash := u128(0xb6ef17a3448492b6918780b90550bf34)
+		testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash)
+		benchmark_print(&str, name, options)
+	}
+}
+
+// Benchmarks
+
+setup_xxhash :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) {
+	assert(options != nil)
+
+	options.input = make([]u8, options.bytes, allocator)
+	return nil if len(options.input) == options.bytes else .Allocation_Error
+}
+
+teardown_xxhash :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) {
+	assert(options != nil)
+
+	delete(options.input)
+	return nil
+}
+
+benchmark_xxh32 :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) {
+	buf := options.input
+
+	h: u32
+	for _ in 0..=options.rounds {
+		h = xxhash.XXH32(buf)
+	}
+	options.count     = options.rounds
+	options.processed = options.rounds * options.bytes
+	options.hash      = u128(h)
+	return nil
+}
+
+benchmark_xxh64 :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) {
+	buf := options.input
+
+	h: u64
+	for _ in 0..=options.rounds {
+		h = xxhash.XXH64(buf)
+	}
+	options.count     = options.rounds
+	options.processed = options.rounds * options.bytes
+	options.hash      = u128(h)
+	return nil
+}
+
+benchmark_xxh3_64 :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) {
+	buf := options.input
+
+	h: u64
+	for _ in 0..=options.rounds {
+		h = xxhash.XXH3_64(buf)
+	}
+	options.count     = options.rounds
+	options.processed = options.rounds * options.bytes
+	options.hash      = u128(h)
+	return nil
+}
+
+benchmark_xxh3_128 :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) {
+	buf := options.input
+
+	h: u128
+	for _ in 0..=options.rounds {
+		h = xxhash.XXH3_128(buf)
+	}
+	options.count     = options.rounds
+	options.processed = options.rounds * options.bytes
+	options.hash      = h
+	return nil
+}
+
+benchmark_print :: proc(str: ^strings.Builder, name: string, options: ^time.Benchmark_Options, loc := #caller_location) {
+	fmt.sbprintfln(str, "[%v] %v rounds, %v bytes processed in %v ns\n\t\t%5.3f rounds/s, %5.3f MiB/s\n",
+		name,
+		options.rounds,
+		options.processed,
+		time.duration_nanoseconds(options.duration),
+		options.rounds_per_second,
+		options.megabytes_per_second,
+	)
+}

+ 0 - 81
tests/common/common.odin

@@ -1,81 +0,0 @@
-// Boilerplate for tests
-package common
-
-import "core:testing"
-import "core:fmt"
-import "core:os"
-import "core:strings"
-
-TEST_count := 0
-TEST_fail  := 0
-
-when ODIN_TEST {
-	expect  :: testing.expect
-	log     :: testing.log
-	errorf  :: testing.errorf
-} else {
-	expect  :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
-		TEST_count += 1
-		if !condition {
-			TEST_fail += 1
-			fmt.printf("[%v:%s] FAIL %v\n", loc, loc.procedure, message)
-			return
-		}
-	}
-	errorf  :: proc(t: ^testing.T, message: string, args: ..any, loc := #caller_location) {
-		TEST_fail += 1
-		fmt.printf("[%v:%s] Error %v\n", loc, loc.procedure, fmt.tprintf(message, ..args))
-		return
-	}
-	log     :: proc(t: ^testing.T, v: any, loc := #caller_location) {
-		fmt.printf("[%v] ", loc)
-		fmt.printf("log: %v\n", v)
-	}
-}
-
-report :: proc(t: ^testing.T) {
-	if TEST_fail > 0 {
-		if TEST_fail > 1 {
-			fmt.printf("%v/%v tests successful, %v tests failed.\n", TEST_count - TEST_fail, TEST_count, TEST_fail)
-		} else {
-			fmt.printf("%v/%v tests successful, 1 test failed.\n", TEST_count - TEST_fail, TEST_count)
-		}
-		os.exit(1)
-	} else {
-		fmt.printf("%v/%v tests successful.\n", TEST_count, TEST_count)
-	}
-}
-
-// Returns absolute path to `sub_path` where `sub_path` is within the "tests/" sub-directory of the Odin project root
-// and we're being run from the Odin project root or from a sub-directory of "tests/"
-// e.g. get_data_path("assets/blah") will return "/Odin_root/tests/assets/blah" if run within "/Odin_root",
-// "/Odin_root/tests" or "/Odin_root/tests/subdir" etc
-get_data_path :: proc(t: ^testing.T, sub_path: string) -> (data_path: string) {
-
-	cwd := os.get_current_directory()
-	defer delete(cwd)
-
-	when ODIN_OS == .Windows {
-		norm, was_allocation := strings.replace_all(cwd, "\\", "/")
-		if !was_allocation {
-			norm = strings.clone(norm)
-		}
-		defer delete(norm)
-	} else {
-		norm := cwd
-	}
-
-	last_index := strings.last_index(norm, "/tests/")
-	if last_index == -1 {
-		len := len(norm)
-		if len >= 6 && norm[len-6:] == "/tests" {
-			data_path = fmt.tprintf("%s/%s", norm, sub_path)
-		} else {
-			data_path = fmt.tprintf("%s/tests/%s", norm, sub_path)
-		}
-	} else {
-		data_path = fmt.tprintf("%s/tests/%s", norm[:last_index], sub_path)
-	}
-
-	return data_path
-}

+ 55 - 50
tests/core/Makefile

@@ -1,16 +1,15 @@
 ODIN=../../odin
 PYTHON=$(shell which python3)
-COMMON=-vet -strict-style
-COLLECTION=-collection:tests=..
+COMMON=-no-bounds-check -vet -strict-style -define:ODIN_TEST_FANCY=false
 
 all: all_bsd \
      net_test
 
-all_bsd: c_libc_test \
+all_bsd: download_test_assets \
+         c_libc_test \
          compress_test \
          container_test \
          crypto_test \
-         download_test_assets \
          encoding_test \
          filepath_test \
          fmt_test \
@@ -21,86 +20,92 @@ all_bsd: c_libc_test \
          match_test \
          math_test \
          noise_test \
+         odin_test \
          os_exit_test \
          reflect_test \
+         runtime_test \
          slice_test \
          strings_test \
          thread_test \
-         runtime_test \
-         time_test \
-         fmt_test
+         time_test
 
 download_test_assets:
 	$(PYTHON) download_assets.py
 
-image_test:
-	$(ODIN) run image $(COMMON) -out:test_core_image
+c_libc_test:
+	$(ODIN) test c/libc $(COMMON) -out:test_core_libc
 
 compress_test:
-	$(ODIN) run compress $(COMMON) -out:test_core_compress
+	$(ODIN) test compress $(COMMON) -out:test_core_compress
 
 container_test:
-	$(ODIN) run container $(COMMON) $(COLLECTION) -out:test_core_container
+	$(ODIN) test container $(COMMON) -out:test_core_container
 
-strings_test:
-	$(ODIN) run strings $(COMMON) -out:test_core_strings
+crypto_test:
+	$(ODIN) test crypto $(COMMON) -o:speed -out:test_crypto
+
+encoding_test:
+	$(ODIN) test encoding/base64 $(COMMON) -out:test_base64
+	$(ODIN) test encoding/cbor   $(COMMON) -out:test_cbor
+	$(ODIN) test encoding/hex    $(COMMON) -out:test_hex
+	$(ODIN) test encoding/hxa    $(COMMON) -out:test_hxa
+	$(ODIN) test encoding/json   $(COMMON) -out:test_json
+	$(ODIN) test encoding/varint $(COMMON) -out:test_varint
+	$(ODIN) test encoding/xml    $(COMMON) -out:test_xml
+
+filepath_test:
+	$(ODIN) test path/filepath $(COMMON) -out:test_core_filepath
+
+fmt_test:
+	$(ODIN) test fmt $(COMMON) -out:test_core_fmt
 
 hash_test:
-	$(ODIN) run hash $(COMMON) -o:speed -no-bounds-check -out:test_hash
+	$(ODIN) test hash $(COMMON) -o:speed -out:test_hash
 
-crypto_test:
-	$(ODIN) run crypto $(COMMON) $(COLLECTION) -o:speed -no-bounds-check -out:test_crypto
+image_test:
+	$(ODIN) test image $(COMMON) -out:test_core_image
 
-noise_test:
-	$(ODIN) run math/noise $(COMMON) -out:test_noise
+i18n_test:
+	$(ODIN) test text/i18n $(COMMON) -out:test_core_i18n
 
-encoding_test:
-	$(ODIN) run encoding/hxa    $(COMMON) $(COLLECTION) -out:test_hxa
-	$(ODIN) run encoding/json   $(COMMON) -out:test_json
-	$(ODIN) run encoding/varint $(COMMON) -out:test_varint
-	$(ODIN) run encoding/xml    $(COMMON) -out:test_xml
-	$(ODIN) run encoding/cbor   $(COMMON) -out:test_cbor
-	$(ODIN) run encoding/hex    $(COMMON) -out:test_hex
-	$(ODIN) run encoding/base64 $(COMMON) -out:test_base64
+match_test:
+	$(ODIN) test text/match $(COMMON) -out:test_core_match
 
 math_test:
-	$(ODIN) run math $(COMMON) $(COLLECTION) -out:test_core_math
+	$(ODIN) test math $(COMMON) -out:test_core_math
 
 linalg_glsl_math_test:
-	$(ODIN) run math/linalg/glsl $(COMMON) $(COLLECTION) -out:test_linalg_glsl_math
+	$(ODIN) test math/linalg/glsl $(COMMON) -out:test_linalg_glsl_math
 
-filepath_test:
-	$(ODIN) run path/filepath $(COMMON) $(COLLECTION) -out:test_core_filepath
-
-reflect_test:
-	$(ODIN) run reflect $(COMMON) $(COLLECTION) -out:test_core_reflect
+noise_test:
+	$(ODIN) test math/noise $(COMMON) -out:test_noise
 
-slice_test:
-	$(ODIN) run slice $(COMMON) -out:test_core_slice
+net_test:
+	$(ODIN) test net $(COMMON) -out:test_core_net
 
 os_exit_test:
 	$(ODIN) run os/test_core_os_exit.odin -file -out:test_core_os_exit && exit 1 || exit 0
 
-i18n_test:
-	$(ODIN) run text/i18n $(COMMON) -out:test_core_i18n
+odin_test:
+	$(ODIN) test odin $(COMMON) -out:test_core_odin
 
-match_test:
-	$(ODIN) run text/match $(COMMON) -out:test_core_match
+reflect_test:
+	$(ODIN) test reflect $(COMMON) -out:test_core_reflect
 
-c_libc_test:
-	$(ODIN) run c/libc $(COMMON) -out:test_core_libc
+runtime_test:
+	$(ODIN) test runtime $(COMMON) -out:test_core_runtime
 
-net_test:
-	$(ODIN) run net $(COMMON) -out:test_core_net
+slice_test:
+	$(ODIN) test slice $(COMMON) -out:test_core_slice
 
-fmt_test:
-	$(ODIN) run fmt $(COMMON) -out:test_core_fmt
+strings_test:
+	$(ODIN) test strings $(COMMON) -out:test_core_strings
 
 thread_test:
-	$(ODIN) run thread $(COMMON) -out:test_core_thread
-
-runtime_test:
-	$(ODIN) run runtime $(COMMON) -out:test_core_runtime
+	$(ODIN) test thread $(COMMON) -out:test_core_thread
 
 time_test:
-	$(ODIN) run time $(COMMON) -out:test_core_time
+	$(ODIN) test time $(COMMON) -out:test_core_time
+
+clean:
+	rm test_*

+ 45 - 36
tests/core/build.bat

@@ -1,110 +1,119 @@
 @echo off
-set COMMON=-no-bounds-check -vet -strict-style
-set COLLECTION=-collection:tests=..
+set COMMON=-no-bounds-check -vet -strict-style -define:ODIN_TEST_FANCY=false
 set PATH_TO_ODIN==..\..\odin
 python3 download_assets.py
+echo ---
+echo Running core:c/libc tests
+echo ---
+%PATH_TO_ODIN% test c\libc %COMMON% -out:test_libc.exe || exit /b
+
 echo ---
 echo Running core:compress tests
 echo ---
-%PATH_TO_ODIN% run compress %COMMON% -out:test_core_compress.exe || exit /b
+%PATH_TO_ODIN% test compress %COMMON% -out:test_core_compress.exe || exit /b
 
 echo ---
 echo Running core:container tests
 echo ---
-%PATH_TO_ODIN% run container %COMMON% %COLLECTION% -out:test_core_container.exe || exit /b
+%PATH_TO_ODIN% test container %COMMON% -out:test_core_container.exe || exit /b
 
 echo ---
 echo Running core:crypto tests
 echo ---
-%PATH_TO_ODIN% run crypto %COMMON% %COLLECTION% -out:test_crypto.exe || exit /b
+%PATH_TO_ODIN% test crypto %COMMON% -o:speed -out:test_crypto.exe || exit /b
 
 echo ---
 echo Running core:encoding tests
 echo ---
-rem %PATH_TO_ODIN% run encoding/hxa    %COMMON% %COLLECTION% -out:test_hxa.exe || exit /b
-%PATH_TO_ODIN% run encoding/json   %COMMON% -out:test_json.exe || exit /b
-%PATH_TO_ODIN% run encoding/varint %COMMON% -out:test_varint.exe || exit /b
-%PATH_TO_ODIN% run encoding/xml    %COMMON% -out:test_xml.exe || exit /b
-%PATH_TO_ODIN% test encoding/cbor  %COMMON% -out:test_cbor.exe || exit /b
-%PATH_TO_ODIN% run encoding/hex    %COMMON% -out:test_hex.exe || exit /b
-%PATH_TO_ODIN% run encoding/base64 %COMMON% -out:test_base64.exe || exit /b
+%PATH_TO_ODIN% test encoding/base64 %COMMON% -out:test_base64.exe || exit /b
+%PATH_TO_ODIN% test encoding/cbor   %COMMON% -out:test_cbor.exe   || exit /b
+%PATH_TO_ODIN% test encoding/hex    %COMMON% -out:test_hex.exe    || exit /b
+%PATH_TO_ODIN% test encoding/hxa    %COMMON% -out:test_hxa.exe    || exit /b
+%PATH_TO_ODIN% test encoding/json   %COMMON% -out:test_json.exe   || exit /b
+%PATH_TO_ODIN% test encoding/varint %COMMON% -out:test_varint.exe || exit /b
+%PATH_TO_ODIN% test encoding/xml    %COMMON% -out:test_xml.exe    || exit /b
+
+echo ---
+echo Running core:path/filepath tests
+echo ---
+%PATH_TO_ODIN% test path/filepath %COMMON% -out:test_core_filepath.exe || exit /b
 
 echo ---
 echo Running core:fmt tests
 echo ---
-%PATH_TO_ODIN% run fmt %COMMON% %COLLECTION% -out:test_core_fmt.exe || exit /b
+%PATH_TO_ODIN% test fmt %COMMON% -out:test_core_fmt.exe || exit /b
 
 echo ---
 echo Running core:hash tests
 echo ---
-%PATH_TO_ODIN% run hash %COMMON% -o:size -out:test_core_hash.exe || exit /b
+%PATH_TO_ODIN% test hash %COMMON% -o:speed -out:test_core_hash.exe || exit /b
 
 echo ---
 echo Running core:image tests
 echo ---
-%PATH_TO_ODIN% run image    %COMMON% -out:test_core_image.exe || exit /b
+%PATH_TO_ODIN% test image %COMMON% -out:test_core_image.exe || exit /b
+
+echo ---
+echo Running core:text/i18n tests
+echo ---
+%PATH_TO_ODIN% test text\i18n %COMMON% -out:test_core_i18n.exe || exit /b
+
+echo ---
+echo Running text:match tests
+echo ---
+%PATH_TO_ODIN% test text/match %COMMON% -out:test_core_match.exe || exit /b
 
 echo ---
 echo Running core:math tests
 echo ---
-%PATH_TO_ODIN% run math %COMMON% %COLLECTION% -out:test_core_math.exe || exit /b
+%PATH_TO_ODIN% test math %COMMON% -out:test_core_math.exe || exit /b
 
 echo ---
 echo Running core:math/linalg/glsl tests
 echo ---
-%PATH_TO_ODIN% run math/linalg/glsl %COMMON% %COLLECTION% -out:test_linalg_glsl.exe || exit /b
+%PATH_TO_ODIN% test math/linalg/glsl %COMMON% -out:test_linalg_glsl.exe || exit /b
 
 echo ---
 echo Running core:math/noise tests
 echo ---
-%PATH_TO_ODIN% run math/noise %COMMON% -out:test_noise.exe || exit /b
+%PATH_TO_ODIN% test math/noise %COMMON% -out:test_noise.exe || exit /b
 
 echo ---
 echo Running core:net
 echo ---
-%PATH_TO_ODIN% run net %COMMON% -out:test_core_net.exe || exit /b
+%PATH_TO_ODIN% test net %COMMON% -out:test_core_net.exe || exit /b
 
 echo ---
 echo Running core:odin tests
 echo ---
-%PATH_TO_ODIN% run odin %COMMON% -o:size -out:test_core_odin.exe || exit /b
-
-echo ---
-echo Running core:path/filepath tests
-echo ---
-%PATH_TO_ODIN% run path/filepath %COMMON% %COLLECTION% -out:test_core_filepath.exe || exit /b
+%PATH_TO_ODIN% test odin %COMMON% -o:size -out:test_core_odin.exe || exit /b
 
 echo ---
 echo Running core:reflect tests
 echo ---
-%PATH_TO_ODIN% run reflect %COMMON% %COLLECTION% -out:test_core_reflect.exe || exit /b
+%PATH_TO_ODIN% test reflect %COMMON% -out:test_core_reflect.exe || exit /b
 
 echo ---
 echo Running core:runtime tests
 echo ---
-%PATH_TO_ODIN% run runtime %COMMON% %COLLECTION% -out:test_core_runtime.exe || exit /b
+%PATH_TO_ODIN% test runtime %COMMON% -out:test_core_runtime.exe || exit /b
 
 echo ---
 echo Running core:slice tests
 echo ---
-%PATH_TO_ODIN% run slice %COMMON% -out:test_core_slice.exe || exit /b
+%PATH_TO_ODIN% test slice %COMMON% -out:test_core_slice.exe || exit /b
 
 echo ---
 echo Running core:strings tests
 echo ---
-%PATH_TO_ODIN% run strings %COMMON% -out:test_core_strings.exe || exit /b
-
-echo ---
-echo Running core:text/i18n tests
-echo ---
-%PATH_TO_ODIN% run text\i18n %COMMON% -out:test_core_i18n.exe || exit /b
+%PATH_TO_ODIN% test strings %COMMON% -out:test_core_strings.exe || exit /b
 
 echo ---
 echo Running core:thread tests
 echo ---
-%PATH_TO_ODIN% run thread %COMMON% %COLLECTION% -out:test_core_thread.exe || exit /b
+%PATH_TO_ODIN% test thread %COMMON% -out:test_core_thread.exe || exit /b
 
 echo ---
 echo Running core:time tests
 echo ---
-%PATH_TO_ODIN% run time %COMMON% %COLLECTION% -out:test_core_time.exe || exit /b
+%PATH_TO_ODIN% test time %COMMON% -out:test_core_time.exe || exit /b

+ 0 - 36
tests/core/c/libc/test_core_libc.odin

@@ -1,36 +0,0 @@
-package test_core_libc
-
-import "core:fmt"
-import "core:os"
-import "core:testing"
-
-TEST_count := 0
-TEST_fail  := 0
-
-when ODIN_TEST {
-	expect  :: testing.expect
-	log     :: testing.log
-} else {
-	expect  :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
-		TEST_count += 1
-		if !condition {
-			TEST_fail += 1
-			fmt.printf("[%v] %v\n", loc, message)
-			return
-		}
-	}
-	log :: proc(t: ^testing.T, v: any, loc := #caller_location) {
-		fmt.printf("[%v] ", loc)
-		fmt.printf("log: %v\n", v)
-	}
-}
-
-main :: proc() {
-	t := testing.T{}
-	test_libc_complex(&t)
-
-	fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
-	if TEST_fail > 0 {
-		os.exit(1)
-	}
-}

+ 7 - 8
tests/core/c/libc/test_core_libc_complex_pow.odin

@@ -1,8 +1,8 @@
 package test_core_libc
 
 import "core:testing"
-import "core:fmt"
 import "core:c/libc"
+import "core:log"
 
 reldiff :: proc(lhs, rhs: $T) -> f64  {
 	if lhs == rhs {
@@ -14,7 +14,7 @@ reldiff :: proc(lhs, rhs: $T) -> f64  {
 	return out
 }
 
-isclose :: proc(lhs, rhs: $T, rtol:f64 = 1e-12, atol:f64 = 1e-12) -> bool {
+isclose :: proc(t: ^testing.T, lhs, rhs: $T, rtol:f64 = 1e-12, atol:f64 = 1e-12) -> bool {
 	adiff := f64(abs(lhs - rhs))
 	if adiff < atol { 
 		return true
@@ -23,7 +23,7 @@ isclose :: proc(lhs, rhs: $T, rtol:f64 = 1e-12, atol:f64 = 1e-12) -> bool {
 	if rdiff < rtol {
 		return true
 	}
-	fmt.printf("not close -- lhs:%v rhs:%v -- adiff:%e   rdiff:%e\n",lhs, rhs, adiff, rdiff)
+	log.infof("not close -- lhs:%v rhs:%v -- adiff:%e   rdiff:%e\n",lhs, rhs, adiff, rdiff)
 	return false
 }
 
@@ -44,7 +44,6 @@ test_libc_complex :: proc(t: ^testing.T) {
 	test_libc_pow_binding(t, libc.complex_float, f32, libc_powf, 1e-12, 1e-5)
 }
 
-@test
 test_libc_pow_binding :: proc(t: ^testing.T, $LIBC_COMPLEX:typeid, $F:typeid, pow: proc(LIBC_COMPLEX, LIBC_COMPLEX) -> LIBC_COMPLEX, 
                               rtol: f64, atol: f64) {
 	// Tests that c/libc/pow(f) functions have two arguments and that the function works as expected for simple inputs
@@ -56,8 +55,8 @@ test_libc_pow_binding :: proc(t: ^testing.T, $LIBC_COMPLEX:typeid, $F:typeid, po
 		for n in -4..=4 {
 			complex_power := LIBC_COMPLEX(complex(F(n), F(0.)))
 			result := pow(complex_base, complex_power) 
-			expect(t, isclose(expected_real, F(real(result)), rtol, atol), fmt.tprintf("ftype:%T, n:%v reldiff(%v, re(%v)) is greater than specified rtol:%e", F{}, n, expected_real, result, rtol))
-			expect(t, isclose(expected_imag, F(imag(result)), rtol, atol), fmt.tprintf("ftype:%T, n:%v reldiff(%v, im(%v)) is greater than specified rtol:%e", F{}, n, expected_imag, result, rtol))
+			testing.expectf(t, isclose(t, expected_real, F(real(result)), rtol, atol), "ftype:%T, n:%v reldiff(%v, re(%v)) is greater than specified rtol:%e", F{}, n, expected_real, result, rtol)
+			testing.expectf(t, isclose(t, expected_imag, F(imag(result)), rtol, atol), "ftype:%T, n:%v reldiff(%v, im(%v)) is greater than specified rtol:%e", F{}, n, expected_imag, result, rtol)
 			expected_real *= 2
 		}
 	}
@@ -83,8 +82,8 @@ test_libc_pow_binding :: proc(t: ^testing.T, $LIBC_COMPLEX:typeid, $F:typeid, po
 					expected_real = 0.
 					expected_imag = -value
 			}
-			expect(t, isclose(expected_real, F(real(result)), rtol, atol), fmt.tprintf("ftype:%T, n:%v reldiff(%v, re(%v)) is greater than specified rtol:%e", F{}, n, expected_real, result, rtol))
-			expect(t, isclose(expected_imag, F(imag(result)), rtol, atol), fmt.tprintf("ftype:%T, n:%v reldiff(%v, im(%v)) is greater than specified rtol:%e", F{}, n, expected_imag, result, rtol))
+			testing.expectf(t, isclose(t, expected_real, F(real(result)), rtol, atol), "ftype:%T, n:%v reldiff(%v, re(%v)) is greater than specified rtol:%e", F{}, n, expected_real, result, rtol)
+			testing.expectf(t, isclose(t, expected_imag, F(imag(result)), rtol, atol), "ftype:%T, n:%v reldiff(%v, im(%v)) is greater than specified rtol:%e", F{}, n, expected_imag, result, rtol)
 			value *= 2
 		}
 	}

+ 17 - 86
tests/core/compress/test_core_compress.odin

@@ -15,47 +15,7 @@ import "core:testing"
 import "core:compress/zlib"
 import "core:compress/gzip"
 import "core:compress/shoco"
-
 import "core:bytes"
-import "core:fmt"
-
-import "core:mem"
-import "core:os"
-import "core:io"
-
-TEST_count := 0
-TEST_fail  := 0
-
-when ODIN_TEST {
-	expect  :: testing.expect
-	log     :: testing.log
-} else {
-	expect  :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
-		TEST_count += 1
-		if !condition {
-			TEST_fail += 1
-			fmt.printf("[%v] %v\n", loc, message)
-			return
-		}
-	}
-	log     :: proc(t: ^testing.T, v: any, loc := #caller_location) {
-		fmt.printf("[%v] ", loc)
-		fmt.printf("log: %v\n", v)
-	}
-}
-
-main :: proc() {
-	w := io.to_writer(os.stream_from_handle(os.stdout))
-	t := testing.T{w=w}
-	zlib_test(&t)
-	gzip_test(&t)
-	shoco_test(&t)
-
-	fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
-	if TEST_fail > 0 {
-		os.exit(1)
-	}
-}
 
 @test
 zlib_test :: proc(t: ^testing.T) {
@@ -80,26 +40,14 @@ zlib_test :: proc(t: ^testing.T) {
 	}
 
 	buf: bytes.Buffer
+	err := zlib.inflate(ODIN_DEMO, &buf)
 
-	track: mem.Tracking_Allocator
-	mem.tracking_allocator_init(&track, context.allocator)
-	context.allocator = mem.tracking_allocator(&track)
-
-	err  := zlib.inflate(ODIN_DEMO, &buf)
-
-	expect(t, err == nil, "ZLIB failed to decompress ODIN_DEMO")
+	testing.expect(t, err == nil, "ZLIB failed to decompress ODIN_DEMO")
 	s := bytes.buffer_to_string(&buf)
 
-	expect(t, s[68] == 240 && s[69] == 159 && s[70] == 152, "ZLIB result should've contained 😃 at position 68.")
-
-	expect(t, len(s) == 438, "ZLIB result has an unexpected length.")
-
+	testing.expect(t, s[68] == 240 && s[69] == 159 && s[70] == 152, "ZLIB result should've contained 😃 at position 68.")
+	testing.expect(t, len(s) == 438, "ZLIB result has an unexpected length.")
 	bytes.buffer_destroy(&buf)
-
-	for _, v in track.allocation_map {
-		error := fmt.tprintf("ZLIB test leaked %v bytes", v.size)
-		expect(t, false, error)
-	}
 }
 
 @test
@@ -117,24 +65,12 @@ gzip_test :: proc(t: ^testing.T) {
 	}
 
 	buf: bytes.Buffer
+	err := gzip.load(TEST, &buf)
 
-	track: mem.Tracking_Allocator
-	mem.tracking_allocator_init(&track, context.allocator)
-	context.allocator = mem.tracking_allocator(&track)
-
-	err := gzip.load(TEST, &buf) // , 438);
-
-	expect(t, err == nil, "GZIP failed to decompress TEST")
-	s := bytes.buffer_to_string(&buf)
-
-	expect(t, s == "payload", "GZIP result wasn't 'payload'")
+	testing.expect(t, err == nil, "GZIP failed to decompress TEST")
+	testing.expect(t, bytes.buffer_to_string(&buf) == "payload", "GZIP result wasn't 'payload'")
 
 	bytes.buffer_destroy(&buf)
-
-	for _, v in track.allocation_map {
-		error := fmt.tprintf("GZIP test leaked %v bytes", v.size)
-		expect(t, false, error)
-	}
 }
 
 @test
@@ -168,31 +104,26 @@ shoco_test :: proc(t: ^testing.T) {
 		defer delete(buffer)
 
 		size, err := shoco.decompress(v.compressed, buffer[:])
-		msg := fmt.tprintf("Expected `decompress` to return `nil`, got %v", err)
-		expect(t, err == nil, msg)
+		testing.expectf(t, err == nil, "Expected `decompress` to return `nil`, got %v", err)
 
-		msg  = fmt.tprintf("Decompressed %v bytes into %v. Expected to decompress into %v bytes.", len(v.compressed), size, expected_raw)
-		expect(t, size == expected_raw, msg)
-		expect(t, string(buffer[:size]) == string(v.raw), "Decompressed contents don't match.")
+		testing.expectf(t, size == expected_raw, "Decompressed %v bytes into %v. Expected to decompress into %v bytes", len(v.compressed), size, expected_raw)
+		testing.expect(t, string(buffer[:size]) == string(v.raw), "Decompressed contents don't match")
 
 		size, err = shoco.compress(string(v.raw), buffer[:])
-		expect(t, err == nil, "Expected `compress` to return `nil`.")
+		testing.expect(t, err == nil, "Expected `compress` to return `nil`.")
 
-		msg = fmt.tprintf("Compressed %v bytes into %v. Expected to compress into %v bytes.", expected_raw, size, expected_compressed)
-		expect(t, size == expected_compressed, msg)
+		testing.expectf(t, size == expected_compressed, "Compressed %v bytes into %v. Expected to compress into %v bytes", expected_raw, size, expected_compressed)
 
 		size, err = shoco.decompress(v.compressed, buffer[:expected_raw - 10])
-		msg = fmt.tprintf("Decompressing into too small a buffer returned %v, expected `.Output_Too_Short`", err)
-		expect(t, err == .Output_Too_Short, msg)
+		testing.expectf(t, err == .Output_Too_Short, "Decompressing into too small a buffer returned %v, expected `.Output_Too_Short`", err)
 
 		size, err = shoco.compress(string(v.raw), buffer[:expected_compressed - 10])
-		msg = fmt.tprintf("Compressing into too small a buffer returned %v, expected `.Output_Too_Short`", err)
-		expect(t, err == .Output_Too_Short, msg)
+		testing.expectf(t, err == .Output_Too_Short, "Compressing into too small a buffer returned %v, expected `.Output_Too_Short`", err)
 
 		size, err = shoco.decompress(v.compressed[:v.short_pack], buffer[:])
-		expect(t, err == .Stream_Too_Short, "Expected `decompress` to return `Stream_Too_Short` because there was no more data after selecting a pack.")
+		testing.expectf(t, err == .Stream_Too_Short, "Insufficient data after pack returned %v, expected `.Stream_Too_Short`", err)
 
 		size, err = shoco.decompress(v.compressed[:v.short_sentinel], buffer[:])
-		expect(t, err == .Stream_Too_Short, "Expected `decompress` to return `Stream_Too_Short` because there was no more data after non-ASCII sentinel.")
+		testing.expectf(t, err == .Stream_Too_Short, "No more data after non-ASCII sentinel returned %v, expected `.Stream_Too_Short`", err)
 	}
-}
+}

+ 37 - 36
tests/core/container/test_core_avl.odin

@@ -4,53 +4,54 @@ import "core:container/avl"
 import "core:math/rand"
 import "core:slice"
 import "core:testing"
-import "core:fmt"
-import tc "tests:common"
+import "core:log"
 
 @(test)
 test_avl :: proc(t: ^testing.T) {
-	tc.log(t, fmt.tprintf("Testing avl, using random seed %v, add -define:RANDOM_SEED=%v to reuse it.", random_seed, random_seed))
+	log.infof("Testing avl using random seed %v.", t.seed)
 
 	// Initialization.
 	tree: avl.Tree(int)
 	avl.init(&tree, slice.cmp_proc(int))
-	tc.expect(t, avl.len(&tree) == 0, "empty: len should be 0")
-	tc.expect(t, avl.first(&tree) == nil, "empty: first should be nil")
-	tc.expect(t, avl.last(&tree) == nil, "empty: last should be nil")
+	testing.expect(t, avl.len(&tree)   == 0,    "empty: len should be 0")
+	testing.expect(t, avl.first(&tree) == nil,  "empty: first should be nil")
+	testing.expect(t, avl.last(&tree)   == nil, "empty: last should be nil")
 
 	iter := avl.iterator(&tree, avl.Direction.Forward)
-	tc.expect(t, avl.iterator_get(&iter) == nil, "empty/iterator: first node should be nil")
+	testing.expect(t, avl.iterator_get(&iter) == nil, "empty/iterator: first node should be nil")
 
 	r: rand.Rand
-	rand.init(&r, random_seed)
+	rand.init(&r, t.seed)
 
 	// Test insertion.
 	NR_INSERTS :: 32 + 1 // Ensure at least 1 collision.
 	inserted_map := make(map[int]^avl.Node(int))
+	defer delete(inserted_map)
 	for i := 0; i < NR_INSERTS; i += 1 {
 		v := int(rand.uint32(&r) & 0x1f)
 		existing_node, in_map := inserted_map[v]
 
 		n, ok, _ := avl.find_or_insert(&tree, v)
-		tc.expect(t, in_map != ok, "insert: ok should match inverse of map lookup")
+		testing.expect(t, in_map != ok, "insert: ok should match inverse of map lookup")
 		if ok {
 			inserted_map[v] = n
 		} else {
-			tc.expect(t, existing_node == n, "insert: expecting existing node")
+			testing.expect(t, existing_node == n, "insert: expecting existing node")
 		}
 	}
 	nrEntries := len(inserted_map)
-	tc.expect(t, avl.len(&tree) == nrEntries, "insert: len after")
+	testing.expect(t, avl.len(&tree) == nrEntries, "insert: len after")
 	validate_avl(t, &tree)
 
 	// Ensure that all entries can be found.
 	for k, v in inserted_map {
-		tc.expect(t, v == avl.find(&tree, k), "Find(): Node")
-		tc.expect(t, k == v.value, "Find(): Node value")
+		testing.expect(t, v == avl.find(&tree, k), "Find(): Node")
+		testing.expect(t, k == v.value, "Find(): Node value")
 	}
 
 	// Test the forward/backward iterators.
 	inserted_values: [dynamic]int
+	defer delete(inserted_values)
 	for k in inserted_map {
 		append(&inserted_values, k)
 	}
@@ -60,38 +61,38 @@ test_avl :: proc(t: ^testing.T) {
 	visited: int
 	for node in avl.iterator_next(&iter) {
 		v, idx := node.value, visited
-		tc.expect(t, inserted_values[idx] == v, "iterator/forward: value")
-		tc.expect(t, node == avl.iterator_get(&iter), "iterator/forward: get")
+		testing.expect(t, inserted_values[idx] == v, "iterator/forward: value")
+		testing.expect(t, node == avl.iterator_get(&iter), "iterator/forward: get")
 		visited += 1
 	}
-	tc.expect(t, visited == nrEntries, "iterator/forward: visited")
+	testing.expect(t, visited == nrEntries, "iterator/forward: visited")
 
 	slice.reverse(inserted_values[:])
 	iter = avl.iterator(&tree, avl.Direction.Backward)
 	visited = 0
 	for node in avl.iterator_next(&iter) {
 		v, idx := node.value, visited
-		tc.expect(t, inserted_values[idx] == v, "iterator/backward: value")
+		testing.expect(t, inserted_values[idx] == v, "iterator/backward: value")
 		visited += 1
 	}
-	tc.expect(t, visited == nrEntries, "iterator/backward: visited")
+	testing.expect(t, visited == nrEntries, "iterator/backward: visited")
 
 	// Test removal.
 	rand.shuffle(inserted_values[:], &r)
 	for v, i in inserted_values {
 		node := avl.find(&tree, v)
-		tc.expect(t, node != nil, "remove: find (pre)")
+		testing.expect(t, node != nil, "remove: find (pre)")
 
 		ok := avl.remove(&tree, v)
-		tc.expect(t, ok, "remove: succeeds")
-		tc.expect(t, nrEntries - (i + 1) == avl.len(&tree), "remove: len (post)")
+		testing.expect(t, ok, "remove: succeeds")
+		testing.expect(t, nrEntries - (i + 1) == avl.len(&tree), "remove: len (post)")
 		validate_avl(t, &tree)
 
-		tc.expect(t, nil == avl.find(&tree, v), "remove: find (post")
+		testing.expect(t, nil == avl.find(&tree, v), "remove: find (post")
 	}
-	tc.expect(t, avl.len(&tree) == 0, "remove: len should be 0")
-	tc.expect(t, avl.first(&tree) == nil, "remove: first should be nil")
-	tc.expect(t, avl.last(&tree) == nil, "remove: last should be nil")
+	testing.expect(t, avl.len(&tree) == 0, "remove: len should be 0")
+	testing.expect(t, avl.first(&tree) == nil, "remove: first should be nil")
+	testing.expect(t, avl.last(&tree) == nil, "remove: last should be nil")
 
 	// Refill the tree.
 	for v in inserted_values {
@@ -104,25 +105,25 @@ test_avl :: proc(t: ^testing.T) {
 		v := node.value
 
 		ok := avl.iterator_remove(&iter)
-		tc.expect(t, ok, "iterator/remove: success")
+		testing.expect(t, ok, "iterator/remove: success")
 
 		ok = avl.iterator_remove(&iter)
-		tc.expect(t, !ok, "iterator/remove: redundant removes should fail")
+		testing.expect(t, !ok, "iterator/remove: redundant removes should fail")
 
-		tc.expect(t, avl.find(&tree, v) == nil, "iterator/remove: node should be gone")
-		tc.expect(t, avl.iterator_get(&iter) == nil, "iterator/remove: get should return nil")
+		testing.expect(t, avl.find(&tree, v) == nil, "iterator/remove: node should be gone")
+		testing.expect(t, avl.iterator_get(&iter) == nil, "iterator/remove: get should return nil")
 
 		// Ensure that iterator_next still works.
 		node, ok = avl.iterator_next(&iter)
-		tc.expect(t, ok == (avl.len(&tree) > 0), "iterator/remove: next should return false")
-		tc.expect(t, node == avl.first(&tree), "iterator/remove: next should return first")
+		testing.expect(t, ok == (avl.len(&tree) > 0), "iterator/remove: next should return false")
+		testing.expect(t, node == avl.first(&tree), "iterator/remove: next should return first")
 
 		validate_avl(t, &tree)
 	}
-	tc.expect(t, avl.len(&tree) == nrEntries - 1, "iterator/remove: len should drop by 1")
+	testing.expect(t, avl.len(&tree) == nrEntries - 1, "iterator/remove: len should drop by 1")
 
 	avl.destroy(&tree)
-	tc.expect(t, avl.len(&tree) == 0, "destroy: len should be 0")
+	testing.expect(t, avl.len(&tree) == 0, "destroy: len should be 0")
 }
 
 @(private)
@@ -141,10 +142,10 @@ tree_check_invariants :: proc(
 	}
 
 	// Validate the parent pointer.
-	tc.expect(t, parent == node._parent, "invalid parent pointer")
+	testing.expect(t, parent == node._parent, "invalid parent pointer")
 
 	// Validate that the balance factor is -1, 0, 1.
-	tc.expect(
+	testing.expect(
 		t,
 		node._balance == -1 || node._balance == 0 || node._balance == 1,
 		"invalid balance factor",
@@ -155,7 +156,7 @@ tree_check_invariants :: proc(
 	r_height := tree_check_invariants(t, tree, node._right, node)
 
 	// Validate the AVL invariant and the balance factor.
-	tc.expect(t, int(node._balance) == r_height - l_height, "AVL balance factor invariant violated")
+	testing.expect(t, int(node._balance) == r_height - l_height, "AVL balance factor invariant violated")
 	if l_height > r_height {
 		return l_height + 1
 	}

+ 0 - 26
tests/core/container/test_core_container.odin

@@ -1,26 +0,0 @@
-package test_core_container
-
-import "core:fmt"
-import "core:testing"
-
-import tc "tests:common"
-
-expect_equal :: proc(t: ^testing.T, the_slice, expected: []int, loc := #caller_location) {
-    _eq :: proc(a, b: []int) -> bool {
-        if len(a) != len(b) do return false
-        for a, i in a {
-            if b[i] != a do return false
-        }
-        return true
-    }
-    tc.expect(t, _eq(the_slice, expected), fmt.tprintf("Expected %v, got %v\n", the_slice, expected), loc)
-}
-
-main :: proc() {
-	t := testing.T{}
-
-	test_avl(&t)
-	test_rbtree(&t)
-	test_small_array(&t)
-	tc.report(&t)
-}

+ 43 - 47
tests/core/container/test_core_rbtree.odin

@@ -3,14 +3,10 @@ package test_core_container
 import rb "core:container/rbtree"
 import "core:math/rand"
 import "core:testing"
-import "core:fmt"
 import "base:intrinsics"
 import "core:mem"
 import "core:slice"
-import tc "tests:common"
-
-RANDOM_SEED :: #config(RANDOM_SEED, 0)
-random_seed := u64(intrinsics.read_cycle_counter()) when RANDOM_SEED == 0 else u64(RANDOM_SEED)
+import "core:log"
 
 test_rbtree_integer :: proc(t: ^testing.T, $Key: typeid, $Value: typeid) {
 	track: mem.Tracking_Allocator
@@ -19,17 +15,17 @@ test_rbtree_integer :: proc(t: ^testing.T, $Key: typeid, $Value: typeid) {
 	context.allocator = mem.tracking_allocator(&track)
 
 	r: rand.Rand
-	rand.init(&r, random_seed)
+	rand.init(&r, t.seed)
 
-	tc.log(t, fmt.tprintf("Testing Red-Black Tree($Key=%v,$Value=%v), using random seed %v, add -define:RANDOM_SEED=%v to reuse it.", type_info_of(Key), type_info_of(Value), random_seed, random_seed))
+	log.infof("Testing Red-Black Tree($Key=%v,$Value=%v) using random seed %v.", type_info_of(Key), type_info_of(Value), t.seed)
 	tree: rb.Tree(Key, Value)
 	rb.init(&tree)
 
-	tc.expect(t, rb.len(&tree)   == 0,   "empty: len should be 0")
-	tc.expect(t, rb.first(&tree) == nil, "empty: first should be nil")
-	tc.expect(t, rb.last(&tree)  == nil, "empty: last should be nil")
+	testing.expect(t, rb.len(&tree)   == 0,   "empty: len should be 0")
+	testing.expect(t, rb.first(&tree) == nil, "empty: first should be nil")
+	testing.expect(t, rb.last(&tree)  == nil, "empty: last should be nil")
 	iter := rb.iterator(&tree, .Forward)
-	tc.expect(t, rb.iterator_get(&iter) == nil, "empty/iterator: first node should be nil")
+	testing.expect(t, rb.iterator_get(&iter) == nil, "empty/iterator: first node should be nil")
 
 	// Test insertion.
 	NR_INSERTS :: 32 + 1 // Ensure at least 1 collision.
@@ -45,27 +41,27 @@ test_rbtree_integer :: proc(t: ^testing.T, $Key: typeid, $Value: typeid) {
 
 		existing_node, in_map := inserted_map[k]
 		n, inserted, _ := rb.find_or_insert(&tree, k, v)
-		tc.expect(t, in_map != inserted, "insert: inserted should match inverse of map lookup")
+		testing.expect(t, in_map != inserted, "insert: inserted should match inverse of map lookup")
 		if inserted {
 			inserted_map[k] = n
 		} else {
-			tc.expect(t, existing_node == n, "insert: expecting existing node")
+			testing.expect(t, existing_node == n, "insert: expecting existing node")
 		}
 	}
 
 	entry_count := len(inserted_map)
-	tc.expect(t, rb.len(&tree) == entry_count, "insert: len after")
+	testing.expect(t, rb.len(&tree) == entry_count, "insert: len after")
 	validate_rbtree(t, &tree)
 
 	first := rb.first(&tree)
 	last  := rb.last(&tree)
-	tc.expect(t, first != nil && first.key == min_key, fmt.tprintf("insert: first should be present with key %v", min_key))
-	tc.expect(t, last  != nil && last.key  == max_key, fmt.tprintf("insert: last should be present with key %v", max_key))
+	testing.expectf(t, first != nil && first.key == min_key, "insert: first should be present with key %v", min_key)
+	testing.expectf(t, last  != nil && last.key  == max_key, "insert: last should be present with key %v", max_key)
 
 	// Ensure that all entries can be found.
 	for k, v in inserted_map {
-		tc.expect(t, v == rb.find(&tree, k), "Find(): Node")
-		tc.expect(t, k == v.key,             "Find(): Node key")
+		testing.expect(t, v == rb.find(&tree, k), "Find(): Node")
+		testing.expect(t, k == v.key,             "Find(): Node key")
 	}
 
 	// Test the forward/backward iterators.
@@ -79,21 +75,21 @@ test_rbtree_integer :: proc(t: ^testing.T, $Key: typeid, $Value: typeid) {
 	visited: int
 	for node in rb.iterator_next(&iter) {
 		k, idx := node.key, visited
-		tc.expect(t, inserted_keys[idx] == k,        "iterator/forward: key")
-		tc.expect(t, node == rb.iterator_get(&iter), "iterator/forward: get")
+		testing.expect(t, inserted_keys[idx] == k,        "iterator/forward: key")
+		testing.expect(t, node == rb.iterator_get(&iter), "iterator/forward: get")
 		visited += 1
 	}
-	tc.expect(t, visited == entry_count, "iterator/forward: visited")
+	testing.expect(t, visited == entry_count, "iterator/forward: visited")
 
 	slice.reverse(inserted_keys[:])
 	iter = rb.iterator(&tree, rb.Direction.Backward)
 	visited = 0
 	for node in rb.iterator_next(&iter) {
 		k, idx := node.key, visited
-		tc.expect(t, inserted_keys[idx] == k, "iterator/backward: key")
+		testing.expect(t, inserted_keys[idx] == k, "iterator/backward: key")
 		visited += 1
 	}
-	tc.expect(t, visited == entry_count, "iterator/backward: visited")
+	testing.expect(t, visited == entry_count, "iterator/backward: visited")
 
 	// Test removal (and on_remove callback)
 	rand.shuffle(inserted_keys[:], &r)
@@ -104,19 +100,19 @@ test_rbtree_integer :: proc(t: ^testing.T, $Key: typeid, $Value: typeid) {
 	}
 	for k, i in inserted_keys {
 		node := rb.find(&tree, k)
-		tc.expect(t, node != nil, "remove: find (pre)")
+		testing.expect(t, node != nil, "remove: find (pre)")
 
 		ok := rb.remove(&tree, k)
-		tc.expect(t, ok, "remove: succeeds")
-		tc.expect(t, entry_count - (i + 1) == rb.len(&tree), "remove: len (post)")
+		testing.expect(t, ok, "remove: succeeds")
+		testing.expect(t, entry_count - (i + 1) == rb.len(&tree), "remove: len (post)")
 		validate_rbtree(t, &tree)
 
-		tc.expect(t, nil == rb.find(&tree, k), "remove: find (post")
+		testing.expect(t, nil == rb.find(&tree, k), "remove: find (post")
 	}
-	tc.expect(t, rb.len(&tree)   == 0,   "remove: len should be 0")
-	tc.expect(t, callback_count  == 0,   fmt.tprintf("remove: on_remove should've been called %v times, it was %v", entry_count, callback_count))
-	tc.expect(t, rb.first(&tree) == nil, "remove: first should be nil")
-	tc.expect(t, rb.last(&tree)  == nil, "remove: last should be nil")
+	testing.expect(t, rb.len(&tree)   == 0,   "remove: len should be 0")
+	testing.expectf(t, callback_count == 0,   "remove: on_remove should've been called %v times, it was %v", entry_count, callback_count)
+	testing.expect(t, rb.first(&tree) == nil, "remove: first should be nil")
+	testing.expect(t, rb.last(&tree)  == nil, "remove: last should be nil")
 
 	// Refill the tree.
 	for k in inserted_keys {
@@ -130,32 +126,32 @@ test_rbtree_integer :: proc(t: ^testing.T, $Key: typeid, $Value: typeid) {
 		k := node.key
 
 		ok := rb.iterator_remove(&iter)
-		tc.expect(t, ok, "iterator/remove: success")
+		testing.expect(t, ok, "iterator/remove: success")
 
 		ok = rb.iterator_remove(&iter)
-		tc.expect(t, !ok, "iterator/remove: redundant removes should fail")
+		testing.expect(t, !ok, "iterator/remove: redundant removes should fail")
 
-		tc.expect(t, rb.find(&tree, k)      == nil, "iterator/remove: node should be gone")
-		tc.expect(t, rb.iterator_get(&iter) == nil, "iterator/remove: get should return nil")
+		testing.expect(t, rb.find(&tree, k)      == nil, "iterator/remove: node should be gone")
+		testing.expect(t, rb.iterator_get(&iter) == nil, "iterator/remove: get should return nil")
 
 		// Ensure that iterator_next still works.
 		node, ok = rb.iterator_next(&iter)
-		tc.expect(t, ok   == (rb.len(&tree) > 0), "iterator/remove: next should return false")
-		tc.expect(t, node == rb.first(&tree),     "iterator/remove: next should return first")
+		testing.expect(t, ok   == (rb.len(&tree) > 0), "iterator/remove: next should return false")
+		testing.expect(t, node == rb.first(&tree),     "iterator/remove: next should return first")
 
 		validate_rbtree(t, &tree)
 	}
-	tc.expect(t, rb.len(&tree) == entry_count - 1, "iterator/remove: len should drop by 1")
+	testing.expect(t, rb.len(&tree) == entry_count - 1, "iterator/remove: len should drop by 1")
 
 	rb.destroy(&tree)
-	tc.expect(t, rb.len(&tree)  == 0, "destroy: len should be 0")
-	tc.expect(t, callback_count == 0, fmt.tprintf("remove: on_remove should've been called %v times, it was %v", entry_count, callback_count))
+	testing.expect(t, rb.len(&tree)  == 0, "destroy: len should be 0")
+	testing.expectf(t, callback_count == 0, "remove: on_remove should've been called %v times, it was %v", entry_count, callback_count)
 
 	// print_tree_node(tree._root)
 	delete(inserted_map)
 	delete(inserted_keys)
-	tc.expect(t, len(track.allocation_map) == 0, fmt.tprintf("Expected 0 leaks, have %v",     len(track.allocation_map)))
-	tc.expect(t, len(track.bad_free_array) == 0, fmt.tprintf("Expected 0 bad frees, have %v", len(track.bad_free_array)))
+	testing.expectf(t, len(track.allocation_map) == 0, "Expected 0 leaks, have %v",     len(track.allocation_map))
+	testing.expectf(t, len(track.bad_free_array) == 0, "Expected 0 bad frees, have %v", len(track.bad_free_array))
 	return
 }
 
@@ -194,7 +190,7 @@ validate_rbtree :: proc(t: ^testing.T, tree: ^$T/rb.Tree($Key, $Value)) {
 }
 
 verify_rbtree_propery_1 :: proc(t: ^testing.T, n: ^$N/rb.Node($Key, $Value)) {
-        tc.expect(t, rb.node_color(n) == .Black || rb.node_color(n) == .Red, "Property #1: Each node is either red or black.")
+        testing.expect(t, rb.node_color(n) == .Black || rb.node_color(n) == .Red, "Property #1: Each node is either red or black.")
 	if n == nil {
 		return
 	}
@@ -203,14 +199,14 @@ verify_rbtree_propery_1 :: proc(t: ^testing.T, n: ^$N/rb.Node($Key, $Value)) {
 }
 
 verify_rbtree_propery_2 :: proc(t: ^testing.T, root: ^$N/rb.Node($Key, $Value)) {
-	tc.expect(t, rb.node_color(root) == .Black, "Property #2: Root node should be black.")
+	testing.expect(t, rb.node_color(root) == .Black, "Property #2: Root node should be black.")
 }
 
 verify_rbtree_propery_4 :: proc(t: ^testing.T, n: ^$N/rb.Node($Key, $Value)) {
 	if rb.node_color(n) == .Red {
 		//  A red node's left, right and parent should be black
 		all_black := rb.node_color(n._left) == .Black && rb.node_color(n._right) == .Black && rb.node_color(n._parent) == .Black
-		tc.expect(t, all_black, "Property #3: Red node's children + parent must be black.")
+		testing.expect(t, all_black, "Property #3: Red node's children + parent must be black.")
 	}
 	if n == nil {
 		return
@@ -233,7 +229,7 @@ verify_rbtree_propery_5_helper :: proc(t: ^testing.T, n: ^$N/rb.Node($Key, $Valu
 		if path_black_count^ == -1 {
 			path_black_count^ = black_count
 		} else {
-			tc.expect(t, black_count == path_black_count^, "Property #5: Paths from a node to its leaves contain same black count.")
+			testing.expect(t, black_count == path_black_count^, "Property #5: Paths from a node to its leaves contain same black count.")
 		}
 		return
 	}
@@ -241,4 +237,4 @@ verify_rbtree_propery_5_helper :: proc(t: ^testing.T, n: ^$N/rb.Node($Key, $Valu
 	verify_rbtree_propery_5_helper(t, n._right, black_count, path_black_count)
 }
 // Properties 4 and 5 together guarantee that no path in the tree is more than about twice as long as any other path,
-// which guarantees that it has O(log n) height.
+// which guarantees that it has O(log n) height.

+ 36 - 33
tests/core/container/test_core_small_array.odin

@@ -3,44 +3,47 @@ package test_core_container
 import "core:testing"
 import "core:container/small_array"
 
-import tc "tests:common"
-
-@(test)
-test_small_array :: proc(t: ^testing.T) {
-	tc.log(t, "Testing small_array")
-
-    test_small_array_removes(t)
-    test_small_array_inject_at(t)
-}
-
 @(test)
 test_small_array_removes :: proc(t: ^testing.T) {
-    array: small_array.Small_Array(10, int)
-    small_array.append(&array, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
-
-    small_array.ordered_remove(&array, 0)
-    expect_equal(t, small_array.slice(&array), []int { 1, 2, 3, 4, 5, 6, 7, 8, 9 })
-    small_array.ordered_remove(&array, 5)
-    expect_equal(t, small_array.slice(&array), []int { 1, 2, 3, 4, 5, 7, 8, 9 })
-    small_array.ordered_remove(&array, 6)
-    expect_equal(t, small_array.slice(&array), []int { 1, 2, 3, 4, 5, 7, 9 })
-    small_array.unordered_remove(&array, 0)
-    expect_equal(t, small_array.slice(&array), []int { 9, 2, 3, 4, 5, 7 })
-    small_array.unordered_remove(&array, 2)
-    expect_equal(t, small_array.slice(&array), []int { 9, 2, 7, 4, 5 })
-    small_array.unordered_remove(&array, 4)
-    expect_equal(t, small_array.slice(&array), []int { 9, 2, 7, 4 })
+	array: small_array.Small_Array(10, int)
+	small_array.append(&array, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
+
+	small_array.ordered_remove(&array, 0)
+	testing.expect(t, slice_equal(small_array.slice(&array), []int { 1, 2, 3, 4, 5, 6, 7, 8, 9 }))
+	small_array.ordered_remove(&array, 5)
+	testing.expect(t, slice_equal(small_array.slice(&array), []int { 1, 2, 3, 4, 5, 7, 8, 9 }))
+	small_array.ordered_remove(&array, 6)
+	testing.expect(t, slice_equal(small_array.slice(&array), []int { 1, 2, 3, 4, 5, 7, 9 }))
+	small_array.unordered_remove(&array, 0)
+	testing.expect(t, slice_equal(small_array.slice(&array), []int { 9, 2, 3, 4, 5, 7 }))
+	small_array.unordered_remove(&array, 2)
+	testing.expect(t, slice_equal(small_array.slice(&array), []int { 9, 2, 7, 4, 5 }))
+	small_array.unordered_remove(&array, 4)
+	testing.expect(t, slice_equal(small_array.slice(&array), []int { 9, 2, 7, 4 }))
 }
 
 @(test)
 test_small_array_inject_at :: proc(t: ^testing.T) {
-    array: small_array.Small_Array(13, int)
-    small_array.append(&array, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
+	array: small_array.Small_Array(13, int)
+	small_array.append(&array, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
+
+	testing.expect(t, small_array.inject_at(&array, 0, 0), "Expected to be able to inject into small array")
+	testing.expect(t, slice_equal(small_array.slice(&array), []int { 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }))
+	testing.expect(t, small_array.inject_at(&array, 0, 5), "Expected to be able to inject into small array")
+	testing.expect(t, slice_equal(small_array.slice(&array), []int { 0, 0, 1, 2, 3, 0, 4, 5, 6, 7, 8, 9 }))
+	testing.expect(t, small_array.inject_at(&array, 0, small_array.len(array)), "Expected to be able to inject into small array")
+	testing.expect(t, slice_equal(small_array.slice(&array), []int { 0, 0, 1, 2, 3, 0, 4, 5, 6, 7, 8, 9, 0 }))
+}
 
-    tc.expect(t, small_array.inject_at(&array, 0, 0), "Expected to be able to inject into small array")
-    expect_equal(t, small_array.slice(&array), []int { 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 })
-    tc.expect(t, small_array.inject_at(&array, 0, 5), "Expected to be able to inject into small array")
-    expect_equal(t, small_array.slice(&array), []int { 0, 0, 1, 2, 3, 0, 4, 5, 6, 7, 8, 9 })
-    tc.expect(t, small_array.inject_at(&array, 0, small_array.len(array)), "Expected to be able to inject into small array")
-    expect_equal(t, small_array.slice(&array), []int { 0, 0, 1, 2, 3, 0, 4, 5, 6, 7, 8, 9, 0 })
+slice_equal :: proc(a, b: []int) -> bool {
+	if len(a) != len(b) {
+		return false
+	}
+
+	for a, i in a {
+		if b[i] != a {
+			return false
+		}
+	}
+	return true
 }

+ 28 - 68
tests/core/crypto/test_core_crypto.odin

@@ -13,41 +13,20 @@ package test_core_crypto
 */
 
 import "core:encoding/hex"
-import "core:fmt"
 import "core:mem"
 import "core:testing"
+import "base:runtime"
+import "core:log"
 
 import "core:crypto"
 import "core:crypto/chacha20"
 import "core:crypto/chacha20poly1305"
 
-import tc "tests:common"
-
-main :: proc() {
-	t := testing.T{}
-
-	test_rand_bytes(&t)
-
-	test_hash(&t)
-	test_mac(&t)
-	test_kdf(&t) // After hash/mac tests because those should pass first.
-	test_ecc25519(&t)
-
-	test_aes(&t)
-	test_chacha20(&t)
-	test_chacha20poly1305(&t)
-	test_sha3_variants(&t)
-
-	bench_crypto(&t)
-
-	tc.report(&t)
-}
-
 _PLAINTEXT_SUNSCREEN_STR := "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it."
 
 @(test)
 test_chacha20 :: proc(t: ^testing.T) {
-	tc.log(t, "Testing (X)ChaCha20")
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 
 	// Test cases taken from RFC 8439, and draft-irtf-cfrg-xchacha-03
 	plaintext := transmute([]byte)(_PLAINTEXT_SUNSCREEN_STR)
@@ -90,14 +69,12 @@ test_chacha20 :: proc(t: ^testing.T) {
 	chacha20.xor_bytes(&ctx, derived_ciphertext[:], plaintext[:])
 
 	derived_ciphertext_str := string(hex.encode(derived_ciphertext[:], context.temp_allocator))
-	tc.expect(
+	testing.expectf(
 		t,
 		derived_ciphertext_str == ciphertext_str,
-		fmt.tprintf(
-			"Expected %s for xor_bytes(plaintext_str), but got %s instead",
-			ciphertext_str,
-			derived_ciphertext_str,
-		),
+		"Expected %s for xor_bytes(plaintext_str), but got %s instead",
+		ciphertext_str,
+		derived_ciphertext_str,
 	)
 
 	xkey := [chacha20.KEY_SIZE]byte {
@@ -137,21 +114,17 @@ test_chacha20 :: proc(t: ^testing.T) {
 	chacha20.xor_bytes(&ctx, derived_ciphertext[:], plaintext[:])
 
 	derived_ciphertext_str = string(hex.encode(derived_ciphertext[:], context.temp_allocator))
-	tc.expect(
+	testing.expectf(
 		t,
 		derived_ciphertext_str == xciphertext_str,
-		fmt.tprintf(
-			"Expected %s for xor_bytes(plaintext_str), but got %s instead",
-			xciphertext_str,
-			derived_ciphertext_str,
-		),
+		"Expected %s for xor_bytes(plaintext_str), but got %s instead",
+		xciphertext_str,
+		derived_ciphertext_str,
 	)
 }
 
 @(test)
 test_chacha20poly1305 :: proc(t: ^testing.T) {
-	tc.log(t, "Testing chacha20poly1205")
-
 	plaintext := transmute([]byte)(_PLAINTEXT_SUNSCREEN_STR)
 
 	aad := [12]byte {
@@ -209,25 +182,21 @@ test_chacha20poly1305 :: proc(t: ^testing.T) {
 	)
 
 	derived_ciphertext_str := string(hex.encode(derived_ciphertext[:], context.temp_allocator))
-	tc.expect(
+	testing.expectf(
 		t,
 		derived_ciphertext_str == ciphertext_str,
-		fmt.tprintf(
-			"Expected ciphertext %s for encrypt(aad, plaintext), but got %s instead",
-			ciphertext_str,
-			derived_ciphertext_str,
-		),
+		"Expected ciphertext %s for encrypt(aad, plaintext), but got %s instead",
+		ciphertext_str,
+		derived_ciphertext_str,
 	)
 
 	derived_tag_str := string(hex.encode(derived_tag[:], context.temp_allocator))
-	tc.expect(
+	testing.expectf(
 		t,
 		derived_tag_str == tag_str,
-		fmt.tprintf(
-			"Expected tag %s for encrypt(aad, plaintext), but got %s instead",
-			tag_str,
-			derived_tag_str,
-		),
+		"Expected tag %s for encrypt(aad, plaintext), but got %s instead",
+		tag_str,
+		derived_tag_str,
 	)
 
 	derived_plaintext: [114]byte
@@ -240,15 +209,13 @@ test_chacha20poly1305 :: proc(t: ^testing.T) {
 		ciphertext[:],
 	)
 	derived_plaintext_str := string(derived_plaintext[:])
-	tc.expect(t, ok, "Expected true for decrypt(tag, aad, ciphertext)")
-	tc.expect(
+	testing.expect(t, ok, "Expected true for decrypt(tag, aad, ciphertext)")
+	testing.expectf(
 		t,
 		derived_plaintext_str == _PLAINTEXT_SUNSCREEN_STR,
-		fmt.tprintf(
-			"Expected plaintext %s for decrypt(tag, aad, ciphertext), but got %s instead",
-			_PLAINTEXT_SUNSCREEN_STR,
-			derived_plaintext_str,
-		),
+		"Expected plaintext %s for decrypt(tag, aad, ciphertext), but got %s instead",
+		_PLAINTEXT_SUNSCREEN_STR,
+		derived_plaintext_str,
 	)
 
 	derived_ciphertext[0] ~= 0xa5
@@ -260,7 +227,7 @@ test_chacha20poly1305 :: proc(t: ^testing.T) {
 		aad[:],
 		derived_ciphertext[:],
 	)
-	tc.expect(t, !ok, "Expected false for decrypt(tag, aad, corrupted_ciphertext)")
+	testing.expect(t, !ok, "Expected false for decrypt(tag, aad, corrupted_ciphertext)")
 
 	aad[0] ~= 0xa5
 	ok = chacha20poly1305.decrypt(
@@ -271,15 +238,13 @@ test_chacha20poly1305 :: proc(t: ^testing.T) {
 		aad[:],
 		ciphertext[:],
 	)
-	tc.expect(t, !ok, "Expected false for decrypt(tag, corrupted_aad, ciphertext)")
+	testing.expect(t, !ok, "Expected false for decrypt(tag, corrupted_aad, ciphertext)")
 }
 
 @(test)
 test_rand_bytes :: proc(t: ^testing.T) {
-	tc.log(t, "Testing rand_bytes")
-
 	if !crypto.HAS_RAND_BYTES {
-		tc.log(t, "rand_bytes not supported - skipping")
+		log.info("rand_bytes not supported - skipping")
 		return
 	}
 
@@ -307,10 +272,5 @@ test_rand_bytes :: proc(t: ^testing.T) {
 			break
 		}
 	}
-
-	tc.expect(
-		t,
-		seems_ok,
-		"Expected to randomize the head and tail of the buffer within a handful of attempts",
-	)
+	testing.expect(t, seems_ok, "Expected to randomize the head and tail of the buffer within a handful of attempts")
 }

+ 55 - 71
tests/core/crypto/test_core_crypto_aes.odin

@@ -2,21 +2,20 @@ package test_core_crypto
 
 import "base:runtime"
 import "core:encoding/hex"
-import "core:fmt"
+import "core:log"
 import "core:testing"
 
 import "core:crypto/aes"
 import "core:crypto/sha2"
 
-import tc "tests:common"
-
 @(test)
 test_aes :: proc(t: ^testing.T) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 
-	tc.log(t, "Testing AES")
+	log.info("Testing AES")
 
 	impls := make([dynamic]aes.Implementation, 0, 2)
+	defer delete(impls)
 	append(&impls, aes.Implementation.Portable)
 	if aes.is_hardware_accelerated() {
 		append(&impls, aes.Implementation.Hardware)
@@ -29,9 +28,8 @@ test_aes :: proc(t: ^testing.T) {
 	}
 }
 
-@(test)
 test_aes_ecb :: proc(t: ^testing.T, impl: aes.Implementation) {
-	tc.log(t, fmt.tprintf("Testing AES-ECB/%v", impl))
+	log.infof("Testing AES-ECB/%v", impl)
 
 	test_vectors := []struct {
 		key: string,
@@ -111,39 +109,34 @@ test_aes_ecb :: proc(t: ^testing.T, impl: aes.Implementation) {
 
 		aes.encrypt_ecb(&ctx, dst[:], plaintext)
 		dst_str := string(hex.encode(dst[:], context.temp_allocator))
-		tc.expect(
+		testing.expectf(
 			t,
 			dst_str == v.ciphertext,
-			fmt.tprintf(
-				"AES-ECB/%v: Expected: %s for encrypt(%s, %s), but got %s instead",
-				impl,
-				v.ciphertext,
-				v.key,
-				v.plaintext,
-				dst_str,
-			),
+			"AES-ECB/%v: Expected: %s for encrypt(%s, %s), but got %s instead",
+			impl,
+			v.ciphertext,
+			v.key,
+			v.plaintext,
+			dst_str,
 		)
 
 		aes.decrypt_ecb(&ctx, dst[:], ciphertext)
 		dst_str = string(hex.encode(dst[:], context.temp_allocator))
-		tc.expect(
+		testing.expectf(
 			t,
 			dst_str == v.plaintext,
-			fmt.tprintf(
-				"AES-ECB/%v: Expected: %s for decrypt(%s, %s), but got %s instead",
-				impl,
-				v.plaintext,
-				v.key,
-				v.ciphertext,
-				dst_str,
-			),
+			"AES-ECB/%v: Expected: %s for decrypt(%s, %s), but got %s instead",
+			impl,
+			v.plaintext,
+			v.key,
+			v.ciphertext,
+			dst_str,
 		)
 	}
 }
 
-@(test)
 test_aes_ctr :: proc(t: ^testing.T, impl: aes.Implementation) {
-	tc.log(t, fmt.tprintf("Testing AES-CTR/%v", impl))
+	log.infof("Testing AES-CTR/%v", impl)
 
 	test_vectors := []struct {
 		key: string,
@@ -185,18 +178,16 @@ test_aes_ctr :: proc(t: ^testing.T, impl: aes.Implementation) {
 		aes.xor_bytes_ctr(&ctx, dst, plaintext)
 
 		dst_str := string(hex.encode(dst[:], context.temp_allocator))
-		tc.expect(
+		testing.expectf(
 			t,
 			dst_str == v.ciphertext,
-			fmt.tprintf(
-				"AES-CTR/%v: Expected: %s for encrypt(%s, %s, %s), but got %s instead",
-				impl,
-				v.ciphertext,
-				v.key,
-				v.iv,
-				v.plaintext,
-				dst_str,
-			),
+			"AES-CTR/%v: Expected: %s for encrypt(%s, %s, %s), but got %s instead",
+			impl,
+			v.ciphertext,
+			v.key,
+			v.iv,
+			v.plaintext,
+			dst_str,
 		)
 	}
 
@@ -224,21 +215,18 @@ test_aes_ctr :: proc(t: ^testing.T, impl: aes.Implementation) {
 	digest_str := string(hex.encode(digest[:], context.temp_allocator))
 
 	expected_digest_str := "d4445343afeb9d1237f95b10d00358aed4c1d7d57c9fe480cd0afb5e2ffd448c"
-	tc.expect(
+	testing.expectf(
 		t,
 		expected_digest_str == digest_str,
-		fmt.tprintf(
-			"AES-CTR/%v: Expected %s for keystream digest, but got %s instead",
-			impl,
-			expected_digest_str,
-			digest_str,
-		),
+		"AES-CTR/%v: Expected %s for keystream digest, but got %s instead",
+		impl,
+		expected_digest_str,
+		digest_str,
 	)
 }
 
-@(test)
 test_aes_gcm :: proc(t: ^testing.T, impl: aes.Implementation) {
-	tc.log(t, fmt.tprintf("Testing AES-GCM/%v", impl))
+	log.infof("Testing AES-GCM/%v", impl)
 
 	// NIST did a reorg of their site, so the source of the test vectors
 	// is only available from an archive.  The commented out tests are
@@ -422,41 +410,37 @@ test_aes_gcm :: proc(t: ^testing.T, impl: aes.Implementation) {
 		dst_str := string(hex.encode(dst[:], context.temp_allocator))
 		tag_str := string(hex.encode(tag_[:], context.temp_allocator))
 
-		tc.expect(
+		testing.expectf(
 			t,
 			dst_str == v.ciphertext && tag_str == v.tag,
-			fmt.tprintf(
-				"AES-GCM/%v: Expected: (%s, %s) for seal(%s, %s, %s, %s), but got (%s, %s) instead",
-				impl,
-				v.ciphertext,
-				v.tag,
-				v.key,
-				v.iv,
-				v.aad,
-				v.plaintext,
-				dst_str,
-				tag_str,
-			),
+			"AES-GCM/%v: Expected: (%s, %s) for seal(%s, %s, %s, %s), but got (%s, %s) instead",
+			impl,
+			v.ciphertext,
+			v.tag,
+			v.key,
+			v.iv,
+			v.aad,
+			v.plaintext,
+			dst_str,
+			tag_str,
 		)
 
 		ok := aes.open_gcm(&ctx, dst, iv, aad, ciphertext, tag)
 		dst_str = string(hex.encode(dst[:], context.temp_allocator))
 
-		tc.expect(
+		testing.expectf(
 			t,
 			ok && dst_str == v.plaintext,
-			fmt.tprintf(
-				"AES-GCM/%v: Expected: (%s, true) for open(%s, %s, %s, %s, %s), but got (%s, %s) instead",
-				impl,
-				v.plaintext,
-				v.key,
-				v.iv,
-				v.aad,
-				v.ciphertext,
-				v.tag,
-				dst_str,
-				ok,
-			),
+			"AES-GCM/%v: Expected: (%s, true) for open(%s, %s, %s, %s, %s), but got (%s, %s) instead",
+			impl,
+			v.plaintext,
+			v.key,
+			v.iv,
+			v.aad,
+			v.ciphertext,
+			v.tag,
+			dst_str,
+			ok,
 		)
 	}
 }

+ 113 - 180
tests/core/crypto/test_core_crypto_ecc25519.odin

@@ -1,8 +1,6 @@
 package test_core_crypto
 
-import "base:runtime"
 import "core:encoding/hex"
-import "core:fmt"
 import "core:testing"
 
 import field "core:crypto/_fiat/field_curve25519"
@@ -10,25 +8,8 @@ import "core:crypto/ed25519"
 import "core:crypto/ristretto255"
 import "core:crypto/x25519"
 
-import tc "tests:common"
-
-@(test)
-test_ecc25519 :: proc(t: ^testing.T) {
-	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
-
-	tc.log(t, "Testing curve25519 ECC")
-
-	test_sqrt_ratio_m1(t)
-	test_ristretto255(t)
-
-	test_ed25519(t)
-	test_x25519(t)
-}
-
 @(test)
 test_sqrt_ratio_m1 :: proc(t: ^testing.T) {
-	tc.log(t, "Testing sqrt_ratio_m1")
-
 	test_vectors := []struct {
 		u: string,
 		v: string,
@@ -90,25 +71,21 @@ test_sqrt_ratio_m1 :: proc(t: ^testing.T) {
 			field.fe_relax_cast(&vee),
 		)
 
-		tc.expect(
+		testing.expectf(
 			t,
 			(was_square == 1) == v.was_square && field.fe_equal_bytes(&r, r_) == 1,
-			fmt.tprintf(
-				"Expected (%v, %s) for SQRT_RATIO_M1(%s, %s), got %s",
-				v.was_square,
-				v.r,
-				v.u,
-				v.v,
-				fe_str(&r),
-			),
+			"Expected (%v, %s) for SQRT_RATIO_M1(%s, %s), got %s",
+			v.was_square,
+			v.r,
+			v.u,
+			v.v,
+			fe_str(&r),
 		)
 	}
 }
 
 @(test)
 test_ristretto255 :: proc(t: ^testing.T) {
-	tc.log(t, "Testing ristretto255")
-
 	ge_gen: ristretto255.Group_Element
 	ristretto255.ge_generator(&ge_gen)
 
@@ -158,7 +135,7 @@ test_ristretto255 :: proc(t: ^testing.T) {
 
 		ge: ristretto255.Group_Element
 		ok := ristretto255.ge_set_bytes(&ge, b)
-		tc.expect(t, !ok, fmt.tprintf("Expected false for %s", x))
+		testing.expectf(t, !ok, "Expected false for %s", x)
 	}
 
 	generator_multiples := []string {
@@ -185,22 +162,20 @@ test_ristretto255 :: proc(t: ^testing.T) {
 
 		ge := &ges[i]
 		ok := ristretto255.ge_set_bytes(ge, b)
-		tc.expect(t, ok, fmt.tprintf("Expected true for %s", x))
+		testing.expectf(t, ok, "Expected true for %s", x)
 
 		x_check := ge_str(ge)
 
-		tc.expect(
+		testing.expectf(
 			t,
 			x == x_check,
-			fmt.tprintf(
-				"Expected %s (round-trip) but got %s instead",
-				x,
-				x_check,
-			),
+			"Expected %s (round-trip) but got %s instead",
+			x,
+			x_check,
 		)
 
 		if i == 1 {
-			tc.expect(
+			testing.expect(
 				t,
 				ristretto255.ge_equal(ge, &ge_gen) == 1,
 				"Expected element 1 to be the generator",
@@ -217,41 +192,35 @@ test_ristretto255 :: proc(t: ^testing.T) {
 
 		ristretto255.ge_scalarmult_generator(&ge_check, &sc)
 		x_check := ge_str(&ge_check)
-		tc.expect(
+		testing.expectf(
 			t,
 			x_check == generator_multiples[i],
-			fmt.tprintf(
-				"Expected %s for G * %d (specialized), got %s",
-				generator_multiples[i],
-				i,
-				x_check,
-			),
+			"Expected %s for G * %d (specialized), got %s",
+			generator_multiples[i],
+			i,
+			x_check,
 		)
 
 		ristretto255.ge_scalarmult(&ge_check, &ges[1], &sc)
 		x_check = ge_str(&ge_check)
-		tc.expect(
+		testing.expectf(
 			t,
 			x_check == generator_multiples[i],
-			fmt.tprintf(
-				"Expected %s for G * %d (generic), got %s (slow compare)",
-				generator_multiples[i],
-				i,
-				x_check,
-			),
+			"Expected %s for G * %d (generic), got %s (slow compare)",
+			generator_multiples[i],
+			i,
+			x_check,
 		)
 
 		ristretto255.ge_scalarmult_vartime(&ge_check, &ges[1], &sc)
 		x_check = ge_str(&ge_check)
-		tc.expect(
+		testing.expectf(
 			t,
 			x_check == generator_multiples[i],
-			fmt.tprintf(
-				"Expected %s for G * %d (generic vartime), got %s (slow compare)",
-				generator_multiples[i],
-				i,
-				x_check,
-			),
+			"Expected %s for G * %d (generic vartime), got %s (slow compare)",
+			generator_multiples[i],
+			i,
+			x_check,
 		)
 
 		switch i {
@@ -261,28 +230,24 @@ test_ristretto255 :: proc(t: ^testing.T) {
 			ristretto255.ge_add(&ge_check, ge_prev, &ge_gen)
 
 			x_check = ge_str(&ge_check)
-			tc.expect(
+			testing.expectf(
 				t,
 				x_check == generator_multiples[i],
-				fmt.tprintf(
-					"Expected %s for ges[%d] + ges[%d], got %s (slow compare)",
-					generator_multiples[i],
-					i-1,
-					1,
-					x_check,
-				),
+				"Expected %s for ges[%d] + ges[%d], got %s (slow compare)",
+				generator_multiples[i],
+				i-1,
+				1,
+				x_check,
 			)
 
-			tc.expect(
+			testing.expectf(
 				t,
 				ristretto255.ge_equal(&ges[i], &ge_check) == 1,
-				fmt.tprintf(
-					"Expected %s for ges[%d] + ges[%d], got %s (fast compare)",
-					generator_multiples[i],
-					i-1,
-					1,
-					x_check,
-				),
+				"Expected %s for ges[%d] + ges[%d], got %s (fast compare)",
+				generator_multiples[i],
+				i-1,
+				1,
+				x_check,
 			)
 		}
 	}
@@ -344,22 +309,18 @@ test_ristretto255 :: proc(t: ^testing.T) {
 		ristretto255.ge_set_wide_bytes(&ge, in_bytes)
 
 		ge_check := ge_str(&ge)
-		tc.expect(
+		testing.expectf(
 			t,
 			ge_check == v.output,
-			fmt.tprintf(
-				"Expected %s for %s, got %s",
-				v.output,
-				ge_check,
-			),
+			"Expected %s for %s, got %s",
+			v.output,
+			ge_check,
 		)
 	}
 }
 
 @(test)
 test_ed25519 :: proc(t: ^testing.T) {
-	tc.log(t, "Testing ed25519")
-
 	test_vectors_rfc := []struct {
 		priv_key: string,
 		pub_key:  string,
@@ -401,87 +362,73 @@ test_ed25519 :: proc(t: ^testing.T) {
 
 		priv_key: ed25519.Private_Key
 		ok := ed25519.private_key_set_bytes(&priv_key, priv_bytes)
-		tc.expect(
+		testing.expectf(
 			t,
 			ok,
-			fmt.tprintf(
-				"Expected %s to be a valid private key",
-				v.priv_key,
-			),
+			"Expected %s to be a valid private key",
+			v.priv_key,
 		)
 
 		key_bytes: [32]byte
 		ed25519.private_key_bytes(&priv_key, key_bytes[:])
-		tc.expect(
+		testing.expectf(
 			t,
 			ok,
-			fmt.tprintf(
-				"Expected private key %s round-trip, got %s",
-				v.priv_key,
-				string(hex.encode(key_bytes[:], context.temp_allocator)),
-			),
+			"Expected private key %s round-trip, got %s",
+			v.priv_key,
+			string(hex.encode(key_bytes[:], context.temp_allocator)),
 		)
 
 		pub_key: ed25519.Public_Key
 		ok = ed25519.public_key_set_bytes(&pub_key, pub_bytes)
-		tc.expect(
+		testing.expectf(
 			t,
 			ok,
-			fmt.tprintf(
-				"Expected %s to be a valid public key (priv->pub: %s)",
-				v.pub_key,
-				string(hex.encode(priv_key._pub_key._b[:], context.temp_allocator)),
-			),
+			"Expected %s to be a valid public key (priv->pub: %s)",
+			v.pub_key,
+			string(hex.encode(priv_key._pub_key._b[:], context.temp_allocator)),
 		)
 
 		ed25519.public_key_bytes(&pub_key, key_bytes[:])
-		tc.expect(
+		testing.expectf(
 			t,
 			ok,
-			fmt.tprintf(
-				"Expected public key %s round-trip, got %s",
-				v.pub_key,
-				string(hex.encode(key_bytes[:], context.temp_allocator)),
-			),
+			"Expected public key %s round-trip, got %s",
+			v.pub_key,
+			string(hex.encode(key_bytes[:], context.temp_allocator)),
 		)
 
 		sig: [ed25519.SIGNATURE_SIZE]byte
 		ed25519.sign(&priv_key, msg_bytes, sig[:])
 		x := string(hex.encode(sig[:], context.temp_allocator))
-		tc.expect(
+		testing.expectf(
 			t,
 			x == v.sig,
-			fmt.tprintf(
-				"Expected %s for sign(%s, %s), got %s",
-				v.sig,
-				v.priv_key,
-				v.msg,
-				x,
-			),
+			"Expected %s for sign(%s, %s), got %s",
+			v.sig,
+			v.priv_key,
+			v.msg,
+			x,
 		)
 
 		ok = ed25519.verify(&pub_key, msg_bytes, sig_bytes)
-		tc.expect(
+		testing.expectf(
 			t,
 			ok,
-			fmt.tprintf(
-				"Expected true for verify(%s, %s, %s)",
-				v.pub_key,
-				v.msg,
-				v.sig,
-			),
+			"Expected true for verify(%s, %s, %s)",
+			v.pub_key,
+			v.msg,
+			v.sig,
 		)
 
 		ok = ed25519.verify(&priv_key._pub_key, msg_bytes, sig_bytes)
-		tc.expect(
+		testing.expectf(
 			t,
 			ok,
-			fmt.tprintf(
-				"Expected true for verify(pub(%s), %s %s)",
-				v.priv_key,
-				v.msg,
-				v.sig,
-			),
+			"Expected true for verify(pub(%s), %s %s)",
+			v.priv_key,
+			v.msg,
+			v.sig,
 		)
 
 		// Corrupt the message and make sure verification fails.
@@ -493,15 +440,13 @@ test_ed25519 :: proc(t: ^testing.T) {
 			msg_bytes[0] = msg_bytes[0] ~ 69
 		}
 		ok = ed25519.verify(&pub_key, msg_bytes, sig_bytes)
-		tc.expect(
+		testing.expectf(
 			t,
 			ok == false,
-			fmt.tprintf(
-				"Expected false for verify(%s, %s (corrupted), %s)",
-				v.pub_key,
-				v.msg,
-				v.sig,
-			),
+			"Expected false for verify(%s, %s (corrupted), %s)",
+			v.pub_key,
+			v.msg,
+			v.sig,
 		)
 	}
 
@@ -634,15 +579,13 @@ test_ed25519 :: proc(t: ^testing.T) {
 
 		pub_key: ed25519.Public_Key
 		ok := ed25519.public_key_set_bytes(&pub_key, pub_bytes)
-		tc.expect(
+		testing.expectf(
 			t,
 			ok == v.pub_key_ok,
-			fmt.tprintf(
-				"speccheck/%d: Expected %s to be a (in)valid public key, got %v",
-				i,
-				v.pub_key,
-				ok,
-			),
+			"speccheck/%d: Expected %s to be a (in)valid public key, got %v",
+			i,
+			v.pub_key,
+			ok,
 		)
 
 		// If A is rejected for being non-canonical, skip signature check.
@@ -651,17 +594,15 @@ test_ed25519 :: proc(t: ^testing.T) {
 		}
 
 		ok = ed25519.verify(&pub_key, msg_bytes, sig_bytes)
-		tc.expect(
+		testing.expectf(
 			t,
 			ok == v.sig_ok,
-			fmt.tprintf(
-				"speccheck/%d Expected %v for verify(%s, %s, %s)",
-				i,
-				v.sig_ok,
-				v.pub_key,
-				v.msg,
-				v.sig,
-			),
+			"speccheck/%d Expected %v for verify(%s, %s, %s)",
+			i,
+			v.sig_ok,
+			v.pub_key,
+			v.msg,
+			v.sig,
 		)
 
 		// If the signature is accepted, skip the relaxed signature check.
@@ -670,25 +611,21 @@ test_ed25519 :: proc(t: ^testing.T) {
 		}
 
 		ok = ed25519.verify(&pub_key, msg_bytes, sig_bytes, true)
-		tc.expect(
+		testing.expectf(
 			t,
 			ok == v.sig_ok_relaxed,
-			fmt.tprintf(
-				"speccheck/%d Expected %v for verify(%s, %s, %s, true)",
-				i,
-				v.sig_ok_relaxed,
-				v.pub_key,
-				v.msg,
-				v.sig,
-			),
+			"speccheck/%d Expected %v for verify(%s, %s, %s, true)",
+			i,
+			v.sig_ok_relaxed,
+			v.pub_key,
+			v.msg,
+			v.sig,
 		)
 	}
 }
 
 @(test)
 test_x25519 :: proc(t: ^testing.T) {
-	tc.log(t, "Testing X25519")
-
 	// Local copy of this so that the base point doesn't need to be exported.
 	_BASE_POINT: [32]byte = {
 		9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
@@ -720,17 +657,15 @@ test_x25519 :: proc(t: ^testing.T) {
 		x25519.scalarmult(derived_point[:], scalar[:], point[:])
 		derived_point_str := string(hex.encode(derived_point[:], context.temp_allocator))
 
-		tc.expect(
+		testing.expectf(
 			t,
 			derived_point_str == v.product,
-			fmt.tprintf(
-				"Expected %s for %s * %s, but got %s instead",
-				v.product,
-				v.scalar,
-				v.point,
-				derived_point_str,
-			),
-		)
+			"Expected %s for %s * %s, but got %s instead",
+			v.product,
+			v.scalar,
+			v.point,
+			derived_point_str,
+			)
 
 		// Abuse the test vectors to sanity-check the scalar-basepoint multiply.
 		p1, p2: [x25519.POINT_SIZE]byte
@@ -738,15 +673,13 @@ test_x25519 :: proc(t: ^testing.T) {
 		x25519.scalarmult(p2[:], scalar[:], _BASE_POINT[:])
 		p1_str := string(hex.encode(p1[:], context.temp_allocator))
 		p2_str := string(hex.encode(p2[:], context.temp_allocator))
-		tc.expect(
+		testing.expectf(
 			t,
 			p1_str == p2_str,
-			fmt.tprintf(
-				"Expected %s for %s * basepoint, but got %s instead",
-				p2_str,
-				v.scalar,
-				p1_str,
-			),
+			"Expected %s for %s * basepoint, but got %s instead",
+			p2_str,
+			v.scalar,
+			p1_str,
 		)
 	}
 }
@@ -763,4 +696,4 @@ fe_str :: proc(fe: ^field.Tight_Field_Element) -> string {
 	b: [32]byte
 	field.fe_to_bytes(&b, fe)
 	return string(hex.encode(b[:], context.temp_allocator))
-}
+}

+ 39 - 59
tests/core/crypto/test_core_crypto_hash.odin

@@ -3,23 +3,17 @@ package test_core_crypto
 import "base:runtime"
 import "core:bytes"
 import "core:encoding/hex"
-import "core:fmt"
 import "core:strings"
 import "core:testing"
-
 import "core:crypto/hash"
 
-import tc "tests:common"
-
 @(test)
 test_hash :: proc(t: ^testing.T) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 
-	tc.log(t, "Testing Hashes")
-
 	// TODO:
 	// - Stick the test vectors in a JSON file or something.
-	data_1_000_000_a := strings.repeat("a", 1_000_000)
+	data_1_000_000_a := strings.repeat("a", 1_000_000, context.temp_allocator)
 
 	digest: [hash.MAX_DIGEST_SIZE]byte
 	test_vectors := []struct{
@@ -496,16 +490,14 @@ test_hash :: proc(t: ^testing.T) {
 
 		dst_str := string(hex.encode(dst, context.temp_allocator))
 
-		tc.expect(
+		testing.expectf(
 			t,
 			dst_str == v.hash,
-			fmt.tprintf(
-				"%s/incremental: Expected: %s for input of %s, but got %s instead",
-				algo_name,
-				v.hash,
-				v.str,
-				dst_str,
-			),
+			"%s/incremental: Expected: %s for input of %s, but got %s instead",
+			algo_name,
+			v.hash,
+			v.str,
+			dst_str,
 		)
 	}
 
@@ -521,25 +513,21 @@ test_hash :: proc(t: ^testing.T) {
 		// still correct.
 		digest_sz := hash.DIGEST_SIZES[algo]
 		block_sz := hash.BLOCK_SIZES[algo]
-		tc.expect(
+		testing.expectf(
 			t,
 			digest_sz <= hash.MAX_DIGEST_SIZE,
-			fmt.tprintf(
-				"%s: Digest size %d exceeds max %d",
-				algo_name,
-				digest_sz,
-				hash.MAX_DIGEST_SIZE,
-			),
+			"%s: Digest size %d exceeds max %d",
+			algo_name,
+			digest_sz,
+			hash.MAX_DIGEST_SIZE,
 		)
-		tc.expect(
+		testing.expectf(
 			t,
 			block_sz <= hash.MAX_BLOCK_SIZE,
-			fmt.tprintf(
-				"%s: Block size %d exceeds max %d",
-				algo_name,
-				block_sz,
-				hash.MAX_BLOCK_SIZE,
-			),
+			"%s: Block size %d exceeds max %d",
+			algo_name,
+			block_sz,
+			hash.MAX_BLOCK_SIZE,
 		)
 
 		// Exercise most of the happy-path for the high level interface.
@@ -553,15 +541,13 @@ test_hash :: proc(t: ^testing.T) {
 		a_str := string(hex.encode(digest_a, context.temp_allocator))
 		b_str := string(hex.encode(digest_b, context.temp_allocator))
 
-		tc.expect(
+		testing.expectf(
 			t,
 			a_str == b_str,
-			fmt.tprintf(
-				"%s/cmp: Expected: %s (hash_stream) == %s (hash_bytes)",
-				algo_name,
-				a_str,
-				b_str,
-			),
+			"%s/cmp: Expected: %s (hash_stream) == %s (hash_bytes)",
+			algo_name,
+			a_str,
+			b_str,
 		)
 
 		// Exercise the rolling digest functionality, which also covers
@@ -571,25 +557,21 @@ test_hash :: proc(t: ^testing.T) {
 
 		api_algo := hash.algorithm(&ctx)
 		api_digest_size := hash.digest_size(&ctx)
-		tc.expect(
+		testing.expectf(
 			t,
 			algo == api_algo,
-			fmt.tprintf(
-				"%s/algorithm: Expected: %v but got %v instead",
-				algo_name,
-				algo,
-				api_algo,
-			),
+			"%s/algorithm: Expected: %v but got %v instead",
+			algo_name,
+			algo,
+			api_algo,
 		)
-		tc.expect(
+		testing.expectf(
 			t,
 			hash.DIGEST_SIZES[algo] == api_digest_size,
-			fmt.tprintf(
-				"%s/digest_size: Expected: %d but got %d instead",
-				algo_name,
-				hash.DIGEST_SIZES[algo],
-				api_digest_size,
-			),
+			"%s/digest_size: Expected: %d but got %d instead",
+			algo_name,
+			hash.DIGEST_SIZES[algo],
+			api_digest_size,
 		)
 
 		hash.update(&ctx, digest_a)
@@ -604,16 +586,14 @@ test_hash :: proc(t: ^testing.T) {
 		b_str = string(hex.encode(digest_b, context.temp_allocator))
 		c_str := string(hex.encode(digest_c, context.temp_allocator))
 
-		tc.expect(
+		testing.expectf(
 			t,
 			a_str == b_str && b_str == c_str,
-			fmt.tprintf(
-				"%s/rolling: Expected: %s (first) == %s (second) == %s (third)",
-				algo_name,
-				a_str,
-				b_str,
-				c_str,
-			),
+			"%s/rolling: Expected: %s (first) == %s (second) == %s (third)",
+			algo_name,
+			a_str,
+			b_str,
+			c_str,
 		)
 	}
-}
+}

+ 18 - 36
tests/core/crypto/test_core_crypto_kdf.odin

@@ -2,28 +2,14 @@ package test_core_crypto
 
 import "base:runtime"
 import "core:encoding/hex"
-import "core:fmt"
 import "core:testing"
-
 import "core:crypto/hash"
 import "core:crypto/hkdf"
 import "core:crypto/pbkdf2"
 
-import tc "tests:common"
-
-@(test)
-test_kdf :: proc(t: ^testing.T) {
-	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
-
-	tc.log(t, "Testing KDFs")
-
-	test_hkdf(t)
-	test_pbkdf2(t)
-}
-
 @(test)
 test_hkdf :: proc(t: ^testing.T) {
-	tc.log(t, "Testing HKDF")
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 
 	tmp: [128]byte // Good enough.
 
@@ -70,25 +56,23 @@ test_hkdf :: proc(t: ^testing.T) {
 
 		dst_str := string(hex.encode(dst, context.temp_allocator))
 
-		tc.expect(
+		testing.expectf(
 			t,
 			dst_str == v.okm,
-			fmt.tprintf(
-				"HKDF-%s: Expected: %s for input of (%s, %s, %s), but got %s instead",
-				algo_name,
-				v.okm,
-				v.ikm,
-				v.salt,
-				v.info,
-				dst_str,
-			),
+			"HKDF-%s: Expected: %s for input of (%s, %s, %s), but got %s instead",
+			algo_name,
+			v.okm,
+			v.ikm,
+			v.salt,
+			v.info,
+			dst_str,
 		)
 	}
 }
 
 @(test)
 test_pbkdf2 :: proc(t: ^testing.T) {
-	tc.log(t, "Testing PBKDF2")
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 
 	tmp: [64]byte // 512-bits is enough for every output for now.
 
@@ -174,18 +158,16 @@ test_pbkdf2 :: proc(t: ^testing.T) {
 
 		dst_str := string(hex.encode(dst, context.temp_allocator))
 
-		tc.expect(
+		testing.expectf(
 			t,
 			dst_str == v.dk,
-			fmt.tprintf(
-				"HMAC-%s: Expected: %s for input of (%s, %s, %d), but got %s instead",
-				algo_name,
-				v.dk,
-				v.password,
-				v.salt,
-				v.iterations,
-				dst_str,
-			),
+			"HMAC-%s: Expected: %s for input of (%s, %s, %d), but got %s instead",
+			algo_name,
+			v.dk,
+			v.password,
+			v.salt,
+			v.iterations,
+			dst_str,
 		)
 	}
 }

+ 29 - 51
tests/core/crypto/test_core_crypto_mac.odin

@@ -2,30 +2,17 @@ package test_core_crypto
 
 import "base:runtime"
 import "core:encoding/hex"
-import "core:fmt"
 import "core:mem"
 import "core:testing"
-
 import "core:crypto/hash"
 import "core:crypto/hmac"
 import "core:crypto/poly1305"
 import "core:crypto/siphash"
 
-import tc "tests:common"
-
 @(test)
-test_mac :: proc(t: ^testing.T) {
+test_hmac :: proc(t: ^testing.T) {
 	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 
-	tc.log(t, "Testing MACs")
-
-	test_hmac(t)
-	test_poly1305(t)
-	test_siphash_2_4(t)
-}
-
-@(test)
-test_hmac :: proc(t: ^testing.T) {
 	// Test cases pulled out of RFC 6234, note that HMAC is a generic
 	// construct so as long as the underlying hash is correct and all
 	// the code paths are covered the implementation is "fine", so
@@ -86,40 +73,36 @@ test_hmac :: proc(t: ^testing.T) {
 		msg_str := string(hex.encode(msg, context.temp_allocator))
 		dst_str := string(hex.encode(dst[:tag_len], context.temp_allocator))
 
-		tc.expect(
+		testing.expectf(
 			t,
 			dst_str == expected_str,
-			fmt.tprintf(
-				"%s/incremental: Expected: %s for input of %s - %s, but got %s instead",
-				algo_name,
-				tags_sha256[i],
-				key_str,
-				msg_str,
-				dst_str,
-			),
+			"%s/incremental: Expected: %s for input of %s - %s, but got %s instead",
+			algo_name,
+			tags_sha256[i],
+			key_str,
+			msg_str,
+			dst_str,
 		)
 
 		hmac.sum(algo, dst, msg, key)
 		oneshot_str := string(hex.encode(dst[:tag_len], context.temp_allocator))
 
-		tc.expect(
+		testing.expectf(
 			t,
 			oneshot_str == expected_str,
-			fmt.tprintf(
-				"%s/oneshot: Expected: %s for input of %s - %s, but got %s instead",
-				algo_name,
-				tags_sha256[i],
-				key_str,
-				msg_str,
-				oneshot_str,
-			),
+			"%s/oneshot: Expected: %s for input of %s - %s, but got %s instead",
+			algo_name,
+			tags_sha256[i],
+			key_str,
+			msg_str,
+			oneshot_str,
 		)
 	}
 }
 
 @(test)
 test_poly1305 :: proc(t: ^testing.T) {
-	tc.log(t, "Testing poly1305")
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 
 	// Test cases taken from poly1305-donna.
 	key := [poly1305.KEY_SIZE]byte {
@@ -157,16 +140,17 @@ test_poly1305 :: proc(t: ^testing.T) {
 
 	// Verify - oneshot + compare
 	ok := poly1305.verify(tag[:], msg[:], key[:])
-	tc.expect(t, ok, "oneshot verify call failed")
+	testing.expect(t, ok, "oneshot verify call failed")
 
 	// Sum - oneshot
 	derived_tag: [poly1305.TAG_SIZE]byte
 	poly1305.sum(derived_tag[:], msg[:], key[:])
 	derived_tag_str := string(hex.encode(derived_tag[:], context.temp_allocator))
-	tc.expect(
+	testing.expectf(
 		t,
 		derived_tag_str == tag_str,
-		fmt.tprintf("Expected %s for sum(msg, key), but got %s instead", tag_str, derived_tag_str),
+		"Expected %s for sum(msg, key), but got %s instead",
+		tag_str, derived_tag_str,
 	)
 
 	// Incremental
@@ -182,21 +166,16 @@ test_poly1305 :: proc(t: ^testing.T) {
 	}
 	poly1305.final(&ctx, derived_tag[:])
 	derived_tag_str = string(hex.encode(derived_tag[:], context.temp_allocator))
-	tc.expect(
+	testing.expectf(
 		t,
 		derived_tag_str == tag_str,
-		fmt.tprintf(
-			"Expected %s for init/update/final - incremental, but got %s instead",
-			tag_str,
-			derived_tag_str,
-		),
+		"Expected %s for init/update/final - incremental, but got %s instead",
+		tag_str, derived_tag_str,
 	)
 }
 
 @(test)
 test_siphash_2_4 :: proc(t: ^testing.T) {
-	tc.log(t, "Testing SipHash-2-4")
-
 	// Test vectors from
 	// https://github.com/veorq/SipHash/blob/master/vectors.h
 	test_vectors := [?]u64 {
@@ -225,6 +204,7 @@ test_siphash_2_4 :: proc(t: ^testing.T) {
 
 	for i in 0 ..< len(test_vectors) {
 		data := make([]byte, i)
+		defer delete(data)
 		for j in 0 ..< i {
 			data[j] = byte(j)
 		}
@@ -232,15 +212,13 @@ test_siphash_2_4 :: proc(t: ^testing.T) {
 		vector := test_vectors[i]
 		computed := siphash.sum_2_4(data[:], key[:])
 
-		tc.expect(
+		testing.expectf(
 			t,
 			computed == vector,
-			fmt.tprintf(
-				"Expected: 0x%x for input of %v, but got 0x%x instead",
-				vector,
-				data,
-				computed,
-			),
+			"Expected: 0x%x for input of %v, but got 0x%x instead",
+			vector,
+			data,
+			computed,
 		)
 	}
 }

+ 36 - 60
tests/core/crypto/test_core_crypto_sha3_variants.odin

@@ -2,30 +2,14 @@ package test_core_crypto
 
 import "base:runtime"
 import "core:encoding/hex"
-import "core:fmt"
 import "core:testing"
-
 import "core:crypto/kmac"
 import "core:crypto/shake"
 import "core:crypto/tuplehash"
 
-import tc "tests:common"
-
-@(test)
-test_sha3_variants :: proc(t: ^testing.T) {
-	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
-
-	tc.log(t, "Testing SHA3 derived functions")
-
-	test_shake(t)
-	test_cshake(t)
-	test_tuplehash(t)
-	test_kmac(t)
-}
-
 @(test)
 test_shake :: proc(t: ^testing.T) {
-	tc.log(t, "Testing SHAKE")
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 
 	test_vectors := []struct {
 		sec_strength: int,
@@ -67,23 +51,21 @@ test_shake :: proc(t: ^testing.T) {
 
 		dst_str := string(hex.encode(dst, context.temp_allocator))
 
-		tc.expect(
+		testing.expectf(
 			t,
 			dst_str == v.output,
-			fmt.tprintf(
-				"SHAKE%d: Expected: %s for input of %s, but got %s instead",
-				v.sec_strength,
-				v.output,
-				v.str,
-				dst_str,
-			),
+			"SHAKE%d: Expected: %s for input of %s, but got %s instead",
+			v.sec_strength,
+			v.output,
+			v.str,
+			dst_str,
 		)
 	}
 }
 
 @(test)
 test_cshake :: proc(t: ^testing.T) {
-	tc.log(t, "Testing cSHAKE")
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 
 	test_vectors := []struct {
 		sec_strength: int,
@@ -135,29 +117,27 @@ test_cshake :: proc(t: ^testing.T) {
 			shake.init_cshake_256(&ctx, domainsep)
 		}
 
-		data, _ := hex.decode(transmute([]byte)(v.str))
+		data, _ := hex.decode(transmute([]byte)(v.str), context.temp_allocator)
 		shake.write(&ctx, data)
 		shake.read(&ctx, dst)
 
 		dst_str := string(hex.encode(dst, context.temp_allocator))
 
-		tc.expect(
+		testing.expectf(
 			t,
 			dst_str == v.output,
-			fmt.tprintf(
-				"cSHAKE%d: Expected: %s for input of %s, but got %s instead",
-				v.sec_strength,
-				v.output,
-				v.str,
-				dst_str,
-			),
+			"cSHAKE%d: Expected: %s for input of %s, but got %s instead",
+			v.sec_strength,
+			v.output,
+			v.str,
+			dst_str,
 		)
 	}
 }
 
 @(test)
 test_tuplehash :: proc(t: ^testing.T) {
-	tc.log(t, "Testing TupleHash(XOF)")
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 
 	test_vectors := []struct {
 		sec_strength: int,
@@ -317,7 +297,7 @@ test_tuplehash :: proc(t: ^testing.T) {
 		}
 
 		for e in v.tuple {
-			data, _ := hex.decode(transmute([]byte)(e))
+			data, _ := hex.decode(transmute([]byte)(e), context.temp_allocator)
 			tuplehash.write_element(&ctx, data)
 		}
 
@@ -332,24 +312,22 @@ test_tuplehash :: proc(t: ^testing.T) {
 
 		dst_str := string(hex.encode(dst, context.temp_allocator))
 
-		tc.expect(
+		testing.expectf(
 			t,
 			dst_str == v.output,
-			fmt.tprintf(
-				"TupleHash%s%d: Expected: %s for input of %v, but got %s instead",
-				suffix,
-				v.sec_strength,
-				v.output,
-				v.tuple,
-				dst_str,
-			),
+			"TupleHash%s%d: Expected: %s for input of %v, but got %s instead",
+			suffix,
+			v.sec_strength,
+			v.output,
+			v.tuple,
+			dst_str,
 		)
 	}
 }
 
 @(test)
 test_kmac :: proc(t:^testing.T) {
-	tc.log(t, "Testing KMAC")
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
 
 	test_vectors := []struct {
 		sec_strength: int,
@@ -410,7 +388,7 @@ test_kmac :: proc(t:^testing.T) {
 	for v in test_vectors {
 		dst := make([]byte, len(v.output) / 2, context.temp_allocator)
 
-		key, _ := hex.decode(transmute([]byte)(v.key))
+		key, _ := hex.decode(transmute([]byte)(v.key), context.temp_allocator)
 		domainsep := transmute([]byte)(v.domainsep)
 
 		ctx: kmac.Context
@@ -421,24 +399,22 @@ test_kmac :: proc(t:^testing.T) {
 			kmac.init_256(&ctx, key, domainsep)
 		}
 
-		data, _ := hex.decode(transmute([]byte)(v.msg))
+		data, _ := hex.decode(transmute([]byte)(v.msg), context.temp_allocator)
 		kmac.update(&ctx, data)
 		kmac.final(&ctx, dst)
 
 		dst_str := string(hex.encode(dst, context.temp_allocator))
 
-		tc.expect(
+		testing.expectf(
 			t,
 			dst_str == v.output,
-			fmt.tprintf(
-				"KMAC%d: Expected: %s for input of (%s, %s, %s), but got %s instead",
-				v.sec_strength,
-				v.output,
-				v.key,
-				v.domainsep,
-				v.msg,
-				dst_str,
-			),
+			"KMAC%d: Expected: %s for input of (%s, %s, %s), but got %s instead",
+			v.sec_strength,
+			v.output,
+			v.key,
+			v.domainsep,
+			v.msg,
+			dst_str,
 		)
 	}
-}
+}

+ 0 - 361
tests/core/crypto/test_crypto_benchmark.odin

@@ -1,361 +0,0 @@
-package test_core_crypto
-
-import "base:runtime"
-import "core:encoding/hex"
-import "core:fmt"
-import "core:testing"
-import "core:time"
-
-import "core:crypto/aes"
-import "core:crypto/chacha20"
-import "core:crypto/chacha20poly1305"
-import "core:crypto/ed25519"
-import "core:crypto/poly1305"
-import "core:crypto/x25519"
-
-import tc "tests:common"
-
-// Cryptographic primitive benchmarks.
-
-@(test)
-bench_crypto :: proc(t: ^testing.T) {
-	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
-
-	fmt.println("Starting benchmarks:")
-
-	bench_chacha20(t)
-	bench_poly1305(t)
-	bench_chacha20poly1305(t)
-	bench_aes256_gcm(t)
-	bench_ed25519(t)
-	bench_x25519(t)
-}
-
-_setup_sized_buf :: proc(
-	options: ^time.Benchmark_Options,
-	allocator := context.allocator,
-) -> (
-	err: time.Benchmark_Error,
-) {
-	assert(options != nil)
-
-	options.input = make([]u8, options.bytes, allocator)
-	return nil if len(options.input) == options.bytes else .Allocation_Error
-}
-
-_teardown_sized_buf :: proc(
-	options: ^time.Benchmark_Options,
-	allocator := context.allocator,
-) -> (
-	err: time.Benchmark_Error,
-) {
-	assert(options != nil)
-
-	delete(options.input)
-	return nil
-}
-
-_benchmark_chacha20 :: proc(
-	options: ^time.Benchmark_Options,
-	allocator := context.allocator,
-) -> (
-	err: time.Benchmark_Error,
-) {
-	buf := options.input
-	key := [chacha20.KEY_SIZE]byte {
-		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
-		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
-		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
-		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
-	}
-	nonce := [chacha20.NONCE_SIZE]byte {
-		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-		0x00, 0x00, 0x00, 0x00,
-	}
-
-	ctx: chacha20.Context = ---
-	chacha20.init(&ctx, key[:], nonce[:])
-
-	for _ in 0 ..= options.rounds {
-		chacha20.xor_bytes(&ctx, buf, buf)
-	}
-	options.count = options.rounds
-	options.processed = options.rounds * options.bytes
-	return nil
-}
-
-_benchmark_poly1305 :: proc(
-	options: ^time.Benchmark_Options,
-	allocator := context.allocator,
-) -> (
-	err: time.Benchmark_Error,
-) {
-	buf := options.input
-	key := [poly1305.KEY_SIZE]byte {
-		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
-		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
-		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
-		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
-	}
-
-	tag: [poly1305.TAG_SIZE]byte = ---
-	for _ in 0 ..= options.rounds {
-		poly1305.sum(tag[:], buf, key[:])
-	}
-	options.count = options.rounds
-	options.processed = options.rounds * options.bytes
-	//options.hash      = u128(h)
-	return nil
-}
-
-_benchmark_chacha20poly1305 :: proc(
-	options: ^time.Benchmark_Options,
-	allocator := context.allocator,
-) -> (
-	err: time.Benchmark_Error,
-) {
-	buf := options.input
-	key := [chacha20.KEY_SIZE]byte {
-		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
-		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
-		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
-		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
-	}
-	nonce := [chacha20.NONCE_SIZE]byte {
-		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-		0x00, 0x00, 0x00, 0x00,
-	}
-
-	tag: [chacha20poly1305.TAG_SIZE]byte = ---
-
-	for _ in 0 ..= options.rounds {
-		chacha20poly1305.encrypt(buf, tag[:], key[:], nonce[:], nil, buf)
-	}
-	options.count = options.rounds
-	options.processed = options.rounds * options.bytes
-	return nil
-}
-
-_benchmark_aes256_gcm :: proc(
-	options: ^time.Benchmark_Options,
-	allocator := context.allocator,
-) -> (
-	err: time.Benchmark_Error,
-) {
-	buf := options.input
-	nonce: [aes.GCM_NONCE_SIZE]byte
-	tag: [aes.GCM_TAG_SIZE]byte = ---
-
-	ctx := transmute(^aes.Context_GCM)context.user_ptr
-
-	for _ in 0 ..= options.rounds {
-		aes.seal_gcm(ctx, buf, tag[:], nonce[:], nil, buf)
-	}
-	options.count = options.rounds
-	options.processed = options.rounds * options.bytes
-	return nil
-}
-
-benchmark_print :: proc(name: string, options: ^time.Benchmark_Options) {
-	fmt.printf(
-		"\t[%v] %v rounds, %v bytes processed in %v ns\n\t\t%5.3f rounds/s, %5.3f MiB/s\n",
-		name,
-		options.rounds,
-		options.processed,
-		time.duration_nanoseconds(options.duration),
-		options.rounds_per_second,
-		options.megabytes_per_second,
-	)
-}
-
-bench_chacha20 :: proc(t: ^testing.T) {
-	name := "ChaCha20 64 bytes"
-	options := &time.Benchmark_Options {
-		rounds = 1_000,
-		bytes = 64,
-		setup = _setup_sized_buf,
-		bench = _benchmark_chacha20,
-		teardown = _teardown_sized_buf,
-	}
-
-	err := time.benchmark(options, context.allocator)
-	tc.expect(t, err == nil, name)
-	benchmark_print(name, options)
-
-	name = "ChaCha20 1024 bytes"
-	options.bytes = 1024
-	err = time.benchmark(options, context.allocator)
-	tc.expect(t, err == nil, name)
-	benchmark_print(name, options)
-
-	name = "ChaCha20 65536 bytes"
-	options.bytes = 65536
-	err = time.benchmark(options, context.allocator)
-	tc.expect(t, err == nil, name)
-	benchmark_print(name, options)
-}
-
-bench_poly1305 :: proc(t: ^testing.T) {
-	name := "Poly1305 64 zero bytes"
-	options := &time.Benchmark_Options {
-		rounds = 1_000,
-		bytes = 64,
-		setup = _setup_sized_buf,
-		bench = _benchmark_poly1305,
-		teardown = _teardown_sized_buf,
-	}
-
-	err := time.benchmark(options, context.allocator)
-	tc.expect(t, err == nil, name)
-	benchmark_print(name, options)
-
-	name = "Poly1305 1024 zero bytes"
-	options.bytes = 1024
-	err = time.benchmark(options, context.allocator)
-	tc.expect(t, err == nil, name)
-	benchmark_print(name, options)
-}
-
-bench_chacha20poly1305 :: proc(t: ^testing.T) {
-	name := "chacha20poly1305 64 bytes"
-	options := &time.Benchmark_Options {
-		rounds = 1_000,
-		bytes = 64,
-		setup = _setup_sized_buf,
-		bench = _benchmark_chacha20poly1305,
-		teardown = _teardown_sized_buf,
-	}
-
-	err := time.benchmark(options, context.allocator)
-	tc.expect(t, err == nil, name)
-	benchmark_print(name, options)
-
-	name = "chacha20poly1305 1024 bytes"
-	options.bytes = 1024
-	err = time.benchmark(options, context.allocator)
-	tc.expect(t, err == nil, name)
-	benchmark_print(name, options)
-
-	name = "chacha20poly1305 65536 bytes"
-	options.bytes = 65536
-	err = time.benchmark(options, context.allocator)
-	tc.expect(t, err == nil, name)
-	benchmark_print(name, options)
-}
-
-bench_aes256_gcm :: proc(t: ^testing.T) {
-	name := "AES256-GCM 64 bytes"
-	options := &time.Benchmark_Options {
-		rounds = 1_000,
-		bytes = 64,
-		setup = _setup_sized_buf,
-		bench = _benchmark_aes256_gcm,
-		teardown = _teardown_sized_buf,
-	}
-
-	key := [aes.KEY_SIZE_256]byte {
-		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
-		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
-		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
-		0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef,
-	}
-	ctx: aes.Context_GCM
-	aes.init_gcm(&ctx, key[:])
-
-	context.user_ptr = &ctx
-
-	err := time.benchmark(options, context.allocator)
-	tc.expect(t, err == nil, name)
-	benchmark_print(name, options)
-
-	name = "AES256-GCM 1024 bytes"
-	options.bytes = 1024
-	err = time.benchmark(options, context.allocator)
-	tc.expect(t, err == nil, name)
-	benchmark_print(name, options)
-
-	name = "AES256-GCM 65536 bytes"
-	options.bytes = 65536
-	err = time.benchmark(options, context.allocator)
-	tc.expect(t, err == nil, name)
-	benchmark_print(name, options)
-}
-
-bench_ed25519 :: proc(t: ^testing.T) {
-	iters :: 10000
-
-	priv_str := "cafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe"
-	priv_bytes, _ := hex.decode(transmute([]byte)(priv_str), context.temp_allocator)
-	priv_key: ed25519.Private_Key
-	start := time.now()
-	for i := 0; i < iters; i = i + 1 {
-		ok := ed25519.private_key_set_bytes(&priv_key, priv_bytes)
-		assert(ok, "private key should deserialize")
-	}
-	elapsed := time.since(start)
-	tc.log(
-		t,
-		fmt.tprintf(
-			"ed25519.private_key_set_bytes: ~%f us/op",
-			time.duration_microseconds(elapsed) / iters,
-		),
-	)
-
-	pub_bytes := priv_key._pub_key._b[:] // "I know what I am doing"
-	pub_key: ed25519.Public_Key
-	start = time.now()
-	for i := 0; i < iters; i = i + 1 {
-		ok := ed25519.public_key_set_bytes(&pub_key, pub_bytes[:])
-		assert(ok, "public key should deserialize")
-	}
-	elapsed = time.since(start)
-	tc.log(
-		t,
-		fmt.tprintf(
-			"ed25519.public_key_set_bytes: ~%f us/op",
-			time.duration_microseconds(elapsed) / iters,
-		),
-	)
-
-	msg := "Got a job for you, 621."
-	sig_bytes: [ed25519.SIGNATURE_SIZE]byte
-	msg_bytes := transmute([]byte)(msg)
-	start = time.now()
-	for i := 0; i < iters; i = i + 1 {
-		ed25519.sign(&priv_key, msg_bytes, sig_bytes[:])
-	}
-	elapsed = time.since(start)
-	tc.log(t, fmt.tprintf("ed25519.sign: ~%f us/op", time.duration_microseconds(elapsed) / iters))
-
-	start = time.now()
-	for i := 0; i < iters; i = i + 1 {
-		ok := ed25519.verify(&pub_key, msg_bytes, sig_bytes[:])
-		assert(ok, "signature should validate")
-	}
-	elapsed = time.since(start)
-	tc.log(
-		t,
-		fmt.tprintf("ed25519.verify: ~%f us/op", time.duration_microseconds(elapsed) / iters),
-	)
-}
-
-bench_x25519 :: proc(t: ^testing.T) {
-	point_str := "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"
-	scalar_str := "cafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe"
-
-	point, _ := hex.decode(transmute([]byte)(point_str), context.temp_allocator)
-	scalar, _ := hex.decode(transmute([]byte)(scalar_str), context.temp_allocator)
-	out: [x25519.POINT_SIZE]byte = ---
-
-	iters :: 10000
-	start := time.now()
-	for i := 0; i < iters; i = i + 1 {
-		x25519.scalarmult(out[:], scalar[:], point[:])
-	}
-	elapsed := time.since(start)
-
-	tc.log(
-		t,
-		fmt.tprintf("x25519.scalarmult: ~%f us/op", time.duration_microseconds(elapsed) / iters),
-	)
-}

+ 21 - 44
tests/core/encoding/base64/base64.odin

@@ -1,61 +1,38 @@
 package test_encoding_base64
 
 import "base:intrinsics"
-
 import "core:encoding/base64"
-import "core:fmt"
-import "core:os"
-import "core:reflect"
 import "core:testing"
 
-TEST_count := 0
-TEST_fail  := 0
-
-when ODIN_TEST {
-	expect_value :: testing.expect_value
-
-} else {
-	expect_value :: proc(t: ^testing.T, value, expected: $T, loc := #caller_location) -> bool where intrinsics.type_is_comparable(T) {
-		TEST_count += 1
-		ok := value == expected || reflect.is_nil(value) && reflect.is_nil(expected)
-		if !ok {
-			TEST_fail += 1
-			fmt.printf("[%v] expected %v, got %v\n", loc, expected, value)
-		}
-		return ok
-	}
+Test :: struct {
+	vector: string,
+	base64: string,
 }
 
-main :: proc() {
-	t := testing.T{}
-
-	test_encoding(&t)
-	test_decoding(&t)
-
-	fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
-	if TEST_fail > 0 {
-		os.exit(1)
-	}
+tests :: []Test{
+	{"",       ""},
+	{"f",      "Zg=="},
+	{"fo",     "Zm8="},
+	{"foo",    "Zm9v"},
+	{"foob",   "Zm9vYg=="},
+	{"fooba",  "Zm9vYmE="},
+	{"foobar", "Zm9vYmFy"},
 }
 
 @(test)
 test_encoding :: proc(t: ^testing.T) {
-	expect_value(t, base64.encode(transmute([]byte)string("")), "")
-	expect_value(t, base64.encode(transmute([]byte)string("f")), "Zg==")
-	expect_value(t, base64.encode(transmute([]byte)string("fo")), "Zm8=")
-	expect_value(t, base64.encode(transmute([]byte)string("foo")), "Zm9v")
-	expect_value(t, base64.encode(transmute([]byte)string("foob")), "Zm9vYg==")
-	expect_value(t, base64.encode(transmute([]byte)string("fooba")), "Zm9vYmE=")
-	expect_value(t, base64.encode(transmute([]byte)string("foobar")), "Zm9vYmFy")
+	for test in tests {
+		v := base64.encode(transmute([]byte)test.vector)
+		defer delete(v)
+		testing.expect_value(t, v, test.base64)
+	}
 }
 
 @(test)
 test_decoding :: proc(t: ^testing.T) {
-	expect_value(t, string(base64.decode("")), "")
-	expect_value(t, string(base64.decode("Zg==")), "f")
-	expect_value(t, string(base64.decode("Zm8=")), "fo")
-	expect_value(t, string(base64.decode("Zm9v")), "foo")
-	expect_value(t, string(base64.decode("Zm9vYg==")), "foob")
-	expect_value(t, string(base64.decode("Zm9vYmE=")), "fooba")
-	expect_value(t, string(base64.decode("Zm9vYmFy")), "foobar")
+	for test in tests {
+		v := string(base64.decode(test.base64))
+		defer delete(v)
+		testing.expect_value(t, v, test.vector)
+	}
 }

+ 132 - 209
tests/core/encoding/cbor/test_core_cbor.odin

@@ -1,105 +1,15 @@
 package test_encoding_cbor
 
 import "base:intrinsics"
-
 import "core:bytes"
 import "core:encoding/cbor"
 import "core:fmt"
 import "core:io"
 import "core:math/big"
-import "core:mem"
-import "core:os"
 import "core:reflect"
 import "core:testing"
 import "core:time"
 
-TEST_count := 0
-TEST_fail  := 0
-
-when ODIN_TEST {
-	expect       :: testing.expect
-	expect_value :: testing.expect_value
-	errorf       :: testing.errorf
-	log          :: testing.log
-
-} else {
-	expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
-		TEST_count += 1
-		if !condition {
-			TEST_fail += 1
-			fmt.printf("[%v] %v\n", loc, message)
-			return
-		}
-	}
-
-	expect_value :: proc(t: ^testing.T, value, expected: $T, loc := #caller_location) -> bool where intrinsics.type_is_comparable(T) {
-		TEST_count += 1
-		ok := value == expected || reflect.is_nil(value) && reflect.is_nil(expected)
-		if !ok {
-			TEST_fail += 1
-			fmt.printf("[%v] expected %v, got %v\n", loc, expected, value)
-		}
-		return ok
-	}
-
-	errorf :: proc(t: ^testing.T, fmts: string, args: ..any, loc := #caller_location) {
-		TEST_fail += 1
-		fmt.printf("[%v] ERROR: ", loc)
-		fmt.printf(fmts, ..args)
-		fmt.println()
-	}
-
-	log :: proc(t: ^testing.T, v: any, loc := #caller_location) {
-		fmt.printf("[%v] ", loc)
-		fmt.printf("log: %v\n", v)
-	}
-}
-
-main :: proc() {
-	t := testing.T{}
-
-	test_marshalling(&t)
-
-	test_marshalling_maybe(&t)
-	test_marshalling_nil_maybe(&t)
-
-	test_marshalling_union(&t)
-
-	test_lying_length_array(&t)
-
-	test_decode_unsigned(&t)
-	test_encode_unsigned(&t)
-
-	test_decode_negative(&t)
-	test_encode_negative(&t)
-
-	test_decode_simples(&t)
-	test_encode_simples(&t)
-
-	test_decode_floats(&t)
-	test_encode_floats(&t)
-
-	test_decode_bytes(&t)
-	test_encode_bytes(&t)
-
-	test_decode_strings(&t)
-	test_encode_strings(&t)
-
-	test_decode_lists(&t)
-	test_encode_lists(&t)
-
-	test_decode_maps(&t)
-	test_encode_maps(&t)
-
-	test_decode_tags(&t)
-	test_encode_tags(&t)
-
-	fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
-	if TEST_fail > 0 {
-		os.exit(1)
-	}
-}
-
 Foo :: struct {
 	str: string,
 	cstr: cstring,
@@ -143,14 +53,6 @@ FooBars :: bit_set[FooBar; u16]
 
 @(test)
 test_marshalling :: proc(t: ^testing.T) {
-	tracker: mem.Tracking_Allocator
-	mem.tracking_allocator_init(&tracker, context.allocator)
-	context.allocator = mem.tracking_allocator(&tracker)
-	context.temp_allocator = context.allocator
-	defer mem.tracking_allocator_destroy(&tracker)
-
-	ev :: expect_value
-
 	{
 		nice := "16 is a nice number"
 		now := time.Time{_nsec = 1701117968 * 1e9}
@@ -205,18 +107,18 @@ test_marshalling :: proc(t: ^testing.T) {
 		}
 		
 		data, err := cbor.marshal(f, cbor.ENCODE_FULLY_DETERMINISTIC)
-		ev(t, err, nil)
+		testing.expect_value(t, err, nil)
 		defer delete(data)
 
 		decoded, derr := cbor.decode(string(data))
-		ev(t, derr, nil)
+		testing.expect_value(t, derr, nil)
 		defer cbor.destroy(decoded)
 
 		diagnosis, eerr := cbor.to_diagnostic_format(decoded)
-		ev(t, eerr, nil)
+		testing.expect_value(t, eerr, nil)
 		defer delete(diagnosis)
 
-		ev(t, diagnosis, `{
+		testing.expect_value(t, diagnosis, `{
 	"base64": 34("MTYgaXMgYSBuaWNlIG51bWJlcg=="),
 	"biggest": 2(h'f951a9fd3c158afdff08ab8e0'),
 	"biggie": 18446744073709551615,
@@ -285,7 +187,7 @@ test_marshalling :: proc(t: ^testing.T) {
 
 		backf: Foo
 		uerr := cbor.unmarshal(string(data), &backf)
-		ev(t, uerr, nil)
+		testing.expect_value(t, uerr, nil)
 		defer {
 			delete(backf.str)
 			delete(backf.cstr)
@@ -304,104 +206,102 @@ test_marshalling :: proc(t: ^testing.T) {
 			big.destroy(&backf.smallest)
 		}
 		
-		ev(t, backf.str, f.str)
-		ev(t, backf.cstr, f.cstr)
+		testing.expect_value(t, backf.str, f.str)
+		testing.expect_value(t, backf.cstr, f.cstr)
 
 		#partial switch v in backf.value {
 		case ^cbor.Map:
 			for entry, i in v {
 				fm := f.value.(^cbor.Map)
-				ev(t, entry.key, fm[i].key)
+				testing.expect_value(t, entry.key, fm[i].key)
 
 				if str, is_str := entry.value.(^cbor.Text); is_str {
-					ev(t, str^, fm[i].value.(^cbor.Text)^)
+					testing.expect_value(t, str^, fm[i].value.(^cbor.Text)^)
 				} else {
-					ev(t, entry.value, fm[i].value)
+					testing.expect_value(t, entry.value, fm[i].value)
 				}
 			}
 
-		case: errorf(t, "wrong type %v", v)
+		case: testing.expectf(t, false, "wrong type %v", v)
 		}
 
-		ev(t, backf.neg, f.neg)
-		ev(t, backf.iamint, f.iamint)
-		ev(t, backf.base64, f.base64)
-		ev(t, backf.renamed, f.renamed)
-		ev(t, backf.now, f.now)
-		ev(t, backf.nowie, f.nowie)
-		for e, i in f.child.dyn { ev(t, backf.child.dyn[i], e) }
-		for key, value in f.child.mappy { ev(t, backf.child.mappy[key], value) }
-		ev(t, backf.child.my_integers, f.child.my_integers)
-		ev(t, len(backf.my_bytes), 0)
-		ev(t, len(backf.my_bytes), len(f.my_bytes))
-		ev(t, backf.ennie, f.ennie)
-		ev(t, backf.ennieb, f.ennieb)
-		ev(t, backf.quat, f.quat)
-		ev(t, backf.comp, f.comp)
-		ev(t, backf.important, f.important)
-		ev(t, backf.no, nil)
-		ev(t, backf.nos, nil)
-		ev(t, backf.yes, f.yes)
-		ev(t, backf.biggie, f.biggie)
-		ev(t, backf.smallie, f.smallie)
-		ev(t, backf.onetwenty, f.onetwenty)
-		ev(t, backf.small_onetwenty, f.small_onetwenty)
-		ev(t, backf.ignore_this, nil)
+		testing.expect_value(t, backf.neg, f.neg)
+		testing.expect_value(t, backf.iamint, f.iamint)
+		testing.expect_value(t, backf.base64, f.base64)
+		testing.expect_value(t, backf.renamed, f.renamed)
+		testing.expect_value(t, backf.now, f.now)
+		testing.expect_value(t, backf.nowie, f.nowie)
+		for e, i in f.child.dyn { testing.expect_value(t, backf.child.dyn[i], e) }
+		for key, value in f.child.mappy { testing.expect_value(t, backf.child.mappy[key], value) }
+		testing.expect_value(t, backf.child.my_integers, f.child.my_integers)
+		testing.expect_value(t, len(backf.my_bytes), 0)
+		testing.expect_value(t, len(backf.my_bytes), len(f.my_bytes))
+		testing.expect_value(t, backf.ennie, f.ennie)
+		testing.expect_value(t, backf.ennieb, f.ennieb)
+		testing.expect_value(t, backf.quat, f.quat)
+		testing.expect_value(t, backf.comp, f.comp)
+		testing.expect_value(t, backf.important, f.important)
+		testing.expect_value(t, backf.no, nil)
+		testing.expect_value(t, backf.nos, nil)
+		testing.expect_value(t, backf.yes, f.yes)
+		testing.expect_value(t, backf.biggie, f.biggie)
+		testing.expect_value(t, backf.smallie, f.smallie)
+		testing.expect_value(t, backf.onetwenty, f.onetwenty)
+		testing.expect_value(t, backf.small_onetwenty, f.small_onetwenty)
+		testing.expect_value(t, backf.ignore_this, nil)
 		
 		s_equals, s_err := big.equals(&backf.smallest, &f.smallest)
-		ev(t, s_err, nil)
+		testing.expect_value(t, s_err, nil)
 		if !s_equals {
-			errorf(t, "smallest: %v does not equal %v", big.itoa(&backf.smallest), big.itoa(&f.smallest))
+			testing.expectf(t, false, "smallest: %v does not equal %v", big.itoa(&backf.smallest), big.itoa(&f.smallest))
 		}
 
 		b_equals, b_err := big.equals(&backf.biggest, &f.biggest)
-		ev(t, b_err, nil)
+		testing.expect_value(t, b_err, nil)
 		if !b_equals {
-			errorf(t, "biggest: %v does not equal %v", big.itoa(&backf.biggest), big.itoa(&f.biggest))
+			testing.expectf(t, false, "biggest: %v does not equal %v", big.itoa(&backf.biggest), big.itoa(&f.biggest))
 		}
 	}
-
-	for _, leak in tracker.allocation_map {
-		errorf(t, "%v leaked %m\n", leak.location, leak.size)
-	}
-
-	for bad_free in tracker.bad_free_array {
-		errorf(t, "%v allocation %p was freed badly\n", bad_free.location, bad_free.memory)
-	}
 }
 
 @(test)
 test_marshalling_maybe :: proc(t: ^testing.T) {
 	maybe_test: Maybe(int) = 1
 	data, err := cbor.marshal(maybe_test)
-	expect_value(t, err, nil)
+	defer delete(data)
+	testing.expect_value(t, err, nil)
 
 	val, derr := cbor.decode(string(data))
-	expect_value(t, derr, nil)
+	testing.expect_value(t, derr, nil)
 
-	expect_value(t, cbor.to_diagnostic_format(val), "1")
+	diag := cbor.to_diagnostic_format(val)
+	testing.expect_value(t, diag, "1")
+	delete(diag)
 	
 	maybe_dest: Maybe(int)
 	uerr := cbor.unmarshal(string(data), &maybe_dest)
-	expect_value(t, uerr, nil)
-	expect_value(t, maybe_dest, 1)
+	testing.expect_value(t, uerr, nil)
+	testing.expect_value(t, maybe_dest, 1)
 }
 
 @(test)
 test_marshalling_nil_maybe :: proc(t: ^testing.T) {
 	maybe_test: Maybe(int)
 	data, err := cbor.marshal(maybe_test)
-	expect_value(t, err, nil)
+	defer delete(data)
+	testing.expect_value(t, err, nil)
 
 	val, derr := cbor.decode(string(data))
-	expect_value(t, derr, nil)
+	testing.expect_value(t, derr, nil)
 
-	expect_value(t, cbor.to_diagnostic_format(val), "nil")
+	diag := cbor.to_diagnostic_format(val)
+	testing.expect_value(t, diag, "nil")
+	delete(diag)
 	
 	maybe_dest: Maybe(int)
 	uerr := cbor.unmarshal(string(data), &maybe_dest)
-	expect_value(t, uerr, nil)
-	expect_value(t, maybe_dest, nil)
+	testing.expect_value(t, uerr, nil)
+	testing.expect_value(t, maybe_dest, nil)
 }
 
 @(test)
@@ -427,17 +327,24 @@ test_marshalling_union :: proc(t: ^testing.T) {
 	{
 		test: My_Union = My_Distinct("Hello, World!")
 		data, err := cbor.marshal(test)
-		expect_value(t, err, nil)
+		defer delete(data)
+		testing.expect_value(t, err, nil)
 
 		val, derr := cbor.decode(string(data))
-		expect_value(t, derr, nil)
+		defer cbor.destroy(val)
+		testing.expect_value(t, derr, nil)
 
-		expect_value(t, cbor.to_diagnostic_format(val, -1), `1010(["My_Distinct", "Hello, World!"])`)
+		diag := cbor.to_diagnostic_format(val, -1)
+		defer delete(diag)
+		testing.expect_value(t, diag, `1010(["My_Distinct", "Hello, World!"])`)
 
 		dest: My_Union
 		uerr := cbor.unmarshal(string(data), &dest)
-		expect_value(t, uerr, nil)
-		expect_value(t, dest, My_Distinct("Hello, World!"))
+		testing.expect_value(t, uerr, nil)
+		testing.expect_value(t, dest, My_Distinct("Hello, World!"))
+		if str, ok := dest.(My_Distinct); ok {
+			delete(string(str))
+		}
 	}
 
 	My_Union_No_Nil :: union #no_nil {
@@ -450,17 +357,21 @@ test_marshalling_union :: proc(t: ^testing.T) {
 	{
 		test: My_Union_No_Nil = My_Struct{.Two}
 		data, err := cbor.marshal(test)
-		expect_value(t, err, nil)
+		defer delete(data)
+		testing.expect_value(t, err, nil)
 
 		val, derr := cbor.decode(string(data))
-		expect_value(t, derr, nil)
+		defer cbor.destroy(val)
+		testing.expect_value(t, derr, nil)
 
-		expect_value(t, cbor.to_diagnostic_format(val, -1), `1010(["My_Struct", {"my_enum": 1}])`)
+		diag := cbor.to_diagnostic_format(val, -1)
+		defer delete(diag)
+		testing.expect_value(t, diag, `1010(["My_Struct", {"my_enum": 1}])`)
 
 		dest: My_Union_No_Nil
 		uerr := cbor.unmarshal(string(data), &dest)
-		expect_value(t, uerr, nil)
-		expect_value(t, dest, My_Struct{.Two})
+		testing.expect_value(t, uerr, nil)
+		testing.expect_value(t, dest, My_Struct{.Two})
 	}
 }
 
@@ -469,7 +380,7 @@ test_lying_length_array :: proc(t: ^testing.T) {
 	// Input says this is an array of length max(u64), this should not allocate that amount.
 	input := []byte{0x9B, 0x00, 0x00, 0x42, 0xFA, 0x42, 0xFA, 0x42, 0xFA, 0x42}
 	_, err := cbor.decode(string(input))
-	expect_value(t, err, io.Error.Unexpected_EOF) // .Out_Of_Memory would be bad.
+	testing.expect_value(t, err, io.Error.Unexpected_EOF) // .Out_Of_Memory would be bad.
 }
 
 @(test)
@@ -691,65 +602,73 @@ test_encode_lists :: proc(t: ^testing.T) {
 	expect_streamed_encoding(t, "\x9f\xff", &cbor.Array{})
 
 	{
-		bytes.buffer_reset(&buf)
+		buf: bytes.Buffer
+		bytes.buffer_init_allocator(&buf, 0, 0)
+		defer bytes.buffer_destroy(&buf)
+		stream  := bytes.buffer_to_stream(&buf)
+		encoder := cbor.Encoder{cbor.ENCODE_FULLY_DETERMINISTIC, stream, {}}
 		
 		err: cbor.Encode_Error
 		err = cbor.encode_stream_begin(stream, .Array)
-		expect_value(t, err, nil)
+		testing.expect_value(t, err, nil)
 
 		{
 			err = cbor.encode_stream_array_item(encoder, u8(1))
-			expect_value(t, err, nil)
+			testing.expect_value(t, err, nil)
 
 			err = cbor.encode_stream_array_item(encoder, &cbor.Array{u8(2), u8(3)})
-			expect_value(t, err, nil)
+			testing.expect_value(t, err, nil)
 
 			err = cbor.encode_stream_begin(stream, .Array)
-			expect_value(t, err, nil)
+			testing.expect_value(t, err, nil)
 
 			{
 				err = cbor.encode_stream_array_item(encoder, u8(4))
-				expect_value(t, err, nil)
+				testing.expect_value(t, err, nil)
 
 				err = cbor.encode_stream_array_item(encoder, u8(5))
-				expect_value(t, err, nil)
+				testing.expect_value(t, err, nil)
 			}
 
 			err = cbor.encode_stream_end(stream)
-			expect_value(t, err, nil)
+			testing.expect_value(t, err, nil)
 		}
 
 		err = cbor.encode_stream_end(stream)
-		expect_value(t, err, nil)
+		testing.expect_value(t, err, nil)
 		
-		expect_value(t, fmt.tprint(bytes.buffer_to_bytes(&buf)), fmt.tprint(transmute([]byte)string("\x9f\x01\x82\x02\x03\x9f\x04\x05\xff\xff")))
+		testing.expect_value(t, fmt.tprint(bytes.buffer_to_bytes(&buf)), fmt.tprint(transmute([]byte)string("\x9f\x01\x82\x02\x03\x9f\x04\x05\xff\xff")))
 	}
 	
 	{
-		bytes.buffer_reset(&buf)
+		buf: bytes.Buffer
+		bytes.buffer_init_allocator(&buf, 0, 0)
+		defer bytes.buffer_destroy(&buf)
+		stream  := bytes.buffer_to_stream(&buf)
+		encoder := cbor.Encoder{cbor.ENCODE_FULLY_DETERMINISTIC, stream, {}}
 	
 		err: cbor.Encode_Error
 		err = cbor._encode_u8(stream, 2, .Array)
-		expect_value(t, err, nil)
+		testing.expect_value(t, err, nil)
 		
 		a := "a"
 		err = cbor.encode(encoder, &a)
-		expect_value(t, err, nil)
+		testing.expect_value(t, err, nil)
 		
 		{
 			err = cbor.encode_stream_begin(stream, .Map)
-			expect_value(t, err, nil)
+			testing.expect_value(t, err, nil)
 			
 			b := "b"
 			c := "c"
 			err = cbor.encode_stream_map_entry(encoder, &b, &c)
-			expect_value(t, err, nil)
+			testing.expect_value(t, err, nil)
 
 			err = cbor.encode_stream_end(stream)
-			expect_value(t, err, nil)
+			testing.expect_value(t, err, nil)
 		}
 		
-		expect_value(t, fmt.tprint(bytes.buffer_to_bytes(&buf)), fmt.tprint(transmute([]byte)string("\x82\x61\x61\xbf\x61\x62\x61\x63\xff")))
+		testing.expect_value(t, fmt.tprint(bytes.buffer_to_bytes(&buf)), fmt.tprint(transmute([]byte)string("\x82\x61\x61\xbf\x61\x62\x61\x63\xff")))
 	}
 }
 
@@ -807,30 +726,30 @@ expect_decoding :: proc(t: ^testing.T, encoded: string, decoded: string, type: t
     res, err := cbor.decode(encoded)
 	defer cbor.destroy(res)
 
-	expect_value(t, reflect.union_variant_typeid(res), type, loc)
-    expect_value(t, err, nil, loc)
+	testing.expect_value(t, reflect.union_variant_typeid(res), type, loc)
+    testing.expect_value(t, err, nil, loc)
 
 	str := cbor.to_diagnostic_format(res, padding=-1)
 	defer delete(str)
 
-    expect_value(t, str, decoded, loc)
+    testing.expect_value(t, str, decoded, loc)
 }
 
 expect_tag :: proc(t: ^testing.T, encoded: string, nr: cbor.Tag_Number, value_decoded: string, loc := #caller_location) {
 	res, err := cbor.decode(encoded)
 	defer cbor.destroy(res)
 
-	expect_value(t, err, nil, loc)
+	testing.expect_value(t, err, nil, loc)
 	
 	if tag, is_tag := res.(^cbor.Tag); is_tag {
-		expect_value(t, tag.number, nr, loc)
+		testing.expect_value(t, tag.number, nr, loc)
 
 		str := cbor.to_diagnostic_format(tag, padding=-1)
 		defer delete(str)
 
-		expect_value(t, str, value_decoded, loc)
+		testing.expect_value(t, str, value_decoded, loc)
 	} else {
-		errorf(t, "Value %#v is not a tag", res, loc)
+		testing.expectf(t, false, "Value %#v is not a tag", res, loc)
 	}
 }
 
@@ -838,35 +757,39 @@ expect_float :: proc(t: ^testing.T, encoded: string, expected: $T, loc := #calle
     res, err := cbor.decode(encoded)
 	defer cbor.destroy(res)
 
-	expect_value(t, reflect.union_variant_typeid(res), typeid_of(T), loc)
-    expect_value(t, err, nil, loc)
+	testing.expect_value(t, reflect.union_variant_typeid(res), typeid_of(T), loc)
+    testing.expect_value(t, err, nil, loc)
 
 	#partial switch r in res {
 	case f16:
-		when T == f16 { expect_value(t, res, expected, loc) } else { unreachable() }
+		when T == f16 { testing.expect_value(t, res, expected, loc) } else { unreachable() }
 	case f32:
-		when T == f32 { expect_value(t, res, expected, loc) } else { unreachable() }
+		when T == f32 { testing.expect_value(t, res, expected, loc) } else { unreachable() }
 	case f64:
-		when T == f64 { expect_value(t, res, expected, loc) } else { unreachable() }
+		when T == f64 { testing.expect_value(t, res, expected, loc) } else { unreachable() }
 	case:
 		unreachable()
 	}
 }
 
-buf: bytes.Buffer
-stream  := bytes.buffer_to_stream(&buf)
-encoder := cbor.Encoder{cbor.ENCODE_FULLY_DETERMINISTIC, stream, {}}
-
 expect_encoding :: proc(t: ^testing.T, val: cbor.Value, encoded: string, loc := #caller_location) {
-	bytes.buffer_reset(&buf)
+	buf: bytes.Buffer
+	bytes.buffer_init_allocator(&buf, 0, 0)
+	defer bytes.buffer_destroy(&buf)
+	stream  := bytes.buffer_to_stream(&buf)
+	encoder := cbor.Encoder{cbor.ENCODE_FULLY_DETERMINISTIC, stream, {}}
 
-	err := cbor.encode(encoder, val)
-	expect_value(t, err, nil, loc)
-	expect_value(t, fmt.tprint(bytes.buffer_to_bytes(&buf)), fmt.tprint(transmute([]byte)encoded), loc)
+	err := cbor.encode(encoder, val, loc)
+	testing.expect_value(t, err, nil, loc)
+	testing.expect_value(t, fmt.tprint(bytes.buffer_to_bytes(&buf)), fmt.tprint(transmute([]byte)encoded), loc)
 }
 
 expect_streamed_encoding :: proc(t: ^testing.T, encoded: string, values: ..cbor.Value, loc := #caller_location) {
-	bytes.buffer_reset(&buf)
+	buf: bytes.Buffer
+	bytes.buffer_init_allocator(&buf, 0, 0)
+	defer bytes.buffer_destroy(&buf)
+	stream  := bytes.buffer_to_stream(&buf)
+	encoder := cbor.Encoder{cbor.ENCODE_FULLY_DETERMINISTIC, stream, {}}
 
 	for value, i in values {
 		err: cbor.Encode_Error
@@ -891,15 +814,15 @@ expect_streamed_encoding :: proc(t: ^testing.T, encoded: string, values: ..cbor.
 				if err2 != nil { break }
 			}
 		case:
-			errorf(t, "%v does not support streamed encoding", reflect.union_variant_typeid(value))
+			testing.expectf(t, false, "%v does not support streamed encoding", reflect.union_variant_typeid(value))
 		}
 
-		expect_value(t, err, nil, loc)
-		expect_value(t, err2, nil, loc)
+		testing.expect_value(t, err, nil, loc)
+		testing.expect_value(t, err2, nil, loc)
 	}
 
 	err := cbor.encode_stream_end(stream)
-	expect_value(t, err, nil, loc)
+	testing.expect_value(t, err, nil, loc)
 
-	expect_value(t, fmt.tprint(bytes.buffer_to_bytes(&buf)), fmt.tprint(transmute([]byte)encoded), loc)
+	testing.expect_value(t, fmt.tprint(bytes.buffer_to_bytes(&buf)), fmt.tprint(transmute([]byte)encoded), loc)
 }

+ 44 - 50
tests/core/encoding/hex/test_core_hex.odin

@@ -2,42 +2,6 @@ package test_core_hex
 
 import "core:encoding/hex"
 import "core:testing"
-import "core:fmt"
-import "core:os"
-
-TEST_count := 0
-TEST_fail  := 0
-
-when ODIN_TEST {
-	expect  :: testing.expect
-	log     :: testing.log
-} else {
-	expect  :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
-		TEST_count += 1
-		if !condition {
-			TEST_fail += 1
-			fmt.printf("[%v] %v\n", loc, message)
-			return
-		}
-	}
-	log     :: proc(t: ^testing.T, v: any, loc := #caller_location) {
-		fmt.printf("[%v] ", loc)
-		fmt.printf("log: %v\n", v)
-	}
-}
-
-main :: proc() {
-	t := testing.T{}
-
-	hex_encode(&t)
-	hex_decode(&t)
-	hex_decode_sequence(&t)
-
-	fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
-	if TEST_fail > 0 {
-		os.exit(1)
-	}
-}
 
 CASES :: [][2]string{
 	{"11", "3131"},
@@ -49,10 +13,14 @@ CASES :: [][2]string{
 hex_encode :: proc(t: ^testing.T) {
 	for test in CASES {
 		encoded := string(hex.encode(transmute([]byte)test[0]))
-		expect(
+		defer delete(encoded)
+		testing.expectf(
 			t,
 			encoded == test[1],
-			fmt.tprintf("encode: %q -> %q (should be: %q)", test[0], encoded, test[1]),
+			"encode: %q -> %q (should be: %q)",
+			test[0],
+			encoded,
+			test[1],
 		)
 	}
 }
@@ -61,11 +29,20 @@ hex_encode :: proc(t: ^testing.T) {
 hex_decode :: proc(t: ^testing.T) {
 	for test in CASES {
 		decoded, ok := hex.decode(transmute([]byte)test[1])
-		expect(t, ok, fmt.tprintf("decode: %q not ok", test[1]))
-		expect(
+		defer delete(decoded)
+		testing.expectf(
+			t,
+			ok,
+			"decode: %q not ok",
+			test[1],
+		)
+		testing.expectf(
 			t,
 			string(decoded) == test[0],
-			fmt.tprintf("decode: %q -> %q (should be: %q)", test[1], string(decoded), test[0]),
+			"decode: %q -> %q (should be: %q)",
+			test[1],
+			string(decoded),
+			test[0],
 		)
 	}
 }
@@ -73,20 +50,37 @@ hex_decode :: proc(t: ^testing.T) {
 @(test)
 hex_decode_sequence :: proc(t: ^testing.T) {
 	b, ok := hex.decode_sequence("0x23")
-	expect(t, ok, "decode_sequence: 0x23 not ok")
-	expect(t, b == '#', fmt.tprintf("decode_sequence: 0x23 -> %c (should be: %c)", b, '#'))
+	testing.expect(t, ok, "decode_sequence: 0x23 not ok")
+	testing.expectf(
+		t,
+		b == '#',
+		"decode_sequence: 0x23 -> %c (should be: %c)",
+		b,
+		'#',
+	)
 
 	b, ok = hex.decode_sequence("0X3F")
-	expect(t, ok, "decode_sequence: 0X3F not ok")
-	expect(t, b == '?', fmt.tprintf("decode_sequence: 0X3F -> %c (should be: %c)", b, '?'))
+	testing.expect(t, ok, "decode_sequence: 0X3F not ok")
+	testing.expectf(
+		t,
+		b == '?',
+		"decode_sequence: 0X3F -> %c (should be: %c)",
+		b,
+		'?',
+	)
 
 	b, ok = hex.decode_sequence("2a")
-	expect(t, ok, "decode_sequence: 2a not ok")
-	expect(t, b == '*', fmt.tprintf("decode_sequence: 2a -> %c (should be: %c)", b, '*'))
+	testing.expect(t, ok, "decode_sequence: 2a not ok")
+	testing.expectf(t,
+		b == '*',
+		"decode_sequence: 2a -> %c (should be: %c)",
+		b,
+		'*',
+	)
 
 	_, ok = hex.decode_sequence("1")
-	expect(t, !ok, "decode_sequence: 1 should be too short")
+	testing.expect(t, !ok, "decode_sequence: 1 should be too short")
 
 	_, ok = hex.decode_sequence("123")
-	expect(t, !ok, "decode_sequence: 123 should be too long")
-}
+	testing.expect(t, !ok, "decode_sequence: 123 should be too long")
+}

+ 87 - 115
tests/core/encoding/hxa/test_core_hxa.odin

@@ -6,127 +6,99 @@ package test_core_hxa
 import "core:encoding/hxa"
 import "core:fmt"
 import "core:testing"
-import tc "tests:common"
 
-TEAPOT_PATH   :: "core/assets/HXA/teapot.hxa"
+TEAPOT_PATH :: ODIN_ROOT + "tests/core/assets/HXA/teapot.hxa"
 
-main :: proc() {
-    t := testing.T{}
-
-	test_read(&t)
-	test_write(&t)
-
-	tc.report(&t)
-}
+import "core:os"
 
 @test
 test_read :: proc(t: ^testing.T) {
-	filename := tc.get_data_path(t, TEAPOT_PATH)
-	defer delete(filename)
+	data, _ := os.read_entire_file(TEAPOT_PATH)
+	// file, err := hxa.read_from_file(TEAPOT_PATH)
+	file, err := hxa.read(data)
+	file.backing = data
+	file.allocator = context.allocator
+	hxa.file_destroy(file)
+	// fmt.printfln("%#v", file)
 
-	file, err := hxa.read_from_file(filename)
 	e :: hxa.Read_Error.None
-	tc.expect(t, err == e, fmt.tprintf("%v: read_from_file(%v) -> %v != %v", #procedure, filename, err, e))
-	defer hxa.file_destroy(file)
+	testing.expectf(t, err == e, "read_from_file(%v) -> %v != %v", TEAPOT_PATH, err, e)
 
 	/* Header */
-	tc.expect(t, file.magic_number == 0x417848, fmt.tprintf("%v: file.magic_number %v != %v",
-															#procedure, file.magic_number, 0x417848))
-	tc.expect(t, file.version == 1, fmt.tprintf("%v: file.version %v != %v",
-															#procedure, file.version, 1))
-	tc.expect(t, file.internal_node_count == 1, fmt.tprintf("%v: file.internal_node_count %v != %v",
-															#procedure, file.internal_node_count, 1))
+	testing.expectf(t, file.magic_number == 0x417848, "file.magic_number %v != %v", file.magic_number, 0x417848)
+	testing.expectf(t, file.version == 1, "file.version %v != %v", file.version, 1)
+	testing.expectf(t, file.internal_node_count == 1, "file.internal_node_count %v != %v", file.internal_node_count, 1)
 
 	/* Nodes (only one) */
-	tc.expect(t, len(file.nodes) == 1, fmt.tprintf("%v: len(file.nodes) %v != %v", #procedure, len(file.nodes), 1))
+	testing.expectf(t, len(file.nodes) == 1, "len(file.nodes) %v != %v", len(file.nodes), 1)
 
 	m := &file.nodes[0].meta_data
-	tc.expect(t, len(m^) == 38, fmt.tprintf("%v: len(m^) %v != %v", #procedure, len(m^), 38))
+	testing.expectf(t, len(m^) == 38, "len(m^) %v != %v", len(m^), 38)
 	{
 		e :: "Texture resolution"
-		tc.expect(t, m[0].name == e, fmt.tprintf("%v: m[0].name %v != %v", #procedure, m[0].name, e))
+		testing.expectf(t, m[0].name == e, "m[0].name %v != %v", m[0].name, e)
 
 		m_v, m_v_ok := m[0].value.([]i64le)
-		tc.expect(t, m_v_ok, fmt.tprintf("%v: m_v_ok %v != %v", #procedure, m_v_ok, true))
-		tc.expect(t, len(m_v) == 1, fmt.tprintf("%v: len(m_v) %v != %v", #procedure, len(m_v), 1))
-		tc.expect(t, m_v[0] == 1024, fmt.tprintf("%v: m_v[0] %v != %v", #procedure, len(m_v), 1024))
+		testing.expectf(t, m_v_ok,           "m_v_ok %v != %v",   m_v_ok,   true)
+		testing.expectf(t, len(m_v) == 1,    "len(m_v) %v != %v", len(m_v), 1)
+		testing.expectf(t, m_v[0]   == 1024, "m_v[0] %v != %v",   len(m_v), 1024)
 	}
 	{
 		e :: "Validate"
-		tc.expect(t, m[37].name == e, fmt.tprintf("%v: m[37].name %v != %v", #procedure, m[37].name, e))
+		testing.expectf(t, m[37].name == e, "m[37].name %v != %v", m[37].name, e)
 
 		m_v, m_v_ok := m[37].value.([]i64le)
-		tc.expect(t, m_v_ok, fmt.tprintf("%v: m_v_ok %v != %v", #procedure, m_v_ok, true))
-		tc.expect(t, len(m_v) == 1, fmt.tprintf("%v: len(m_v) %v != %v", #procedure, len(m_v), 1))
-		tc.expect(t, m_v[0] == -2054847231, fmt.tprintf("%v: m_v[0] %v != %v", #procedure, len(m_v), -2054847231))
+		testing.expectf(t, m_v_ok,                  "m_v_ok %v != %v",   m_v_ok,   true)
+		testing.expectf(t, len(m_v) == 1,           "len(m_v) %v != %v", len(m_v), 1)
+		testing.expectf(t, m_v[0]   == -2054847231, "m_v[0] %v != %v",   len(m_v), -2054847231)
 	}
 
 	/* Node content */
 	v, v_ok := file.nodes[0].content.(hxa.Node_Geometry)
-	tc.expect(t, v_ok, fmt.tprintf("%v: v_ok %v != %v", #procedure, v_ok, true))
+	testing.expectf(t, v_ok, "v_ok %v != %v", v_ok, true)
 
-	tc.expect(t, v.vertex_count == 530, fmt.tprintf("%v: v.vertex_count %v != %v", #procedure, v.vertex_count, 530))
-	tc.expect(t, v.edge_corner_count == 2026, fmt.tprintf("%v: v.edge_corner_count %v != %v",
-														  #procedure, v.edge_corner_count, 2026))
-	tc.expect(t, v.face_count == 517, fmt.tprintf("%v: v.face_count %v != %v", #procedure, v.face_count, 517))
+	testing.expectf(t, v.vertex_count      == 530,  "v.vertex_count %v != %v",      v.vertex_count, 530)
+	testing.expectf(t, v.edge_corner_count == 2026, "v.edge_corner_count %v != %v", v.edge_corner_count, 2026)
+	testing.expectf(t, v.face_count        == 517,  "v.face_count %v != %v",        v.face_count, 517)
 
 	/* Vertex stack */
-	tc.expect(t, len(v.vertex_stack) == 1, fmt.tprintf("%v: len(v.vertex_stack) %v != %v",
-													   #procedure, len(v.vertex_stack), 1))
+	testing.expectf(t, len(v.vertex_stack) == 1, "len(v.vertex_stack) %v != %v", len(v.vertex_stack), 1)
 	{
 		e := "vertex"
-		tc.expect(t, v.vertex_stack[0].name == e, fmt.tprintf("%v: v.vertex_stack[0].name %v != %v",
-															  #procedure, v.vertex_stack[0].name, e))
+		testing.expectf(t, v.vertex_stack[0].name == e, "v.vertex_stack[0].name %v != %v", v.vertex_stack[0].name, e)
 	}
-	tc.expect(t, v.vertex_stack[0].components == 3, fmt.tprintf("%v: v.vertex_stack[0].components %v != %v",
-																#procedure, v.vertex_stack[0].components, 3))
+	testing.expectf(t, v.vertex_stack[0].components == 3, "v.vertex_stack[0].components %v != %v", v.vertex_stack[0].components, 3)
 
 	/* Vertex stack data */
 	vs_d, vs_d_ok := v.vertex_stack[0].data.([]f64le)
-	tc.expect(t, vs_d_ok, fmt.tprintf("%v: vs_d_ok %v != %v", #procedure, vs_d_ok, true))
-	tc.expect(t, len(vs_d) == 1590, fmt.tprintf("%v: len(vs_d) %v != %v", #procedure, len(vs_d), 1590))
-
-	tc.expect(t, vs_d[0] == 4.06266, fmt.tprintf("%v: vs_d[0] %v (%h) != %v (%h)",
-												 #procedure, vs_d[0], vs_d[0], 4.06266, 4.06266))
-	tc.expect(t, vs_d[1] == 2.83457, fmt.tprintf("%v: vs_d[1] %v (%h) != %v (%h)",
-												 #procedure, vs_d[1], vs_d[1], 2.83457, 2.83457))
-	tc.expect(t, vs_d[2] == 0hbfbc5da6a4441787, fmt.tprintf("%v: vs_d[2] %v (%h) != %v (%h)",
-															#procedure, vs_d[2], vs_d[2],
-															0hbfbc5da6a4441787, 0hbfbc5da6a4441787))
-	tc.expect(t, vs_d[3] == 0h4010074fb549f948, fmt.tprintf("%v: vs_d[3] %v (%h) != %v (%h)",
-															#procedure, vs_d[3], vs_d[3],
-															0h4010074fb549f948, 0h4010074fb549f948))
-	tc.expect(t, vs_d[1587] == 0h400befa82e87d2c7, fmt.tprintf("%v: vs_d[1587] %v (%h) != %v (%h)",
-															   #procedure, vs_d[1587], vs_d[1587],
-															   0h400befa82e87d2c7, 0h400befa82e87d2c7))
-	tc.expect(t, vs_d[1588] == 2.83457, fmt.tprintf("%v: vs_d[1588] %v (%h) != %v (%h)",
-													#procedure, vs_d[1588], vs_d[1588], 2.83457, 2.83457))
-	tc.expect(t, vs_d[1589] == -1.56121, fmt.tprintf("%v: vs_d[1589] %v (%h) != %v (%h)",
-													 #procedure, vs_d[1589], vs_d[1589], -1.56121, -1.56121))
+	testing.expectf(t, vs_d_ok,                          "vs_d_ok %v != %v",              vs_d_ok, true)
+	testing.expectf(t, len(vs_d) == 1590,                "len(vs_d) %v != %v",            len(vs_d), 1590)
+	testing.expectf(t, vs_d[0] == 4.06266,               "vs_d[0] %v (%h) != %v (%h)",    vs_d[0], vs_d[0], 4.06266, 4.06266)
+	testing.expectf(t, vs_d[1] == 2.83457,               "vs_d[1] %v (%h) != %v (%h)",    vs_d[1], vs_d[1], 2.83457, 2.83457)
+	testing.expectf(t, vs_d[2] == 0hbfbc5da6a4441787,    "vs_d[2] %v (%h) != %v (%h)",    vs_d[2], vs_d[2], 0hbfbc5da6a4441787, 0hbfbc5da6a4441787)
+	testing.expectf(t, vs_d[3] == 0h4010074fb549f948,    "vs_d[3] %v (%h) != %v (%h)",    vs_d[3], vs_d[3], 0h4010074fb549f948, 0h4010074fb549f948)
+	testing.expectf(t, vs_d[1587] == 0h400befa82e87d2c7, "vs_d[1587] %v (%h) != %v (%h)", vs_d[1587], vs_d[1587], 0h400befa82e87d2c7, 0h400befa82e87d2c7)
+	testing.expectf(t, vs_d[1588] == 2.83457,            "vs_d[1588] %v (%h) != %v (%h)", vs_d[1588], vs_d[1588], 2.83457, 2.83457)
+	testing.expectf(t, vs_d[1589] == -1.56121,           "vs_d[1589] %v (%h) != %v (%h)", vs_d[1589], vs_d[1589], -1.56121, -1.56121)
 
 	/* Corner stack */
-	tc.expect(t, len(v.corner_stack) == 1,
-			  fmt.tprintf("%v: len(v.corner_stack) %v != %v", #procedure, len(v.corner_stack), 1))
+	testing.expectf(t, len(v.corner_stack) == 1,         "len(v.corner_stack) %v != %v", len(v.corner_stack), 1)
 	{
 		e := "reference"
-		tc.expect(t, v.corner_stack[0].name == e, fmt.tprintf("%v: v.corner_stack[0].name %v != %v",
-															  #procedure, v.corner_stack[0].name, e))
+		testing.expectf(t, v.corner_stack[0].name == e, "v.corner_stack[0].name %v != %v", v.corner_stack[0].name, e)
 	}
-	tc.expect(t, v.corner_stack[0].components == 1, fmt.tprintf("%v: v.corner_stack[0].components %v != %v",
-																#procedure, v.corner_stack[0].components, 1))
+	testing.expectf(t, v.corner_stack[0].components == 1, "v.corner_stack[0].components %v != %v", v.corner_stack[0].components, 1)
 
 	/* Corner stack data */
 	cs_d, cs_d_ok := v.corner_stack[0].data.([]i32le)
-	tc.expect(t, cs_d_ok, fmt.tprintf("%v: cs_d_ok %v != %v", #procedure, cs_d_ok, true))
-	tc.expect(t, len(cs_d) == 2026, fmt.tprintf("%v: len(cs_d) %v != %v", #procedure, len(cs_d), 2026))
-	tc.expect(t, cs_d[0] == 6, fmt.tprintf("%v: cs_d[0] %v != %v", #procedure, cs_d[0], 6))
-	tc.expect(t, cs_d[2025] == -32, fmt.tprintf("%v: cs_d[2025] %v != %v", #procedure, cs_d[2025], -32))
+	testing.expectf(t, cs_d_ok,                "cs_d_ok %v != %v",           cs_d_ok, true)
+	testing.expectf(t, len(cs_d) == 2026,      "len(cs_d) %v != %v",         len(cs_d), 2026)
+	testing.expectf(t, cs_d[0] == 6,           "cs_d[0] %v != %v",           cs_d[0], 6)
+	testing.expectf(t, cs_d[2025] == -32,      "cs_d[2025] %v != %v",        cs_d[2025], -32)
 
 	/* Edge and face stacks (empty) */
-	tc.expect(t, len(v.edge_stack) == 0, fmt.tprintf("%v: len(v.edge_stack) %v != %v",
-													 #procedure, len(v.edge_stack), 0))
-	tc.expect(t, len(v.face_stack) == 0, fmt.tprintf("%v: len(v.face_stack) %v != %v",
-													 #procedure, len(v.face_stack), 0))
+	testing.expectf(t, len(v.edge_stack) == 0, "len(v.edge_stack) %v != %v", len(v.edge_stack), 0)
+	testing.expectf(t, len(v.face_stack) == 0, "len(v.face_stack) %v != %v", len(v.face_stack), 0)
 }
 
 @test
@@ -154,72 +126,72 @@ test_write :: proc(t: ^testing.T) {
 
 	n, write_err := hxa.write(buf, w_file)
 	write_e :: hxa.Write_Error.None
-	tc.expect(t, write_err == write_e, fmt.tprintf("%v: write_err %v != %v", #procedure, write_err, write_e))
-	tc.expect(t, n == required_size, fmt.tprintf("%v: n %v != %v", #procedure, n, required_size))
+	testing.expectf(t, write_err == write_e, fmt.tprintf("write_err %v != %v", write_err, write_e))
+	testing.expectf(t, n == required_size, fmt.tprintf("n %v != %v", n, required_size))
 
 	file, read_err := hxa.read(buf)
 	read_e :: hxa.Read_Error.None
-	tc.expect(t, read_err == read_e, fmt.tprintf("%v: read_err %v != %v", #procedure, read_err, read_e))
+	testing.expectf(t, read_err == read_e, fmt.tprintf("read_err %v != %v", read_err, read_e))
 	defer hxa.file_destroy(file)
 
-	tc.expect(t, file.magic_number == 0x417848, fmt.tprintf("%v: file.magic_number %v != %v",
-															#procedure, file.magic_number, 0x417848))
-	tc.expect(t, file.version == 3, fmt.tprintf("%v: file.version %v != %v", #procedure, file.version, 3))
-	tc.expect(t, file.internal_node_count == 1, fmt.tprintf("%v: file.internal_node_count %v != %v",
-															#procedure, file.internal_node_count, 1))
+	testing.expectf(t, file.magic_number == 0x417848, fmt.tprintf("file.magic_number %v != %v",
+															file.magic_number, 0x417848))
+	testing.expectf(t, file.version == 3, fmt.tprintf("file.version %v != %v", file.version, 3))
+	testing.expectf(t, file.internal_node_count == 1, fmt.tprintf("file.internal_node_count %v != %v",
+															file.internal_node_count, 1))
 
-	tc.expect(t, len(file.nodes) == len(w_file.nodes), fmt.tprintf("%v: len(file.nodes) %v != %v",
-																   #procedure, len(file.nodes), len(w_file.nodes)))
+	testing.expectf(t, len(file.nodes) == len(w_file.nodes), fmt.tprintf("len(file.nodes) %v != %v",
+																   len(file.nodes), len(w_file.nodes)))
 
 	m := &file.nodes[0].meta_data
 	w_m := &w_file.nodes[0].meta_data
-	tc.expect(t, len(m^) == len(w_m^), fmt.tprintf("%v: len(m^) %v != %v", #procedure, len(m^), len(w_m^)))
-	tc.expect(t, m[0].name == w_m[0].name, fmt.tprintf("%v: m[0].name %v != %v", #procedure, m[0].name, w_m[0].name))
+	testing.expectf(t, len(m^) == len(w_m^), fmt.tprintf("len(m^) %v != %v", len(m^), len(w_m^)))
+	testing.expectf(t, m[0].name == w_m[0].name, fmt.tprintf("m[0].name %v != %v", m[0].name, w_m[0].name))
 
 	m_v, m_v_ok := m[0].value.([]f64le)
-	tc.expect(t, m_v_ok, fmt.tprintf("%v: m_v_ok %v != %v", #procedure, m_v_ok, true))
-	tc.expect(t, len(m_v) == len(n1_m1_value), fmt.tprintf("%v: %v != len(m_v) %v",
-														   #procedure, len(m_v), len(n1_m1_value)))
+	testing.expectf(t, m_v_ok, fmt.tprintf("m_v_ok %v != %v", m_v_ok, true))
+	testing.expectf(t, len(m_v) == len(n1_m1_value), fmt.tprintf("%v != len(m_v) %v",
+														   len(m_v), len(n1_m1_value)))
 	for i := 0; i < len(m_v); i += 1 {
-		tc.expect(t, m_v[i] == n1_m1_value[i], fmt.tprintf("%v: m_v[%d] %v != %v",
-														   #procedure, i, m_v[i], n1_m1_value[i]))
+		testing.expectf(t, m_v[i] == n1_m1_value[i], fmt.tprintf("m_v[%d] %v != %v",
+														   i, m_v[i], n1_m1_value[i]))
 	}
 
 	v, v_ok := file.nodes[0].content.(hxa.Node_Image)
-	tc.expect(t, v_ok, fmt.tprintf("%v: v_ok %v != %v", #procedure, v_ok, true))
-	tc.expect(t, v.type == n1_content.type, fmt.tprintf("%v: v.type %v != %v", #procedure, v.type, n1_content.type))
-	tc.expect(t, len(v.resolution) == 3, fmt.tprintf("%v: len(v.resolution) %v != %v",
-													 #procedure, len(v.resolution), 3))
-	tc.expect(t, len(v.image_stack) == len(n1_content.image_stack), fmt.tprintf("%v: len(v.image_stack) %v != %v",
-			  #procedure, len(v.image_stack), len(n1_content.image_stack)))
+	testing.expectf(t, v_ok, fmt.tprintf("v_ok %v != %v", v_ok, true))
+	testing.expectf(t, v.type == n1_content.type, fmt.tprintf("v.type %v != %v", v.type, n1_content.type))
+	testing.expectf(t, len(v.resolution) == 3, fmt.tprintf("len(v.resolution) %v != %v",
+													 len(v.resolution), 3))
+	testing.expectf(t, len(v.image_stack) == len(n1_content.image_stack), fmt.tprintf("len(v.image_stack) %v != %v",
+			  len(v.image_stack), len(n1_content.image_stack)))
 	for i := 0; i < len(v.image_stack); i += 1 {
-		tc.expect(t, v.image_stack[i].name == n1_content.image_stack[i].name,
-				  fmt.tprintf("%v: v.image_stack[%d].name %v != %v",
-							  #procedure, i, v.image_stack[i].name, n1_content.image_stack[i].name))
-		tc.expect(t, v.image_stack[i].components == n1_content.image_stack[i].components,
-				  fmt.tprintf("%v: v.image_stack[%d].components %v != %v",
-				  			  #procedure, i, v.image_stack[i].components, n1_content.image_stack[i].components))
+		testing.expectf(t, v.image_stack[i].name == n1_content.image_stack[i].name,
+				  fmt.tprintf("v.image_stack[%d].name %v != %v",
+							  i, v.image_stack[i].name, n1_content.image_stack[i].name))
+		testing.expectf(t, v.image_stack[i].components == n1_content.image_stack[i].components,
+				  fmt.tprintf("v.image_stack[%d].components %v != %v",
+				  			  i, v.image_stack[i].components, n1_content.image_stack[i].components))
 
 		switch n1_t in n1_content.image_stack[i].data {
 		case []u8:
-			tc.expect(t, false, fmt.tprintf("%v: n1_content.image_stack[i].data []u8", #procedure))
+			testing.expectf(t, false, fmt.tprintf("n1_content.image_stack[i].data []u8", #procedure))
 		case []i32le:
-			tc.expect(t, false, fmt.tprintf("%v: n1_content.image_stack[i].data []i32le", #procedure))
+			testing.expectf(t, false, fmt.tprintf("n1_content.image_stack[i].data []i32le", #procedure))
 		case []f32le:
 			l, l_ok := v.image_stack[i].data.([]f32le)
-			tc.expect(t, l_ok, fmt.tprintf("%v: l_ok %v != %v", #procedure, l_ok, true))
-			tc.expect(t, len(l) == len(n1_t), fmt.tprintf("%v: len(l) %v != %v", #procedure, len(l), len(n1_t)))
+			testing.expectf(t, l_ok, fmt.tprintf("l_ok %v != %v", l_ok, true))
+			testing.expectf(t, len(l) == len(n1_t), fmt.tprintf("len(l) %v != %v", len(l), len(n1_t)))
 			for j := 0; j < len(l); j += 1 {
-				tc.expect(t, l[j] == n1_t[j], fmt.tprintf("%v: l[%d] %v (%h) != %v (%h)",
-														  #procedure, j, l[j], l[j], n1_t[j], n1_t[j]))
+				testing.expectf(t, l[j] == n1_t[j], fmt.tprintf("l[%d] %v (%h) != %v (%h)",
+														  j, l[j], l[j], n1_t[j], n1_t[j]))
 			}
 		case []f64le:
 			l, l_ok := v.image_stack[i].data.([]f64le)
-			tc.expect(t, l_ok, fmt.tprintf("%v: l_ok %v != %v", #procedure, l_ok, true))
-			tc.expect(t, len(l) == len(n1_t), fmt.tprintf("%v: len(l) %v != %v", #procedure, len(l), len(n1_t)))
+			testing.expectf(t, l_ok, fmt.tprintf("l_ok %v != %v", l_ok, true))
+			testing.expectf(t, len(l) == len(n1_t), fmt.tprintf("len(l) %v != %v", len(l), len(n1_t)))
 			for j := 0; j < len(l); j += 1 {
-				tc.expect(t, l[j] == n1_t[j], fmt.tprintf("%v: l[%d] %v != %v", #procedure, j, l[j], n1_t[j]))
+				testing.expectf(t, l[j] == n1_t[j], fmt.tprintf("l[%d] %v != %v", j, l[j], n1_t[j]))
 			}
 		}
 	}
-}
+}

+ 23 - 65
tests/core/encoding/json/test_core_json.odin

@@ -2,46 +2,8 @@ package test_core_json
 
 import "core:encoding/json"
 import "core:testing"
-import "core:fmt"
-import "core:os"
 import "core:mem/virtual"
 
-TEST_count := 0
-TEST_fail  := 0
-
-when ODIN_TEST {
-	expect  :: testing.expect
-	log     :: testing.log
-} else {
-	expect  :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
-		TEST_count += 1
-		if !condition {
-			TEST_fail += 1
-			fmt.printf("[%v] %v\n", loc, message)
-			return
-		}
-	}
-	log     :: proc(t: ^testing.T, v: any, loc := #caller_location) {
-		fmt.printf("[%v] ", loc)
-		fmt.printf("log: %v\n", v)
-	}
-}
-
-main :: proc() {
-	t := testing.T{}
-
-	parse_json(&t)
-	marshal_json(&t)
-	unmarshal_json(&t)
-	surrogate(&t)
-	utf8_string_of_multibyte_characters(&t)
-
-	fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
-	if TEST_fail > 0 {
-		os.exit(1)
-	}
-}
-
 @test
 parse_json :: proc(t: ^testing.T) {
    
@@ -72,10 +34,9 @@ parse_json :: proc(t: ^testing.T) {
 	}
 	`
    
-	_, err := json.parse(transmute([]u8)json_data)
-
-	msg := fmt.tprintf("Expected `json.parse` to return nil, got %v", err)
-	expect(t, err == nil, msg)
+	val, err := json.parse(transmute([]u8)json_data)
+	json.destroy_value(val)
+	testing.expectf(t, err == nil, "Expected `json.parse` to return nil, got %v", err)
 }
 
 @test
@@ -83,7 +44,7 @@ out_of_memory_in_parse_json :: proc(t: ^testing.T) {
 	arena: virtual.Arena
 	arena_buffer: [256]byte
 	arena_init_error := virtual.arena_init_buffer(&arena, arena_buffer[:])
-	testing.expect(t, arena_init_error == nil, fmt.tprintf("Expected arena initialization to not return error, got: %v\n", arena_init_error))
+	testing.expectf(t, arena_init_error == nil, "Expected arena initialization to not return error, got: %v\n", arena_init_error)
 
 	context.allocator = virtual.arena_allocator(&arena)
 	
@@ -114,11 +75,11 @@ out_of_memory_in_parse_json :: proc(t: ^testing.T) {
 	}
 	`
 
-	_, err := json.parse(transmute([]u8)json_data)
+	val, err := json.parse(transmute([]u8)json_data)
+	json.destroy_value(val)
 
 	expected_error := json.Error.Out_Of_Memory
-	msg := fmt.tprintf("Expected `json.parse` to fail with %v, got %v", expected_error, err)
-	expect(t, err == json.Error.Out_Of_Memory, msg)
+	testing.expectf(t, err == json.Error.Out_Of_Memory, "Expected `json.parse` to fail with %v, got %v", expected_error, err)
 }
 
 @test
@@ -134,9 +95,9 @@ marshal_json :: proc(t: ^testing.T) {
 		b = 5,
 	}
    
-	_, err := json.marshal(my_struct)
-	msg := fmt.tprintf("Expected `json.marshal` to return nil, got %v", err)
-	expect(t, err == nil, msg)
+	data, err := json.marshal(my_struct)
+	defer delete(data)
+	testing.expectf(t, err == nil, "Expected `json.marshal` to return nil, got %v", err)
 }
 
 PRODUCTS := `
@@ -378,17 +339,12 @@ unmarshal_json :: proc(t: ^testing.T) {
 	err := json.unmarshal(transmute([]u8)PRODUCTS, &g, json.DEFAULT_SPECIFICATION)
 	defer cleanup(g)
 
-	msg := fmt.tprintf("Expected `json.unmarshal` to return nil, got %v", err)
-	expect(t, err == nil, msg)
-
-	msg = fmt.tprintf("Expected %v products to have been unmarshaled, got %v", len(original_data.products), len(g.products))
-	expect(t, len(g.products) == len(original_data.products), msg)
-
-	msg = fmt.tprintf("Expected cash to have been unmarshaled as %v, got %v", original_data.cash, g.cash)
-	expect(t, original_data.cash == g.cash, msg)
+	testing.expectf(t, err == nil,                                     "Expected `json.unmarshal` to return nil, got %v", err)
+	testing.expectf(t, len(g.products) == len(original_data.products), "Expected %v products to have been unmarshaled, got %v", len(original_data.products), len(g.products))
+	testing.expectf(t, original_data.cash == g.cash,                   "Expected cash to have been unmarshaled as %v, got %v", original_data.cash, g.cash)
 
 	for p, i in g.products {
-		expect(t, p == original_data.products[i], "Producted unmarshaled improperly")
+		testing.expect(t, p == original_data.products[i], "Producted unmarshaled improperly")
 	}
 }
 
@@ -397,17 +353,19 @@ surrogate :: proc(t: ^testing.T) {
 	input := `+ + * 😃 - /`
 
 	out, err := json.marshal(input)
-	expect(t, err == nil, fmt.tprintf("Expected `json.marshal(%q)` to return a nil error, got %v", input, err))
+	defer delete(out)
+	testing.expectf(t, err == nil,    "Expected `json.marshal(%q)` to return a nil error, got %v", input, err)
 
 	back: string
 	uerr := json.unmarshal(out, &back)
-	expect(t, uerr == nil, fmt.tprintf("Expected `json.unmarshal(%q)` to return a nil error, got %v", string(out), uerr))
-	expect(t, back == input, fmt.tprintf("Expected `json.unmarshal(%q)` to return %q, got %v", string(out), input, uerr))
+	defer delete(back)
+	testing.expectf(t, uerr == nil,   "Expected `json.unmarshal(%q)` to return a nil error, got %v", string(out), uerr)
+	testing.expectf(t, back == input, "Expected `json.unmarshal(%q)` to return %q, got %v", string(out), input, uerr)
 }
 
 @test
 utf8_string_of_multibyte_characters :: proc(t: ^testing.T) {
-	_, err := json.parse_string(`"🐛✅"`)
-	msg := fmt.tprintf("Expected `json.parse` to return nil, got %v", err)
-	expect(t, err == nil, msg)
-}
+	val, err := json.parse_string(`"🐛✅"`)
+	defer json.destroy_value(val)
+	testing.expectf(t, err == nil, "Expected `json.parse` to return nil, got %v", err)
+}

+ 24 - 60
tests/core/encoding/varint/test_core_varint.odin

@@ -2,110 +2,74 @@ package test_core_varint
 
 import "core:encoding/varint"
 import "core:testing"
-import "core:fmt"
-import "core:os"
 import "core:slice"
 import "core:math/rand"
 
-TEST_count := 0
-TEST_fail  := 0
-
-RANDOM_TESTS :: 100
-
-when ODIN_TEST {
-	expect  :: testing.expect
-	log     :: testing.log
-} else {
-	expect  :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
-		TEST_count += 1
-		if !condition {
-			TEST_fail += 1
-			fmt.printf("[%v] %v\n", loc, message)
-			return
-		}
-	}
-	log     :: proc(t: ^testing.T, v: any, loc := #caller_location) {
-		fmt.printf("[%v] ", loc)
-		fmt.printf("log: %v\n", v)
-	}
-}
-
-main :: proc() {
-	t := testing.T{}
-
-	test_leb128(&t)
-
-	fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
-	if TEST_fail > 0 {
-		os.exit(1)
-	}
-}
+NUM_RANDOM_TESTS_PER_BYTE_SIZE :: 10_000
 
 @(test)
-test_leb128 :: proc(t: ^testing.T) {
+test_uleb :: proc(t: ^testing.T) {
 	buf: [varint.LEB128_MAX_BYTES]u8
 
 	for vector in ULEB_Vectors {
 		val, size, err := varint.decode_uleb128(vector.encoded)
 
-		msg := fmt.tprintf("Expected %02x to decode to %v consuming %v bytes, got %v and %v", vector.encoded, vector.value, vector.size, val, size)
-		expect(t, size == vector.size && val == vector.value, msg)
-
-		msg  = fmt.tprintf("Expected decoder to return error %v, got %v for vector %v", vector.error, err, vector)
-		expect(t, err == vector.error, msg)
+		testing.expectf(t, size == vector.size && val == vector.value, "Expected %02x to decode to %v consuming %v bytes, got %v and %v", vector.encoded, vector.value, vector.size, val, size)
+		testing.expectf(t, err == vector.error, "Expected decoder to return error %v, got %v for vector %v", vector.error, err, vector)
 
 		if err == .None { // Try to roundtrip
 			size, err = varint.encode_uleb128(buf[:], vector.value)
 
-			msg = fmt.tprintf("Expected %v to encode to %02x, got %02x", vector.value, vector.encoded, buf[:size])
-			expect(t, size == vector.size && slice.simple_equal(vector.encoded, buf[:size]), msg)
+			testing.expectf(t, size == vector.size && slice.simple_equal(vector.encoded, buf[:size]), "Expected %v to encode to %02x, got %02x", vector.value, vector.encoded, buf[:size])
 		}
 	}
+}
+
+@(test)
+test_ileb :: proc(t: ^testing.T) {
+	buf: [varint.LEB128_MAX_BYTES]u8
 
 	for vector in ILEB_Vectors {
 		val, size, err := varint.decode_ileb128(vector.encoded)
 
-		msg := fmt.tprintf("Expected %02x to decode to %v consuming %v bytes, got %v and %v", vector.encoded, vector.value, vector.size, val, size)
-		expect(t, size == vector.size && val == vector.value, msg)
-
-		msg  = fmt.tprintf("Expected decoder to return error %v, got %v", vector.error, err)
-		expect(t, err == vector.error, msg)
+		testing.expectf(t, size == vector.size && val == vector.value, "Expected %02x to decode to %v consuming %v bytes, got %v and %v", vector.encoded, vector.value, vector.size, val, size)
+		testing.expectf(t, err == vector.error, "Expected decoder to return error %v, got %v", vector.error, err)
 
 		if err == .None { // Try to roundtrip
 			size, err = varint.encode_ileb128(buf[:], vector.value)
 
-			msg = fmt.tprintf("Expected %v to encode to %02x, got %02x", vector.value, vector.encoded, buf[:size])
-			expect(t, size == vector.size && slice.simple_equal(vector.encoded, buf[:size]), msg)
+			testing.expectf(t, size == vector.size && slice.simple_equal(vector.encoded, buf[:size]), "Expected %v to encode to %02x, got %02x", vector.value, vector.encoded, buf[:size])
 		}
 	}
+}
+
+@(test)
+test_random :: proc(t: ^testing.T) {
+	buf: [varint.LEB128_MAX_BYTES]u8
 
 	for num_bytes in 1..=uint(16) {
-		for _ in 0..=RANDOM_TESTS {
+		for _ in 0..=NUM_RANDOM_TESTS_PER_BYTE_SIZE {
 			unsigned, signed := get_random(num_bytes)
-
 			{
 				encode_size, encode_err := varint.encode_uleb128(buf[:], unsigned)
-				msg := fmt.tprintf("%v failed to encode as an unsigned LEB128 value, got %v", unsigned, encode_err)
-				expect(t, encode_err == .None, msg)
+				testing.expectf(t, encode_err == .None, "%v failed to encode as an unsigned LEB128 value, got %v", unsigned, encode_err)
 
 				decoded, decode_size, decode_err := varint.decode_uleb128(buf[:])
-				msg = fmt.tprintf("Expected %02x to decode as %v, got %v", buf[:encode_size], unsigned, decoded)
-				expect(t, decode_err == .None && decode_size == encode_size && decoded == unsigned, msg)
+				testing.expectf(t, decode_err == .None && decode_size == encode_size && decoded == unsigned, "Expected %02x to decode as %v, got %v", buf[:encode_size], unsigned, decoded)
 			}
 
 			{
 				encode_size, encode_err := varint.encode_ileb128(buf[:], signed)
-				msg := fmt.tprintf("%v failed to encode as a signed LEB128 value, got %v", signed, encode_err)
-				expect(t, encode_err == .None, msg)
+				testing.expectf(t, encode_err == .None, "%v failed to encode as a signed LEB128 value, got %v", signed, encode_err)
 
 				decoded, decode_size, decode_err := varint.decode_ileb128(buf[:])
-				msg = fmt.tprintf("Expected %02x to decode as %v, got %v, err: %v", buf[:encode_size], signed, decoded, decode_err)
-				expect(t, decode_err == .None && decode_size == encode_size && decoded == signed, msg)
+				testing.expectf(t, decode_err == .None && decode_size == encode_size && decoded == signed, "Expected %02x to decode as %v, got %v, err: %v", buf[:encode_size], signed, decoded, decode_err)
 			}
 		}
 	}
 }
 
+@(private)
 get_random :: proc(byte_count: uint) -> (u: u128, i: i128) {
 	assert(byte_count >= 0 && byte_count <= size_of(u128))
 

+ 84 - 136
tests/core/encoding/xml/test_core_xml.odin

@@ -2,10 +2,10 @@ package test_core_xml
 
 import "core:encoding/xml"
 import "core:testing"
-import "core:mem"
 import "core:strings"
 import "core:io"
 import "core:fmt"
+import "core:log"
 import "core:hash"
 
 Silent :: proc(pos: xml.Pos, format: string, args: ..any) {}
@@ -14,9 +14,6 @@ OPTIONS :: xml.Options{ flags = { .Ignore_Unsupported, .Intern_Comments, },
 	expected_doctype = "",
 }
 
-TEST_count := 0
-TEST_fail  := 0
-
 TEST :: struct {
 	filename: string,
 	options:  xml.Options,
@@ -24,22 +21,14 @@ TEST :: struct {
 	crc32:    u32,
 }
 
-/*
-	Relative to ODIN_ROOT
-*/
-TEST_FILE_PATH_PREFIX :: "tests/core/assets"
-
-TESTS :: []TEST{
-	/*
-		First we test that certain files parse without error.
-	*/
+TEST_SUITE_PATH :: ODIN_ROOT + "tests/core/assets/"
 
-	{
-		/*
-			Tests UTF-8 idents and values.
-			Test namespaced ident.
-			Tests that nested partial CDATA start doesn't trip up parser.
-		*/
+@(test)
+xml_test_utf8_normal :: proc(t: ^testing.T) {
+	run_test(t, {
+		// Tests UTF-8 idents and values.
+		// Test namespaced ident.
+		// Tests that nested partial CDATA start doesn't trip up parser.
 		filename  = "XML/utf8.xml",
 		options   = {
 			flags = {
@@ -48,13 +37,14 @@ TESTS :: []TEST{
 			expected_doctype = "恥ずべきフクロウ",
 		},
 		crc32     = 0xe9b62f03,
-	},
+	})
+}
 
-	{
-		/*
-			Same as above.
-			Unbox CDATA in data tag.
-		*/
+@(test)
+xml_test_utf8_unbox_cdata :: proc(t: ^testing.T) {
+	run_test(t, {
+		// Same as above.
+		// Unbox CDATA in data tag.
 		filename  = "XML/utf8.xml",
 		options   = {
 			flags = {
@@ -63,13 +53,14 @@ TESTS :: []TEST{
 			expected_doctype = "恥ずべきフクロウ",
 		},
 		crc32     = 0x9c2643ed,
-	},
+	})
+}
 
-	{
-		/*
-			Simple Qt TS translation file.
-			`core:i18n` requires it to be parsed properly.
-		*/
+@(test)
+xml_test_nl_qt_ts :: proc(t: ^testing.T) {
+	run_test(t, {
+		// Simple Qt TS translation file.
+		// `core:i18n` requires it to be parsed properly.
 		filename  = "I18N/nl_NL-qt-ts.ts",
 		options   = {
 			flags = {
@@ -78,13 +69,14 @@ TESTS :: []TEST{
 			expected_doctype = "TS",
 		},
 		crc32     = 0x859b7443,
-	},
+	})
+}
 
-	{
-		/*
-			Simple XLiff 1.2 file.
-			`core:i18n` requires it to be parsed properly.
-		*/
+@(test)
+xml_test_xliff_1_2 :: proc(t: ^testing.T) {
+	run_test(t, {
+		// Simple XLiff 1.2 file.
+		// `core:i18n` requires it to be parsed properly.
 		filename  = "I18N/nl_NL-xliff-1.2.xliff",
 		options   = {
 			flags = {
@@ -93,13 +85,14 @@ TESTS :: []TEST{
 			expected_doctype = "xliff",
 		},
 		crc32     = 0x3deaf329,
-	},
+	})
+}
 
-	{
-		/*
-			Simple XLiff 2.0 file.
-			`core:i18n` requires it to be parsed properly.
-		*/
+@(test)
+xml_test_xliff_2_0 :: proc(t: ^testing.T) {
+	run_test(t, {
+		// Simple XLiff 2.0 file.
+		// `core:i18n` requires it to be parsed properly.
 		filename  = "I18N/nl_NL-xliff-2.0.xliff",
 		options   = {
 			flags = {
@@ -108,9 +101,12 @@ TESTS :: []TEST{
 			expected_doctype = "xliff",
 		},
 		crc32     = 0x0c55e287,
-	},
+	})
+}
 
-	{
+@(test)
+xml_test_entities :: proc(t: ^testing.T) {
+	run_test(t, {
 		filename  = "XML/entities.html",
 		options   = {
 			flags = {
@@ -119,9 +115,12 @@ TESTS :: []TEST{
 			expected_doctype = "html",
 		},
 		crc32     = 0x05373317,
-	},
+	})
+}
 
-	{
+@(test)
+xml_test_entities_unbox :: proc(t: ^testing.T) {
+	run_test(t, {
 		filename  = "XML/entities.html",
 		options   = {
 			flags = {
@@ -130,9 +129,12 @@ TESTS :: []TEST{
 			expected_doctype = "html",
 		},
 		crc32     = 0x3b6d4a90,
-	},
+	})
+}
 
-	{
+@(test)
+xml_test_entities_unbox_decode :: proc(t: ^testing.T) {
+	run_test(t, {
 		filename  = "XML/entities.html",
 		options   = {
 			flags = {
@@ -141,12 +143,12 @@ TESTS :: []TEST{
 			expected_doctype = "html",
 		},
 		crc32     = 0x5be2ffdc,
-	},
+	})
+}
 
-	/*
-		Then we test that certain errors are returned as expected.
-	*/
-	{
+@(test)
+xml_test_invalid_doctype :: proc(t: ^testing.T) {
+	run_test(t, {
 		filename  = "XML/utf8.xml",
 		options   = {
 			flags            = {
@@ -156,12 +158,12 @@ TESTS :: []TEST{
 		},
 		err       = .Invalid_DocType,
 		crc32     = 0x49b83d0a,
-	},
+	})
+}
 
-	/*
-		Parse the 9.08 MiB unicode.xml for good measure.
-	*/
-	{
+@(test)
+xml_test_unicode :: proc(t: ^testing.T) {
+	run_test(t, {
 		filename  = "XML/unicode.xml",
 		options   = {
 			flags            = {
@@ -171,39 +173,37 @@ TESTS :: []TEST{
 		},
 		err       = .None,
 		crc32     = 0x0b6100ab,
-	},
+	})
 }
 
-when ODIN_TEST {
-	expect  :: testing.expect
-	log     :: testing.log
-} else {
-	expect  :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
-		TEST_count += 1
-		if !condition {
-			TEST_fail += 1
-			fmt.printf("[%v] %v\n", loc, message)
-			return
-		}
-	}
-	log     :: proc(t: ^testing.T, v: any, loc := #caller_location) {
-		fmt.printf("[%v] LOG:\n\t%v\n", loc, v)
-	}
-}
+@(private)
+run_test :: proc(t: ^testing.T, test: TEST) {
+	path := strings.concatenate({TEST_SUITE_PATH, test.filename})
+	defer delete(path)
 
-test_file_path :: proc(filename: string) -> (path: string) {
+	doc, err := xml.load_from_file(path, test.options, Silent)
+	defer xml.destroy(doc)
 
-	path = fmt.tprintf("%v%v/%v", ODIN_ROOT, TEST_FILE_PATH_PREFIX, filename)
-	temp := transmute([]u8)path
+	tree_string := doc_to_string(doc)
+	tree_bytes  := transmute([]u8)tree_string
+	defer delete(tree_bytes)
 
-	for r, i in path {
-		if r == '\\' {
-			temp[i] = '/'
-		}
+	crc32 := hash.crc32(tree_bytes)
+
+	failed := err != test.err
+	testing.expectf(t, err == test.err, "%v: Expected return value %v, got %v", test.filename, test.err, err)
+
+	failed |= crc32 != test.crc32
+	testing.expectf(t, crc32 == test.crc32, "%v: Expected CRC 0x%08x, got 0x%08x, with options %v", test.filename, test.crc32, crc32, test.options)
+
+	if failed {
+		// Don't fully print big trees.
+		tree_string = tree_string[:min(2_048, len(tree_string))]
+		log.error(tree_string)
 	}
-	return path
 }
 
+@(private)
 doc_to_string :: proc(doc: ^xml.Document) -> (result: string) {
 	/*
 		Effectively a clone of the debug printer in the xml package.
@@ -284,56 +284,4 @@ doc_to_string :: proc(doc: ^xml.Document) -> (result: string) {
 
 	print(strings.to_writer(&buf), doc)
 	return strings.clone(strings.to_string(buf))
-}
-
-@test
-run_tests :: proc(t: ^testing.T) {
-	for test in TESTS {
-		path := test_file_path(test.filename)
-		log(t, fmt.tprintf("Trying to parse %v", path))
-
-		doc, err := xml.load_from_file(path, test.options, Silent)
-		defer xml.destroy(doc)
-
-		tree_string := doc_to_string(doc)
-		tree_bytes  := transmute([]u8)tree_string
-		defer delete(tree_bytes)
-
-		crc32 := hash.crc32(tree_bytes)
-
-		failed := err != test.err
-		err_msg := fmt.tprintf("Expected return value %v, got %v", test.err, err)
-		expect(t, err == test.err, err_msg)
-
-		failed |= crc32 != test.crc32
-		err_msg  = fmt.tprintf("Expected CRC 0x%08x, got 0x%08x, with options %v", test.crc32, crc32, test.options)
-		expect(t, crc32 == test.crc32, err_msg)
-
-		if failed {
-			/*
-				Don't fully print big trees.
-			*/
-			tree_string = tree_string[:min(2_048, len(tree_string))]
-			fmt.println(tree_string)
-		}
-	}
-}
-
-main :: proc() {
-	t := testing.T{}
-
-	track: mem.Tracking_Allocator
-	mem.tracking_allocator_init(&track, context.allocator)
-	context.allocator = mem.tracking_allocator(&track)
-
-	run_tests(&t)
-
-	if len(track.allocation_map) > 0 {
-		for _, v in track.allocation_map {
-			err_msg := fmt.tprintf("%v Leaked %v bytes.", v.location, v.size)
-			expect(&t, false, err_msg)
-		}
-	}	
-
-	fmt.printf("\n%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
 }

+ 20 - 41
tests/core/fmt/test_core_fmt.odin

@@ -2,48 +2,9 @@ package test_core_fmt
 
 import "base:runtime"
 import "core:fmt"
-import "core:os"
-import "core:testing"
 import "core:math"
 import "core:mem"
-
-TEST_count := 0
-TEST_fail  := 0
-
-when ODIN_TEST {
-	expect  :: testing.expect
-	log     :: testing.log
-} else {
-	expect  :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
-		TEST_count += 1
-		if !condition {
-			TEST_fail += 1
-			fmt.printf("[%v] %v\n", loc, message)
-			return
-		}
-	}
-	log     :: proc(t: ^testing.T, v: any, loc := #caller_location) {
-		fmt.printf("[%v] ", loc)
-		fmt.printf("log: %v\n", v)
-	}
-}
-
-main :: proc() {
-	t := testing.T{}
-	test_fmt_memory(&t)
-	test_fmt_doc_examples(&t)
-	test_fmt_options(&t)
-
-	fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
-	if TEST_fail > 0 {
-		os.exit(1)
-	}
-}
-
-check :: proc(t: ^testing.T, exp: string, format: string, args: ..any, loc := #caller_location) {
-	got := fmt.tprintf(format, ..args)
-	expect(t, got == exp, fmt.tprintf("(%q, %v): %q != %q", format, args, got, exp), loc)
-}
+import "core:testing"
 
 @(test)
 test_fmt_memory :: proc(t: ^testing.T) {
@@ -141,7 +102,7 @@ test_fmt_doc_examples :: proc(t: ^testing.T) {
 }
 
 @(test)
-test_fmt_options :: proc(t: ^testing.T) {
+test_fmt_escaping_prefixes :: proc(t: ^testing.T) {
 	// Escaping
 	check(t, "% { } 0 { } } {", "%% {{ }} {} {{ }} }} {{", 0 )
 
@@ -152,7 +113,10 @@ test_fmt_options :: proc(t: ^testing.T) {
 	check(t, "+3",     "%+i",   3 )
 	check(t, "0b11",   "%#b",   3 )
 	check(t, "0xA",    "%#X",   10 )
+}
 
+@(test)
+test_fmt_indexing :: proc(t: ^testing.T) {
 	// Specific index formatting
 	check(t, "1 2 3", "%i %i %i",          1, 2, 3)
 	check(t, "1 2 3", "%[0]i %[1]i %[2]i", 1, 2, 3)
@@ -161,7 +125,10 @@ test_fmt_options :: proc(t: ^testing.T) {
 	check(t, "1 2 3", "%i %[1]i %i",       1, 2, 3)
 	check(t, "1 3 2", "%i %[2]i %i",       1, 2, 3)
 	check(t, "1 1 1", "%[0]i %[0]i %[0]i", 1)
+}
 
+@(test)
+test_fmt_width_precision :: proc(t: ^testing.T) {
 	// Width
 	check(t, "3.140",  "%f",  3.14)
 	check(t, "3.140",  "%4f", 3.14)
@@ -199,7 +166,10 @@ test_fmt_options :: proc(t: ^testing.T) {
 	check(t, "3.140", "%*[1].*[2][0]f", 3.14, 5, 3)
 	check(t, "3.140", "%*[2].*[1]f",    3.14, 3, 5)
 	check(t, "3.140", "%5.*[1]f",       3.14, 3)
+}
 
+@(test)
+test_fmt_arg_errors :: proc(t: ^testing.T) {
 	// Error checking
 	check(t, "%!(MISSING ARGUMENT)%!(NO VERB)", "%" )
 
@@ -222,7 +192,10 @@ test_fmt_options :: proc(t: ^testing.T) {
 	check(t, "%!(BAD ARGUMENT NUMBER)%!(NO VERB)%!(EXTRA 0)", "%[1]", 0)
 
 	check(t, "3.1%!(EXTRA 3.14)", "%.1f", 3.14, 3.14)
+}
 
+@(test)
+test_fmt_python_syntax :: proc(t: ^testing.T) {
 	// Python-like syntax
 	check(t, "1 2 3", "{} {} {}",          1, 2, 3)
 	check(t, "3 2 1", "{2} {1} {0}",       1, 2, 3)
@@ -247,3 +220,9 @@ test_fmt_options :: proc(t: ^testing.T) {
 	check(t, "%!(MISSING CLOSE BRACE)%!(EXTRA 1)",          "{",  1)
 	check(t, "%!(MISSING CLOSE BRACE)%!(EXTRA 1)",          "{0", 1 )
 }
+
+@(private)
+check :: proc(t: ^testing.T, exp: string, format: string, args: ..any) {
+	got := fmt.tprintf(format, ..args)
+	testing.expectf(t, got == exp, "(%q, %v): %q != %q", format, args, got, exp)
+}

+ 38 - 270
tests/core/hash/test_core_hash.odin

@@ -2,201 +2,12 @@ package test_core_hash
 
 import "core:hash/xxhash"
 import "core:hash"
-import "core:time"
 import "core:testing"
-import "core:fmt"
-import "core:os"
 import "core:math/rand"
 import "base:intrinsics"
 
-TEST_count := 0
-TEST_fail  := 0
-
-when ODIN_TEST {
-	expect  :: testing.expect
-	log     :: testing.log
-} else {
-	expect  :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
-		TEST_count += 1
-		if !condition {
-			TEST_fail += 1
-			fmt.printf("[%v] %v\n", loc, message)
-			return
-		}
-	}
-	log     :: proc(t: ^testing.T, v: any, loc := #caller_location) {
-		fmt.printf("[%v] ", loc)
-		fmt.printf("log: %v\n", v)
-	}
-}
-
-main :: proc() {
-	t := testing.T{}
-	test_benchmark_runner(&t)
-	test_crc64_vectors(&t)
-	test_xxhash_vectors(&t)
-	test_xxhash_large(&t)
-
-	fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
-	if TEST_fail > 0 {
-		os.exit(1)
-	}
-}
-
-/*
-	Benchmarks
-*/
-
-setup_xxhash :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) {
-	assert(options != nil)
-
-	options.input = make([]u8, options.bytes, allocator)
-	return nil if len(options.input) == options.bytes else .Allocation_Error
-}
-
-teardown_xxhash :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) {
-	assert(options != nil)
-
-	delete(options.input)
-	return nil
-}
-
-benchmark_xxh32 :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) {
-	buf := options.input
-
-	h: u32
-	for _ in 0..=options.rounds {
-		h = xxhash.XXH32(buf)
-	}
-	options.count     = options.rounds
-	options.processed = options.rounds * options.bytes
-	options.hash      = u128(h)
-	return nil
-}
-
-benchmark_xxh64 :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) {
-	buf := options.input
-
-	h: u64
-	for _ in 0..=options.rounds {
-		h = xxhash.XXH64(buf)
-	}
-	options.count     = options.rounds
-	options.processed = options.rounds * options.bytes
-	options.hash      = u128(h)
-	return nil
-}
-
-benchmark_xxh3_64 :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) {
-	buf := options.input
-
-	h: u64
-	for _ in 0..=options.rounds {
-		h = xxhash.XXH3_64(buf)
-	}
-	options.count     = options.rounds
-	options.processed = options.rounds * options.bytes
-	options.hash      = u128(h)
-	return nil
-}
-
-benchmark_xxh3_128 :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) {
-	buf := options.input
-
-	h: u128
-	for _ in 0..=options.rounds {
-		h = xxhash.XXH3_128(buf)
-	}
-	options.count     = options.rounds
-	options.processed = options.rounds * options.bytes
-	options.hash      = h
-	return nil
-}
-
-benchmark_print :: proc(name: string, options: ^time.Benchmark_Options) {
-	fmt.printf("\t[%v] %v rounds, %v bytes processed in %v ns\n\t\t%5.3f rounds/s, %5.3f MiB/s\n",
-		name,
-		options.rounds,
-		options.processed,
-		time.duration_nanoseconds(options.duration),
-		options.rounds_per_second,
-		options.megabytes_per_second,
-	)
-}
-
-@test
-test_benchmark_runner :: proc(t: ^testing.T) {
-	fmt.println("Starting benchmarks:")
-
-	name    := "XXH32 100 zero bytes"
-	options := &time.Benchmark_Options{
-		rounds   = 1_000,
-		bytes    = 100,
-		setup    = setup_xxhash,
-		bench    = benchmark_xxh32,
-		teardown = teardown_xxhash,
-	}
-
-	err  := time.benchmark(options, context.allocator)
-	expect(t, err == nil, name)
-	expect(t, options.hash == 0x85f6413c, name)
-	benchmark_print(name, options)
-
-	name = "XXH32 1 MiB zero bytes"
-	options.bytes = 1_048_576
-	err = time.benchmark(options, context.allocator)
-	expect(t, err == nil, name)
-	expect(t, options.hash == 0x9430f97f, name)
-	benchmark_print(name, options)
-
-	name = "XXH64 100 zero bytes"
-	options.bytes  = 100
-	options.bench = benchmark_xxh64
-	err = time.benchmark(options, context.allocator)
-	expect(t, err == nil, name)
-	expect(t, options.hash == 0x17bb1103c92c502f, name)
-	benchmark_print(name, options)
-
-	name = "XXH64 1 MiB zero bytes"
-	options.bytes = 1_048_576
-	err = time.benchmark(options, context.allocator)
-	expect(t, err == nil, name)
-	expect(t, options.hash == 0x87d2a1b6e1163ef1, name)
-	benchmark_print(name, options)
-
-	name = "XXH3_64 100 zero bytes"
-	options.bytes  = 100
-	options.bench = benchmark_xxh3_64
-	err = time.benchmark(options, context.allocator)
-	expect(t, err == nil, name)
-	expect(t, options.hash == 0x801fedc74ccd608c, name)
-	benchmark_print(name, options)
-
-	name = "XXH3_64 1 MiB zero bytes"
-	options.bytes = 1_048_576
-	err = time.benchmark(options, context.allocator)
-	expect(t, err == nil, name)
-	expect(t, options.hash == 0x918780b90550bf34, name)
-	benchmark_print(name, options)
-
-	name = "XXH3_128 100 zero bytes"
-	options.bytes  = 100
-	options.bench = benchmark_xxh3_128
-	err = time.benchmark(options, context.allocator)
-	expect(t, err == nil, name)
-	expect(t, options.hash == 0x6ba30a4e9dffe1ff801fedc74ccd608c, name)
-	benchmark_print(name, options)
-
-	name = "XXH3_128 1 MiB zero bytes"
-	options.bytes = 1_048_576
-	err = time.benchmark(options, context.allocator)
-	expect(t, err == nil, name)
-	expect(t, options.hash == 0xb6ef17a3448492b6918780b90550bf34, name)
-	benchmark_print(name, options)
-}
-
 @test
-test_xxhash_large :: proc(t: ^testing.T) {
+test_xxhash_zero_fixed :: proc(t: ^testing.T) {
 	many_zeroes := make([]u8, 16 * 1024 * 1024)
 	defer delete(many_zeroes)
 
@@ -204,62 +15,45 @@ test_xxhash_large :: proc(t: ^testing.T) {
 	for i, v in ZERO_VECTORS {
 		b := many_zeroes[:i]
 
-		fmt.printf("[test_xxhash_large] All at once. Size: %v\n", i)
-
 		xxh32    := xxhash.XXH32(b)
 		xxh64    := xxhash.XXH64(b)
 		xxh3_64  := xxhash.XXH3_64(b)
 		xxh3_128 := xxhash.XXH3_128(b)
 
-		xxh32_error     := fmt.tprintf("[   XXH32(%03d) ] Expected: %08x. Got: %08x.", i,   v.xxh_32,   xxh32)
-		xxh64_error     := fmt.tprintf("[   XXH64(%03d) ] Expected: %16x. Got: %16x.", i,   v.xxh_64,   xxh64)
-		xxh3_64_error   := fmt.tprintf("[XXH3_64(%03d)  ] Expected: %16x. Got: %16x.", i,  v.xxh3_64, xxh3_64)
-		xxh3_128_error  := fmt.tprintf("[XXH3_128(%03d) ] Expected: %32x. Got: %32x.", i, v.xxh3_128, xxh3_128)
-
-		expect(t, xxh32     == v.xxh_32,   xxh32_error)
-		expect(t, xxh64     == v.xxh_64,   xxh64_error)
-		expect(t, xxh3_64   == v.xxh3_64,  xxh3_64_error)
-		expect(t, xxh3_128  == v.xxh3_128, xxh3_128_error)
+		testing.expectf(t, xxh32    == v.xxh_32,   "[   XXH32(%03d) ] Expected: %08x, got: %08x", i, v.xxh_32,   xxh32)
+		testing.expectf(t, xxh64    == v.xxh_64,   "[   XXH64(%03d) ] Expected: %16x, got: %16x", i, v.xxh_64,   xxh64)
+		testing.expectf(t, xxh3_64  == v.xxh3_64,  "[XXH3_64(%03d)  ] Expected: %16x, got: %16x", i, v.xxh3_64,  xxh3_64)
+		testing.expectf(t, xxh3_128 == v.xxh3_128, "[XXH3_128(%03d) ] Expected: %32x, got: %32x", i, v.xxh3_128, xxh3_128)
 	}
+}
 
-	when #config(RAND_STATE, -1) >= 0 && #config(RAND_INC, -1) >= 0 {
-		random_seed := rand.Rand{
-			state = u64(#config(RAND_STATE, -1)),
-			inc   = u64(#config(RAND_INC,   -1)),
-		}
-		fmt.printf("Using user-selected seed {{%v,%v}} for update size randomness.\n", random_seed.state, random_seed.inc)
-	} else {
-		random_seed := rand.create(u64(intrinsics.read_cycle_counter()))
-		fmt.printf("Randonly selected seed {{%v,%v}} for update size randomness.\n", random_seed.state, random_seed.inc)
-	}
+@(test)
+test_xxhash_zero_streamed_random_updates :: proc(t: ^testing.T) {
+	many_zeroes := make([]u8, 16 * 1024 * 1024)
+	defer delete(many_zeroes)
 
 	// Streamed
 	for i, v in ZERO_VECTORS {
 		b := many_zeroes[:i]
 
-		fmt.printf("[test_xxhash_large] Streamed. Size: %v\n", i)
-
-		// bytes_per_update := []int{1, 42, 13, 7, 16, 5, 23, 74, 1024, 511, 1023, 47}
-		// update_size_idx: int
-
 		xxh_32_state, xxh_32_err := xxhash.XXH32_create_state()
 		defer xxhash.XXH32_destroy_state(xxh_32_state)
-		expect(t, xxh_32_err == nil, "Problem initializing XXH_32 state.")
+		testing.expect(t, xxh_32_err == nil, "Problem initializing XXH_32 state")
 
 		xxh_64_state, xxh_64_err := xxhash.XXH64_create_state()
 		defer xxhash.XXH64_destroy_state(xxh_64_state)
-		expect(t, xxh_64_err == nil, "Problem initializing XXH_64 state.")
+		testing.expect(t, xxh_64_err == nil, "Problem initializing XXH_64 state")
 
 		xxh3_64_state, xxh3_64_err := xxhash.XXH3_create_state()
 		defer xxhash.XXH3_destroy_state(xxh3_64_state)
-		expect(t, xxh3_64_err == nil, "Problem initializing XXH3_64 state.")
+		testing.expect(t, xxh3_64_err == nil, "Problem initializing XXH3_64 state")
 
 		xxh3_128_state, xxh3_128_err := xxhash.XXH3_create_state()
 		defer xxhash.XXH3_destroy_state(xxh3_128_state)
-		expect(t, xxh3_128_err == nil, "Problem initializing XXH3_128 state.")
+		testing.expect(t, xxh3_128_err == nil, "Problem initializing XXH3_128 state")
 
 		// XXH3_128_update
-
+		random_seed := rand.create(t.seed)
 		for len(b) > 0 {
 			update_size := min(len(b), rand.int_max(8192, &random_seed))
 			if update_size > 4096 {
@@ -281,28 +75,19 @@ test_xxhash_large :: proc(t: ^testing.T) {
 		xxh3_64  := xxhash.XXH3_64_digest(xxh3_64_state)
 		xxh3_128 := xxhash.XXH3_128_digest(xxh3_128_state)
 
-		xxh32_error     := fmt.tprintf("[   XXH32(%03d) ] Expected: %08x. Got: %08x.", i,   v.xxh_32,   xxh32)
-		xxh64_error     := fmt.tprintf("[   XXH64(%03d) ] Expected: %16x. Got: %16x.", i,   v.xxh_64,   xxh64)
-		xxh3_64_error   := fmt.tprintf("[XXH3_64(%03d)  ] Expected: %16x. Got: %16x.", i,  v.xxh3_64, xxh3_64)
-		xxh3_128_error  := fmt.tprintf("[XXH3_128(%03d) ] Expected: %32x. Got: %32x.", i, v.xxh3_128, xxh3_128)
-
-		expect(t, xxh32     == v.xxh_32,   xxh32_error)
-		expect(t, xxh64     == v.xxh_64,   xxh64_error)
-		expect(t, xxh3_64   == v.xxh3_64,  xxh3_64_error)
-		expect(t, xxh3_128  == v.xxh3_128, xxh3_128_error)
+		testing.expectf(t, xxh32     == v.xxh_32,   "[   XXH32(%03d) ] Expected: %08x, got: %08x", i,   v.xxh_32,   xxh32)
+		testing.expectf(t, xxh64     == v.xxh_64,   "[   XXH64(%03d) ] Expected: %16x, got: %16x", i,   v.xxh_64,   xxh64)
+		testing.expectf(t, xxh3_64   == v.xxh3_64,  "[XXH3_64(%03d)  ] Expected: %16x, got: %16x", i,  v.xxh3_64, xxh3_64)
+		testing.expectf(t, xxh3_128  == v.xxh3_128, "[XXH3_128(%03d) ] Expected: %32x, got: %32x", i, v.xxh3_128, xxh3_128)
 	}
 }
 
 @test
-test_xxhash_vectors :: proc(t: ^testing.T) {
-	fmt.println("Verifying against XXHASH_TEST_VECTOR_SEEDED:")
-
+test_xxhash_seeded :: proc(t: ^testing.T) {
 	buf := make([]u8, 256)
 	defer delete(buf)
 
 	for seed, table in XXHASH_TEST_VECTOR_SEEDED {
-		fmt.printf("\tSeed: %v\n", seed)
-
 		for v, i in table {
 			b := buf[:i]
 
@@ -311,60 +96,48 @@ test_xxhash_vectors :: proc(t: ^testing.T) {
 			xxh3_64  := xxhash.XXH3_64(b, seed)
 			xxh3_128 := xxhash.XXH3_128(b, seed)
 
-			xxh32_error     := fmt.tprintf("[   XXH32(%03d) ] Expected: %08x. Got: %08x.", i,   v.xxh_32, xxh32)
-			xxh64_error     := fmt.tprintf("[   XXH64(%03d) ] Expected: %16x. Got: %16x.", i,   v.xxh_64, xxh64)
-
-			xxh3_64_error   := fmt.tprintf("[XXH3_64(%03d)  ] Expected: %16x. Got: %16x.", i, v.xxh3_64, xxh3_64)
-			xxh3_128_error  := fmt.tprintf("[XXH3_128(%03d) ] Expected: %32x. Got: %32x.", i, v.xxh3_128, xxh3_128)
-
-			expect(t, xxh32     == v.xxh_32,   xxh32_error)
-			expect(t, xxh64     == v.xxh_64,   xxh64_error)
-			expect(t, xxh3_64   == v.xxh3_64,  xxh3_64_error)
-			expect(t, xxh3_128  == v.xxh3_128, xxh3_128_error)
+			testing.expectf(t, xxh32    == v.xxh_32,   "[   XXH32(%03d) ] Expected: %08x, got: %08x", i,   v.xxh_32, xxh32)
+			testing.expectf(t, xxh64    == v.xxh_64,   "[   XXH64(%03d) ] Expected: %16x, got: %16x", i,   v.xxh_64, xxh64)
+			testing.expectf(t, xxh3_64  == v.xxh3_64,  "[XXH3_64(%03d)  ] Expected: %16x, got: %16x", i, v.xxh3_64, xxh3_64)
+			testing.expectf(t, xxh3_128 == v.xxh3_128, "[XXH3_128(%03d) ] Expected: %32x, got: %32x", i, v.xxh3_128, xxh3_128)
 
 			if len(b) > xxhash.XXH3_MIDSIZE_MAX {
-				fmt.printf("XXH3 - size: %v\n", len(b))
-
 				xxh3_state, _ := xxhash.XXH3_create_state()
 				xxhash.XXH3_64_reset_with_seed(xxh3_state, seed)
 				xxhash.XXH3_64_update(xxh3_state, b)
 				xxh3_64_streamed := xxhash.XXH3_64_digest(xxh3_state)
 				xxhash.XXH3_destroy_state(xxh3_state)
-				xxh3_64s_error  := fmt.tprintf("[XXH3_64s(%03d) ] Expected: %16x. Got: %16x.", i, v.xxh3_64, xxh3_64_streamed)
-				expect(t, xxh3_64_streamed == v.xxh3_64, xxh3_64s_error)
+				testing.expectf(t, xxh3_64_streamed == v.xxh3_64, "[XXH3_64s(%03d) ] Expected: %16x, got: %16x", i, v.xxh3_64, xxh3_64_streamed)
 
 				xxh3_state2, _ := xxhash.XXH3_create_state()
 				xxhash.XXH3_128_reset_with_seed(xxh3_state2, seed)
 				xxhash.XXH3_128_update(xxh3_state2, b)
 				xxh3_128_streamed := xxhash.XXH3_128_digest(xxh3_state2)
 				xxhash.XXH3_destroy_state(xxh3_state2)
-				xxh3_128s_error  := fmt.tprintf("[XXH3_128s(%03d) ] Expected: %32x. Got: %32x.", i, v.xxh3_128, xxh3_128_streamed)
-				expect(t, xxh3_128_streamed == v.xxh3_128, xxh3_128s_error)
+				testing.expectf(t, xxh3_128_streamed == v.xxh3_128, "[XXH3_128s(%03d) ] Expected: %32x, got: %32x", i, v.xxh3_128, xxh3_128_streamed)
 			}
 		}
 	}
+}
 
-	fmt.println("Verifying against XXHASH_TEST_VECTOR_SECRET:")
-	for secret, table in XXHASH_TEST_VECTOR_SECRET {
-		fmt.printf("\tSecret:\n\t\t\"%v\"\n", secret)
+@test
+test_xxhash_secret :: proc(t: ^testing.T) {
+	buf := make([]u8, 256)
+	defer delete(buf)
 
+	for secret, table in XXHASH_TEST_VECTOR_SECRET {
 		secret_bytes := transmute([]u8)secret
-
 		for v, i in table {
 			b := buf[:i]
 
 			xxh3_128 := xxhash.XXH3_128(b, secret_bytes)
-			xxh3_128_error := fmt.tprintf("[XXH3_128(%03d)] Expected: %32x. Got: %32x.", i, v.xxh3_128_secret, xxh3_128)
-
-			expect(t, xxh3_128  == v.xxh3_128_secret, xxh3_128_error)
+			testing.expectf(t, xxh3_128  == v.xxh3_128_secret, "[XXH3_128(%03d)] Expected: %32x, got: %32x", i, v.xxh3_128_secret, xxh3_128)
 		}
 	}
 }
 
 @test
 test_crc64_vectors :: proc(t: ^testing.T) {
-	fmt.println("Verifying CRC-64:")
-
 	vectors := map[string][4]u64 {
 		"123456789" = {
 			0x6c40df5f0b497347, // ECMA-182,
@@ -379,23 +152,18 @@ test_crc64_vectors :: proc(t: ^testing.T) {
 			0xe7fcf1006b503b61, // ISO 3306, input and output inverted
 		},
 	}
+	defer delete(vectors)
 
 	for vector, expected in vectors {
-		fmt.println("\tVector:", vector)
 		b := transmute([]u8)vector
 		ecma := hash.crc64_ecma_182(b)
 		xz   := hash.crc64_xz(b)
 		iso  := hash.crc64_iso_3306(b)
 		iso2 := hash.crc64_iso_3306_inverse(b)
 
-		ecma_error := fmt.tprintf("[ CRC-64 ECMA    ] Expected: %016x. Got: %016x.", expected[0], ecma)
-		xz_error   := fmt.tprintf("[ CRC-64 XZ      ] Expected: %016x. Got: %016x.", expected[1], xz)
-		iso_error  := fmt.tprintf("[ CRC-64 ISO 3306] Expected: %016x. Got: %016x.", expected[2], iso)
-		iso2_error := fmt.tprintf("[~CRC-64 ISO 3306] Expected: %016x. Got: %016x.", expected[3], iso2)
-
-		expect(t, ecma == expected[0], ecma_error)
-		expect(t, xz   == expected[1], xz_error)
-		expect(t, iso  == expected[2], iso_error)
-		expect(t, iso2 == expected[3], iso2_error)
+		testing.expectf(t, ecma == expected[0], "[ CRC-64 ECMA    ] Expected: %016x, got: %016x", expected[0], ecma)
+		testing.expectf(t, xz   == expected[1], "[ CRC-64 XZ      ] Expected: %016x, got: %016x", expected[1], xz)
+		testing.expectf(t, iso  == expected[2], "[ CRC-64 ISO 3306] Expected: %016x, got: %016x", expected[2], iso)
+		testing.expectf(t, iso2 == expected[3], "[~CRC-64 ISO 3306] Expected: %016x, got: %016x", expected[3], iso2)
 	}
 }

+ 2 - 4
tests/core/hash/test_vectors_xxhash.odin

@@ -1,6 +1,4 @@
-/*
-	Hash Test Vectors
-*/
+// Hash Test Vectors
 package test_core_hash
 
 XXHASH_Test_Vectors :: struct #packed {
@@ -6789,4 +6787,4 @@ XXHASH_TEST_VECTOR_SECRET := map[string][257]XXHASH_Test_Vectors_With_Secret{
 			/* XXH3_128_with_secret */ 0x0f9b41191242ade48bbde48dff0d38ec,
 		},
 	},
-}
+}

+ 2 - 4
tests/core/image/build.bat

@@ -1,4 +1,2 @@
-@echo off
-pushd ..
-odin run image
-popd
+@echo off
+odin test . -define:ODIN_TEST_TRACK_MEMORY=true -define:ODIN_TEST_PROGRESS_WIDTH=12 -vet -strict-style

+ 112 - 191
tests/core/image/test_core_image.odin

@@ -20,49 +20,14 @@ import "core:image/tga"
 
 import "core:bytes"
 import "core:hash"
-import "core:fmt"
 import "core:strings"
-
 import "core:mem"
-import "core:os"
 import "core:time"
 
-import "base:runtime"
-
-TEST_SUITE_PATH   :: "assets/PNG"
+TEST_SUITE_PATH :: ODIN_ROOT + "tests/core/assets/PNG/"
 
-TEST_count := 0
-TEST_fail  := 0
-
-when ODIN_TEST {
-	expect  :: testing.expect
-	log     :: testing.log
-} else {
-	expect  :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
-		TEST_count += 1
-		if !condition {
-			TEST_fail += 1
-			fmt.printf("[%v] %v\n", loc, message)
-			return
-		}
-	}
-	log     :: proc(t: ^testing.T, v: any, loc := #caller_location) {
-		fmt.printf("[%v] ", loc)
-		fmt.printf("log: %v\n", v)
-	}
-}
 I_Error :: image.Error
 
-main :: proc() {
-	t := testing.T{}
-	png_test(&t)
-
-	fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
-	if TEST_fail > 0 {
-		os.exit(1)
-	}
-}
-
 PNG_Test :: struct {
 	file:   string,
 	tests:  []struct {
@@ -72,7 +37,6 @@ PNG_Test :: struct {
 		hash:           u32,
 	},
 }
-
 Default              :: image.Options{}
 Alpha_Add            :: image.Options{.alpha_add_if_missing}
 Premul_Drop          :: image.Options{.alpha_premultiply, .alpha_drop_if_present}
@@ -82,7 +46,7 @@ Blend_BG_Keep        :: image.Options{.blend_background, .alpha_add_if_missing}
 Return_Metadata      :: image.Options{.return_metadata}
 No_Channel_Expansion :: image.Options{.do_not_expand_channels, .return_metadata}
 
-PNG_Dims    :: struct {
+PNG_Dims :: struct {
 	width:     int,
 	height:    int,
 	channels:  int,
@@ -1430,81 +1394,94 @@ Expected_Text := map[string]map[string]png.Text {
 }
 
 @test
-png_test :: proc(t: ^testing.T) {
-
-	total_tests    := 0
-	total_expected := 235
-
-	PNG_Suites := [][]PNG_Test{
-		Basic_PNG_Tests,
-		Interlaced_PNG_Tests,
-		Odd_Sized_PNG_Tests,
-		PNG_bKGD_Tests,
-		PNG_tRNS_Tests,
-		PNG_Filter_Tests,
-		PNG_Varied_IDAT_Tests,
-		PNG_ZLIB_Levels_Tests,
-		PNG_sPAL_Tests,
-		PNG_Ancillary_Tests,
-		Corrupt_PNG_Tests,
-
-		No_Postprocesing_Tests,
+png_test_basic :: proc(t: ^testing.T) {
+	run_png_suite(t, Basic_PNG_Tests)
+}
 
-	}
+@test
+png_test_interlaced :: proc(t: ^testing.T) {
+	run_png_suite(t, Interlaced_PNG_Tests)
+}
 
-	for suite in PNG_Suites {
-		total_tests += run_png_suite(t, suite)
-	}
+@test
+png_test_odd_sized :: proc(t: ^testing.T) {
+	run_png_suite(t, Odd_Sized_PNG_Tests)
+}
+
+@test
+png_test_bKGD :: proc(t: ^testing.T) {
+	run_png_suite(t, PNG_bKGD_Tests)
+}
+
+@test
+png_test_tRNS :: proc(t: ^testing.T) {
+	run_png_suite(t, PNG_tRNS_Tests)
+}
+
+@test
+png_test_sPAL :: proc(t: ^testing.T) {
+	run_png_suite(t, PNG_sPAL_Tests)
+}
+
+@test
+png_test_filter :: proc(t: ^testing.T) {
+	run_png_suite(t, PNG_Filter_Tests)
+}
 
-	error  := fmt.tprintf("Expected %v PNG tests, %v ran.", total_expected, total_tests)
-	expect(t, total_tests == total_expected, error)
+@test
+png_test_varied_idat :: proc(t: ^testing.T) {
+	run_png_suite(t, PNG_Varied_IDAT_Tests)
 }
 
-run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) {
+@test
+png_test_zlib_levels :: proc(t: ^testing.T) {
+	run_png_suite(t, PNG_ZLIB_Levels_Tests)
+}
 
-	context = runtime.default_context()
+@test
+png_test_ancillary :: proc(t: ^testing.T) {
+	run_png_suite(t, PNG_Ancillary_Tests)
+}
 
+@test
+png_test_corrupt :: proc(t: ^testing.T) {
+	run_png_suite(t, Corrupt_PNG_Tests)
+}
+
+@test
+png_test_no_postproc :: proc(t: ^testing.T) {
+	run_png_suite(t, No_Postprocesing_Tests)
+}
+
+run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) {
 	for file in suite {
-		test_file := strings.concatenate({TEST_SUITE_PATH, "/", file.file, ".png"}, context.temp_allocator)
+		test_file := strings.concatenate({TEST_SUITE_PATH, file.file, ".png"})
+		defer delete(test_file)
 
 		img: ^png.Image
 		err: png.Error
 
 		count := 0
 		for test in file.tests {
-			count        += 1
-			subtotal     += 1
-			passed       := false
-
-			track: mem.Tracking_Allocator
-			mem.tracking_allocator_init(&track, context.allocator)
-			context.allocator = mem.tracking_allocator(&track)
+			count += 1
 
 			img, err = png.load(test_file, test.options)
 
-			error  := fmt.tprintf("%v failed with %v.", test_file, err)
-
-			passed = (test.expected_error == nil && err == nil) || (test.expected_error == err)
-
-			expect(t, passed, error)
+			passed := (test.expected_error == nil && err == nil) || (test.expected_error == err)
+			testing.expectf(t, passed, "%v failed with %v.", test_file, err)
 
 			if err == nil { // No point in running the other tests if it didn't load.
 				pixels := bytes.buffer_to_bytes(&img.pixels)
 
 				// This struct compare fails at -opt:2 if PNG_Dims is not #packed.
-
-				dims   := PNG_Dims{img.width, img.height, img.channels, img.depth}
-				error  = fmt.tprintf("%v has %v, expected: %v.", file.file, dims, test.dims)
-
+				dims := PNG_Dims{img.width, img.height, img.channels, img.depth}
 				dims_pass := test.dims == dims
 
-				expect(t, dims_pass, error)
-
+				testing.expectf(t, dims_pass, "%v has %v, expected: %v.", file.file, dims, test.dims)
 				passed &= dims_pass
 
-				png_hash   := hash.crc32(pixels)
-				error  = fmt.tprintf("%v test %v hash is %08x, expected %08x with %v.", file.file, count, png_hash, test.hash, test.options)
-				expect(t, test.hash == png_hash, error)
+				png_hash := hash.crc32(pixels)
+				testing.expectf(t, test.hash == png_hash, "%v test %v hash is %08x, expected %08x with %v.", file.file, count, png_hash, test.hash, test.options)
 
 				passed &= test.hash == png_hash
 
@@ -1515,19 +1492,16 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) {
 						defer bytes.buffer_destroy(&qoi_buffer)
 						qoi_save_err := qoi.save(&qoi_buffer, img)
 
-						error  = fmt.tprintf("%v test %v QOI save failed with %v.", file.file, count, qoi_save_err)
-						expect(t, qoi_save_err == nil, error)
+						testing.expectf(t, qoi_save_err == nil, "%v test %v QOI save failed with %v.", file.file, count, qoi_save_err)
 
 						if qoi_save_err == nil {
 							qoi_img, qoi_load_err := qoi.load(qoi_buffer.buf[:])
 							defer qoi.destroy(qoi_img)
 
-							error  = fmt.tprintf("%v test %v QOI load failed with %v.", file.file, count, qoi_load_err)
-							expect(t, qoi_load_err == nil, error)
+							testing.expectf(t, qoi_load_err == nil, "%v test %v QOI load failed with %v.", file.file, count, qoi_load_err)
 
 							qoi_hash := hash.crc32(qoi_img.pixels.buf[:])
-							error  = fmt.tprintf("%v test %v QOI load hash is %08x, expected it match PNG's %08x with %v.", file.file, count, qoi_hash, png_hash, test.options)
-							expect(t, qoi_hash == png_hash, error)
+							testing.expectf(t, qoi_hash == png_hash, "%v test %v QOI load hash is %08x, expected it match PNG's %08x with %v.", file.file, count, qoi_hash, png_hash, test.options)
 						}
 					}
 
@@ -1537,19 +1511,15 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) {
 						defer bytes.buffer_destroy(&tga_buffer)
 						tga_save_err := tga.save(&tga_buffer, img)
 
-						error  = fmt.tprintf("%v test %v TGA save failed with %v.", file.file, count, tga_save_err)
-						expect(t, tga_save_err == nil, error)
-
+						testing.expectf(t, tga_save_err == nil, "%v test %v TGA save failed with %v.", file.file, count, tga_save_err)
 						if tga_save_err == nil {
 							tga_img, tga_load_err := tga.load(tga_buffer.buf[:])
 							defer tga.destroy(tga_img)
 
-							error  = fmt.tprintf("%v test %v TGA load failed with %v.", file.file, count, tga_load_err)
-							expect(t, tga_load_err == nil, error)
+							testing.expectf(t, tga_load_err == nil, "%v test %v TGA load failed with %v.", file.file, count, tga_load_err)
 
 							tga_hash := hash.crc32(tga_img.pixels.buf[:])
-							error  = fmt.tprintf("%v test %v TGA load hash is %08x, expected it match PNG's %08x with %v.", file.file, count, tga_hash, png_hash, test.options)
-							expect(t, tga_hash == png_hash, error)
+							testing.expectf(t, tga_hash == png_hash, "%v test %v TGA load hash is %08x, expected it match PNG's %08x with %v.", file.file, count, tga_hash, png_hash, test.options)
 						}
 					}
 
@@ -1558,22 +1528,18 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) {
 						pbm_buf, pbm_save_err := pbm.save_to_buffer(img)
 						defer delete(pbm_buf)
 
-						error = fmt.tprintf("%v test %v PBM save failed with %v.", file.file, count, pbm_save_err)
-						expect(t, pbm_save_err == nil, error)
+						testing.expectf(t, pbm_save_err == nil, "%v test %v PBM save failed with %v.", file.file, count, pbm_save_err)
 
 						if pbm_save_err == nil {
 							// Try to load it again.
 							pbm_img, pbm_load_err := pbm.load(pbm_buf)
 							defer pbm.destroy(pbm_img)
 
-							error  = fmt.tprintf("%v test %v PBM load failed with %v.", file.file, count, pbm_load_err)
-							expect(t, pbm_load_err == nil, error)
+							testing.expectf(t, pbm_load_err == nil, "%v test %v PBM load failed with %v.", file.file, count, pbm_load_err)
 
 							if pbm_load_err == nil {
 								pbm_hash := hash.crc32(pbm_img.pixels.buf[:])
-
-								error  = fmt.tprintf("%v test %v PBM load hash is %08x, expected it match PNG's %08x with %v.", file.file, count, pbm_hash, png_hash, test.options)
-								expect(t, pbm_hash == png_hash, error)
+								testing.expectf(t, pbm_hash == png_hash, "%v test %v PBM load hash is %08x, expected it match PNG's %08x with %v.", file.file, count, pbm_hash, png_hash, test.options)
 							}
 						}
 					}
@@ -1587,22 +1553,18 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) {
 							pbm_buf, pbm_save_err := pbm.save_to_buffer(img, pbm_info)
 							defer delete(pbm_buf)
 
-							error = fmt.tprintf("%v test %v PBM save failed with %v.", file.file, count, pbm_save_err)
-							expect(t, pbm_save_err == nil, error)
+							testing.expectf(t, pbm_save_err == nil, "%v test %v PBM save failed with %v.", file.file, count, pbm_save_err)
 
 							if pbm_save_err == nil {
 								// Try to load it again.
 								pbm_img, pbm_load_err := pbm.load(pbm_buf)
 								defer pbm.destroy(pbm_img)
 
-								error  = fmt.tprintf("%v test %v PBM load failed with %v.", file.file, count, pbm_load_err)
-								expect(t, pbm_load_err == nil, error)
+								testing.expectf(t, pbm_load_err == nil, "%v test %v PBM load failed with %v.", file.file, count, pbm_load_err)
 
 								if pbm_load_err == nil {
 									pbm_hash := hash.crc32(pbm_img.pixels.buf[:])
-
-									error  = fmt.tprintf("%v test %v PBM load hash is %08x, expected it match PNG's %08x with %v.", file.file, count, pbm_hash, png_hash, test.options)
-									expect(t, pbm_hash == png_hash, error)
+									testing.expectf(t, pbm_hash == png_hash, "%v test %v PBM load hash is %08x, expected it match PNG's %08x with %v.", file.file, count, pbm_hash, png_hash, test.options)
 								}
 							}
 						}
@@ -1655,21 +1617,18 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) {
 							float_pbm_buf, float_pbm_save_err := pbm.save_to_buffer(float_img, pbm_info)
 							defer delete(float_pbm_buf)
 
-							error = fmt.tprintf("%v test %v save as PFM failed with %v", file.file, count, float_pbm_save_err)
-							expect(t, float_pbm_save_err == nil, error)
+							testing.expectf(t, float_pbm_save_err == nil, "%v test %v save as PFM failed with %v", file.file, count, float_pbm_save_err)
 
 							if float_pbm_save_err == nil {
 								// Load float image and compare.
 								float_pbm_img, float_pbm_load_err := pbm.load(float_pbm_buf)
 								defer pbm.destroy(float_pbm_img)
 
-								error = fmt.tprintf("%v test %v PFM load failed with %v", file.file, count, float_pbm_load_err)
-								expect(t, float_pbm_load_err == nil, error)
+								testing.expectf(t, float_pbm_load_err == nil, "%v test %v PFM load failed with %v", file.file, count, float_pbm_load_err)
 
 								load_float := mem.slice_data_cast([]f32, float_pbm_img.pixels.buf[:])
 
-								error = fmt.tprintf("%v test %v PFM load returned %v floats, expected %v", file.file, count, len(load_float), len(orig_float))
-								expect(t, len(load_float) == len(orig_float), error)
+								testing.expectf(t, len(load_float) == len(orig_float), "%v test %v PFM load returned %v floats, expected %v", file.file, count, len(load_float), len(orig_float))
 
 								// Compare floats
 								equal := true
@@ -1679,15 +1638,13 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) {
 										break
 									}
 								}
-								error = fmt.tprintf("%v test %v PFM loaded floats to match", file.file, count)
-								expect(t, equal, error)
+								testing.expectf(t, equal, "%v test %v PFM loaded floats to match", file.file, count)
 							}
 						}
 					}
 				}
 
 				if .return_metadata in test.options {
-
 					if v, ok := img.metadata.(^image.PNG_Info); ok {
 						for c in v.chunks {
 							#partial switch(c.header.type) {
@@ -1696,8 +1653,7 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) {
 								case "pp0n2c16", "pp0n6a08":
 									gamma, gamma_ok := png.gamma(c)
 									expected_gamma := f32(1.0)
-									error  = fmt.tprintf("%v test %v gAMA is %v, expected %v.", file.file, count, gamma, expected_gamma)
-									expect(t, gamma == expected_gamma && gamma_ok, error)
+									testing.expectf(t, gamma == expected_gamma && gamma_ok, "%v test %v gAMA is %v, expected %v.", file.file, count, gamma, expected_gamma)
 								}
 							case .PLTE:
 								switch(file.file) {
@@ -1705,8 +1661,7 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) {
 									plte, plte_ok := png.plte(c)
 
 									expected_plte_len := u16(216)
-									error  = fmt.tprintf("%v test %v PLTE length is %v, expected %v.", file.file, count, plte.used, expected_plte_len)
-									expect(t, expected_plte_len == plte.used && plte_ok, error)
+									testing.expectf(t, expected_plte_len == plte.used && plte_ok, "%v test %v PLTE length is %v, expected %v.", file.file, count, plte.used, expected_plte_len)
 								}
 							case .sPLT:
 								switch(file.file) {
@@ -1714,12 +1669,10 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) {
 									splt, splt_ok := png.splt(c)
 
 									expected_splt_len  := u16(216)
-									error  = fmt.tprintf("%v test %v sPLT length is %v, expected %v.", file.file, count, splt.used, expected_splt_len)
-									expect(t, expected_splt_len == splt.used && splt_ok, error)
+									testing.expectf(t, expected_splt_len == splt.used && splt_ok, "%v test %v sPLT length is %v, expected %v.", file.file, count, splt.used, expected_splt_len)
 
 									expected_splt_name := "six-cube"
-									error  = fmt.tprintf("%v test %v sPLT name is %v, expected %v.", file.file, count, splt.name, expected_splt_name)
-									expect(t, expected_splt_name == splt.name && splt_ok, error)
+									testing.expectf(t, expected_splt_name == splt.name && splt_ok, "%v test %v sPLT name is %v, expected %v.", file.file, count, splt.name, expected_splt_name)
 
 									png.splt_destroy(splt)
 								}
@@ -1733,48 +1686,37 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) {
 										g = png.CIE_1931{x = 0.3000, y = 0.6000},
 										b = png.CIE_1931{x = 0.1500, y = 0.0600},
 									}
-									error  = fmt.tprintf("%v test %v cHRM is %v, expected %v.", file.file, count, chrm, expected_chrm)
-									expect(t, expected_chrm == chrm && chrm_ok, error)
+									testing.expectf(t, expected_chrm == chrm && chrm_ok, "%v test %v cHRM is %v, expected %v.", file.file, count, chrm, expected_chrm)
 								}
 							case .pHYs:
 								phys, phys_ok := png.phys(c)
-								phys_err := "%v test %v cHRM is %v, expected %v."
 								switch (file.file) {
 								case "cdfn2c08":
 									expected_phys := png.pHYs{ppu_x =    1, ppu_y =    4, unit = .Unknown}
-									error  = fmt.tprintf(phys_err, file.file, count, phys, expected_phys)
-									expect(t, expected_phys == phys && phys_ok, error)
+									testing.expectf(t, expected_phys == phys && phys_ok, "%v test %v cHRM is %v, expected %v.", file.file, count, phys, expected_phys)
 								case "cdhn2c08":
 									expected_phys := png.pHYs{ppu_x =    4, ppu_y =    1, unit = .Unknown}
-									error  = fmt.tprintf(phys_err, file.file, count, phys, expected_phys)
-									expect(t, expected_phys == phys && phys_ok, error)
+									testing.expectf(t, expected_phys == phys && phys_ok, "%v test %v cHRM is %v, expected %v.", file.file, count, phys, expected_phys)
 								case "cdsn2c08":
 									expected_phys := png.pHYs{ppu_x =    1, ppu_y =    1, unit = .Unknown}
-									error  = fmt.tprintf(phys_err, file.file, count, phys, expected_phys)
-									expect(t, expected_phys == phys && phys_ok, error)
+									testing.expectf(t, expected_phys == phys && phys_ok, "%v test %v cHRM is %v, expected %v.", file.file, count, phys, expected_phys)
 								case "cdun2c08":
 									expected_phys := png.pHYs{ppu_x = 1000, ppu_y = 1000, unit = .Meter}
-									error  = fmt.tprintf(phys_err, file.file, count, phys, expected_phys)
-									expect(t, expected_phys == phys && phys_ok, error)
+									testing.expectf(t, expected_phys == phys && phys_ok, "%v test %v cHRM is %v, expected %v.", file.file, count, phys, expected_phys)
 								}
 							case .hIST:
 								hist, hist_ok := png.hist(c)
-								hist_err := "%v test %v hIST has %v entries, expected %v."
 								switch (file.file) {
 								case "ch1n3p04":
-									error  = fmt.tprintf(hist_err, file.file, count, hist.used, 15)
-									expect(t, hist.used == 15 && hist_ok, error)
+									testing.expectf(t, hist.used == 15 && hist_ok, "%v test %v hIST has %v entries, expected %v.", file.file, count, hist.used, 15)
 								case "ch2n3p08":
-									error  = fmt.tprintf(hist_err, file.file, count, hist.used, 256)
-									expect(t, hist.used == 256 && hist_ok, error)
+									testing.expectf(t, hist.used == 256 && hist_ok, "%v test %v hIST has %v entries, expected %v.", file.file, count, hist.used, 256)
 								}
 							case .tIME:
 								png_time, png_time_ok := png.time(c)
-								time_err := "%v test %v tIME was %v, expected %v."
 								expected_time: png.tIME
 
 								core_time, core_time_ok := png.core_time(c)
-								time_core_err := "%v test %v tIME->core:time is %v, expected %v."
 								expected_core: time.Time
 
 								switch(file.file) {
@@ -1789,14 +1731,10 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) {
 									expected_core = time.Time{_nsec = 946684799000000000}
 
 								}
-								error  = fmt.tprintf(time_err, file.file, count, png_time, expected_time)
-								expect(t, png_time  == expected_time && png_time_ok,  error)
-
-								error  = fmt.tprintf(time_core_err, file.file, count, core_time, expected_core)
-								expect(t, core_time == expected_core && core_time_ok, error)
+								testing.expectf(t, png_time  == expected_time && png_time_ok,  "%v test %v tIME was %v, expected %v.", file.file, count, png_time, expected_time)
+								testing.expectf(t, core_time == expected_core && core_time_ok, "%v test %v tIME->core:time is %v, expected %v.", file.file, count, core_time, expected_core)
 							case .sBIT:
 								sbit, sbit_ok  := png.sbit(c)
-								sbit_err       := "%v test %v sBIT was %v, expected %v."
 								expected_sbit: [4]u8
 
 								switch (file.file) {
@@ -1815,8 +1753,7 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) {
 								case "cdfn2c08", "cdhn2c08", "cdsn2c08", "cdun2c08", "ch1n3p04", "basn3p04":
 									expected_sbit = [4]u8{ 4,  4,  4,  0}
 								}
-								error  = fmt.tprintf(sbit_err, file.file, count, sbit, expected_sbit)
-								expect(t, sbit == expected_sbit && sbit_ok, error)
+								testing.expectf(t, sbit == expected_sbit && sbit_ok, "%v test %v sBIT was %v, expected %v.", file.file, count, sbit, expected_sbit)
 							case .tEXt, .zTXt:
 								text, text_ok := png.text(c)
 								defer png.text_destroy(text)
@@ -1828,8 +1765,7 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) {
 									if file.file in Expected_Text {
 										if text.keyword in Expected_Text[file.file] {
 											test_text := Expected_Text[file.file][text.keyword].text
-											error  = fmt.tprintf("%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text.text, test_text)
-											expect(t, text.text == test_text && text_ok, error)
+											testing.expectf(t, text.text == test_text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text.text, test_text)
 										}
 									}
 								}
@@ -1842,74 +1778,59 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) {
 									if file.file in Expected_Text {
 										if text.keyword in Expected_Text[file.file] {
 											test := Expected_Text[file.file][text.keyword]
-											error  = fmt.tprintf("%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test)
-											expect(t, text.language == test.language && text_ok, error)
-											expect(t, text.keyword_localized == test.keyword_localized && text_ok, error)
+											testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test)
+											testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test)
 										}
 									}
 								case "ctfn0g04": // international UTF-8, finnish
 									if file.file in Expected_Text {
 										if text.keyword in Expected_Text[file.file] {
 											test := Expected_Text[file.file][text.keyword]
-											error  = fmt.tprintf("%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test)
-											expect(t, text.text == test.text && text_ok, error)
-											expect(t, text.language == test.language && text_ok, error)
-											expect(t, text.keyword_localized == test.keyword_localized && text_ok, error)
+											testing.expectf(t, text.text == test.text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test)
+											testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test)
+											testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test)
 										}
 									}
 								case "ctgn0g04": // international UTF-8, greek
 									if file.file in Expected_Text {
 										if text.keyword in Expected_Text[file.file] {
 											test := Expected_Text[file.file][text.keyword]
-											error  = fmt.tprintf("%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test)
-											expect(t, text.text == test.text && text_ok, error)
-											expect(t, text.language == test.language && text_ok, error)
-											expect(t, text.keyword_localized == test.keyword_localized && text_ok, error)
+											testing.expectf(t, text.text == test.text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test)
+											testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test)
+											testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test)
 										}
 									}
 								case "cthn0g04": // international UTF-8, hindi
 									if file.file in Expected_Text {
 										if text.keyword in Expected_Text[file.file] {
 											test := Expected_Text[file.file][text.keyword]
-											error  = fmt.tprintf("%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test)
-											expect(t, text.text == test.text && text_ok, error)
-											expect(t, text.language == test.language && text_ok, error)
-											expect(t, text.keyword_localized == test.keyword_localized && text_ok, error)
+											testing.expectf(t, text.text == test.text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test)
+											testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test)
+											testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test)
 										}
 									}
 								case "ctjn0g04": // international UTF-8, japanese
 									if file.file in Expected_Text {
 										if text.keyword in Expected_Text[file.file] {
 											test := Expected_Text[file.file][text.keyword]
-											error  = fmt.tprintf("%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test)
-											expect(t, text.text == test.text && text_ok, error)
-											expect(t, text.language == test.language && text_ok, error)
-											expect(t, text.keyword_localized == test.keyword_localized && text_ok, error)
+											testing.expectf(t, text.text == test.text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test)
+											testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test)
+											testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test)
 										}
 									}
 								}
 							case .eXIf:
 								if file.file == "exif2c08" { // chunk with jpeg exif data
 									exif, exif_ok := png.exif(c)
-									error      = fmt.tprintf("%v test %v eXIf byte order '%v', expected 'big_endian'.", file.file, count, exif.byte_order)
-									error_len := fmt.tprintf("%v test %v eXIf data length '%v', expected '%v'.", file.file, len(exif.data), 978)
-									expect(t, exif.byte_order == .big_endian && exif_ok, error)
-									expect(t, len(exif.data)  == 978         && exif_ok, error_len)
+									testing.expectf(t, exif.byte_order == .big_endian && exif_ok, "%v test %v eXIf byte order '%v', expected 'big_endian'.", file.file, count, exif.byte_order)
+									testing.expectf(t, len(exif.data)  == 978         && exif_ok, "%v test %v eXIf data length '%v', expected '%v'.", file.file, len(exif.data), 978)
 								}
 							}
 						}
 					}
 				}
 			}
-
 			png.destroy(img)
-
-			for _, v in track.allocation_map {
-				error = fmt.tprintf("%v test %v leaked %v bytes @ loc %v.", file.file, count, v.size, v.location)
-				expect(t, false, error)
-			}
 		}
 	}
-
-	return
-}
+}

+ 1 - 1
tests/core/math/big/build.bat

@@ -5,7 +5,7 @@ set TEST_ARGS=-fast-tests
 set TEST_ARGS=-no-random
 set TEST_ARGS=
 set OUT_NAME=math_big_test_library.dll
-set COMMON=-build-mode:shared -show-timings -no-bounds-check -define:MATH_BIG_EXE=false -vet -strict-style
+set COMMON=-build-mode:shared -show-timings -no-bounds-check -define:MATH_BIG_EXE=false -vet -strict-style -define:ODIN_TEST_FANCY=false
 echo ---
 echo Running core:math/big tests
 echo ---

+ 3 - 17
tests/core/math/linalg/glsl/test_linalg_glsl_math.odin

@@ -1,24 +1,10 @@
 // Tests "linalg_glsl_math.odin" in "core:math/linalg/glsl".
-// Must be run with `-collection:tests=` flag, e.g.
-// ./odin run tests/core/math/linalg/glsl/test_linalg_glsl_math.odin -collection:tests=./tests
 package test_core_math_linalg_glsl_math
 
 import glsl "core:math/linalg/glsl"
 
-import "core:fmt"
 import "core:math"
 import "core:testing"
-import tc "tests:common"
-
-main :: proc() {
-
-    t := testing.T{}
-
-	test_fract_f32(&t)
-	test_fract_f64(&t)
-
-	tc.report(&t)
-}
 
 @test
 test_fract_f32 :: proc(t: ^testing.T) {
@@ -45,7 +31,7 @@ test_fract_f32 :: proc(t: ^testing.T) {
 	for d, i in data {
 		assert(i == d.i)
 		r = glsl.fract(d.v)
-		tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%v (%h)) -> %v (%h) != %v", i, #procedure, d.v, d.v, r, r, d.e))
+		testing.expectf(t, r == d.e, "%v (%h) -> %v (%h) != %v", d.v, d.v, r, r, d.e)
 	}
 }
 
@@ -74,6 +60,6 @@ test_fract_f64 :: proc(t: ^testing.T) {
 	for d, i in data {
 		assert(i == d.i)
 		r = glsl.fract(d.v)
-		tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%v (%h)) -> %v (%h) != %v", i, #procedure, d.v, d.v, r, r, d.e))
+		testing.expectf(t, r == d.e, "%v (%h) -> %v (%h) != %v", d.v, d.v, r, r, d.e)
 	}
-}
+}

+ 74 - 121
tests/core/math/noise/test_core_math_noise.odin

@@ -2,42 +2,6 @@ package test_core_math_noise
 
 import "core:testing"
 import "core:math/noise"
-import "core:fmt"
-import "core:os"
-
-TEST_count := 0
-TEST_fail  := 0
-
-V2 :: noise.Vec2
-V3 :: noise.Vec3
-V4 :: noise.Vec4
-
-when ODIN_TEST {
-	expect  :: testing.expect
-	log     :: testing.log
-} else {
-	expect  :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
-		TEST_count += 1
-		if !condition {
-			TEST_fail += 1
-			fmt.printf("[%v] %v\n", loc, message)
-			return
-		}
-	}
-	log     :: proc(t: ^testing.T, v: any, loc := #caller_location) {
-		fmt.printf("[%v] ", loc)
-		fmt.printf("log: %v\n", v)
-	}
-}
-
-main :: proc() {
-	t := testing.T{}
-	noise_test(&t)
-	fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
-	if TEST_fail > 0 {
-		os.exit(1)
-	}
-}
 
 Test_Vector :: struct {
 	seed:      i64,
@@ -51,6 +15,10 @@ Test_Vector :: struct {
 	},
 }
 
+V2 :: noise.Vec2
+V3 :: noise.Vec3
+V4 :: noise.Vec4
+
 SEED_1 :: 2324223232
 SEED_2 :: 932466901
 SEED_3 :: 9321
@@ -59,93 +27,78 @@ COORD_1 :: V4{  242.0,  3433.0,      920.0,    222312.0}
 COORD_2 :: V4{  590.0,  9411.0,     5201.0, 942124256.0}
 COORD_3 :: V4{12090.0, 19411.0, 81950901.0,   4224219.0}
 
-Noise_Tests := []Test_Vector{
-	/*
-		`noise_2d` tests.
-	*/
-	{SEED_1, COORD_1.xy,   0.25010583,  noise.noise_2d},
-	{SEED_2, COORD_2.xy,  -0.92513955,  noise.noise_2d},
-	{SEED_3, COORD_3.xy,   0.67327416,  noise.noise_2d},
-
-	/*
-		`noise_2d_improve_x` tests.
-	*/
-	{SEED_1, COORD_1.xy,   0.17074019,  noise.noise_2d_improve_x},
-	{SEED_2, COORD_2.xy,   0.72330487,  noise.noise_2d_improve_x},
-	{SEED_3, COORD_3.xy,  -0.032076947, noise.noise_2d_improve_x},
-
-	/*
-		`noise_3d_improve_xy` tests.
-	*/
-	{SEED_1, COORD_1.xyz,  0.14819577,  noise.noise_3d_improve_xy},
-	{SEED_2, COORD_2.xyz, -0.065345764, noise.noise_3d_improve_xy},
-	{SEED_3, COORD_3.xyz, -0.37761918,  noise.noise_3d_improve_xy},
-
-	/*
-		`noise_3d_improve_xz` tests.
-	*/
-	{SEED_1, COORD_1.xyz, -0.50075006,  noise.noise_3d_improve_xz},
-	{SEED_2, COORD_2.xyz, -0.36039603,  noise.noise_3d_improve_xz},
-	{SEED_3, COORD_3.xyz, -0.3479203,   noise.noise_3d_improve_xz},
-
-	/*
-		`noise_3d_fallback` tests.
-	*/
-	{SEED_1, COORD_1.xyz,  0.6557345,   noise.noise_3d_fallback},
-	{SEED_2, COORD_2.xyz,  0.55452216,  noise.noise_3d_fallback},
-	{SEED_3, COORD_3.xyz, -0.26408964,  noise.noise_3d_fallback},
-
-	/*
-		`noise_3d_fallback` tests.
-	*/
-	{SEED_1, COORD_1.xyz,  0.6557345,   noise.noise_3d_fallback},
-	{SEED_2, COORD_2.xyz,  0.55452216,  noise.noise_3d_fallback},
-	{SEED_3, COORD_3.xyz, -0.26408964,  noise.noise_3d_fallback},
-
-	/*
-		`noise_4d_improve_xyz_improve_xy` tests.
-	*/
-	{SEED_1, COORD_1,      0.44929826,  noise.noise_4d_improve_xyz_improve_xy},
-	{SEED_2, COORD_2,     -0.13270882,  noise.noise_4d_improve_xyz_improve_xy},
-	{SEED_3, COORD_3,      0.10298563,  noise.noise_4d_improve_xyz_improve_xy},
-
-	/*
-		`noise_4d_improve_xyz_improve_xz` tests.
-	*/
-	{SEED_1, COORD_1,    -0.078514606,  noise.noise_4d_improve_xyz_improve_xz},
-	{SEED_2, COORD_2,    -0.032157656,  noise.noise_4d_improve_xyz_improve_xz},
-	{SEED_3, COORD_3,     -0.38607058,  noise.noise_4d_improve_xyz_improve_xz},
-
-	/*
-		`noise_4d_improve_xyz` tests.
-	*/
-	{SEED_1, COORD_1,      -0.4442258,  noise.noise_4d_improve_xyz},
-	{SEED_2, COORD_2,      0.36822623,  noise.noise_4d_improve_xyz},
-	{SEED_3, COORD_3,      0.22628775,  noise.noise_4d_improve_xyz},
-
-	/*
-		`noise_4d_fallback` tests.
-	*/
-	{SEED_1, COORD_1,     -0.14233987,  noise.noise_4d_fallback},
-	{SEED_2, COORD_2,      0.1354035,  noise.noise_4d_fallback},
-	{SEED_3, COORD_3,      0.14565045,  noise.noise_4d_fallback},
+@(test)
+test_noise_2d :: proc(t: ^testing.T) {
+	test(t, {SEED_1, COORD_1.xy,   0.25010583,  noise.noise_2d})
+	test(t, {SEED_2, COORD_2.xy,  -0.92513955,  noise.noise_2d})
+	test(t, {SEED_3, COORD_3.xy,   0.67327416,  noise.noise_2d})
+}
+
+@(test)
+test_noise_2d_improve_x :: proc(t: ^testing.T) {
+	test(t, {SEED_1, COORD_1.xy,   0.17074019,  noise.noise_2d_improve_x})
+	test(t, {SEED_2, COORD_2.xy,   0.72330487,  noise.noise_2d_improve_x})
+	test(t, {SEED_3, COORD_3.xy,  -0.032076947, noise.noise_2d_improve_x})
+}
+
+@(test)
+test_noise_3d_improve_xy :: proc(t: ^testing.T) {
+	test(t, {SEED_1, COORD_1.xyz,  0.14819577,  noise.noise_3d_improve_xy})
+	test(t, {SEED_2, COORD_2.xyz, -0.065345764, noise.noise_3d_improve_xy})
+	test(t, {SEED_3, COORD_3.xyz, -0.37761918,  noise.noise_3d_improve_xy})
+}
+
+@(test)
+test_noise_3d_improve_xz :: proc(t: ^testing.T) {
+	test(t, {SEED_1, COORD_1.xyz, -0.50075006,  noise.noise_3d_improve_xz})
+	test(t, {SEED_2, COORD_2.xyz, -0.36039603,  noise.noise_3d_improve_xz})
+	test(t, {SEED_3, COORD_3.xyz, -0.3479203,   noise.noise_3d_improve_xz})
+}
+
+@(test)
+test_noise_3d_fallback :: proc(t: ^testing.T) {
+	test(t, {SEED_1, COORD_1.xyz,  0.6557345,   noise.noise_3d_fallback})
+	test(t, {SEED_2, COORD_2.xyz,  0.55452216,  noise.noise_3d_fallback})
+	test(t, {SEED_3, COORD_3.xyz, -0.26408964,  noise.noise_3d_fallback})
+}
+
+@(test)
+test_noise_4d_improve_xyz_improve_xy :: proc(t: ^testing.T) {
+	test(t, {SEED_1, COORD_1,      0.44929826,  noise.noise_4d_improve_xyz_improve_xy})
+	test(t, {SEED_2, COORD_2,     -0.13270882,  noise.noise_4d_improve_xyz_improve_xy})
+	test(t, {SEED_3, COORD_3,      0.10298563,  noise.noise_4d_improve_xyz_improve_xy})
+}
+
+@(test)
+test_noise_4d_improve_xyz_improve_xz :: proc(t: ^testing.T) {
+	test(t, {SEED_1, COORD_1,    -0.078514606,  noise.noise_4d_improve_xyz_improve_xz})
+	test(t, {SEED_2, COORD_2,    -0.032157656,  noise.noise_4d_improve_xyz_improve_xz})
+	test(t, {SEED_3, COORD_3,     -0.38607058,  noise.noise_4d_improve_xyz_improve_xz})
+}
+
+@(test)
+test_noise_4d_improve_xyz :: proc(t: ^testing.T) {
+	test(t, {SEED_1, COORD_1,      -0.4442258,  noise.noise_4d_improve_xyz})
+	test(t, {SEED_2, COORD_2,      0.36822623,  noise.noise_4d_improve_xyz})
+	test(t, {SEED_3, COORD_3,      0.22628775,  noise.noise_4d_improve_xyz})
+}
 
+@(test)
+test_noise_4d_fallback :: proc(t: ^testing.T) {
+	test(t, {SEED_1, COORD_1,     -0.14233987,  noise.noise_4d_fallback})
+	test(t, {SEED_2, COORD_2,      0.1354035,   noise.noise_4d_fallback})
+	test(t, {SEED_3, COORD_3,      0.14565045,  noise.noise_4d_fallback})
 }
 
-noise_test :: proc(t: ^testing.T) {
-	for test in Noise_Tests {
-		output: f32
-
-		switch coord in test.coord {
-		case V2:
-			output = test.test_proc.(proc(_: i64, _: V2) -> f32)(test.seed, test.coord.(V2))
-		case V3:
-			output = test.test_proc.(proc(_: i64, _: V3) -> f32)(test.seed, test.coord.(V3))
-		case V4:
-			output = test.test_proc.(proc(_: i64, _: V4) -> f32)(test.seed, test.coord.(V4))
-		}
-	
-		error  := fmt.tprintf("Seed %v, Coord: %v, Expected: %3.8f. Got %3.8f", test.seed, test.coord, test.expected, output)
-		expect(t, test.expected == output, error)
+test :: proc(t: ^testing.T, test: Test_Vector) {
+	output: f32
+	switch coord in test.coord {
+	case V2:
+		output = test.test_proc.(proc(_: i64, _: V2) -> f32)(test.seed, test.coord.(V2))
+	case V3:
+		output = test.test_proc.(proc(_: i64, _: V3) -> f32)(test.seed, test.coord.(V3))
+	case V4:
+		output = test.test_proc.(proc(_: i64, _: V4) -> f32)(test.seed, test.coord.(V4))
 	}
+	testing.expectf(t, test.expected == output, "Seed %v, Coord: %v, Expected: %3.8f. Got %3.8f", test.seed, test.coord, test.expected, output)
 }

+ 93 - 165
tests/core/math/test_core_math.odin

@@ -1,49 +1,8 @@
 // Tests "math.odin" in "core:math".
-// Must be run with `-collection:tests=` flag, e.g.
-// ./odin run tests/core/math/test_core_math.odin -collection:tests=./tests
 package test_core_math
 
-import "core:fmt"
 import "core:math"
 import "core:testing"
-import tc "tests:common"
-
-main :: proc() {
-	t := testing.T{}
-
-	test_classify_f16(&t)
-	test_classify_f32(&t)
-	test_classify_f64(&t)
-
-	test_trunc_f16(&t)
-	test_trunc_f32(&t)
-	test_trunc_f64(&t)
-
-	test_round_f16(&t)
-	test_round_f32(&t)
-	test_round_f64(&t)
-
-	test_nan(&t)
-	test_acos(&t)
-	test_acosh(&t)
-	test_asin(&t)
-	test_asinh(&t)
-	test_atan(&t)
-	test_atanh(&t)
-	test_atan2(&t)
-	test_cos(&t)
-	test_cosh(&t)
-	test_sin(&t)
-	test_sinh(&t)
-	test_sqrt(&t)
-	test_tan(&t)
-	test_tanh(&t)
-	test_large_cos(&t)
-	test_large_sin(&t)
-	test_large_tan(&t)
-
-	tc.report(&t)
-}
 
 @test
 test_classify_f16 :: proc(t: ^testing.T) {
@@ -68,7 +27,7 @@ test_classify_f16 :: proc(t: ^testing.T) {
 	for d, i in data {
 		assert(i == d.i)
 		r = math.classify_f16(d.v)
-		tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%h) -> %v != %v", i, #procedure, d.v, r, d.e))
+		testing.expectf(t, r == d.e, "%h -> %v != %v", d.v, r, d.e)
 	}
 
 	/* Check all subnormals (exponent 0, 10-bit significand non-zero) */
@@ -76,7 +35,7 @@ test_classify_f16 :: proc(t: ^testing.T) {
 		v := transmute(f16)i
 		r = math.classify_f16(v)
 		e :: math.Float_Class.Subnormal
-		tc.expect(t, r == e, fmt.tprintf("i:%d %s(%h) -> %v != %v", i, #procedure, v, r, e))
+		testing.expectf(t, r == e, "%h -> %v != %v", v, r, e)
 	}
 }
 
@@ -103,7 +62,7 @@ test_classify_f32 :: proc(t: ^testing.T) {
 	for d, i in data {
 		assert(i == d.i)
 		r = math.classify_f32(d.v)
-		tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%h) -> %v != %v", i, #procedure, d.v, r, d.e))
+		testing.expectf(t, r == d.e, "%h -> %v != %v", d.v, r, d.e)
 	}
 }
 
@@ -130,7 +89,7 @@ test_classify_f64 :: proc(t: ^testing.T) {
 	for d, i in data {
 		assert(i == d.i)
 		r = math.classify_f64(d.v)
-		tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%h) -> %v != %v", i, #procedure, d.v, r, d.e))
+		testing.expectf(t, r == d.e, "%h -> %v != %v", d.v, r, d.e)
 	}
 }
 
@@ -175,16 +134,16 @@ test_trunc_f16 :: proc(t: ^testing.T) {
 	for d, i in data {
 		assert(i == d.i)
 		r = math.trunc_f16(d.v)
-		tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%h) -> %h != %h", i, #procedure, d.v, r, d.e))
+		testing.expectf(t, r == d.e, "%h -> %h != %h", d.v, r, d.e)
 	}
 
 	v = math.SNAN_F16
 	r = math.trunc_f16(v)
-	tc.expect(t, math.is_nan_f16(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r))
+	testing.expectf(t, math.is_nan_f16(r), "%f != NaN", v, r)
 
 	v = math.QNAN_F16
 	r = math.trunc_f16(v)
-	tc.expect(t, math.is_nan_f16(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r))
+	testing.expectf(t, math.is_nan_f16(r), "%f != NaN", v, r)
 }
 
 @test
@@ -237,16 +196,16 @@ test_trunc_f32 :: proc(t: ^testing.T) {
 	for d, i in data {
 		assert(i == d.i)
 		r = math.trunc_f32(d.v)
-		tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%h) -> %h != %h", i, #procedure, d.v, r, d.e))
+		testing.expectf(t, r == d.e, "%h -> %h != %h", d.v, r, d.e)
 	}
 
 	v = math.SNAN_F32
 	r = math.trunc_f32(v)
-	tc.expect(t, math.is_nan_f32(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r))
+	testing.expectf(t, math.is_nan_f32(r), "%f -> %f != NaN", v, r)
 
 	v = math.QNAN_F32
 	r = math.trunc_f32(v)
-	tc.expect(t, math.is_nan_f32(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r))
+	testing.expectf(t, math.is_nan_f32(r), "%f -> %f != NaN", v, r)
 }
 
 @test
@@ -299,16 +258,16 @@ test_trunc_f64 :: proc(t: ^testing.T) {
 	for d, i in data {
 		assert(i == d.i)
 		r = math.trunc_f64(d.v)
-		tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%h) -> %h != %h", i, #procedure, d.v, r, d.e))
+		testing.expectf(t, r == d.e, "%h -> %h != %h", d.v, r, d.e)
 	}
 
 	v = math.SNAN_F64
 	r = math.trunc_f64(v)
-	tc.expect(t, math.is_nan_f64(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r))
+	testing.expectf(t, math.is_nan_f64(r), "%f -> %f != NaN", v, r)
 
 	v = math.QNAN_F64
 	r = math.trunc_f64(v)
-	tc.expect(t, math.is_nan_f64(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r))
+	testing.expectf(t, math.is_nan_f64(r), "%f -> %f != NaN", v, r)
 }
 
 @test
@@ -352,16 +311,16 @@ test_round_f16 :: proc(t: ^testing.T) {
 	for d, i in data {
 		assert(i == d.i)
 		r = math.round_f16(d.v)
-		tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%h) -> %h != %h", i, #procedure, d.v, r, d.e))
+		testing.expectf(t, r == d.e, "%h -> %h != %h", d.v, r, d.e)
 	}
 
 	v = math.SNAN_F16
 	r = math.round_f16(v)
-	tc.expect(t, math.is_nan_f16(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r))
+	testing.expectf(t, math.is_nan_f16(r), "%f -> %f != NaN", v, r)
 
 	v = math.QNAN_F16
 	r = math.round_f16(v)
-	tc.expect(t, math.is_nan_f16(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r))
+	testing.expectf(t, math.is_nan_f16(r), "%f -> %f != NaN", v, r)
 }
 
 @test
@@ -414,16 +373,16 @@ test_round_f32 :: proc(t: ^testing.T) {
 	for d, i in data {
 		assert(i == d.i)
 		r = math.round_f32(d.v)
-		tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%h) -> %h != %h", i, #procedure, d.v, r, d.e))
+		testing.expectf(t, r == d.e, "%h -> %h != %h", i, d.v, r, d.e)
 	}
 
 	v = math.SNAN_F32
 	r = math.round_f32(v)
-	tc.expect(t, math.is_nan_f32(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r))
+	testing.expectf(t, math.is_nan_f32(r), "%f -> %f != NaN", v, r)
 
 	v = math.QNAN_F32
 	r = math.round_f32(v)
-	tc.expect(t, math.is_nan_f32(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r))
+	testing.expectf(t, math.is_nan_f32(r), "%f -> %f != NaN", v, r)
 }
 
 @test
@@ -476,16 +435,16 @@ test_round_f64 :: proc(t: ^testing.T) {
 	for d, i in data {
 		assert(i == d.i)
 		r = math.round_f64(d.v)
-		tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%h) -> %h != %h", i, #procedure, d.v, r, d.e))
+		testing.expectf(t, r == d.e, "%h -> %h != %h", d.v, r, d.e)
 	}
 
 	v = math.SNAN_F64
 	r = math.round_f64(v)
-	tc.expect(t, math.is_nan_f64(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r))
+	testing.expectf(t, math.is_nan_f64(r), "%f -> %f != NaN", v, r)
 
 	v = math.QNAN_F64
 	r = math.round_f64(v)
-	tc.expect(t, math.is_nan_f64(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r))
+	testing.expectf(t, math.is_nan_f64(r), "%f -> %f != NaN", v, r)
 }
 
 
@@ -1033,17 +992,17 @@ tolerance :: proc(a, b, e: f64) -> bool {
 }
 close :: proc(t: ^testing.T, a, b: f64, loc := #caller_location) -> bool {
 	ok := tolerance(a, b, 1e-9)
-	// tc.expect(t, ok, fmt.tprintf("%.15g is not close to %.15g", a, b), loc)
+	testing.expectf(t, ok, "%.15g is not close to %.15g", a, b, loc=loc)
 	return ok
 }
 veryclose :: proc(t: ^testing.T, a, b: f64, loc := #caller_location) -> bool {
 	ok := tolerance(a, b, 4e-14)
-	// tc.expect(t, ok, fmt.tprintf("%.15g is not veryclose to %.15g", a, b), loc)
+	testing.expectf(t, ok, "%.15g is not veryclose to %.15g", a, b, loc=loc)
 	return ok
 }
 soclose :: proc(t: ^testing.T, a, b, e: f64, loc := #caller_location) -> bool {
 	ok := tolerance(a, b, e)
-	// tc.expect(t, ok, fmt.tprintf("%.15g is not soclose to %.15g", a, b), loc)
+	testing.expectf(t, ok, "%.15g is not soclose to %.15g", a, b, loc=loc)
 	return ok
 }
 alike :: proc(t: ^testing.T, a, b: f64, loc := #caller_location) -> bool {
@@ -1054,34 +1013,34 @@ alike :: proc(t: ^testing.T, a, b: f64, loc := #caller_location) -> bool {
 	case a == b:
 		ok = math.signbit(a) == math.signbit(b)
 	}
-	// tc.expect(t, ok, fmt.tprintf("%.15g is not alike to %.15g", a, b), loc)
+	testing.expectf(t, ok, "%.15g is not alike to %.15g", a, b, loc=loc)
 	return ok
 }
 
 @test
-test_nan :: proc(t: ^testing.T) {
+test_nan32 :: proc(t: ^testing.T) {
+	float32 := f32(NaN)
+	equal := float32 == float32
+	testing.expectf(t, !equal, "float32(NaN) is %.15g, expected NaN", float32)
+}
+
+@test
+test_nan64 :: proc(t: ^testing.T) {
 	float64 := NaN
-	if float64 == float64 {
-		tc.errorf(t, "NaN returns %.15g, expected NaN", float64)
-	}
-	float32 := f32(float64)
-	if float32 == float32 {
-		tc.errorf(t, "float32(NaN) is %.15g, expected NaN", float32)
-	}
+	equal := float64 == float64
+	testing.expectf(t, !equal, "NaN returns %.15g, expected NaN", float64)
 }
 
 @test
 test_acos :: proc(t: ^testing.T) {
 	for _, i in vf {
 		a := vf[i] / 10
-		if f := math.acos(a); !close(t, acos[i], f) {
-			tc.errorf(t, "math.acos(%.15g) = %.15g, want %.15g", a, f, acos[i])
-		}
+		f := math.acos(a)
+		testing.expectf(t, close(t, acos[i], f), "math.acos(%.15g) = %.15g, want %.15g", a, f, acos[i])
 	}
 	for _, i in vfacos_sc {
-		if f := math.acos(vfacos_sc[i]); !alike(t, acos_sc[i], f) {
-			tc.errorf(t, "math.acos(%.15g) = %.15g, want %.15g", vfacos_sc[i], f, acos_sc[i])
-		}
+		f := math.acos(vfacos_sc[i])
+		testing.expectf(t, alike(t, acos_sc[i], f), "math.acos(%.15g) = %.15g, want %.15g", vfacos_sc[i], f, acos_sc[i])
 	}
 }
 
@@ -1089,14 +1048,12 @@ test_acos :: proc(t: ^testing.T) {
 test_acosh :: proc(t: ^testing.T) {
 	for _, i in vf {
 		a := 1 + abs(vf[i])
-		if f := math.acosh(a); !veryclose(t, acosh[i], f) {
-			tc.errorf(t, "math.acosh(%.15g) = %.15g, want %.15g", a, f, acosh[i])
-		}
+		f := math.acosh(a)
+		testing.expectf(t, veryclose(t, acosh[i], f), "math.acosh(%.15g) = %.15g, want %.15g", a, f, acosh[i])
 	}
 	for _, i in vfacosh_sc {
-		if f := math.acosh(vfacosh_sc[i]); !alike(t, acosh_sc[i], f) {
-			tc.errorf(t, "math.acosh(%.15g) = %.15g, want %.15g", vfacosh_sc[i], f, acosh_sc[i])
-		}
+		f := math.acosh(vfacosh_sc[i])
+		testing.expectf(t, alike(t, acosh_sc[i], f), "math.acosh(%.15g) = %.15g, want %.15g", vfacosh_sc[i], f, acosh_sc[i])
 	}
 }
 
@@ -1104,42 +1061,36 @@ test_acosh :: proc(t: ^testing.T) {
 test_asin :: proc(t: ^testing.T) {
 	for _, i in vf {
 		a := vf[i] / 10
-		if f := math.asin(a); !veryclose(t, asin[i], f) {
-			tc.errorf(t, "math.asin(%.15g) = %.15g, want %.15g", a, f, asin[i])
-		}
+		f := math.asin(a)
+		testing.expectf(t, veryclose(t, asin[i], f), "math.asin(%.15g) = %.15g, want %.15g", a, f, asin[i])
 	}
 	for _, i in vfasin_sc {
-		if f := math.asin(vfasin_sc[i]); !alike(t, asin_sc[i], f) {
-			tc.errorf(t, "math.asin(%.15g) = %.15g, want %.15g", vfasin_sc[i], f, asin_sc[i])
-		}
+		f := math.asin(vfasin_sc[i])
+		testing.expectf(t, alike(t, asin_sc[i], f), "math.asin(%.15g) = %.15g, want %.15g", vfasin_sc[i], f, asin_sc[i])
 	}
 }
 
 @test
 test_asinh :: proc(t: ^testing.T) {
 	for _, i in vf {
-		if f := math.asinh(vf[i]); !veryclose(t, asinh[i], f) {
-			tc.errorf(t, "math.asinh(%.15g) = %.15g, want %.15g", vf[i], f, asinh[i])
-		}
+		f := math.asinh(vf[i])
+		testing.expectf(t, veryclose(t, asinh[i], f), "math.asinh(%.15g) = %.15g, want %.15g", vf[i], f, asinh[i])
 	}
 	for _, i in vfasinh_sc {
-		if f := math.asinh(vfasinh_sc[i]); !alike(t, asinh_sc[i], f) {
-			tc.errorf(t, "math.asinh(%.15g) = %.15g, want %.15g", vfasinh_sc[i], f, asinh_sc[i])
-		}
+		f := math.asinh(vfasinh_sc[i])
+		testing.expectf(t, alike(t, asinh_sc[i], f), "math.asinh(%.15g) = %.15g, want %.15g", vfasinh_sc[i], f, asinh_sc[i])
 	}
 }
 
 @test
 test_atan :: proc(t: ^testing.T) {
 	for _, i in vf {
-		if f := math.atan(vf[i]); !veryclose(t, atan[i], f) {
-			tc.errorf(t, "math.atan(%.15g) = %.15g, want %.15g", vf[i], f, atan[i])
-		}
+		f := math.atan(vf[i])
+		testing.expectf(t, veryclose(t, atan[i], f), "math.atan(%.15g) = %.15g, want %.15g", vf[i], f, atan[i])
 	}
 	for _, i in vfatan_sc {
-		if f := math.atan(vfatan_sc[i]); !alike(t, atan_sc[i], f) {
-			tc.errorf(t, "math.atan(%.15g) = %.15g, want %.15g", vfatan_sc[i], f, atan_sc[i])
-		}
+		f := math.atan(vfatan_sc[i])
+		testing.expectf(t, alike(t, atan_sc[i], f), "math.atan(%.15g) = %.15g, want %.15g", vfatan_sc[i], f, atan_sc[i])
 	}
 }
 
@@ -1147,84 +1098,72 @@ test_atan :: proc(t: ^testing.T) {
 test_atanh :: proc(t: ^testing.T) {
 	for _, i in vf {
 		a := vf[i] / 10
-		if f := math.atanh(a); !veryclose(t, atanh[i], f) {
-			tc.errorf(t, "math.atanh(%.15g) = %.15g, want %.15g", a, f, atanh[i])
-		}
+		f := math.atanh(a)
+		testing.expectf(t, veryclose(t, atanh[i], f), "math.atanh(%.15g) = %.15g, want %.15g", a, f, atanh[i])
 	}
 	for _, i in vfatanh_sc {
-		if f := math.atanh(vfatanh_sc[i]); !alike(t, atanh_sc[i], f) {
-			tc.errorf(t, "math.atanh(%.15g) = %.15g, want %.15g", vfatanh_sc[i], f, atanh_sc[i])
-		}
+		f := math.atanh(vfatanh_sc[i])
+		testing.expectf(t, alike(t, atanh_sc[i], f), "math.atanh(%.15g) = %.15g, want %.15g", vfatanh_sc[i], f, atanh_sc[i])
 	}
 }
 
 @test
 test_atan2 :: proc(t: ^testing.T) {
 	for _, i in vf {
-		if f := math.atan2(10, vf[i]); !veryclose(t, atan2[i], f) {
-			tc.errorf(t, "math.atan2(10, %.15g) = %.15g, want %.15g", vf[i], f, atan2[i])
-		}
+		f := math.atan2(10, vf[i])
+		testing.expectf(t, veryclose(t, atan2[i], f), "math.atan2(10, %.15g) = %.15g, want %.15g", vf[i], f, atan2[i])
 	}
 	for _, i in vfatan2_sc {
-		if f := math.atan2(vfatan2_sc[i][0], vfatan2_sc[i][1]); !alike(t, atan2_sc[i], f) {
-			tc.errorf(t, "math.atan2(%.15g, %.15g) = %.15g, want %.15g", vfatan2_sc[i][0], vfatan2_sc[i][1], f, atan2_sc[i])
-		}
+		f := math.atan2(vfatan2_sc[i][0], vfatan2_sc[i][1])
+		testing.expectf(t, alike(t, atan2_sc[i], f), "math.atan2(%.15g, %.15g) = %.15g, want %.15g", vfatan2_sc[i][0], vfatan2_sc[i][1], f, atan2_sc[i])
 	}
 }
 
 @test
 test_cos :: proc(t: ^testing.T) {
 	for _, i in vf {
-		if f := math.cos(vf[i]); !veryclose(t, cos[i], f) {
-			tc.errorf(t, "math.cos(%.15g) = %.15g, want %.15g", vf[i], f, cos[i])
-		}
+		f := math.cos(vf[i])
+		testing.expectf(t, veryclose(t, cos[i], f), "math.cos(%.15g) = %.15g, want %.15g", vf[i], f, cos[i])
 	}
 	for _, i in vfcos_sc {
-		if f := math.cos(vfcos_sc[i]); !alike(t, cos_sc[i], f) {
-			tc.errorf(t, "math.cos(%.15g) = %.15g, want %.15g", vfcos_sc[i], f, cos_sc[i])
-		}
+		f := math.cos(vfcos_sc[i])
+		testing.expectf(t, alike(t, cos_sc[i], f), "math.cos(%.15g) = %.15g, want %.15g", vfcos_sc[i], f, cos_sc[i])
 	}
 }
 
 @test
 test_cosh :: proc(t: ^testing.T) {
 	for _, i in vf {
-		if f := math.cosh(vf[i]); !close(t, cosh[i], f) {
-			tc.errorf(t, "math.cosh(%.15g) = %.15g, want %.15g", vf[i], f, cosh[i])
-		}
+		f := math.cosh(vf[i])
+		testing.expectf(t, close(t, cosh[i], f), "math.cosh(%.15g) = %.15g, want %.15g", vf[i], f, cosh[i])
 	}
 	for _, i in vfcosh_sc {
-		if f := math.cosh(vfcosh_sc[i]); !alike(t, cosh_sc[i], f) {
-			tc.errorf(t, "math.cosh(%.15g) = %.15g, want %.15g", vfcosh_sc[i], f, cosh_sc[i])
-		}
+		f := math.cosh(vfcosh_sc[i])
+		testing.expectf(t, alike(t, cosh_sc[i], f), "math.cosh(%.15g) = %.15g, want %.15g", vfcosh_sc[i], f, cosh_sc[i])
 	}
 }
 
 @test
 test_sin :: proc(t: ^testing.T) {
 	for _, i in vf {
-		if f := math.sin(vf[i]); !veryclose(t, sin[i], f) {
-			tc.errorf(t, "math.sin(%.15g) = %.15g, want %.15g", vf[i], f, sin[i])
-		}
+		f := math.sin(vf[i])
+		testing.expectf(t, veryclose(t, sin[i], f), "math.sin(%.15g) = %.15g, want %.15g", vf[i], f, sin[i])
 	}
 	for _, i in vfsin_sc {
-		if f := math.sin(vfsin_sc[i]); !alike(t, sin_sc[i], f) {
-			tc.errorf(t, "math.sin(%.15g) = %.15g, want %.15g", vfsin_sc[i], f, sin_sc[i])
-		}
+		f := math.sin(vfsin_sc[i])
+		testing.expectf(t, alike(t, sin_sc[i], f), "math.sin(%.15g) = %.15g, want %.15g", vfsin_sc[i], f, sin_sc[i])
 	}
 }
 
 @test
 test_sinh :: proc(t: ^testing.T) {
 	for _, i in vf {
-		if f := math.sinh(vf[i]); !close(t, sinh[i], f) {
-			tc.errorf(t, "math.sinh(%.15g) = %.15g, want %.15g", vf[i], f, sinh[i])
-		}
+		f := math.sinh(vf[i])
+		testing.expectf(t, close(t, sinh[i], f), "math.sinh(%.15g) = %.15g, want %.15g", vf[i], f, sinh[i])
 	}
 	for _, i in vfsinh_sc {
-		if f := math.sinh(vfsinh_sc[i]); !alike(t, sinh_sc[i], f) {
-			tc.errorf(t, "math.sinh(%.15g) = %.15g, want %.15g", vfsinh_sc[i], f, sinh_sc[i])
-		}
+		f := math.sinh(vfsinh_sc[i])
+		testing.expectf(t, alike(t, sinh_sc[i], f), "math.sinh(%.15g) = %.15g, want %.15g", vfsinh_sc[i], f, sinh_sc[i])
 	}
 }
 
@@ -1232,38 +1171,33 @@ test_sinh :: proc(t: ^testing.T) {
 test_sqrt :: proc(t: ^testing.T) {
 	for _, i in vf {
 		a := abs(vf[i])
-		if f := math.sqrt(a); !veryclose(t, sqrt[i], f) {
-			tc.errorf(t, "math.sqrt(%.15g) = %.15g, want %.15g", a, f, sqrt[i])
-		}
+		f := math.sqrt(a)
+		testing.expectf(t, veryclose(t, sqrt[i], f), "math.sqrt(%.15g) = %.15g, want %.15g", a, f, sqrt[i])
 	}
 }
 
 @test
 test_tan :: proc(t: ^testing.T) {
 	for _, i in vf {
-		if f := math.tan(vf[i]); !veryclose(t, tan[i], f) {
-			tc.errorf(t, "math.tan(%.15g) = %.15g, want %.15g", vf[i], f, tan[i])
-		}
+		f := math.tan(vf[i])
+		testing.expectf(t, veryclose(t, tan[i], f), "math.tan(%.15g) = %.15g, want %.15g", vf[i], f, tan[i])
 	}
 	// same special cases as Sin
 	for _, i in vfsin_sc {
-		if f := math.tan(vfsin_sc[i]); !alike(t, sin_sc[i], f) {
-			tc.errorf(t, "math.tan(%.15g) = %.15g, want %.15g", vfsin_sc[i], f, sin_sc[i])
-		}
+		f := math.tan(vfsin_sc[i])
+		testing.expectf(t, alike(t, sin_sc[i], f), "math.tan(%.15g) = %.15g, want %.15g", vfsin_sc[i], f, sin_sc[i])
 	}
 }
 
 @test
 test_tanh :: proc(t: ^testing.T) {
 	for _, i in vf {
-		if f := math.tanh(vf[i]); !veryclose(t, tanh[i], f) {
-			tc.errorf(t, "math.tanh(%.15g) = %.15g, want %.15g", vf[i], f, tanh[i])
-		}
+		f := math.tanh(vf[i])
+		testing.expectf(t, veryclose(t, tanh[i], f), "math.tanh(%.15g) = %.15g, want %.15g", vf[i], f, tanh[i])
 	}
 	for _, i in vftanh_sc {
-		if f := math.tanh(vftanh_sc[i]); !alike(t, tanh_sc[i], f) {
-			tc.errorf(t, "math.tanh(%.15g) = %.15g, want %.15g", vftanh_sc[i], f, tanh_sc[i])
-		}
+		f := math.tanh(vftanh_sc[i])
+		testing.expectf(t, alike(t, tanh_sc[i], f), "math.tanh(%.15g) = %.15g, want %.15g", vftanh_sc[i], f, tanh_sc[i])
 	}
 }
 
@@ -1273,9 +1207,7 @@ test_large_cos :: proc(t: ^testing.T) {
 	for _, i in vf {
 		f1 := cosLarge[i]
 		f2 := math.cos(vf[i] + large)
-		if !close(t, f1, f2) {
-			tc.errorf(t, "math.cos(%.15g) = %.15g, want %.15g", vf[i]+large, f2, f1)
-		}
+		testing.expectf(t, close(t, f1, f2), "math.cos(%.15g) = %.15g, want %.15g", vf[i]+large, f2, f1)
 	}
 }
 
@@ -1285,9 +1217,7 @@ test_large_sin :: proc(t: ^testing.T) {
 	for _, i in vf {
 		f1 := sinLarge[i]
 		f2 := math.sin(vf[i] + large)
-		if !close(t, f1, f2) {
-			tc.errorf(t, "math.sin(%.15g) = %.15g, want %.15g", vf[i]+large, f2, f1)
-		}
+		testing.expectf(t, close(t, f1, f2), "math.sin(%.15g) = %.15g, want %.15g", vf[i]+large, f2, f1)
 	}
 }
 
@@ -1297,8 +1227,6 @@ test_large_tan :: proc(t: ^testing.T) {
 	for _, i in vf {
 		f1 := tanLarge[i]
 		f2 := math.tan(vf[i] + large)
-		if !close(t, f1, f2) {
-			tc.errorf(t, "math.tan(%.15g) = %.15g, want %.15g", vf[i]+large, f2, f1)
-		}
+		testing.expectf(t, close(t, f1, f2), "math.tan(%.15g) = %.15g, want %.15g", vf[i]+large, f2, f1)
 	}
 }

+ 83 - 193
tests/core/net/test_core_net.odin

@@ -11,71 +11,12 @@
 package test_core_net
 
 import "core:testing"
-import "core:mem"
-import "core:fmt"
 import "core:net"
 import "core:strconv"
 import "core:sync"
 import "core:time"
 import "core:thread"
-import "core:os"
-
-_, _ :: time, thread
-
-TEST_count := 0
-TEST_fail  := 0
-
-t := &testing.T{}
-
-when ODIN_TEST {
-    expect  :: testing.expect
-    log     :: testing.log
-} else {
-    expect  :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
-        TEST_count += 1
-        if !condition {
-            TEST_fail += 1
-            fmt.printf("[%v] %v\n", loc, message)
-            return
-        }
-    }
-    log     :: proc(t: ^testing.T, v: any, loc := #caller_location) {
-        fmt.printf("[%v] ", loc)
-        fmt.printf("log: %v\n", v)
-    }
-}
-
-_tracking_allocator := mem.Tracking_Allocator{}
-
-print_tracking_allocator_report :: proc() {
-	for _, leak in _tracking_allocator.allocation_map {
-		fmt.printf("%v leaked %v bytes\n", leak.location, leak.size)
-	}
-
-	for bf in _tracking_allocator.bad_free_array {
-		fmt.printf("%v allocation %p was freed badly\n", bf.location, bf.memory)
-	}
-}
-
-main :: proc() {
-	mem.tracking_allocator_init(&_tracking_allocator, context.allocator)
-	context.allocator = mem.tracking_allocator(&_tracking_allocator)
-
-	address_parsing_test(t)
-
-	tcp_tests(t)
-
-	split_url_test(t)
-	join_url_test(t)
-
-	fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
-
-	print_tracking_allocator_report()
-
-	if TEST_fail > 0 {
-		os.exit(1)
-	}
-}
+import "core:fmt"
 
 @test
 address_parsing_test :: proc(t: ^testing.T) {
@@ -89,127 +30,66 @@ address_parsing_test :: proc(t: ^testing.T) {
 		}
 
 		valid := len(vector.binstr) > 0
-
-		fmt.printf("%v %v\n", kind, vector.input)
-
-		msg := "-set a proper message-"
 		switch vector.family {
 		case .IP4, .IP4_Alt:
-			/*
-				Does `net.parse_ip4_address` think we parsed the address properly?
-			*/
+			// Does `net.parse_ip4_address` think we parsed the address properly?
 			non_decimal := vector.family == .IP4_Alt
+			any_addr    := net.parse_address(vector.input, non_decimal)
+			parsed_ok   := any_addr != nil
+			parsed: net.IP4_Address
 
-			any_addr  := net.parse_address(vector.input, non_decimal)
-			parsed_ok := any_addr != nil
-			parsed:   net.IP4_Address
-
-			/*
-				Ensure that `parse_address` doesn't parse IPv4 addresses into IPv6 addreses by mistake.
-			*/
+			// Ensure that `parse_address` doesn't parse IPv4 addresses into IPv6 addreses by mistake.
 			switch addr in any_addr {
 			case net.IP4_Address:
 				parsed = addr
 			case net.IP6_Address:
 				parsed_ok = false
-				msg = fmt.tprintf("parse_address mistook %v as IPv6 address %04x", vector.input, addr)
-				expect(t, false, msg)
+				testing.expectf(t, false, "parse_address mistook %v as IPv6 address %04x", vector.input, addr)
 			}
 
 			if !parsed_ok && valid {
-				msg = fmt.tprintf("parse_ip4_address failed to parse %v, expected %v", vector.input, binstr_to_address(vector.binstr))
+				testing.expectf(t, parsed_ok == valid, "parse_ip4_address failed to parse %v, expected %v", vector.input, binstr_to_address(t, vector.binstr))
 
 			} else if parsed_ok && !valid {
-				msg = fmt.tprintf("parse_ip4_address parsed %v into %v, expected failure", vector.input, parsed)
+				testing.expectf(t, parsed_ok == valid, "parse_ip4_address parsed %v into %v, expected failure", vector.input, parsed)
 			}
-			expect(t, parsed_ok == valid, msg)
 
 			if valid && parsed_ok {
 				actual_binary := address_to_binstr(parsed)
-				msg = fmt.tprintf("parse_ip4_address parsed %v into %v, expected %v", vector.input, actual_binary, vector.binstr)
-				expect(t, actual_binary == vector.binstr, msg)
+				testing.expectf(t, actual_binary == vector.binstr, "parse_ip4_address parsed %v into %v, expected %v", vector.input, actual_binary, vector.binstr)
 
-				/*
-					Do we turn an address back into the same string properly?
-					No point in testing the roundtrip if the first part failed.
-				*/
+				// Do we turn an address back into the same string properly? No point in testing the roundtrip if the first part failed.
 				if len(vector.output) > 0 && actual_binary == vector.binstr {
 					stringified := net.address_to_string(parsed)
-					msg = fmt.tprintf("address_to_string turned %v into %v, expected %v", parsed, stringified, vector.output)
-					expect(t, stringified == vector.output, msg)
+					testing.expectf(t, stringified == vector.output, "address_to_string turned %v into %v, expected %v", parsed, stringified, vector.output)
 				}
 			}
 
 		case .IP6:
-			/*
-				Do we parse the address properly?
-			*/
+			// Do we parse the address properly?
 			parsed, parsed_ok := net.parse_ip6_address(vector.input)
 
 			if !parsed_ok && valid {
-				msg = fmt.tprintf("parse_ip6_address failed to parse %v, expected %04x", vector.input, binstr_to_address(vector.binstr))
+				testing.expectf(t, parsed_ok == valid, "parse_ip6_address failed to parse %v, expected %04x", vector.input, binstr_to_address(t, vector.binstr))
 
 			} else if parsed_ok && !valid {
-				msg = fmt.tprintf("parse_ip6_address parsed %v into %04x, expected failure", vector.input, parsed)
+				testing.expectf(t, parsed_ok == valid, "parse_ip6_address parsed %v into %04x, expected failure", vector.input, parsed)
 			}
-			expect(t, parsed_ok == valid, msg)
 
 			if valid && parsed_ok {
 				actual_binary := address_to_binstr(parsed)
-				msg = fmt.tprintf("parse_ip6_address parsed %v into %v, expected %v", vector.input, actual_binary, vector.binstr)
-				expect(t, actual_binary == vector.binstr, msg)
+				testing.expectf(t, actual_binary == vector.binstr, "parse_ip6_address parsed %v into %v, expected %v", vector.input, actual_binary, vector.binstr)
 
-				/*
-					Do we turn an address back into the same string properly?
-					No point in testing the roundtrip if the first part failed.
-				*/
+				// Do we turn an address back into the same string properly? No point in testing the roundtrip if the first part failed.
 				if len(vector.output) > 0 && actual_binary == vector.binstr {
 					stringified := net.address_to_string(parsed)
-					msg = fmt.tprintf("address_to_string turned %v into %v, expected %v", parsed, stringified, vector.output)
-					expect(t, stringified == vector.output, msg)
+					testing.expectf(t, stringified == vector.output, "address_to_string turned %v into %v, expected %v", parsed, stringified, vector.output)
 				}
 			}
 		}
 	}
 }
 
-address_to_binstr :: proc(address: net.Address) -> (binstr: string) {
-	switch t in address {
-	case net.IP4_Address:
-		b := transmute(u32be)t
-		return fmt.tprintf("%08x", b)
-	case net.IP6_Address:
-		b := transmute(u128be)t
-		return fmt.tprintf("%32x", b)
-	case:
-		return ""
-	}
-	unreachable()
-}
-
-binstr_to_address :: proc(binstr: string) -> (address: net.Address) {
-	switch len(binstr) {
-	case 8:  // IPv4
-		a, ok := strconv.parse_u64_of_base(binstr, 16)
-		expect(t, ok, "failed to parse test case bin string")
-
-		ipv4 := u32be(a)
-		return net.IP4_Address(transmute([4]u8)ipv4)
-
-
-	case 32: // IPv6
-		a, ok := strconv.parse_u128_of_base(binstr, 16)
-		expect(t, ok, "failed to parse test case bin string")
-
-		ipv4 := u128be(a)
-		return net.IP6_Address(transmute([8]u16be)ipv4)
-
-	case 0:
-		return nil
-	}
-	panic("Invalid test case")
-}
-
 Kind :: enum {
 	IP4,     // Decimal IPv4
 	IP4_Alt, // Non-decimal address
@@ -223,10 +103,7 @@ IP_Address_Parsing_Test_Vector :: struct {
 	// Input address to try and parse.
 	input:            string,
 
-	/*
-		Hexadecimal representation of the expected numeric value of the address.
-		Zero length means input is invalid and the parser should report failure.
-	*/
+	// Hexadecimal representation of the expected numeric value of the address. Zero length means input is invalid and the parser should report failure.
 	binstr:           string,
 
 	// Expected `address_to_string` output, if a valid input and this string is non-empty.
@@ -335,38 +212,30 @@ IP_Address_Parsing_Test_Vectors :: []IP_Address_Parsing_Test_Vector{
 	{ .IP6, "c0a8",                    "", ""},
 }
 
-tcp_tests :: proc(t: ^testing.T) {
-	fmt.println("Testing two servers trying to bind to the same endpoint...")
-	two_servers_binding_same_endpoint(t)
-	fmt.println("Testing client connecting to a closed port...")
-	client_connects_to_closed_port(t)
-	fmt.println("Testing client sending server data...")
-	client_sends_server_data(t)
-}
-
-ENDPOINT := net.Endpoint{
-	net.IP4_Address{127, 0, 0, 1},
-	9999,
-}
+ENDPOINT_TWO_SERVERS  := net.Endpoint{net.IP4_Address{127, 0, 0, 1}, 9991}
+ENDPOINT_CLOSED_PORT  := net.Endpoint{net.IP4_Address{127, 0, 0, 1}, 9992}
+ENDPOINT_SERVER_SENDS := net.Endpoint{net.IP4_Address{127, 0, 0, 1}, 9993}
 
 @(test)
 two_servers_binding_same_endpoint :: proc(t: ^testing.T) {
-	skt1, err1 := net.listen_tcp(ENDPOINT)
+	skt1, err1 := net.listen_tcp(ENDPOINT_TWO_SERVERS)
 	defer net.close(skt1)
-	skt2, err2 := net.listen_tcp(ENDPOINT)
+	skt2, err2 := net.listen_tcp(ENDPOINT_TWO_SERVERS)
 	defer net.close(skt2)
 
-	expect(t, err1 == nil, "expected first server binding to endpoint to do so without error")
-	expect(t, err2 == net.Bind_Error.Address_In_Use, "expected second server to bind to an endpoint to return .Address_In_Use")
+	testing.expect(t, err1 == nil, "expected first server binding to endpoint to do so without error")
+	testing.expect(t, err2 == net.Bind_Error.Address_In_Use, "expected second server to bind to an endpoint to return .Address_In_Use")
 }
 
 @(test)
 client_connects_to_closed_port :: proc(t: ^testing.T) {
-	skt, err := net.dial_tcp(ENDPOINT)
+
+	skt, err := net.dial_tcp(ENDPOINT_CLOSED_PORT)
 	defer net.close(skt)
-	expect(t, err == net.Dial_Error.Refused, "expected dial of a closed endpoint to return .Refused")
+	testing.expect(t, err == net.Dial_Error.Refused, "expected dial of a closed endpoint to return .Refused")
 }
 
+
 @(test)
 client_sends_server_data :: proc(t: ^testing.T) {
 	CONTENT: string: "Hellope!"
@@ -390,8 +259,8 @@ client_sends_server_data :: proc(t: ^testing.T) {
 
 		defer sync.wait_group_done(r.wg)
 
-		if r.skt, r.err = net.dial_tcp(ENDPOINT); r.err != nil {
-			log(r.t, r.err)
+		if r.skt, r.err = net.dial_tcp(ENDPOINT_SERVER_SENDS); r.err != nil {
+			testing.expectf(r.t, false, "[tcp_client:dial_tcp] %v", r.err)
 			return
 		}
 
@@ -405,19 +274,17 @@ client_sends_server_data :: proc(t: ^testing.T) {
 
 		defer sync.wait_group_done(r.wg)
 
-		log(r.t, "tcp_server listen")
-		if r.skt, r.err = net.listen_tcp(ENDPOINT); r.err != nil {
+		if r.skt, r.err = net.listen_tcp(ENDPOINT_SERVER_SENDS); r.err != nil {
 			sync.wait_group_done(r.wg)
-			log(r.t, r.err)
+			testing.expectf(r.t, false, "[tcp_server:listen_tcp] %v", r.err)
 			return
 		}
 
 		sync.wait_group_done(r.wg)
 
-		log(r.t, "tcp_server accept")
 		client: net.TCP_Socket
 		if client, _, r.err = net.accept_tcp(r.skt.(net.TCP_Socket)); r.err != nil {
-			log(r.t, r.err)
+			testing.expectf(r.t, false, "[tcp_server:accept_tcp] %v", r.err)
 			return
 		}
 		defer net.close(client)
@@ -437,10 +304,7 @@ client_sends_server_data :: proc(t: ^testing.T) {
 	thread_data[0].wg = &wg
 	thread_data[0].tid = thread.create_and_start_with_data(&thread_data[0], tcp_server, context)
 	
-	log(t, "waiting for server to start listening")
 	sync.wait_group_wait(&wg)
-	log(t, "starting up client")
-
 	sync.wait_group_add(&wg, 2)
 
 	thread_data[1].t = t
@@ -454,20 +318,15 @@ client_sends_server_data :: proc(t: ^testing.T) {
 		net.close(thread_data[1].skt)
 		thread.destroy(thread_data[1].tid)
 	}
-
-	log(t, "waiting for threads to finish")
 	sync.wait_group_wait(&wg)
-	log(t, "threads finished")
 
 	okay := thread_data[0].err == nil && thread_data[1].err == nil
-	msg  := fmt.tprintf("Expected client and server to return `nil`, got %v and %v", thread_data[0].err, thread_data[1].err)
-	expect(t, okay, msg)
+	testing.expectf(t, okay, "Expected client and server to return `nil`, got %v and %v", thread_data[0].err, thread_data[1].err)
 
 	received := string(thread_data[0].data[:thread_data[0].length])
 
 	okay  = received == CONTENT
-	msg   = fmt.tprintf("Expected client to send \"{}\", got \"{}\"", CONTENT, received)
-	expect(t, okay, msg)
+	testing.expectf(t, okay, "Expected client to send \"{}\", got \"{}\"", CONTENT, received)
 }
 
 URL_Test :: struct {
@@ -559,22 +418,15 @@ split_url_test :: proc(t: ^testing.T) {
 			delete(test.queries)
 		}
 
-		msg := fmt.tprintf("Expected `net.split_url` to return %s, got %s", test.scheme, scheme)
-		expect(t, scheme == test.scheme, msg)
-		msg = fmt.tprintf("Expected `net.split_url` to return %s, got %s", test.host, host)
-		expect(t, host == test.host, msg)
-		msg = fmt.tprintf("Expected `net.split_url` to return %s, got %s", test.path, path)
-		expect(t, path == test.path, msg)
-		msg = fmt.tprintf("Expected `net.split_url` to return %d queries, got %d queries", len(test.queries), len(queries))
-		expect(t, len(queries) == len(test.queries), msg)
+		testing.expectf(t, scheme       == test.scheme,       "Expected `net.split_url` to return %s, got %s", test.scheme, scheme)
+		testing.expectf(t, host         == test.host,         "Expected `net.split_url` to return %s, got %s", test.host, host)
+		testing.expectf(t, path         == test.path,         "Expected `net.split_url` to return %s, got %s", test.path, path)
+		testing.expectf(t, len(queries) == len(test.queries), "Expected `net.split_url` to return %d queries, got %d queries", len(test.queries), len(queries))
 		for k, v in queries {
 			expected := test.queries[k]
-			msg = fmt.tprintf("Expected `net.split_url` to return %s, got %s", expected, v)
-			expect(t, v == expected, msg)
+			testing.expectf(t, v == expected, "Expected `net.split_url` to return %s, got %s", expected, v)
 		}
-		msg = fmt.tprintf("Expected `net.split_url` to return %s, got %s", test.fragment, fragment)
-		expect(t, fragment == test.fragment, msg)
-
+		testing.expectf(t, fragment == test.fragment, "Expected `net.split_url` to return %s, got %s", test.fragment, fragment)
 	}
 }
 
@@ -659,7 +511,45 @@ join_url_test :: proc(t: ^testing.T) {
 		for test_url in test.url {
 			pass |= url == test_url
 		}
-		msg := fmt.tprintf("Expected `net.join_url` to return one of %s, got %s", test.url, url)
-		expect(t, pass, msg)
+		testing.expectf(t, pass, "Expected `net.join_url` to return one of %s, got %s", test.url, url)
+	}
+}
+
+@(private)
+address_to_binstr :: proc(address: net.Address) -> (binstr: string) {
+	switch t in address {
+	case net.IP4_Address:
+		b := transmute(u32be)t
+		return fmt.tprintf("%08x", b)
+	case net.IP6_Address:
+		b := transmute(u128be)t
+		return fmt.tprintf("%32x", b)
+	case:
+		return ""
 	}
+	unreachable()
 }
+
+@(private)
+binstr_to_address :: proc(t: ^testing.T, binstr: string) -> (address: net.Address) {
+	switch len(binstr) {
+	case 8:  // IPv4
+		a, ok := strconv.parse_u64_of_base(binstr, 16)
+		testing.expect(t, ok, "failed to parse test case bin string")
+
+		ipv4 := u32be(a)
+		return net.IP4_Address(transmute([4]u8)ipv4)
+
+
+	case 32: // IPv6
+		a, ok := strconv.parse_u128_of_base(binstr, 16)
+		testing.expect(t, ok, "failed to parse test case bin string")
+
+		ipv4 := u128be(a)
+		return net.IP6_Address(transmute([8]u16be)ipv4)
+
+	case 0:
+		return nil
+	}
+	panic("Invalid test case")
+}

+ 12 - 41
tests/core/odin/test_parser.odin

@@ -1,58 +1,29 @@
 package test_core_odin_parser
 
-import "core:fmt"
 import "core:odin/ast"
 import "core:odin/parser"
-import "core:os"
+import "base:runtime"
 import "core:testing"
 
-
-TEST_count := 0
-TEST_fail  := 0
-
-when ODIN_TEST {
-	expect  :: testing.expect
-	log     :: testing.log
-} else {
-	expect  :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
-		TEST_count += 1
-		if !condition {
-			TEST_fail += 1
-			fmt.printf("[%v] %v\n", loc, message)
-			return
-		}
-	}
-	log     :: proc(t: ^testing.T, v: any, loc := #caller_location) {
-		fmt.printf("[%v] ", loc)
-		fmt.printf("log: %v\n", v)
-	}
-}
-
-main :: proc() {
-	t := testing.T{}
-	test_parse_demo(&t)
-	test_parse_bitfield(&t)
-
-	fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
-	if TEST_fail > 0 {
-		os.exit(1)
-	}
-}
-
-
 @test
 test_parse_demo :: proc(t: ^testing.T) {
-	pkg, ok := parser.parse_package_from_path("examples/demo")
+	context.allocator = context.temp_allocator
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
+
+	pkg, ok := parser.parse_package_from_path(ODIN_ROOT + "examples/demo")
 	
-	expect(t, ok == true, "parser.parse_package_from_path failed")
+	testing.expect(t, ok, "parser.parse_package_from_path failed")
 
 	for key, value in pkg.files {
-		expect(t, value.syntax_error_count == 0, fmt.tprintf("%v should contain zero errors", key))
+		testing.expectf(t, value.syntax_error_count == 0, "%v should contain zero errors", key)
 	}
 }
 
 @test
 test_parse_bitfield :: proc(t: ^testing.T) {
+	context.allocator = context.temp_allocator
+	runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
+
 	file := ast.File{
 		fullpath = "test.odin",
 		src = `
@@ -78,5 +49,5 @@ Foo :: bit_field uint {
 
 	p := parser.default_parser()
 	ok := parser.parse_file(&p, &file)
-	expect(t, ok == true, "bad parse")
-}
+	testing.expect(t, ok, "bad parse")
+}

+ 35 - 38
tests/core/path/filepath/test_core_filepath.odin

@@ -1,26 +1,19 @@
 // Tests "path.odin" in "core:path/filepath".
-// Must be run with `-collection:tests=` flag, e.g.
-// ./odin run tests/core/path/filepath/test_core_filepath.odin -collection:tests=tests
 package test_core_filepath
 
 import "core:fmt"
 import "core:path/filepath"
 import "core:testing"
-import tc "tests:common"
-
-main :: proc() {
-	t := testing.T{}
 
+@(test)
+test_split_list :: proc(t: ^testing.T) {
 	when ODIN_OS == .Windows {
-		test_split_list_windows(&t)
+		test_split_list_windows(t)
 	} else {
-		test_split_list_unix(&t)
+		test_split_list_unix(t)
 	}
-
-	tc.report(&t)
 }
 
-@test
 test_split_list_windows :: proc(t: ^testing.T) {
 	Datum :: struct {
 		i: int,
@@ -41,12 +34,12 @@ test_split_list_windows :: proc(t: ^testing.T) {
 	for d, i in data {
 		assert(i == d.i, fmt.tprintf("wrong data index: i %d != d.i %d\n", i, d.i))
 		r := filepath.split_list(d.v)
-		defer delete(r)
-		tc.expect(t, len(r) == len(d.e), fmt.tprintf("i:%d %s(%s) len(r) %d != len(d.e) %d",
+		defer delete_split(r)
+		testing.expect(t, len(r) == len(d.e), fmt.tprintf("i:%d %s(%s) len(r) %d != len(d.e) %d",
 													 i, #procedure, d.v, len(r), len(d.e)))
 		if len(r) == len(d.e) {
 			for _, j in r {
-				tc.expect(t, r[j] == d.e[j], fmt.tprintf("i:%d %s(%v) -> %v[%d] != %v",
+				testing.expect(t, r[j] == d.e[j], fmt.tprintf("i:%d %s(%v) -> %v[%d] != %v",
 														 i, #procedure, d.v, r[j], j, d.e[j]))
 			}
 		}
@@ -55,47 +48,43 @@ test_split_list_windows :: proc(t: ^testing.T) {
 	{
 		v := ""
 		r := filepath.split_list(v)
-		tc.expect(t, r == nil, fmt.tprintf("%s(%s) -> %v != nil", #procedure, v, r))
+		defer delete_split(r)
+		testing.expect(t, r == nil, fmt.tprintf("%s(%s) -> %v != nil", #procedure, v, r))
 	}
 	{
 		v := "a"
 		r := filepath.split_list(v)
-		defer delete(r)
-		tc.expect(t, len(r) == 1, fmt.tprintf("%s(%s) len(r) %d != 1", #procedure, v, len(r)))
+		defer delete_split(r)
+		testing.expect(t, len(r) == 1, fmt.tprintf("%s(%s) len(r) %d != 1", #procedure, v, len(r)))
 		if len(r) == 1 {
-			tc.expect(t, r[0] == "a", fmt.tprintf("%s(%v) -> %v[0] != a", #procedure, v, r[0]))
+			testing.expect(t, r[0] == "a", fmt.tprintf("%s(%v) -> %v[0] != a", #procedure, v, r[0]))
 		}
 	}
 }
 
-@test
 test_split_list_unix :: proc(t: ^testing.T) {
 	Datum :: struct {
-		i: int,
 		v: string,
 		e: [3]string,
 	}
 	@static data := []Datum{
-		{ 0, "/opt/butler:/home/fancykillerpanda/Projects/Odin/Odin:/usr/local/sbin",
+		{ "/opt/butler:/home/fancykillerpanda/Projects/Odin/Odin:/usr/local/sbin",
 			[3]string{"/opt/butler", "/home/fancykillerpanda/Projects/Odin/Odin", "/usr/local/sbin"} }, // Issue #1537
-		{ 1, "a::b", [3]string{"a", "", "b"} },
-		{ 2, "a:b:", [3]string{"a", "b", ""} },
-		{ 3, ":a:b", [3]string{"", "a", "b"} },
-		{ 4, "::", [3]string{"", "", ""} },
-		{ 5, "\"a:b\"c:d:\"f\"", [3]string{"a:bc", "d", "f"} },
-		{ 6, "\"a:b:c\":d\":e\":f", [3]string{"a:b:c", "d:e", "f"} },
+		{ "a::b", [3]string{"a", "", "b"} },
+		{ "a:b:", [3]string{"a", "b", ""} },
+		{ ":a:b", [3]string{"", "a", "b"} },
+		{ "::", [3]string{"", "", ""} },
+		{ "\"a:b\"c:d:\"f\"", [3]string{"a:bc", "d", "f"} },
+		{ "\"a:b:c\":d\":e\":f", [3]string{"a:b:c", "d:e", "f"} },
 	}
 
-	for d, i in data {
-		assert(i == d.i, fmt.tprintf("wrong data index: i %d != d.i %d\n", i, d.i))
+	for d in data {
 		r := filepath.split_list(d.v)
-		defer delete(r)
-		tc.expect(t, len(r) == len(d.e), fmt.tprintf("i:%d %s(%s) len(r) %d != len(d.e) %d",
-													 i, #procedure, d.v, len(r), len(d.e)))
+		defer delete_split(r)
+		testing.expectf(t, len(r) == len(d.e), "%s len(r) %d != len(d.e) %d", d.v, len(r), len(d.e))
 		if len(r) == len(d.e) {
 			for _, j in r {
-				tc.expect(t, r[j] == d.e[j], fmt.tprintf("i:%d %s(%v) -> %v[%d] != %v",
-														 i, #procedure, d.v, r[j], j, d.e[j]))
+				testing.expectf(t, r[j] == d.e[j], "%v -> %v[%d] != %v", d.v, r[j], j, d.e[j])
 			}
 		}
 	}
@@ -103,15 +92,23 @@ test_split_list_unix :: proc(t: ^testing.T) {
 	{
 		v := ""
 		r := filepath.split_list(v)
-		tc.expect(t, r == nil, fmt.tprintf("%s(%s) -> %v != nil", #procedure, v, r))
+		testing.expectf(t, r == nil, "'%s' -> '%v' != nil", v, r)
 	}
 	{
 		v := "a"
 		r := filepath.split_list(v)
-		defer delete(r)
-		tc.expect(t, len(r) == 1, fmt.tprintf("%s(%s) len(r) %d != 1", #procedure, v, len(r)))
+		defer delete_split(r)
+		testing.expectf(t, len(r) == 1, "'%s' len(r) %d != 1", v, len(r))
 		if len(r) == 1 {
-			tc.expect(t, r[0] == "a", fmt.tprintf("%s(%v) -> %v[0] != a", #procedure, v, r[0]))
+			testing.expectf(t, r[0] == "a", "'%v' -> %v[0] != a", v, r[0])
 		}
 	}
 }
+
+@(private)
+delete_split :: proc(s: []string) {
+	for part in s {
+		delete(part)
+	}
+	delete(s)
+}

+ 33 - 54
tests/core/reflect/test_core_reflect.odin

@@ -1,21 +1,8 @@
 // Tests "core:reflect/reflect".
-// Must be run with `-collection:tests=` flag, e.g.
-// ./odin run tests/core/reflect/test_core_reflect.odin -out=tests/core/test_core_reflect -collection:tests=./tests
 package test_core_reflect
 
-import "core:fmt"
 import "core:reflect"
 import "core:testing"
-import tc "tests:common"
-
-main :: proc() {
-    t := testing.T{}
-
-	test_as_u64(&t)
-	test_as_f64(&t)
-
-	tc.report(&t)
-}
 
 @test
 test_as_u64 :: proc(t: ^testing.T) {
@@ -31,9 +18,8 @@ test_as_u64 :: proc(t: ^testing.T) {
 		for d, i in data {
 			assert(i == d.i)
 			r, valid := reflect.as_u64(d.v)
-			tc.expect(t, valid, fmt.tprintf("i:%d %s(i8 %v) !valid\n", i, #procedure, d.v))
-			tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i8 %v) -> %v (0x%X) != %v (0x%X)\n",
-												i, #procedure, d.v, r, r, d.e, d.e))
+			testing.expectf(t, valid,    "i8 %v !valid",                    d.v)
+			testing.expectf(t, r == d.e, "i8 %v -> %v (0x%X) != %v (0x%X)", d.v, r, r, d.e, d.e)
 		}
 	}
 	{
@@ -48,9 +34,8 @@ test_as_u64 :: proc(t: ^testing.T) {
 		for d, i in data {
 			assert(i == d.i)
 			r, valid := reflect.as_u64(d.v)
-			tc.expect(t, valid, fmt.tprintf("i:%d %s(i16 %v) !valid\n", i, #procedure, d.v))
-			tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i16 %v) -> %v (0x%X) != %v (0x%X)\n",
-												i, #procedure, d.v, r, r, d.e, d.e))
+			testing.expectf(t, valid,    "i16 %v !valid",                    d.v)
+			testing.expectf(t, r == d.e, "i16 %v -> %v (0x%X) != %v (0x%X)", d.v, r, r, d.e, d.e)
 		}
 	}
 	{
@@ -65,9 +50,8 @@ test_as_u64 :: proc(t: ^testing.T) {
 		for d, i in data {
 			assert(i == d.i)
 			r, valid := reflect.as_u64(d.v)
-			tc.expect(t, valid, fmt.tprintf("i:%d %s(i32 %v) !valid\n", i, #procedure, d.v))
-			tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i32 %v) -> %v (0x%X) != %v (0x%X)\n",
-												i, #procedure, d.v, r, r, d.e, d.e))
+			testing.expectf(t, valid,    "i32 %v !valid",                    d.v)
+			testing.expectf(t, r == d.e, "i32 %v -> %v (0x%X) != %v (0x%X)", d.v, r, r, d.e, d.e)
 		}
 	}
 	{
@@ -82,9 +66,8 @@ test_as_u64 :: proc(t: ^testing.T) {
 		for d, i in data {
 			assert(i == d.i)
 			r, valid := reflect.as_u64(d.v)
-			tc.expect(t, valid, fmt.tprintf("i:%d %s(i64 %v) !valid\n", i, #procedure, d.v))
-			tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i64 %v) -> %v (0x%X) != %v (0x%X)\n",
-												i, #procedure, d.v, r, r, d.e, d.e))
+			testing.expectf(t, valid,    "i64 %v !valid",                    d.v)
+			testing.expectf(t, r == d.e, "i64 %v -> %v (0x%X) != %v (0x%X)", d.v, r, r, d.e, d.e)
 		}
 	}
 	{
@@ -102,9 +85,8 @@ test_as_u64 :: proc(t: ^testing.T) {
 		for d, i in data {
 			assert(i == d.i)
 			r, valid := reflect.as_u64(d.v)
-			tc.expect(t, valid, fmt.tprintf("i:%d %s(i128 %v) !valid\n", i, #procedure, d.v))
-			tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i128 %v) -> %v (0x%X) != %v (0x%X)\n",
-												i, #procedure, d.v, r, r, d.e, d.e))
+			testing.expectf(t, valid,    "i128 %v !valid",                    d.v)
+			testing.expectf(t, r == d.e, "i128 %v -> %v (0x%X) != %v (0x%X)", d.v, r, r, d.e, d.e)
 		}
 	}
 	{
@@ -118,8 +100,8 @@ test_as_u64 :: proc(t: ^testing.T) {
 		for d, i in data {
 			assert(i == d.i)
 			r, valid := reflect.as_u64(d.v)
-			tc.expect(t, valid, fmt.tprintf("i:%d %s(f16 %v) !valid\n", i, #procedure, d.v))
-			tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(f16 %v) -> %v != %v\n", i, #procedure, d.v, r, d.e))
+			testing.expectf(t, valid,    "f16 %v !valid",      d.v)
+			testing.expectf(t, r == d.e, "f16 %v -> %v != %v", d.v, r, d.e)
 		}
 	}
 	{
@@ -132,8 +114,8 @@ test_as_u64 :: proc(t: ^testing.T) {
 		for d, i in data {
 			assert(i == d.i)
 			r, valid := reflect.as_u64(d.v)
-			tc.expect(t, valid, fmt.tprintf("i:%d %s(f32 %v) !valid\n", i, #procedure, d.v))
-			tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(f32 %v) -> %v != %v\n", i, #procedure, d.v, r, d.e))
+			testing.expectf(t, valid,    "f32 %v !valid",      d.v)
+			testing.expectf(t, r == d.e, "f32 %v -> %v != %v", d.v, r, d.e)
 		}
 	}
 	{
@@ -146,8 +128,8 @@ test_as_u64 :: proc(t: ^testing.T) {
 		for d, i in data {
 			assert(i == d.i)
 			r, valid := reflect.as_u64(d.v)
-			tc.expect(t, valid, fmt.tprintf("i:%d %s(f64 %v) !valid\n", i, #procedure, d.v))
-			tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(f64 %v) -> %v != %v\n", i, #procedure, d.v, r, d.e))
+			testing.expectf(t, valid,    "f64 %v !valid",      d.v)
+			testing.expectf(t, r == d.e, "f64 %v -> %v != %v", d.v, r, d.e)
 		}
 	}
 }
@@ -166,8 +148,8 @@ test_as_f64 :: proc(t: ^testing.T) {
 		for d, i in data {
 			assert(i == d.i)
 			r, valid := reflect.as_f64(d.v)
-			tc.expect(t, valid, fmt.tprintf("i:%d %s(i8 %v) !valid\n", i, #procedure, d.v))
-			tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i8 %v) -> %v != %v\n", i, #procedure, d.v, r, d.e))
+			testing.expectf(t, valid,    "i8 %v !valid",      d.v)
+			testing.expectf(t, r == d.e, "i8 %v -> %v != %v", d.v, r, d.e)
 		}
 	}
 	{
@@ -182,8 +164,8 @@ test_as_f64 :: proc(t: ^testing.T) {
 		for d, i in data {
 			assert(i == d.i)
 			r, valid := reflect.as_f64(d.v)
-			tc.expect(t, valid, fmt.tprintf("i:%d %s(i16 %v) !valid\n", i, #procedure, d.v))
-			tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i16 %v) -> %v != %v\n", i, #procedure, d.v, r, d.e))
+			testing.expectf(t, valid,    "i16 %v !valid",      d.v)
+			testing.expectf(t, r == d.e, "i16 %v -> %v != %v", d.v, r, d.e)
 		}
 	}
 	{
@@ -198,8 +180,8 @@ test_as_f64 :: proc(t: ^testing.T) {
 		for d, i in data {
 			assert(i == d.i)
 			r, valid := reflect.as_f64(d.v)
-			tc.expect(t, valid, fmt.tprintf("i:%d %s(i32 %v) !valid\n", i, #procedure, d.v))
-			tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i32 %v) -> %v != %v\n", i, #procedure, d.v, r, d.e))
+			testing.expectf(t, valid,    "i32 %v !valid",      d.v)
+			testing.expectf(t, r == d.e, "i32 %v -> %v != %v", d.v, r, d.e)
 		}
 	}
 	{
@@ -214,8 +196,8 @@ test_as_f64 :: proc(t: ^testing.T) {
 		for d, i in data {
 			assert(i == d.i)
 			r, valid := reflect.as_f64(d.v)
-			tc.expect(t, valid, fmt.tprintf("i:%d %s(i64 %v) !valid\n", i, #procedure, d.v))
-			tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i64 %v) -> %v != %v\n", i, #procedure, d.v, r, d.e))
+			testing.expectf(t, valid,    "i64 %v !valid",      d.v)
+			testing.expectf(t, r == d.e, "i64 %v -> %v != %v", d.v, r, d.e)
 		}
 	}
 	{
@@ -231,9 +213,8 @@ test_as_f64 :: proc(t: ^testing.T) {
 		for d, i in data {
 			assert(i == d.i)
 			r, valid := reflect.as_f64(d.v)
-			tc.expect(t, valid, fmt.tprintf("i:%d %s(i128 %v) !valid\n", i, #procedure, d.v))
-			tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i128 %v) -> %v (%H) != %v (%H)\n",
-												i, #procedure, d.v, r, r, d.e, d.e))
+			testing.expectf(t, valid,    "i128 %v !valid",                d.v)
+			testing.expectf(t, r == d.e, "i128 %v -> %v (%H) != %v (%H)", d.v, r, r, d.e, d.e)
 		}
 	}
 	{
@@ -247,9 +228,8 @@ test_as_f64 :: proc(t: ^testing.T) {
 		for d, i in data {
 			assert(i == d.i)
 			r, valid := reflect.as_f64(d.v)
-			tc.expect(t, valid, fmt.tprintf("i:%d %s(f16 %v) !valid\n", i, #procedure, d.v))
-			tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(f16 %v (%H)) -> %v (%H) != %v (%H)\n",
-												i, #procedure, d.v, d.v, r, r, d.e, d.e))
+			testing.expectf(t, valid,    "f16 %v !valid",                     d.v)
+			testing.expectf(t, r == d.e, "f16 %v (%H) -> %v (%H) != %v (%H)", d.v, d.v, r, r, d.e, d.e)
 		}
 	}
 	{
@@ -262,9 +242,8 @@ test_as_f64 :: proc(t: ^testing.T) {
 		for d, i in data {
 			assert(i == d.i)
 			r, valid := reflect.as_f64(d.v)
-			tc.expect(t, valid, fmt.tprintf("i:%d %s(f32 %v) !valid\n", i, #procedure, d.v))
-			tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(f32 %v (%H)) -> %v (%H) != %v (%H)\n",
-												i, #procedure, d.v, d.v, r, r, d.e, d.e))
+			testing.expectf(t, valid,    "f32 %v !valid", d.v)
+			testing.expectf(t, r == d.e, "f32 %v (%H) -> %v (%H) != %v (%H)", d.v, d.v, r, r, d.e, d.e)
 		}
 	}
 	{
@@ -277,8 +256,8 @@ test_as_f64 :: proc(t: ^testing.T) {
 		for d, i in data {
 			assert(i == d.i)
 			r, valid := reflect.as_f64(d.v)
-			tc.expect(t, valid, fmt.tprintf("i:%d %s(f64 %v) !valid\n", i, #procedure, d.v))
-			tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(f64 %v) -> %v != %v\n", i, #procedure, d.v, r, d.e))
+			testing.expectf(t, valid,    "f64 %v !valid",      d.v)
+			testing.expectf(t, r == d.e, "f64 %v -> %v != %v", d.v, r, d.e)
 		}
 	}
-}
+}

+ 5 - 38
tests/core/runtime/test_core_runtime.odin

@@ -1,43 +1,10 @@
 package test_core_runtime
 
-import "core:fmt"
 import "base:intrinsics"
 import "core:mem"
-import "core:os"
-import "core:reflect"
 import "base:runtime"
 import "core:testing"
 
-TEST_count := 0
-TEST_fail  := 0
-
-when ODIN_TEST {
-	expect_value :: testing.expect_value
-} else {
-	expect_value :: proc(t: ^testing.T, value, expected: $T, loc := #caller_location) -> bool where intrinsics.type_is_comparable(T) {
-		TEST_count += 1
-		ok := value == expected || reflect.is_nil(value) && reflect.is_nil(expected)
-		if !ok {
-			TEST_fail += 1
-			fmt.printf("[%v] expected %v, got %v\n", loc, expected, value)
-		}
-		return ok
-	}
-}
-
-main :: proc() {
-	t := testing.T{}
-
-	test_temp_allocator_big_alloc_and_alignment(&t)
-	test_temp_allocator_alignment_boundary(&t)
-	test_temp_allocator_returns_correct_size(&t)
-
-	fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
-	if TEST_fail > 0 {
-		os.exit(1)
-	}
-}
-
 // Tests that having space for the allocation, but not for the allocation and alignment
 // is handled correctly.
 @(test)
@@ -47,7 +14,7 @@ test_temp_allocator_alignment_boundary :: proc(t: ^testing.T) {
 
 	_, _ = mem.alloc(int(runtime.DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE)-120)
 	_, err := mem.alloc(112, 32)
-	expect_value(t, err, nil)
+	testing.expect(t, err == nil)
 }
 
 // Tests that big allocations with big alignments are handled correctly.
@@ -58,7 +25,7 @@ test_temp_allocator_big_alloc_and_alignment :: proc(t: ^testing.T) {
 
 	mappy: map[[8]int]int
 	err := reserve(&mappy, 50000)
-	expect_value(t, err, nil)
+	testing.expect(t, err == nil)
 }
 
 @(test)
@@ -67,6 +34,6 @@ test_temp_allocator_returns_correct_size :: proc(t: ^testing.T) {
 	context.allocator = runtime.arena_allocator(&arena)
 
 	bytes, err := mem.alloc_bytes(10, 16)
-	expect_value(t, err, nil)
-	expect_value(t, len(bytes), 10)
-}
+	testing.expect(t, err == nil)
+	testing.expect(t, len(bytes) == 10)
+}

+ 39 - 97
tests/core/slice/test_core_slice.odin

@@ -1,56 +1,16 @@
 package test_core_slice
 
 import "core:slice"
-import "core:strings"
 import "core:testing"
-import "core:fmt"
-import "core:os"
 import "core:math/rand"
 
-TEST_count := 0
-TEST_fail  := 0
-
-when ODIN_TEST {
-	expect  :: testing.expect
-	log     :: testing.log
-} else {
-	expect  :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
-		TEST_count += 1
-		if !condition {
-			TEST_fail += 1
-			fmt.printf("[%v] %v\n", loc, message)
-			return
-		}
-	}
-	log     :: proc(t: ^testing.T, v: any, loc := #caller_location) {
-		fmt.printf("[%v] ", loc)
-		fmt.printf("log: %v\n", v)
-	}
-}
-
-main :: proc() {
-	t := testing.T{}
-	test_sort_with_indices(&t)
-	test_binary_search(&t)
-
-	fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
-	if TEST_fail > 0 {
-		os.exit(1)
-	}
-}
-
 @test
 test_sort_with_indices :: proc(t: ^testing.T) {
-	seed := rand.uint64()
-	fmt.printf("Random seed: %v\n", seed)
-
 	// Test sizes are all prime.
 	test_sizes :: []int{7, 13, 347, 1031, 10111, 100003}
 
 	for test_size in test_sizes {
-		fmt.printf("Sorting %v random u64 values along with index.\n", test_size)
-
-		r := rand.create(seed)
+		r := rand.create(t.seed)
 
 		vals  := make([]u64, test_size)
 		r_idx := make([]int, test_size) // Reverse index
@@ -61,7 +21,7 @@ test_sort_with_indices :: proc(t: ^testing.T) {
 
 		// Set up test values
 		for _, i in vals {
-			vals[i]     = rand.uint64(&r)
+			vals[i] = rand.uint64(&r)
 		}
 
 		// Sort
@@ -69,7 +29,7 @@ test_sort_with_indices :: proc(t: ^testing.T) {
 		defer delete(f_idx)
 
 		// Verify sorted test values
-		rand.init(&r, seed)
+		rand.init(&r, t.seed)
 
 		for v, i in f_idx {
 			r_idx[v] = i
@@ -79,14 +39,14 @@ test_sort_with_indices :: proc(t: ^testing.T) {
 		for v, i in vals {
 			if i > 0 {
 				val_pass := v >= last
-				expect(t, val_pass, "Expected values to have been sorted.")
+				testing.expect(t, val_pass, "Expected randomized test values to have been sorted")
 				if !val_pass {
 					break
 				}
 			}
 
 			idx_pass := vals[r_idx[i]] == rand.uint64(&r)
-			expect(t, idx_pass, "Expected index to have been sorted.")
+			testing.expect(t, idx_pass, "Expected index to have been sorted")
 			if !idx_pass {
 				break
 			}
@@ -97,16 +57,11 @@ test_sort_with_indices :: proc(t: ^testing.T) {
 
 @test
 test_sort_by_indices :: proc(t: ^testing.T) {
-	seed := rand.uint64()
-	fmt.printf("Random seed: %v\n", seed)
-
 	// Test sizes are all prime.
 	test_sizes :: []int{7, 13, 347, 1031, 10111, 100003}
 
 	for test_size in test_sizes {
-		fmt.printf("Sorting %v random u64 values along with index.\n", test_size)
-
-		r := rand.create(seed)
+		r := rand.create(t.seed)
 
 		vals  := make([]u64, test_size)
 		r_idx := make([]int, test_size) // Reverse index
@@ -117,7 +72,7 @@ test_sort_by_indices :: proc(t: ^testing.T) {
 
 		// Set up test values
 		for _, i in vals {
-			vals[i]     = rand.uint64(&r)
+			vals[i] = rand.uint64(&r)
 		}
 
 		// Sort
@@ -125,7 +80,7 @@ test_sort_by_indices :: proc(t: ^testing.T) {
 		defer delete(f_idx)
 
 		// Verify sorted test values
-		rand.init(&r, seed)
+		rand.init(&r, t.seed)
 
 		{
 			indices := make([]int, test_size)
@@ -138,7 +93,7 @@ test_sort_by_indices :: proc(t: ^testing.T) {
 			defer delete(sorted_indices)
 			for v, i in sorted_indices {
 				idx_pass := v == f_idx[i]
-				expect(t, idx_pass, "Expected the sorted index to be the same as the result from sort_with_indices")
+				testing.expect(t, idx_pass, "Expected the sorted index to be the same as the result from sort_with_indices")
 				if !idx_pass {
 					break
 				}
@@ -154,7 +109,7 @@ test_sort_by_indices :: proc(t: ^testing.T) {
 			slice.sort_by_indices_overwrite(indices, f_idx)
 			for v, i in indices {
 				idx_pass := v == f_idx[i]
-				expect(t, idx_pass, "Expected the sorted index to be the same as the result from sort_with_indices")
+				testing.expect(t, idx_pass, "Expected the sorted index to be the same as the result from sort_with_indices")
 				if !idx_pass {
 					break
 				}
@@ -174,7 +129,7 @@ test_sort_by_indices :: proc(t: ^testing.T) {
 			slice.sort_by_indices(indices, swap, f_idx)
 			for v, i in swap {
 				idx_pass := v == f_idx[i]
-				expect(t, idx_pass, "Expected the sorted index to be the same as the result from sort_with_indices")
+				testing.expect(t, idx_pass, "Expected the sorted index to be the same as the result from sort_with_indices")
 				if !idx_pass {
 					break
 				}
@@ -185,61 +140,48 @@ test_sort_by_indices :: proc(t: ^testing.T) {
 
 @test
 test_binary_search :: proc(t: ^testing.T) {
-	builder := strings.Builder{}
-	defer strings.builder_destroy(&builder)
-
-	test_search :: proc(t: ^testing.T, b: ^strings.Builder, s: []i32, v: i32) -> (int, bool) {
-		log(t, fmt.sbprintf(b, "Searching for %v in %v", v, s))
-		strings.builder_reset(b)
-		index, found := slice.binary_search(s, v)
-		log(t, fmt.sbprintf(b, "index: %v, found: %v", index, found))
-		strings.builder_reset(b	)
-
-		return index, found
-	}
-
 	index: int
 	found: bool
 
 	s := []i32{0, 1, 1, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55}
 
-	index, found = test_search(t, &builder, s, 13)
-	expect(t, index == 9, "Expected index to be 9.")
-	expect(t, found == true, "Expected found to be true.")
+	index, found = slice.binary_search(s, 13)
+	testing.expect(t, index == 9,    "Expected index to be 9")
+	testing.expect(t, found == true, "Expected found to be true")
 
-	index, found = test_search(t, &builder, s, 4)
-	expect(t, index == 7, "Expected index to be 7.")
-	expect(t, found == false, "Expected found to be false.")
+	index, found = slice.binary_search(s, 4)
+	testing.expect(t, index == 7,     "Expected index to be 7.")
+	testing.expect(t, found == false, "Expected found to be false.")
 
-	index, found = test_search(t, &builder, s, 100)
-	expect(t, index == 13, "Expected index to be 13.")
-	expect(t, found == false, "Expected found to be false.")
+	index, found = slice.binary_search(s, 100)
+	testing.expect(t, index == 13,    "Expected index to be 13.")
+	testing.expect(t, found == false, "Expected found to be false.")
 
-	index, found = test_search(t, &builder, s, 1)
-	expect(t, index >= 1 && index <= 4, "Expected index to be 1, 2, 3, or 4.")
-	expect(t, found == true, "Expected found to be true.")
+	index, found = slice.binary_search(s, 1)
+	testing.expect(t, index >= 1 && index <= 4, "Expected index to be 1, 2, 3, or 4.")
+	testing.expect(t, found == true, "Expected found to be true.")
 
-	index, found = test_search(t, &builder, s, -1)
-	expect(t, index == 0, "Expected index to be 0.")
-	expect(t, found == false, "Expected found to be false.")
+	index, found = slice.binary_search(s, -1)
+	testing.expect(t, index == 0,     "Expected index to be 0.")
+	testing.expect(t, found == false, "Expected found to be false.")
 
 	a := []i32{}
 
-	index, found = test_search(t, &builder, a, 13)
-	expect(t, index == 0, "Expected index to be 0.")
-	expect(t, found == false, "Expected found to be false.")
+	index, found = slice.binary_search(a, 13)
+	testing.expect(t, index == 0,     "Expected index to be 0.")
+	testing.expect(t, found == false, "Expected found to be false.")
 
 	b := []i32{1}
 
-	index, found = test_search(t, &builder, b, 13)
-	expect(t, index == 1, "Expected index to be 1.")
-	expect(t, found == false, "Expected found to be false.")
+	index, found = slice.binary_search(b, 13)
+	testing.expect(t, index == 1,     "Expected index to be 1.")
+	testing.expect(t, found == false, "Expected found to be false.")
 
-	index, found = test_search(t, &builder, b, 1)
-	expect(t, index == 0, "Expected index to be 0.")
-	expect(t, found == true, "Expected found to be true.")
+	index, found = slice.binary_search(b, 1)
+	testing.expect(t, index == 0,    "Expected index to be 0.")
+	testing.expect(t, found == true, "Expected found to be true.")
 
-	index, found = test_search(t, &builder, b, 0)
-	expect(t, index == 0, "Expected index to be 0.")
-	expect(t, found == false, "Expected found to be false.")
-}
+	index, found = slice.binary_search(b, 0)
+	testing.expect(t, index == 0,     "Expected index to be 0.")
+	testing.expect(t, found == false, "Expected found to be false.")
+}

+ 24 - 62
tests/core/strings/test_core_strings.odin

@@ -2,81 +2,42 @@ package test_core_strings
 
 import "core:strings"
 import "core:testing"
-import "core:fmt"
-import "core:os"
 import "base:runtime"
-import "core:mem"
-
-TEST_count := 0
-TEST_fail  := 0
-
-when ODIN_TEST {
-	expect  :: testing.expect
-	log     :: testing.log
-} else {
-	expect  :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
-		TEST_count += 1
-		if !condition {
-			TEST_fail += 1
-			fmt.printf("[%v] %v\n", loc, message)
-			return
-		}
-	}
-	log     :: proc(t: ^testing.T, v: any, loc := #caller_location) {
-		fmt.printf("[%v] ", loc)
-		fmt.printf("log: %v\n", v)
-	}
-}
-
-main :: proc() {
-	t := testing.T{}
-	test_index_any_small_string_not_found(&t)
-	test_index_any_larger_string_not_found(&t)
-	test_index_any_small_string_found(&t)
-	test_index_any_larger_string_found(&t)
-	test_cut(&t)
-	test_case_conversion(&t)
-
-	fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
-	if TEST_fail > 0 {
-		os.exit(1)
-	}
-}
 
 @test
 test_index_any_small_string_not_found :: proc(t: ^testing.T) {
 	index := strings.index_any(".", "/:\"")
-	expect(t, index == -1, "index_any should be negative")
+	testing.expect(t, index == -1, "index_any should be negative")
 }
 
 @test
 test_index_any_larger_string_not_found :: proc(t: ^testing.T) {
 	index := strings.index_any("aaaaaaaa.aaaaaaaa", "/:\"")
-	expect(t, index == -1, "index_any should be negative")
+	testing.expect(t, index == -1, "index_any should be negative")
 }
 
 @test
 test_index_any_small_string_found :: proc(t: ^testing.T) {
 	index := strings.index_any(".", "/:.\"")
-	expect(t, index == 0, "index_any should be 0")
+	testing.expect(t, index == 0, "index_any should be 0")
 }
 
 @test
 test_index_any_larger_string_found :: proc(t: ^testing.T) {
 	index := strings.index_any("aaaaaaaa:aaaaaaaa", "/:\"")
-	expect(t, index == 8, "index_any should be 8")
+	testing.expect(t, index == 8, "index_any should be 8")
 }
 
 @test
 test_last_index_any_small_string_found :: proc(t: ^testing.T) {
 	index := strings.last_index_any(".", "/:.\"")
-	expect(t, index == 0, "last_index_any should be 0")
+	testing.expect(t, index == 0, "last_index_any should be 0")
 }
 
 @test
 test_last_index_any_small_string_not_found :: proc(t: ^testing.T) {
 	index := strings.last_index_any(".", "/:\"")
-	expect(t, index == -1, "last_index_any should be -1")
+	testing.expect(t, index == -1, "last_index_any should be -1")
 }
 
 Cut_Test :: struct {
@@ -100,9 +61,12 @@ test_cut :: proc(t: ^testing.T) {
 		res := strings.cut(test.input, test.offset, test.length)
 		defer delete(res)
 
-		msg := fmt.tprintf("cut(\"%v\", %v, %v) expected to return \"%v\", got \"%v\"",
-			test.input, test.offset, test.length, test.output, res)
-		expect(t, res == test.output, msg)
+		testing.expectf(
+			t,
+			res == test.output,
+			"cut(\"%v\", %v, %v) expected to return \"%v\", got \"%v\"",
+			test.input, test.offset, test.length, test.output, res,
+		)
 	}
 }
 
@@ -118,7 +82,7 @@ Case_Kind :: enum {
 	Ada_Case,
 }
 
-Case_Proc :: proc(r: string, allocator: runtime.Allocator) -> (string, mem.Allocator_Error)
+Case_Proc :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error)
 
 test_cases := [Case_Kind]struct{s: string, p: Case_Proc}{
 	.Lower_Space_Case = {"hellope world", to_lower_space_case},
@@ -132,33 +96,31 @@ test_cases := [Case_Kind]struct{s: string, p: Case_Proc}{
 	.Ada_Case         = {"Hellope_World", to_ada_case},
 }
 
-to_lower_space_case :: proc(r: string, allocator: runtime.Allocator) -> (string, mem.Allocator_Error) {
+to_lower_space_case :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) {
 	return strings.to_delimiter_case(r, ' ', false, allocator)
 }
-to_upper_space_case :: proc(r: string, allocator: runtime.Allocator) -> (string, mem.Allocator_Error) {
+to_upper_space_case :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) {
 	return strings.to_delimiter_case(r, ' ', true, allocator)
 }
 
 // NOTE: we have these wrappers as having #optional_allocator_error changes the type to not be equivalent
-to_snake_case :: proc(r: string, allocator: runtime.Allocator) -> (string, mem.Allocator_Error) { return strings.to_snake_case(r, allocator) }
-to_upper_snake_case :: proc(r: string, allocator: runtime.Allocator) -> (string, mem.Allocator_Error) { return strings.to_upper_snake_case(r, allocator) }
-to_kebab_case :: proc(r: string, allocator: runtime.Allocator) -> (string, mem.Allocator_Error) { return strings.to_kebab_case(r, allocator) }
-to_upper_kebab_case :: proc(r: string, allocator: runtime.Allocator) -> (string, mem.Allocator_Error) { return strings.to_upper_kebab_case(r, allocator) }
-to_camel_case :: proc(r: string, allocator: runtime.Allocator) -> (string, mem.Allocator_Error) { return strings.to_camel_case(r, allocator) }
-to_pascal_case :: proc(r: string, allocator: runtime.Allocator) -> (string, mem.Allocator_Error) { return strings.to_pascal_case(r, allocator) }
-to_ada_case :: proc(r: string, allocator: runtime.Allocator) -> (string, mem.Allocator_Error) { return strings.to_ada_case(r, allocator) }
+to_snake_case :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { return strings.to_snake_case(r, allocator) }
+to_upper_snake_case :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { return strings.to_upper_snake_case(r, allocator) }
+to_kebab_case :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { return strings.to_kebab_case(r, allocator) }
+to_upper_kebab_case :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { return strings.to_upper_kebab_case(r, allocator) }
+to_camel_case :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { return strings.to_camel_case(r, allocator) }
+to_pascal_case :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { return strings.to_pascal_case(r, allocator) }
+to_ada_case :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { return strings.to_ada_case(r, allocator) }
 
 @test
 test_case_conversion :: proc(t: ^testing.T) {
 	for entry in test_cases {
 		for test_case, case_kind in test_cases {
 			result, err := entry.p(test_case.s, context.allocator)
-			msg := fmt.tprintf("ERROR: We got the allocation error '{}'\n", err)
-			expect(t, err == nil, msg)
+			testing.expectf(t, err == nil, "ERROR: We got the allocation error '{}'\n", err)
 			defer delete(result)
 
-			msg = fmt.tprintf("ERROR: Input `{}` to converter {} does not match `{}`, got `{}`.\n", test_case.s, case_kind, entry.s, result)
-			expect(t, result == entry.s, msg)
+			testing.expectf(t, result == entry.s, "ERROR: Input `{}` to converter {} does not match `{}`, got `{}`.\n", test_case.s, case_kind, entry.s, result)
 		}
 	}
 }

+ 66 - 101
tests/core/text/i18n/test_core_text_i18n.odin

@@ -1,31 +1,9 @@
 package test_core_text_i18n
 
-import "core:mem"
-import "core:fmt"
-import "core:os"
+import "base:runtime"
 import "core:testing"
 import "core:text/i18n"
 
-TEST_count := 0
-TEST_fail  := 0
-
-when ODIN_TEST {
-	expect  :: testing.expect
-	log     :: testing.log
-} else {
-	expect  :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
-		TEST_count += 1
-		if !condition {
-			TEST_fail += 1
-			fmt.printf("[%v] %v\n", loc, message)
-			return
-		}
-	}
-	log     :: proc(t: ^testing.T, v: any, loc := #caller_location) {
-		fmt.printf("[%v] ", loc)
-		fmt.printf("log: %v\n", v)
-	}
-}
 T :: i18n.get
 
 Test :: struct {
@@ -37,25 +15,28 @@ Test :: struct {
 
 Test_Suite :: struct {
 	file:    string,
-	loader:  proc(string, i18n.Parse_Options, proc(int) -> int, mem.Allocator) -> (^i18n.Translation, i18n.Error),
+	loader:  proc(string, i18n.Parse_Options, proc(int) -> int, runtime.Allocator) -> (^i18n.Translation, i18n.Error),
 	plural:  proc(int) -> int,
 	err:     i18n.Error,
 	options: i18n.Parse_Options,
 	tests:   []Test,
 }
 
-// Custom pluralizer for plur.mo
-plur_mo_pluralizer :: proc(n: int) -> (slot: int) {
-	switch {
-	case n == 1:                       return 0
-	case n != 0 && n % 1_000_000 == 0: return 1
-	case:                              return 2
+TEST_SUITE_PATH :: ODIN_ROOT + "tests/core/assets/I18N/"
+
+@(test)
+test_custom_pluralizer :: proc(t: ^testing.T) {
+	// Custom pluralizer for plur.mo
+	plur_mo_pluralizer :: proc(n: int) -> (slot: int) {
+		switch {
+		case n == 1:                       return 0
+		case n != 0 && n % 1_000_000 == 0: return 1
+		case:                              return 2
+		}
 	}
-}
 
-TESTS := []Test_Suite{
-	{
-		file   = "assets/I18N/plur.mo",
+	test(t, {
+		file   = TEST_SUITE_PATH + "plur.mo",
 		loader = i18n.parse_mo_file,
 		plural = plur_mo_pluralizer,
 		tests  = {
@@ -66,14 +47,16 @@ TESTS := []Test_Suite{
 			{"", "Message1/plural",               "This is message 1",             1},
 			{"", "Message1/plural",               "This is message 1 - plural A",  1_000_000},
 			{"", "Message1/plural",               "This is message 1 - plural B",  42},
-
 			// This isn't in the catalog, so should ruturn the key.
 			{"", "Come visit us on Discord!",     "Come visit us on Discord!",      1},
 		},
-	},
+	})
+}
 
-	{
-		file   = "assets/I18N/mixed_context.mo",
+@(test)
+test_mixed_context :: proc(t: ^testing.T) {
+	test(t, {
+		file   = TEST_SUITE_PATH + "mixed_context.mo",
 		loader = i18n.parse_mo_file,
 		plural = nil,
 		tests  = {
@@ -84,19 +67,25 @@ TESTS := []Test_Suite{
 			// This isn't in the catalog, so should ruturn the key.
 			{"", "Come visit us on Discord!",     "Come visit us on Discord!",         1},
 		},
-	},
+	})
+}
 
-	{
-		file    = "assets/I18N/mixed_context.mo",
+@(test)
+test_mixed_context_dupe :: proc(t: ^testing.T) {
+	test(t, {
+		file    = TEST_SUITE_PATH + "mixed_context.mo",
 		loader  = i18n.parse_mo_file,
 		plural  = nil,
 		// Message1 exists twice, once within Context, which has been merged into ""
 		err     = .Duplicate_Key,
 		options = {merge_sections = true},
-	},
+	})
+}
 
-	{
-		file   = "assets/I18N/nl_NL.mo",
+@(test)
+test_nl_mo :: proc(t: ^testing.T) {
+	test(t, {
+		file   = TEST_SUITE_PATH + "nl_NL.mo",
 		loader = i18n.parse_mo_file,
 		plural = nil, // Default pluralizer
 		tests  = {
@@ -111,12 +100,13 @@ TESTS := []Test_Suite{
 			// This isn't in the catalog, so should ruturn the key.
 			{"", "Come visit us on Discord!",     "Come visit us on Discord!",      1},
 		},
-	},
-
+	})
+}
 
-	// QT Linguist with default loader options.
-	{
-		file   = "assets/I18N/nl_NL-qt-ts.ts",
+@(test)
+test_qt_linguist :: proc(t: ^testing.T) {
+	test(t, {
+		file   = TEST_SUITE_PATH + "nl_NL-qt-ts.ts",
 		loader = i18n.parse_qt_linguist_file,
 		plural = nil, // Default pluralizer
 		tests  = {
@@ -131,11 +121,13 @@ TESTS := []Test_Suite{
 			{"",              "Come visit us on Discord!",      "Come visit us on Discord!",   1},
 			{"Fake_Section",  "Come visit us on Discord!",      "Come visit us on Discord!",   1},
 		},
-	},
+	})
+}
 
-	// QT Linguist, merging sections.
-	{
-		file    = "assets/I18N/nl_NL-qt-ts.ts",
+@(test)
+test_qt_linguist_merge_sections :: proc(t: ^testing.T) {
+	test(t, {
+		file    = TEST_SUITE_PATH + "nl_NL-qt-ts.ts",
 		loader  = i18n.parse_qt_linguist_file,
 		plural  = nil, // Default pluralizer
 		options = {merge_sections = true},
@@ -154,65 +146,38 @@ TESTS := []Test_Suite{
 			{"apple_count",   "%d apple(s)",                    "%d apple(s)",                     1},
 			{"apple_count",   "%d apple(s)",                    "%d apple(s)",                    42},
 		},
-	},
+	})
+}
 
-	// QT Linguist, merging sections. Expecting .Duplicate_Key error because same key exists in more than 1 section.
-	{
-		file    = "assets/I18N/duplicate-key.ts",
+@(test)
+test_qt_linguist_duplicate_key_err :: proc(t: ^testing.T) {
+	test(t, { // QT Linguist, merging sections. Expecting .Duplicate_Key error because same key exists in more than 1 section.
+		file    = TEST_SUITE_PATH + "duplicate-key.ts",
 		loader  = i18n.parse_qt_linguist_file,
 		plural  = nil, // Default pluralizer
 		options = {merge_sections = true},
 		err     = .Duplicate_Key,
-	},
+	})
+}
 
-	// QT Linguist, not merging sections. Shouldn't return error despite same key existing in more than 1 section.
-	{
-		file    = "assets/I18N/duplicate-key.ts",
+@(test)
+test_qt_linguist_duplicate_key :: proc(t: ^testing.T) {
+	test(t, { // QT Linguist, not merging sections. Shouldn't return error despite same key existing in more than 1 section.
+		file    = TEST_SUITE_PATH + "duplicate-key.ts",
 		loader  = i18n.parse_qt_linguist_file,
 		plural  = nil, // Default pluralizer
-	},
+	})
 }
 
-@test
-tests :: proc(t: ^testing.T) {
-	cat: ^i18n.Translation
-	err: i18n.Error
-
-	for suite in TESTS {
-		cat, err = suite.loader(suite.file, suite.options, suite.plural, context.allocator)
-
-		msg := fmt.tprintf("Expected loading %v to return %v, got %v", suite.file, suite.err, err)
-		expect(t, err == suite.err, msg)
-
-		if err == .None {
-			for test in suite.tests {
-				val := T(test.section, test.key, test.n, cat)
-
-				msg  = fmt.tprintf("Expected key `%v` from section `%v`'s form for value `%v` to equal `%v`, got `%v`", test.key, test.section, test.n, test.val, val)
-				expect(t, val == test.val, msg)
-			}
-		}
-		i18n.destroy(cat)
-	}
-}
-
-main :: proc() {
-	track: mem.Tracking_Allocator
-	mem.tracking_allocator_init(&track, context.allocator)
-	context.allocator = mem.tracking_allocator(&track)
-
-	t := testing.T{}
-	tests(&t)
-
-	fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
-	if TEST_fail > 0 {
-		os.exit(1)
-	}
+test :: proc(t: ^testing.T, suite: Test_Suite, loc := #caller_location) {
+	cat, err := suite.loader(suite.file, suite.options, suite.plural, context.allocator)
+	testing.expectf(t, err == suite.err, "Expected loading %v to return %v, got %v", suite.file, suite.err, err, loc=loc)
 
-	if len(track.allocation_map) > 0 {
-		fmt.println()
-		for _, v in track.allocation_map {
-			fmt.printf("%v Leaked %v bytes.\n", v.location, v.size)
+	if err == .None {
+		for test in suite.tests {
+			val := T(test.section, test.key, test.n, cat)
+			testing.expectf(t, val == test.val, "Expected key `%v` from section `%v`'s form for value `%v` to equal `%v`, got `%v`", test.key, test.section, test.n, test.val, val, loc=loc)
 		}
 	}
+	i18n.destroy(cat)
 }

+ 62 - 97
tests/core/text/match/test_core_text_match.odin

@@ -2,31 +2,6 @@ package test_strlib
 
 import "core:text/match"
 import "core:testing"
-import "core:fmt"
-import "core:os"
-import "core:io"
-
-TEST_count: int
-TEST_fail: int
-
-// inline expect with custom props
-failed :: proc(t: ^testing.T, ok: bool, loc := #caller_location) -> bool {
-	TEST_count += 1
-	
-	if !ok {
-		fmt.wprintf(t.w, "%v: ", loc)
-		t.error_count += 1	
-		TEST_fail += 1
-	}
-
-	return !ok
-}
-
-expect :: testing.expect
-
-logf :: proc(t: ^testing.T, format: string, args: ..any) {
-	fmt.wprintf(t.w, format, ..args)
-}
 
 // find correct byte offsets 
 @test
@@ -61,18 +36,17 @@ test_find :: proc(t: ^testing.T) {
 		{ "helelo", "h.-l", 0, { 0, 3, true } },
 	}
 
-	for entry, i in ENTRIES {
+	for entry in ENTRIES {
 		matcher := match.matcher_init(entry.s, entry.p, entry.offset)
 		start, end, ok := match.matcher_find(&matcher)
 		success := entry.match.ok == ok && start == entry.match.start && end == entry.match.end
 
-		if failed(t, success) {
-			logf(t, "Find %d failed!\n", i)
-			logf(t, "\tHAYSTACK %s\tPATTERN %s\n", entry.s, entry.p)
-			logf(t, "\tSTART: %d == %d?\n", entry.match.start, start)
-			logf(t, "\tEND: %d == %d?\n", entry.match.end, end)
-			logf(t, "\tErr: %v\tLength %d\n", matcher.err, matcher.captures_length)			
-		}		
+		testing.expectf(
+			t,
+			success,
+			"HAYSTACK %q PATTERN %q, START: %d == %d? END: %d == %d? Err: %v Length %d",
+			entry.s, entry.p, entry.match.start, start, entry.match.end, end, matcher.err, matcher.captures_length,
+		)
 	}
 }
 
@@ -178,17 +152,17 @@ test_match :: proc(t: ^testing.T) {
 		{ "testing _this_ out", "%b_", "", false },
 	}
 
-	for entry, i in ENTRIES {
+	for entry in ENTRIES {
 		matcher := match.matcher_init(entry.s, entry.p)
 		result, ok := match.matcher_match(&matcher)
 		success := entry.ok == ok && result == entry.result
 
-		if failed(t, success) {
-			logf(t, "Match %d failed!\n", i)
-			logf(t, "\tHAYSTACK %s\tPATTERN %s\n", entry.s, entry.p)
-			logf(t, "\tResults: WANTED %s\tGOT %s\n", entry.result, result)
-			logf(t, "\tErr: %v\tLength %d\n", matcher.err, matcher.captures_length)
-		}
+		testing.expectf(
+			t,
+			success,
+			"HAYSTACK %q PATTERN %q WANTED %q GOT %q Err: %v Length %d",
+			entry.s, entry.p, entry.result, result, matcher.err, matcher.captures_length,
+		)
 	}
 }
 
@@ -203,19 +177,23 @@ test_captures :: proc(t: ^testing.T) {
 	compare_captures :: proc(t: ^testing.T, test: ^Temp, haystack: string, comp: []string, loc := #caller_location) {
 		length, err := match.find_aux(haystack, test.pattern, 0, false, &test.captures)
 		result := len(comp) == length && err == .OK
-		if failed(t, result == true) {
-			logf(t, "Captures Compare Failed!\n")
-			logf(t, "\tErr: %v\n", err)
-			logf(t, "\tLengths: %v != %v\n", len(comp), length)
-		}
+		testing.expectf(
+			t,
+			result,
+			"Captures Compare Failed! Lengths: %v != %v Err: %v",
+			len(comp), length, err,
+		)
 
 		for i in 0..<length {
 			cap := test.captures[i]
 			text := haystack[cap.byte_start:cap.byte_end]
 
-			if failed(t, comp[i] == text) {
-				logf(t, "Capture don't equal -> %s != %s\n", comp[i], text)
-			}
+			testing.expectf(
+				t,
+				comp[i] == text,
+				"Capture don't equal -> %q != %q\n",
+				comp[i], text,
+			)
 		}
 	}
 
@@ -224,11 +202,12 @@ test_captures :: proc(t: ^testing.T) {
 		length, err := match.find_aux(haystack, test.pattern, 0, false, &test.captures)
 		result := length > 0 && err == .OK
 
-		if failed(t, result == ok) {
-			logf(t, "Capture match failed!\n")
-			logf(t, "\tErr: %v\n", err)
-			logf(t, "\tLength: %v\n", length)
-		}
+		testing.expectf(
+			t,
+			result == ok,
+			"Capture match failed! Length: %v Pattern: %q Haystack: %q Err: %v",
+			length, test.pattern, haystack, err,
+		)
 	}
 
 	temp := Temp { pattern = "(one).+" }
@@ -253,15 +232,8 @@ test_captures :: proc(t: ^testing.T) {
 		cap2 := captures[2]
 		text1 := haystack[cap1.byte_start:cap1.byte_end]
 		text2 := haystack[cap2.byte_start:cap2.byte_end]
-		expect(t, text1 == "233", "Multi-Capture failed at 1")
-		expect(t, text2 == "hello", "Multi-Capture failed at 2")
-	}
-}
-
-gmatch_check :: proc(t: ^testing.T, index: int, a: []string, b: string) {
-	if failed(t, a[index] == b) {
-		logf(t, "GMATCH %d failed!\n", index)
-		logf(t, "\t%s != %s\n", a[index], b)
+		testing.expect(t, text1 == "233",   "Multi-Capture failed at 1")
+		testing.expect(t, text2 == "hello", "Multi-Capture failed at 2")
 	}
 }
 
@@ -298,9 +270,9 @@ test_gmatch :: proc(t: ^testing.T) {
 @test
 test_gsub :: proc(t: ^testing.T) {
 	result := match.gsub("testing123testing", "%d+", " sup ", context.temp_allocator)
-	expect(t, result == "testing sup testing", "GSUB 0: failed")
+	testing.expect(t, result == "testing sup testing", "GSUB 0: failed")
 	result = match.gsub("testing123testing", "%a+", "345", context.temp_allocator)
-	expect(t, result == "345123345", "GSUB 1: failed")
+	testing.expect(t, result == "345123345", "GSUB 1: failed")
 }
 
 @test
@@ -313,10 +285,12 @@ test_gfind :: proc(t: ^testing.T) {
 	index: int
 
 	for word in match.gfind(s, pattern, &captures) {
-		if failed(t, output[index] == word) {
-			logf(t, "GFIND %d failed!\n", index)
-			logf(t, "\t%s != %s\n", output[index], word)
-		}
+		testing.expectf(
+			t,
+			output[index] == word,
+			"GFIND %d failed! %q != %q",
+			index, output[index], word,
+		)
 		index += 1
 	}
 }
@@ -332,11 +306,12 @@ test_frontier :: proc(t: ^testing.T) {
 	call :: proc(data: rawptr, word: string, haystack: string, captures: []match.Match) {
 		temp := cast(^Temp) data
 
-		if failed(temp.t, word == temp.output[temp.index]) {
-			logf(temp.t, "GSUB_WITH %d failed!\n", temp.index)
-			logf(temp.t, "\t%s != %s\n", temp.output[temp.index], word)			
-		}
-
+		testing.expectf(
+			temp.t,
+			word == temp.output[temp.index],
+			"GSUB_WITH %d failed! %q != %q",
+			temp.index, temp.output[temp.index], word,
+		)
 		temp.index += 1
 	}
 
@@ -369,31 +344,21 @@ test_case_insensitive :: proc(t: ^testing.T) {
 		pattern := match.pattern_case_insensitive("test", 256, context.temp_allocator)
 		goal := "[tT][eE][sS][tT]"
 		
-		if failed(t, pattern == goal) {
-			logf(t, "Case Insensitive Pattern doesn't match result\n")
-			logf(t, "\t%s != %s\n", pattern, goal)
-		}
+		testing.expectf(
+			t,
+			pattern == goal,
+			"Case Insensitive Pattern doesn't match result. %q != %q",
+			pattern, goal,
+		)
 	}
 }
 
-main :: proc() {
-	t: testing.T
-	stream := os.stream_from_handle(os.stdout)
-	w := io.to_writer(stream)
-	t.w = w
-	
-	test_find(&t)
-	test_match(&t)
-	test_captures(&t)
-	test_gmatch(&t)
-	test_gsub(&t)
-	test_gfind(&t)
-	test_frontier(&t)
-	test_utf8(&t)
-	test_case_insensitive(&t)
-
-	fmt.wprintf(w, "%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
-	if TEST_fail > 0 {
-		os.exit(1)
-	}
+@(private)
+gmatch_check :: proc(t: ^testing.T, index: int, a: []string, b: string) {
+	testing.expectf(
+		t,
+		a[index] == b,
+		"GMATCH %d failed! %q != %q",
+		index, a[index], b,
+	)
 }

+ 12 - 44
tests/core/thread/test_core_thread.odin

@@ -2,39 +2,7 @@ package test_core_thread
 
 import "core:testing"
 import "core:thread"
-import "core:fmt"
-import "core:os"
-
-TEST_count := 0
-TEST_fail  := 0
-
-t := &testing.T{}
-
-when ODIN_TEST {
-    expect  :: testing.expect
-    log     :: testing.log
-} else {
-    expect  :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
-        TEST_count += 1
-        if !condition {
-            TEST_fail += 1
-            fmt.printf("[%v] %v\n", loc, message)
-            return
-        }
-    }
-    log     :: proc(t: ^testing.T, v: any, loc := #caller_location) {
-        fmt.printf("[%v] ", loc)
-        fmt.printf("log: %v\n", v)
-    }
-}
-
-main :: proc() {
-	poly_data_test(t)
-
-	if TEST_fail > 0 {
-		os.exit(1)
-	}
-}
+import "base:intrinsics"
 
 @(test)
 poly_data_test :: proc(_t: ^testing.T) {
@@ -46,7 +14,7 @@ poly_data_test :: proc(_t: ^testing.T) {
 	b: [MAX]byte = 8
 	t1 := thread.create_and_start_with_poly_data(b, proc(b: [MAX]byte) {
 		b_expect: [MAX]byte = 8
-		expect(poly_data_test_t, b == b_expect, "thread poly data not correct")
+		testing.expect(poly_data_test_t, b == b_expect, "thread poly data not correct")
 	})
 	defer free(t1)
 
@@ -55,8 +23,8 @@ poly_data_test :: proc(_t: ^testing.T) {
 	t2 := thread.create_and_start_with_poly_data2(b1, b2, proc(b: [3]uintptr, b2: [MAX / 2]byte) {
 		b_expect: [3]uintptr = 1
 		b2_expect: [MAX / 2]byte = 3
-		expect(poly_data_test_t, b == b_expect,   "thread poly data not correct")
-		expect(poly_data_test_t, b2 == b2_expect, "thread poly data not correct")
+		testing.expect(poly_data_test_t, b == b_expect,   "thread poly data not correct")
+		testing.expect(poly_data_test_t, b2 == b2_expect, "thread poly data not correct")
 	})
 	defer free(t2)
 
@@ -64,21 +32,21 @@ poly_data_test :: proc(_t: ^testing.T) {
 		b_expect: [3]uintptr = 1
 		b2_expect: [MAX / 2]byte = 3
 
-		expect(poly_data_test_t, b == b_expect,   "thread poly data not correct")
-		expect(poly_data_test_t, b2 == b2_expect, "thread poly data not correct")
-		expect(poly_data_test_t, b3 == 333,       "thread poly data not correct")
+		testing.expect(poly_data_test_t, b == b_expect,   "thread poly data not correct")
+		testing.expect(poly_data_test_t, b2 == b2_expect, "thread poly data not correct")
+		testing.expect(poly_data_test_t, b3 == 333,       "thread poly data not correct")
 	})
 	defer free(t3)
 
 	t4 := thread.create_and_start_with_poly_data4(uintptr(111), b1, uintptr(333), u8(5), proc(n: uintptr, b: [3]uintptr, n2: uintptr, n4: u8) {
 		b_expect: [3]uintptr = 1
 
-		expect(poly_data_test_t, n == 111,        "thread poly data not correct")
-		expect(poly_data_test_t, b == b_expect,   "thread poly data not correct")
-		expect(poly_data_test_t, n2 == 333,       "thread poly data not correct")
-		expect(poly_data_test_t, n4 == 5,         "thread poly data not correct")
+		testing.expect(poly_data_test_t, n == 111,        "thread poly data not correct")
+		testing.expect(poly_data_test_t, b == b_expect,   "thread poly data not correct")
+		testing.expect(poly_data_test_t, n2 == 333,       "thread poly data not correct")
+		testing.expect(poly_data_test_t, n4 == 5,         "thread poly data not correct")
 	})
 	defer free(t4)
 
 	thread.join_multiple(t1, t2, t3, t4)
-}
+}

+ 92 - 78
tests/core/time/test_core_time.odin

@@ -1,68 +1,17 @@
 package test_core_time
 
-import "core:fmt"
-import "core:mem"
-import "core:os"
 import "core:testing"
 import "core:time"
 import dt "core:time/datetime"
 
 is_leap_year :: time.is_leap_year
 
-TEST_count := 0
-TEST_fail  := 0
-
-when ODIN_TEST {
-	expect       :: testing.expect
-	expect_value :: testing.expect_value
-	log          :: testing.log
-} else {
-	expect  :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
-		TEST_count += 1
-		if !condition {
-			TEST_fail += 1
-			fmt.printf("[%v] %v\n", loc, message)
-			return
-		}
-	}
-	log     :: proc(t: ^testing.T, v: any, loc := #caller_location) {
-		fmt.printf("[%v] ", loc)
-		fmt.printf("log: %v\n", v)
-	}
-}
-
-main :: proc() {
-	t := testing.T{}
-
-	track: mem.Tracking_Allocator
-	mem.tracking_allocator_init(&track, context.allocator)
-	defer mem.tracking_allocator_destroy(&track)
-	context.allocator = mem.tracking_allocator(&track)
-
-	test_ordinal_date_roundtrip(&t)
-	test_component_to_time_roundtrip(&t)
-	test_parse_rfc3339_string(&t)
-	test_parse_iso8601_string(&t)
-
-	for _, leak in track.allocation_map {
-		expect(&t, false, fmt.tprintf("%v leaked %m\n", leak.location, leak.size))
-	}
-	for bad_free in track.bad_free_array {
-		expect(&t, false, fmt.tprintf("%v allocation %p was freed badly\n", bad_free.location, bad_free.memory))
-	}
-
-	fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
-	if TEST_fail > 0 {
-		os.exit(1)
-	}
-}
-
 @test
 test_ordinal_date_roundtrip :: proc(t: ^testing.T) {
-	expect(t, dt.unsafe_ordinal_to_date(dt.unsafe_date_to_ordinal(dt.MIN_DATE)) == dt.MIN_DATE, "Roundtripping MIN_DATE failed.")
-	expect(t, dt.unsafe_date_to_ordinal(dt.unsafe_ordinal_to_date(dt.MIN_ORD))  == dt.MIN_ORD,  "Roundtripping MIN_ORD failed.")
-	expect(t, dt.unsafe_ordinal_to_date(dt.unsafe_date_to_ordinal(dt.MAX_DATE)) == dt.MAX_DATE, "Roundtripping MAX_DATE failed.")
-	expect(t, dt.unsafe_date_to_ordinal(dt.unsafe_ordinal_to_date(dt.MAX_ORD))  == dt.MAX_ORD,  "Roundtripping MAX_ORD failed.")
+	testing.expect(t, dt.unsafe_ordinal_to_date(dt.unsafe_date_to_ordinal(dt.MIN_DATE)) == dt.MIN_DATE, "Roundtripping MIN_DATE failed.")
+	testing.expect(t, dt.unsafe_date_to_ordinal(dt.unsafe_ordinal_to_date(dt.MIN_ORD))  == dt.MIN_ORD,  "Roundtripping MIN_ORD failed.")
+	testing.expect(t, dt.unsafe_ordinal_to_date(dt.unsafe_date_to_ordinal(dt.MAX_DATE)) == dt.MAX_DATE, "Roundtripping MAX_DATE failed.")
+	testing.expect(t, dt.unsafe_date_to_ordinal(dt.unsafe_ordinal_to_date(dt.MAX_ORD))  == dt.MAX_ORD,  "Roundtripping MAX_ORD failed.")
 }
 
 /*
@@ -160,22 +109,51 @@ test_parse_rfc3339_string :: proc(t: ^testing.T) {
 		is_leap := false
 		if test.apply_offset {
 			res, consumed := time.rfc3339_to_time_utc(test.rfc_3339, &is_leap)
-			msg := fmt.tprintf("[apply offet] Parsing failed: %v -> %v (nsec: %v). Expected %v consumed, got %v", test.rfc_3339, res, res._nsec, test.consumed, consumed)
-			expect(t, test.consumed == consumed, msg)
+			testing.expectf(
+				t,
+				test.consumed == consumed,
+				"[apply offet] Parsing failed: %v -> %v (nsec: %v). Expected %v consumed, got %v",
+				test.rfc_3339, res, res._nsec, test.consumed, consumed,
+			)
 
 			if test.consumed == consumed {
-				expect(t, test.datetime == res,     fmt.tprintf("Time didn't match. Expected %v (%v), got %v (%v)", test.datetime, test.datetime._nsec, res, res._nsec))
-				expect(t, test.is_leap  == is_leap, "Expected a leap second, got none.")
+				testing.expectf(
+					t,
+					test.datetime == res,
+					"Time didn't match. Expected %v (%v), got %v (%v)",
+					test.datetime, test.datetime._nsec, res, res._nsec,
+				)
+				testing.expect(
+					t,
+					test.is_leap == is_leap,
+					"Expected a leap second, got none",
+				)
 			}
 		} else {
 			res, offset, consumed := time.rfc3339_to_time_and_offset(test.rfc_3339)
-			msg := fmt.tprintf("Parsing failed: %v -> %v (nsec: %v), offset: %v. Expected %v consumed, got %v", test.rfc_3339, res, res._nsec, offset, test.consumed, consumed)
-			expect(t, test.consumed == consumed, msg)
+			testing.expectf(
+				t,
+				test.consumed == consumed,
+				"Parsing failed: %v -> %v (nsec: %v), offset: %v. Expected %v consumed, got %v",
+				test.rfc_3339, res, res._nsec, offset, test.consumed, consumed,
+			)
 
 			if test.consumed == consumed {
-				expect(t, test.datetime   == res,     fmt.tprintf("Time didn't match. Expected %v (%v), got %v (%v)", test.datetime, test.datetime._nsec, res, res._nsec))
-				expect(t, test.utc_offset == offset,  fmt.tprintf("UTC offset didn't match. Expected %v, got %v", test.utc_offset, offset))
-				expect(t, test.is_leap    == is_leap, "Expected a leap second, got none.")
+				testing.expectf(
+					t, test.datetime == res,
+					"Time didn't match. Expected %v (%v), got %v (%v)",
+					test.datetime, test.datetime._nsec, res, res._nsec,
+				)
+				testing.expectf(
+					t,
+					test.utc_offset == offset,
+					"UTC offset didn't match. Expected %v, got %v",
+					test.utc_offset, offset,
+				)
+				testing.expect(
+					t, test.is_leap == is_leap,
+					"Expected a leap second, got none",
+				)
 			}
 		}
 	}
@@ -187,22 +165,52 @@ test_parse_iso8601_string :: proc(t: ^testing.T) {
 		is_leap := false
 		if test.apply_offset {
 			res, consumed := time.iso8601_to_time_utc(test.iso_8601, &is_leap)
-			msg := fmt.tprintf("[apply offet] Parsing failed: %v -> %v (nsec: %v). Expected %v consumed, got %v", test.iso_8601, res, res._nsec, test.consumed, consumed)
-			expect(t, test.consumed == consumed, msg)
+			testing.expectf(
+				t,
+				test.consumed == consumed,
+				"[apply offet] Parsing failed: %v -> %v (nsec: %v). Expected %v consumed, got %v",
+				test.iso_8601, res, res._nsec, test.consumed, consumed,
+			)
 
 			if test.consumed == consumed {
-				expect(t, test.datetime == res,     fmt.tprintf("Time didn't match. Expected %v (%v), got %v (%v)", test.datetime, test.datetime._nsec, res, res._nsec))
-				expect(t, test.is_leap  == is_leap, "Expected a leap second, got none.")
+				testing.expectf(
+					t,
+					test.datetime == res,
+					"Time didn't match. Expected %v (%v), got %v (%v)",
+					test.datetime, test.datetime._nsec, res, res._nsec,
+				)
+				testing.expect(
+					t,
+					test.is_leap == is_leap,
+					"Expected a leap second, got none",
+				)
 			}
 		} else {
 			res, offset, consumed := time.iso8601_to_time_and_offset(test.iso_8601)
-			msg := fmt.tprintf("Parsing failed: %v -> %v (nsec: %v), offset: %v. Expected %v consumed, got %v", test.iso_8601, res, res._nsec, offset, test.consumed, consumed)
-			expect(t, test.consumed == consumed, msg)
+			testing.expectf(
+				t,
+				test.consumed == consumed,
+				"Parsing failed: %v -> %v (nsec: %v), offset: %v. Expected %v consumed, got %v",
+				test.iso_8601, res, res._nsec, offset, test.consumed, consumed,
+			)
 
 			if test.consumed == consumed {
-				expect(t, test.datetime   == res,     fmt.tprintf("Time didn't match. Expected %v (%v), got %v (%v)", test.datetime, test.datetime._nsec, res, res._nsec))
-				expect(t, test.utc_offset == offset,  fmt.tprintf("UTC offset didn't match. Expected %v, got %v", test.utc_offset, offset))
-				expect(t, test.is_leap    == is_leap, "Expected a leap second, got none.")
+				testing.expectf(
+					t, test.datetime == res,
+					"Time didn't match. Expected %v (%v), got %v (%v)",
+					test.datetime, test.datetime._nsec, res, res._nsec,
+				)
+				testing.expectf(
+					t,
+					test.utc_offset == offset,
+					"UTC offset didn't match. Expected %v, got %v",
+					test.utc_offset, offset,
+				)
+				testing.expect(
+					t,
+					test.is_leap == is_leap,
+					"Expected a leap second, got none",
+				)
 			}
 		}
 	}
@@ -231,15 +239,21 @@ test_component_to_time_roundtrip :: proc(t: ^testing.T) {
 
 date_component_roundtrip_test :: proc(t: ^testing.T, moment: dt.DateTime) {
 	res, ok := time.datetime_to_time(moment.year, moment.month, moment.day, moment.hour, moment.minute, moment.second)
-	expect(t, ok, "Couldn't convert date components into date")
+	testing.expect(
+		t,
+		ok,
+		"Couldn't convert date components into date",
+	)
 
 	YYYY, MM, DD := time.date(res)
 	hh,   mm, ss := time.clock(res)
 
-	expected := fmt.tprintf("Expected %4d-%2d-%2d %2d:%2d:%2d, got %4d-%2d-%2d %2d:%2d:%2d",
-	                        moment.year, moment.month, moment.day, moment.hour, moment.minute, moment.second, YYYY, MM, DD, hh, mm, ss)
-
 	ok =  moment.year == i64(YYYY) && moment.month == i8(MM) && moment.day    == i8(DD)
 	ok &= moment.hour == i8(hh)   && moment.minute == i8(mm) && moment.second == i8(ss)
-	expect(t, ok, expected)
+	testing.expectf(
+		t,
+		ok,
+		"Expected %4d-%2d-%2d %2d:%2d:%2d, got %4d-%2d-%2d %2d:%2d:%2d",
+		moment.year, moment.month, moment.day, moment.hour, moment.minute, moment.second, YYYY, MM, DD, hh, mm, ss,
+	)
 }

+ 8 - 9
tests/internal/Makefile

@@ -1,23 +1,22 @@
 ODIN=../../odin
+COMMON=-file -vet -strict-style -o:minimal
 
-all: all_bsd asan_test
-
-all_bsd: rtti_test map_test pow_test 128_test string_compare_test
+all: asan_test rtti_test map_test pow_test 128_test string_compare_test
 
 rtti_test:
-	$(ODIN) run test_rtti.odin -file -vet -strict-style -o:minimal
+	$(ODIN) test test_rtti.odin $(COMMON)
 
 map_test:
-	$(ODIN) run test_map.odin -file -vet -strict-style -o:minimal
+	$(ODIN) test test_map.odin $(COMMON)
 
 pow_test:
-	$(ODIN) run test_pow.odin -file -vet -strict-style -o:minimal
+	$(ODIN) test test_pow.odin $(COMMON)
 
 128_test:
-	$(ODIN) run test_128.odin -file -vet -strict-style -o:minimal
+	$(ODIN) test test_128.odin $(COMMON)
 
 asan_test:
-	$(ODIN) run test_asan.odin -file -sanitize:address -debug
+	$(ODIN) test test_asan.odin $(COMMON) -sanitize:address -debug
 
 string_compare_test:
-	$(ODIN) run test_string_compare.odin -file -vet -strict-style -o:minimal
+	$(ODIN) test test_string_compare.odin $(COMMON)

+ 7 - 8
tests/internal/build.bat

@@ -1,10 +1,9 @@
 @echo off
 set PATH_TO_ODIN==..\..\odin
-rem %PATH_TO_ODIN% run test_rtti.odin -file -vet -strict-style -o:minimal || exit /b
-%PATH_TO_ODIN% run test_map.odin -file -vet -strict-style -o:minimal || exit /b
-rem -define:SEED=42
-%PATH_TO_ODIN% run test_pow.odin -file -vet -strict-style -o:minimal || exit /b
-
-%PATH_TO_ODIN% run test_128.odin -file -vet -strict-style -o:minimal || exit /b
-
-%PATH_TO_ODIN% run test_string_compare.odin -file -vet -strict-style -o:minimal || exit /b
+set COMMON=-file -vet -strict-style -o:minimal
+%PATH_TO_ODIN% test test_rtti.odin %COMMON% || exit /b
+%PATH_TO_ODIN% test test_map.odin  %COMMON% || exit /b
+%PATH_TO_ODIN% test test_pow.odin  %COMMON% || exit /b
+%PATH_TO_ODIN% test test_asan.odin %COMMON% || exit /b
+%PATH_TO_ODIN% test test_128.odin  %COMMON% || exit /b
+%PATH_TO_ODIN% test test_string_compare.odin %COMMON% || exit /b

+ 8 - 42
tests/internal/test_128.odin

@@ -1,41 +1,7 @@
 package test_128
 
-import "core:fmt"
-import "core:os"
 import "core:testing"
 
-TEST_count := 0
-TEST_fail  := 0
-
-when ODIN_TEST {
-	expect  :: testing.expect
-	log     :: testing.log
-} else {
-	expect  :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
-		TEST_count += 1
-		if !condition {
-			TEST_fail += 1
-			fmt.printf("[%v] %v\n", loc, message)
-			return
-		}
-	}
-	log     :: proc(t: ^testing.T, v: any, loc := #caller_location) {
-		fmt.printf("[%v] ", loc)
-		fmt.printf("log: %v\n", v)
-	}
-}
-
-main :: proc() {
-	t := testing.T{}
-
-	test_128_align(&t)
-
-	fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
-	if TEST_fail > 0 {
-		os.exit(1)
-	}
-}
-
 @test
 test_128_align :: proc(t: ^testing.T) {
 	Danger_Struct :: struct {
@@ -45,15 +11,15 @@ test_128_align :: proc(t: ^testing.T) {
 
 	list := [?]Danger_Struct{{0, 0}, {1, 0}, {2, 0}, {3, 0}}
 
-	expect(t, list[0].x == 0, fmt.tprintf("[0].x (%v) != 0", list[0].x))
-	expect(t, list[0].y == 0, fmt.tprintf("[0].y (%v) != 0", list[0].y))
+	testing.expectf(t, list[0].x == 0, "[0].x (%v) != 0", list[0].x)
+	testing.expectf(t, list[0].y == 0, "[0].y (%v) != 0", list[0].y)
 
-	expect(t, list[1].x == 1, fmt.tprintf("[1].x (%v) != 1", list[1].x))
-	expect(t, list[1].y == 0, fmt.tprintf("[1].y (%v) != 0", list[1].y))
+	testing.expectf(t, list[1].x == 1, "[1].x (%v) != 1", list[1].x)
+	testing.expectf(t, list[1].y == 0, "[1].y (%v) != 0", list[1].y)
 
-	expect(t, list[2].x == 2, fmt.tprintf("[2].x (%v) != 2", list[2].x))
-	expect(t, list[2].y == 0, fmt.tprintf("[2].y (%v) != 0", list[2].y))
+	testing.expectf(t, list[2].x == 2, "[2].x (%v) != 2", list[2].x)
+	testing.expectf(t, list[2].y == 0, "[2].y (%v) != 0", list[2].y)
 
-	expect(t, list[3].x == 3, fmt.tprintf("[3].x (%v) != 3", list[3].x))
-	expect(t, list[3].y == 0, fmt.tprintf("[3].y (%v) != 0", list[3].y))
+	testing.expectf(t, list[3].x == 3, "[3].x (%v) != 3", list[3].x)
+	testing.expectf(t, list[3].y == 0, "[3].y (%v) != 0", list[3].y)
 }

+ 5 - 40
tests/internal/test_asan.odin

@@ -1,42 +1,7 @@
 // Intended to contain code that would trigger asan easily if the abi was set up badly.
 package test_asan
 
-import "core:fmt"
 import "core:testing"
-import "core:os"
-
-TEST_count := 0
-TEST_fail  := 0
-
-when ODIN_TEST {
-	expect  :: testing.expect
-	log     :: testing.log
-} else {
-	expect  :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
-		TEST_count += 1
-		if !condition {
-			TEST_fail += 1
-			fmt.printf("[%v] %v\n", loc, message)
-			return
-		}
-	}
-	log     :: proc(t: ^testing.T, v: any, loc := #caller_location) {
-		fmt.printf("[%v] ", loc)
-		fmt.printf("log: %v\n", v)
-	}
-}
-
-main :: proc() {
-	t := testing.T{}
-
-	test_12_bytes(&t)
-	test_12_bytes_two(&t)
-
-	fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
-	if TEST_fail > 0 {
-		os.exit(1)
-	}
-}
 
 @(test)
 test_12_bytes :: proc(t: ^testing.T) {
@@ -45,9 +10,9 @@ test_12_bytes :: proc(t: ^testing.T) {
 	}
 
 	a, b, ok := internal()
-	expect(t, a == max(f32), fmt.tprintf("a (%v) != max(f32)", a))
-	expect(t, b == 0,        fmt.tprintf("b (%v) != 0", b))
-	expect(t, ok,            fmt.tprintf("ok (%v) != true", ok))
+	testing.expectf(t, a == max(f32), "a (%v) != max(f32)", a)
+	testing.expectf(t, b == 0,        "b (%v) != 0", b)
+	testing.expectf(t, ok,            "ok (%v) != true", ok)
 }
 
 @(test)
@@ -57,6 +22,6 @@ test_12_bytes_two :: proc(t: ^testing.T) {
 	}
 
 	a, b := internal()
-	expect(t, a == 100.,     fmt.tprintf("a (%v) != 100.", a))
-	expect(t, b == max(int), fmt.tprintf("b (%v) != max(int)", b))
+	testing.expectf(t, a == 100.,     "a (%v) != 100.", a)
+	testing.expectf(t, b == max(int), "b (%v) != max(int)", b)
 }

+ 46 - 116
tests/internal/test_map.odin

@@ -1,26 +1,22 @@
 package test_internal_map
 
-import "core:fmt"
+import "core:log"
 import "base:intrinsics"
 import "core:math/rand"
-import "core:mem"
-import "core:os"
 import "core:testing"
 
-seed: u64
-
 ENTRY_COUNTS := []int{11, 101, 1_001, 10_001, 100_001, 1_000_001}
 
 @test
 map_insert_random_key_value :: proc(t: ^testing.T) {
 	seed_incr := u64(0)
 	for entries in ENTRY_COUNTS {
-		fmt.printf("[map_insert_random_key_value] Testing %v entries.\n", entries)
+		log.infof("Testing %v entries", entries)
 		m: map[i64]i64
 		defer delete(m)
 
 		unique_keys := 0
-		r := rand.create(seed + seed_incr)
+		r := rand.create(t.seed + seed_incr)
 		for _ in 0..<entries {
 			k := rand.int63(&r)
 			v := rand.int63(&r)
@@ -36,11 +32,11 @@ map_insert_random_key_value :: proc(t: ^testing.T) {
 			key_count += 1
 		}
 
-		expect(t, key_count == unique_keys, fmt.tprintf("Expected key_count to equal %v, got %v", unique_keys, key_count))
-		expect(t, len(m)    == unique_keys, fmt.tprintf("Expected len(map) to equal %v, got %v",  unique_keys, len(m)))
+		testing.expectf(t, key_count == unique_keys, "Expected key_count to equal %v, got %v", unique_keys, key_count)
+		testing.expectf(t, len(m)    == unique_keys, "Expected len(map) to equal %v, got %v",  unique_keys, len(m))
 
 		// Reset randomizer and verify
-		r = rand.create(seed + seed_incr)
+		r = rand.create(t.seed + seed_incr)
 
 		num_fails := 0
 		for _ in 0..<entries {
@@ -51,10 +47,10 @@ map_insert_random_key_value :: proc(t: ^testing.T) {
 			if !cond {
 				num_fails += 1
 				if num_fails > 5 {
-					fmt.println("... and more")
+					log.info("... and more")
 					break
 				}
-				expect(t, false, fmt.tprintf("Unexpected value. Expected m[%v] = %v, got %v", k, v, m[k]))
+				testing.expectf(t, false, "Unexpected value. Expected m[%v] = %v, got %v", k, v, m[k])
 			}
 		}
 		seed_incr += 1
@@ -65,12 +61,12 @@ map_insert_random_key_value :: proc(t: ^testing.T) {
 map_update_random_key_value :: proc(t: ^testing.T) {
 	seed_incr := u64(0)
 	for entries in ENTRY_COUNTS {
-		fmt.printf("[map_update_random_key_value] Testing %v entries.\n", entries)
+		log.infof("Testing %v entries", entries)
 		m: map[i64]i64
 		defer delete(m)
 
 		unique_keys := 0
-		r := rand.create(seed + seed_incr)
+		r := rand.create(t.seed + seed_incr)
 		for _ in 0..<entries {
 			k := rand.int63(&r)
 			v := rand.int63(&r)
@@ -86,13 +82,13 @@ map_update_random_key_value :: proc(t: ^testing.T) {
 			key_count += 1
 		}
 
-		expect(t, key_count == unique_keys, fmt.tprintf("Expected key_count to equal %v, got %v", unique_keys, key_count))
-		expect(t, len(m)    == unique_keys, fmt.tprintf("Expected len(map) to equal %v, got %v",  unique_keys, len(m)))
+		testing.expectf(t, key_count == unique_keys, "Expected key_count to equal %v, got %v", unique_keys, key_count)
+		testing.expectf(t, len(m)    == unique_keys, "Expected len(map) to equal %v, got %v",  unique_keys, len(m))
 
 		half_entries := entries / 2
 
 		// Reset randomizer and update half the entries
-		r = rand.create(seed + seed_incr)
+		r = rand.create(t.seed + seed_incr)
 		for _ in 0..<half_entries {
 			k := rand.int63(&r)
 			v := rand.int63(&r)
@@ -101,7 +97,7 @@ map_update_random_key_value :: proc(t: ^testing.T) {
 		}
 
 		// Reset randomizer and verify
-		r = rand.create(seed + seed_incr)
+		r = rand.create(t.seed + seed_incr)
 
 		num_fails := 0
 		for i in 0..<entries {
@@ -113,10 +109,10 @@ map_update_random_key_value :: proc(t: ^testing.T) {
 			if !cond {
 				num_fails += 1
 				if num_fails > 5 {
-					fmt.println("... and more")
+					log.info("... and more")
 					break
 				}
-				expect(t, false, fmt.tprintf("Unexpected value. Expected m[%v] = %v, got %v", k, v, m[k]))
+				testing.expectf(t, false, "Unexpected value. Expected m[%v] = %v, got %v", k, v, m[k])
 			}
 		}
 		seed_incr += 1
@@ -127,12 +123,12 @@ map_update_random_key_value :: proc(t: ^testing.T) {
 map_delete_random_key_value :: proc(t: ^testing.T) {
 	seed_incr := u64(0)
 	for entries in ENTRY_COUNTS {
-		fmt.printf("[map_delete_random_key_value] Testing %v entries.\n", entries)
+		log.infof("Testing %v entries", entries)
 		m: map[i64]i64
 		defer delete(m)
 
 		unique_keys := 0
-		r := rand.create(seed + seed_incr)
+		r := rand.create(t.seed + seed_incr)
 		for _ in 0..<entries {
 			k := rand.int63(&r)
 			v := rand.int63(&r)
@@ -148,13 +144,13 @@ map_delete_random_key_value :: proc(t: ^testing.T) {
 			key_count += 1
 		}
 
-		expect(t, key_count == unique_keys, fmt.tprintf("Expected key_count to equal %v, got %v", unique_keys, key_count))
-		expect(t, len(m)    == unique_keys, fmt.tprintf("Expected len(map) to equal %v, got %v",  unique_keys, len(m)))
+		testing.expectf(t, key_count == unique_keys, "Expected key_count to equal %v, got %v", unique_keys, key_count)
+		testing.expectf(t, len(m)    == unique_keys, "Expected len(map) to equal %v, got %v",  unique_keys, len(m))
 
 		half_entries := entries / 2
 
 		// Reset randomizer and delete half the entries
-		r = rand.create(seed + seed_incr)
+		r = rand.create(t.seed + seed_incr)
 		for _ in 0..<half_entries {
 			k := rand.int63(&r)
 			_  = rand.int63(&r)
@@ -163,7 +159,7 @@ map_delete_random_key_value :: proc(t: ^testing.T) {
 		}
 
 		// Reset randomizer and verify
-		r = rand.create(seed + seed_incr)
+		r = rand.create(t.seed + seed_incr)
 
 		num_fails := 0
 		for i in 0..<entries {
@@ -174,26 +170,26 @@ map_delete_random_key_value :: proc(t: ^testing.T) {
 				if k in m {
 					num_fails += 1
 					if num_fails > 5 {
-						fmt.println("... and more")
+						log.info("... and more")
 						break
 					}
-					expect(t, false, fmt.tprintf("Unexpected key present. Expected m[%v] to have been deleted, got %v", k, m[k]))
+					testing.expectf(t, false, "Unexpected key present. Expected m[%v] to have been deleted, got %v", k, m[k])
 				}
 			} else {
 				if k not_in m {
 					num_fails += 1
 					if num_fails > 5 {
-						fmt.println("... and more")
+						log.info("... and more")
 						break
 					}
-					expect(t, false, fmt.tprintf("Expected key not present. Expected m[%v] = %v", k, v))
+					testing.expectf(t, false, "Expected key not present. Expected m[%v] = %v", k, v)
 				} else if m[k] != v {
 					num_fails += 1
 					if num_fails > 5 {
-						fmt.println("... and more")
+						log.info("... and more")
 						break
 					}
-					expect(t, false, fmt.tprintf("Unexpected value. Expected m[%v] = %v, got %v", k, v, m[k]))
+					testing.expectf(t, false, "Unexpected value. Expected m[%v] = %v, got %v", k, v, m[k])
 				}
 			}
 		}
@@ -205,12 +201,12 @@ map_delete_random_key_value :: proc(t: ^testing.T) {
 set_insert_random_key_value :: proc(t: ^testing.T) {
 	seed_incr := u64(0)
 	for entries in ENTRY_COUNTS {
-		fmt.printf("[set_insert_random_key_value] Testing %v entries.\n", entries)
+		log.infof("Testing %v entries", entries)
 		m: map[i64]struct{}
 		defer delete(m)
 
 		unique_keys := 0
-		r := rand.create(seed + seed_incr)
+		r := rand.create(t.seed + seed_incr)
 		for _ in 0..<entries {
 			k := rand.int63(&r)
 			if k not_in m {
@@ -224,11 +220,11 @@ set_insert_random_key_value :: proc(t: ^testing.T) {
 			key_count += 1
 		}
 
-		expect(t, key_count == unique_keys, fmt.tprintf("Expected key_count to equal %v, got %v", unique_keys, key_count))
-		expect(t, len(m)    == unique_keys, fmt.tprintf("Expected len(map) to equal %v, got %v",  unique_keys, len(m)))
+		testing.expectf(t, key_count == unique_keys, "Expected key_count to equal %v, got %v", unique_keys, key_count)
+		testing.expectf(t, len(m)    == unique_keys, "Expected len(map) to equal %v, got %v",  unique_keys, len(m))
 
 		// Reset randomizer and verify
-		r = rand.create(seed + seed_incr)
+		r = rand.create(t.seed + seed_incr)
 
 		num_fails := 0
 		for _ in 0..<entries {
@@ -238,10 +234,10 @@ set_insert_random_key_value :: proc(t: ^testing.T) {
 			if !cond {
 				num_fails += 1
 				if num_fails > 5 {
-					fmt.println("... and more")
+					log.info("... and more")
 					break
 				}
-				expect(t, false, fmt.tprintf("Unexpected value. Expected m[%v] to exist", k))
+				testing.expectf(t, false, "Unexpected value. Expected m[%v] to exist", k)
 			}
 		}
 		seed_incr += 1
@@ -252,12 +248,12 @@ set_insert_random_key_value :: proc(t: ^testing.T) {
 set_delete_random_key_value :: proc(t: ^testing.T) {
 	seed_incr := u64(0)
 	for entries in ENTRY_COUNTS {
-		fmt.printf("[set_delete_random_key_value] Testing %v entries.\n", entries)
+		log.infof("Testing %v entries", entries)
 		m: map[i64]struct{}
 		defer delete(m)
 
 		unique_keys := 0
-		r := rand.create(seed + seed_incr)
+		r := rand.create(t.seed + seed_incr)
 		for _ in 0..<entries {
 			k := rand.int63(&r)
 
@@ -272,20 +268,20 @@ set_delete_random_key_value :: proc(t: ^testing.T) {
 			key_count += 1
 		}
 
-		expect(t, key_count == unique_keys, fmt.tprintf("Expected key_count to equal %v, got %v", unique_keys, key_count))
-		expect(t, len(m)    == unique_keys, fmt.tprintf("Expected len(map) to equal %v, got %v",  unique_keys, len(m)))
+		testing.expectf(t, key_count == unique_keys, "Expected key_count to equal %v, got %v", unique_keys, key_count)
+		testing.expectf(t, len(m)    == unique_keys, "Expected len(map) to equal %v, got %v",  unique_keys, len(m))
 
 		half_entries := entries / 2
 
 		// Reset randomizer and delete half the entries
-		r = rand.create(seed + seed_incr)
+		r = rand.create(t.seed + seed_incr)
 		for _ in 0..<half_entries {
 			k := rand.int63(&r)
 			delete_key(&m, k)
 		}
 
 		// Reset randomizer and verify
-		r = rand.create(seed + seed_incr)
+		r = rand.create(t.seed + seed_incr)
 
 		num_fails := 0
 		for i in 0..<entries {
@@ -295,88 +291,22 @@ set_delete_random_key_value :: proc(t: ^testing.T) {
 				if k in m {
 					num_fails += 1
 					if num_fails > 5 {
-						fmt.println("... and more")
+						log.info("... and more")
 						break
 					}
-					expect(t, false, fmt.tprintf("Unexpected key present. Expected m[%v] to have been deleted", k))
+					testing.expectf(t, false, "Unexpected key present. Expected m[%v] to have been deleted", k)
 				}
 			} else {
 				if k not_in m {
 					num_fails += 1
 					if num_fails > 5 {
-						fmt.println("... and more")
+						log.info("... and more")
 						break
 					}
-					expect(t, false, fmt.tprintf("Expected key not present. Expected m[%v] to exist", k))
+					testing.expectf(t, false, "Expected key not present. Expected m[%v] to exist", k)
 				}
 			}
 		}
 		seed_incr += 1
 	}
-}
-
-// -------- -------- -------- -------- -------- -------- -------- -------- -------- --------
-
-main :: proc() {
-	t := testing.T{}
-
-	// Allow tests to be repeatable
-	SEED :: #config(SEED, -1)
-	when SEED > 0 {
-		seed = u64(SEED)
-	} else {
-		seed = u64(intrinsics.read_cycle_counter())
-	}
-	fmt.println("Initialized seed to", seed)
-
-	mem_track_test(&t, map_insert_random_key_value)
-	mem_track_test(&t, map_update_random_key_value)
-	mem_track_test(&t, map_delete_random_key_value)
-
-	mem_track_test(&t, set_insert_random_key_value)
-	mem_track_test(&t, set_delete_random_key_value)
-
-	fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
-	if TEST_fail > 0 {
-		os.exit(1)
-	}
-}
-
-mem_track_test :: proc(t: ^testing.T, test: proc(t: ^testing.T)) {
-	track: mem.Tracking_Allocator
-	mem.tracking_allocator_init(&track, context.allocator)
-	context.allocator = mem.tracking_allocator(&track)
-
-	test(t)
-
-	expect(t, len(track.allocation_map) == 0, "Expected no leaks.")
-	expect(t, len(track.bad_free_array) == 0, "Expected no leaks.")
-
-	for _, leak in track.allocation_map {
-		fmt.printf("%v leaked %v bytes\n", leak.location, leak.size)
-	}
-	for bad_free in track.bad_free_array {
-		fmt.printf("%v allocation %p was freed badly\n", bad_free.location, bad_free.memory)
-	}
-}
-
-TEST_count := 0
-TEST_fail  := 0
-
-when ODIN_TEST {
-	expect  :: testing.expect
-	log     :: testing.log
-} else {
-	expect  :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
-		TEST_count += 1
-		if !condition {
-			TEST_fail += 1
-			fmt.printf("[%v] %v\n", loc, message)
-			return
-		}
-	}
-	log     :: proc(t: ^testing.T, v: any, loc := #caller_location) {
-		fmt.printf("[%v] ", loc)
-		fmt.printf("log: %v\n", v)
-	}
-}
+}

+ 6 - 42
tests/internal/test_pow.odin

@@ -1,8 +1,7 @@
 package test_internal_math_pow
 
-import "core:fmt"
+@(require) import "core:log"
 import "core:math"
-import "core:os"
 import "core:testing"
 
 @test
@@ -19,14 +18,14 @@ pow_test :: proc(t: ^testing.T) {
 				// pow2_f64 returns the same float on all platforms because it isn't this stupid
 				_v1 = 0h00000000_00000000
 			}
-			expect(t, _v1 == _v2, fmt.tprintf("Expected math.pow2_f64(%d) == math.pow(2, %d) (= %16x), got %16x", exp, exp, _v1, _v2))
+			testing.expectf(t,  _v1 == _v2, "Expected math.pow2_f64(%d) == math.pow(2, %d) (= %16x), got %16x", exp, exp, _v1, _v2)
 		}
 		{
 			v1 := math.pow(2, f32(exp))
 			v2 := math.pow2_f32(exp)
 			_v1 := transmute(u32)v1
 			_v2 := transmute(u32)v2
-			expect(t, _v1 == _v2, fmt.tprintf("Expected math.pow2_f32(%d) == math.pow(2, %d) (= %08x), got %08x", exp, exp, _v1, _v2))
+			testing.expectf(t,  _v1 == _v2, "Expected math.pow2_f32(%d) == math.pow(2, %d) (= %08x), got %08x", exp, exp, _v1, _v2)
 		}
 		{
 			v1 := math.pow(2, f16(exp))
@@ -36,46 +35,11 @@ pow_test :: proc(t: ^testing.T) {
 
 			when ODIN_OS == .Darwin && ODIN_ARCH == .arm64 {
 				if exp == -25 {
-					testing.logf(t, "skipping known test failure on darwin+arm64, Expected math.pow2_f16(-25) == math.pow(2, -25) (= 0000), got 0001")
+					log.info("skipping known test failure on darwin+arm64, Expected math.pow2_f16(-25) == math.pow(2, -25) (= 0000), got 0001")
 					_v2 = 0
 				}
 			}
-
-			expect(t, _v1 == _v2, fmt.tprintf("Expected math.pow2_f16(%d) == math.pow(2, %d) (= %04x), got %04x", exp, exp, _v1, _v2))
+			testing.expectf(t,  _v1 == _v2, "Expected math.pow2_f16(%d) == math.pow(2, %d) (= %04x), got %04x", exp, exp, _v1, _v2)
 		}
 	}
-}
-
-// -------- -------- -------- -------- -------- -------- -------- -------- -------- --------
-
-main :: proc() {
-	t := testing.T{}
-
-	pow_test(&t)
-
-	fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
-	if TEST_fail > 0 {
-		os.exit(1)
-	}
-}
-
-TEST_count := 0
-TEST_fail  := 0
-
-when ODIN_TEST {
-	expect  :: testing.expect
-	log     :: testing.log
-} else {
-	expect  :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
-		TEST_count += 1
-		if !condition {
-			TEST_fail += 1
-			fmt.printf("[%v] %v\n", loc, message)
-			return
-		}
-	}
-	log     :: proc(t: ^testing.T, v: any, loc := #caller_location) {
-		fmt.printf("[%v] ", loc)
-		fmt.printf("log: %v\n", v)
-	}
-}
+}

+ 7 - 62
tests/internal/test_rtti.odin

@@ -1,11 +1,8 @@
 package test_internal_rtti
 
 import "core:fmt"
-import "core:mem"
-import "core:os"
 import "core:testing"
 
-
 Buggy_Struct :: struct {
 	a: int,
 	b: bool,
@@ -28,74 +25,22 @@ rtti_test :: proc(t: ^testing.T) {
 		for v, i in g_b {
 			checksum += (i+1) * int(v)
 		}
-		expect(t, checksum == 0, fmt.tprintf("Expected g_b to be zero-initialized, got %v", g_b))
+		testing.expectf(t, checksum == 0, "Expected g_b to be zero-initialized, got %v", g_b)
 	}
 	{
 		checksum := 0
 		for v, i in l_b {
 			checksum += (i+1) * int(v)
 		}
-		expect(t, checksum == 0, fmt.tprintf("Expected l_b to be zero-initialized, got %v", l_b))
+		testing.expectf(t, checksum == 0, "Expected l_b to be zero-initialized, got %v", l_b)
 	}
 
-	expect(t, size_of(Buggy_Struct) == 40, fmt.tprintf("Expected size_of(Buggy_Struct) == 40, got %v", size_of(Buggy_Struct)))
-	expect(t, size_of(g_buggy)      == 40, fmt.tprintf("Expected size_of(g_buggy) == 40, got %v", size_of(g_buggy)))
-	expect(t, size_of(l_buggy)      == 40, fmt.tprintf("Expected size_of(l_buggy) == 40, got %v", size_of(l_buggy)))
+	testing.expectf(t, size_of(Buggy_Struct) == 40, "Expected size_of(Buggy_Struct) == 40, got %v", size_of(Buggy_Struct))
+	testing.expectf(t, size_of(g_buggy)      == 40, "Expected size_of(g_buggy) == 40, got %v", size_of(g_buggy))
+	testing.expectf(t, size_of(l_buggy)      == 40, "Expected size_of(l_buggy) == 40, got %v", size_of(l_buggy))
 
 	g_s := fmt.tprintf("%s", g_buggy)
 	l_s := fmt.tprintf("%s", l_buggy)
-	expect(t, g_s == EXPECTED_REPR, fmt.tprintf("Expected fmt.tprintf(\"%%s\", g_s)) to return \"%v\", got \"%v\"", EXPECTED_REPR, g_s))
-	expect(t, l_s == EXPECTED_REPR, fmt.tprintf("Expected fmt.tprintf(\"%%s\", l_s)) to return \"%v\", got \"%v\"", EXPECTED_REPR, l_s))
-}
-
-// -------- -------- -------- -------- -------- -------- -------- -------- -------- --------
-
-main :: proc() {
-	t := testing.T{}
-
-	mem_track_test(&t, rtti_test)
-
-	fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
-	if TEST_fail > 0 {
-		os.exit(1)
-	}
-}
-
-mem_track_test :: proc(t: ^testing.T, test: proc(t: ^testing.T)) {
-	track: mem.Tracking_Allocator
-	mem.tracking_allocator_init(&track, context.allocator)
-	context.allocator = mem.tracking_allocator(&track)
-
-	test(t)
-
-	expect(t, len(track.allocation_map) == 0, "Expected no leaks.")
-	expect(t, len(track.bad_free_array) == 0, "Expected no leaks.")
-
-	for _, leak in track.allocation_map {
-		fmt.printf("%v leaked %v bytes\n", leak.location, leak.size)
-	}
-	for bad_free in track.bad_free_array {
-		fmt.printf("%v allocation %p was freed badly\n", bad_free.location, bad_free.memory)
-	}
-}
-
-TEST_count := 0
-TEST_fail  := 0
-
-when ODIN_TEST {
-	expect  :: testing.expect
-	log     :: testing.log
-} else {
-	expect  :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
-		TEST_count += 1
-		if !condition {
-			TEST_fail += 1
-			fmt.printf("[%v] %v\n", loc, message)
-			return
-		}
-	}
-	log     :: proc(t: ^testing.T, v: any, loc := #caller_location) {
-		fmt.printf("[%v] ", loc)
-		fmt.printf("log: %v\n", v)
-	}
+	testing.expectf(t, g_s == EXPECTED_REPR, "Expected fmt.tprintf(\"%%s\", g_s)) to return \"%v\", got \"%v\"", EXPECTED_REPR, g_s)
+	testing.expectf(t, l_s == EXPECTED_REPR, "Expected fmt.tprintf(\"%%s\", l_s)) to return \"%v\", got \"%v\"", EXPECTED_REPR, l_s)
 }

+ 12 - 48
tests/internal/test_string_compare.odin

@@ -1,7 +1,5 @@
 package test_internal_string_compare
 
-import "core:fmt"
-import "core:os"
 import "core:testing"
 
 Op :: enum { Eq, Lt, Gt }
@@ -29,65 +27,31 @@ string_compare :: proc(t: ^testing.T) {
 		for res, op in v.res {
 			switch op {
 			case .Eq:
-				expect(t, (v.a == v.b) == res,  fmt.tprintf("Expected cstring(\"%v\") == cstring(\"%v\") to be %v", v.a, v.b, res))
-				expect(t, (s_a == s_b) == res,  fmt.tprintf("Expected string(\"%v\") == string(\"%v\") to be %v", v.a, v.b, res))
+				testing.expectf(t, (v.a == v.b) == res,  "Expected cstring(\"%v\") == cstring(\"%v\") to be %v", v.a, v.b, res)
+				testing.expectf(t, (s_a == s_b) == res,  "Expected string(\"%v\") == string(\"%v\") to be %v", v.a, v.b, res)
 
 				// If a == b then a != b
-				expect(t, (v.a != v.b) == !res, fmt.tprintf("Expected cstring(\"%v\") != cstring(\"%v\") to be %v", v.a, v.b, !res))
-				expect(t, (s_a != s_b) == !res, fmt.tprintf("Expected string(\"%v\") != string(\"%v\") to be %v", v.a, v.b, !res))
+				testing.expectf(t, (v.a != v.b) == !res, "Expected cstring(\"%v\") != cstring(\"%v\") to be %v", v.a, v.b, !res)
+				testing.expectf(t, (s_a != s_b) == !res, "Expected string(\"%v\") != string(\"%v\") to be %v", v.a, v.b, !res)
 
 			case .Lt:
-				expect(t, (v.a < v.b) == res,  fmt.tprintf("Expected cstring(\"%v\") < cstring(\"%v\") to be %v", v.a, v.b, res))
-				expect(t, (s_a < s_b) == res,  fmt.tprintf("Expected string(\"%v\") < string(\"%v\") to be %v", v.a, v.b, res))
+				testing.expectf(t, (v.a < v.b) == res,   "Expected cstring(\"%v\") < cstring(\"%v\") to be %v", v.a, v.b, res)
+				testing.expectf(t, (s_a < s_b) == res,   "Expected string(\"%v\") < string(\"%v\") to be %v", v.a, v.b, res)
 
 				// .Lt | .Eq == .LtEq
 				lteq := v.res[.Eq] | res
-				expect(t, (v.a <= v.b) == lteq, fmt.tprintf("Expected cstring(\"%v\") <= cstring(\"%v\") to be %v", v.a, v.b, lteq))
-				expect(t, (s_a <= s_b) == lteq, fmt.tprintf("Expected string(\"%v\") <= string(\"%v\") to be %v", v.a, v.b, lteq))
+				testing.expectf(t, (v.a <= v.b) == lteq, "Expected cstring(\"%v\") <= cstring(\"%v\") to be %v", v.a, v.b, lteq)
+				testing.expectf(t, (s_a <= s_b) == lteq, "Expected string(\"%v\") <= string(\"%v\") to be %v", v.a, v.b, lteq)
 
 			case .Gt:
-				expect(t, (v.a > v.b) == res,  fmt.tprintf("Expected cstring(\"%v\") > cstring(\"%v\") to be %v", v.a, v.b, res))
-				expect(t, (s_a > s_b) == res,  fmt.tprintf("Expected string(\"%v\") > string(\"%v\") to be %v", v.a, v.b, res))
+				testing.expectf(t, (v.a > v.b) == res,   "Expected cstring(\"%v\") > cstring(\"%v\") to be %v", v.a, v.b, res)
+				testing.expectf(t, (s_a > s_b) == res,   "Expected string(\"%v\") > string(\"%v\") to be %v", v.a, v.b, res)
 
 				// .Gt | .Eq == .GtEq
 				gteq := v.res[.Eq] | res
-				expect(t, (v.a >= v.b) == gteq, fmt.tprintf("Expected cstring(\"%v\") >= cstring(\"%v\") to be %v", v.a, v.b, gteq))
-				expect(t, (s_a >= s_b) == gteq, fmt.tprintf("Expected string(\"%v\") >= string(\"%v\") to be %v", v.a, v.b, gteq))
+				testing.expectf(t, (v.a >= v.b) == gteq, "Expected cstring(\"%v\") >= cstring(\"%v\") to be %v", v.a, v.b, gteq)
+				testing.expectf(t, (s_a >= s_b) == gteq, "Expected string(\"%v\") >= string(\"%v\") to be %v", v.a, v.b, gteq)
 			}
 		}
 	}
-}
-
-// -------- -------- -------- -------- -------- -------- -------- -------- -------- --------
-
-main :: proc() {
-	t := testing.T{}
-
-	string_compare(&t)
-
-	fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
-	if TEST_fail > 0 {
-		os.exit(1)
-	}
-}
-
-TEST_count := 0
-TEST_fail  := 0
-
-when ODIN_TEST {
-	expect  :: testing.expect
-	log     :: testing.log
-} else {
-	expect  :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) {
-		TEST_count += 1
-		if !condition {
-			TEST_fail += 1
-			fmt.printf("[%v] %v\n", loc, message)
-			return
-		}
-	}
-	log     :: proc(t: ^testing.T, v: any, loc := #caller_location) {
-		fmt.printf("[%v] ", loc)
-		fmt.printf("log: %v\n", v)
-	}
 }

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است