1#define _POSIX_C_SOURCE 200112L
2#include "minunit.h"
3
4#define NONSTD_IMPLEMENTATION
5#include "nonstd.h"
6
7#include <fcntl.h>
8#include <stdlib.h>
9#include <unistd.h>
10
11// Custom test runner that prints test names
12#define RUN_TEST_WITH_NAME(test) \
13 do { \
14 printf(" %-50s ", #test); \
15 fflush(stdout); \
16 int before_fail = minunit_fail; \
17 int saved_stdout = dup(STDOUT_FILENO); \
18 int devnull = open("/dev/null", O_WRONLY); \
19 dup2(devnull, STDOUT_FILENO); \
20 close(devnull); \
21 MU_RUN_TEST(test); \
22 fflush(stdout); \
23 dup2(saved_stdout, STDOUT_FILENO); \
24 close(saved_stdout); \
25 if (minunit_fail == before_fail) { \
26 printf("PASS\n"); \
27 } \
28 } while (0)
29
30// String view tests
31MU_TEST(test_sv_from_cstr) {
32 stringv sv = sv_from_cstr("hello");
33 mu_assert_int_eq(5, sv.length);
34 mu_check(sv.data != NULL);
35 mu_check(strncmp(sv.data, "hello", 5) == 0);
36}
37
38MU_TEST(test_sv_from_cstr_null) {
39 stringv sv = sv_from_cstr(NULL);
40 mu_assert_int_eq(0, sv.length);
41}
42
43MU_TEST(test_sv_from_parts) {
44 const char *str = "hello world";
45 stringv sv = sv_from_parts(str, 5);
46 mu_assert_int_eq(5, sv.length);
47 mu_check(sv.data == str);
48}
49
50MU_TEST(test_sv_from_cstr_literal) {
51 stringv sv = sv_from_cstr("test");
52 mu_assert_int_eq(4, sv.length);
53 mu_check(strncmp(sv.data, "test", 4) == 0);
54}
55
56MU_TEST(test_sv_slice_normal) {
57 stringv sv = sv_from_cstr("hello world");
58 stringv sliced = sv_slice(sv, 0, 5);
59 mu_assert_int_eq(5, sliced.length);
60 mu_check(strncmp(sliced.data, "hello", 5) == 0);
61}
62
63MU_TEST(test_sv_slice_middle) {
64 stringv sv = sv_from_cstr("hello world");
65 stringv sliced = sv_slice(sv, 6, 11);
66 mu_assert_int_eq(5, sliced.length);
67 mu_check(strncmp(sliced.data, "world", 5) == 0);
68}
69
70MU_TEST(test_sv_slice_out_of_bounds) {
71 stringv sv = sv_from_cstr("hello");
72 stringv sliced = sv_slice(sv, 0, 100);
73 mu_assert_int_eq(5, sliced.length);
74}
75
76MU_TEST(test_sv_slice_invalid_range) {
77 stringv sv = sv_from_cstr("hello");
78 stringv sliced = sv_slice(sv, 10, 5);
79 mu_assert_int_eq(0, sliced.length);
80}
81
82MU_TEST(test_sv_equals_same) {
83 stringv a = sv_from_cstr("hello");
84 stringv b = sv_from_cstr("hello");
85 mu_check(sv_equals(a, b));
86}
87
88MU_TEST(test_sv_equals_different) {
89 stringv a = sv_from_cstr("hello");
90 stringv b = sv_from_cstr("world");
91 mu_check(!sv_equals(a, b));
92}
93
94MU_TEST(test_sv_equals_different_length) {
95 stringv a = sv_from_cstr("hello");
96 stringv b = sv_from_cstr("hi");
97 mu_check(!sv_equals(a, b));
98}
99
100MU_TEST(test_sv_equals_empty) {
101 stringv a = sv_from_parts(NULL, 0);
102 stringv b = sv_from_parts(NULL, 0);
103 mu_check(sv_equals(a, b));
104}
105
106MU_TEST(test_sv_starts_with_true) {
107 stringv sv = sv_from_cstr("hello world");
108 stringv prefix = sv_from_cstr("hello");
109 mu_check(sv_starts_with(sv, prefix));
110}
111
112MU_TEST(test_sv_starts_with_false) {
113 stringv sv = sv_from_cstr("hello world");
114 stringv prefix = sv_from_cstr("world");
115 mu_check(!sv_starts_with(sv, prefix));
116}
117
118MU_TEST(test_sv_starts_with_empty_prefix) {
119 stringv sv = sv_from_cstr("hello");
120 stringv prefix = sv_from_parts(NULL, 0);
121 mu_check(sv_starts_with(sv, prefix));
122}
123
124MU_TEST(test_sv_starts_with_longer_prefix) {
125 stringv sv = sv_from_cstr("hi");
126 stringv prefix = sv_from_cstr("hello");
127 mu_check(!sv_starts_with(sv, prefix));
128}
129
130MU_TEST(test_sv_ends_with_true) {
131 stringv sv = sv_from_cstr("hello world");
132 stringv suffix = sv_from_cstr("world");
133 mu_check(sv_ends_with(sv, suffix));
134}
135
136MU_TEST(test_sv_ends_with_false) {
137 stringv sv = sv_from_cstr("hello world");
138 stringv suffix = sv_from_cstr("hello");
139 mu_check(!sv_ends_with(sv, suffix));
140}
141
142MU_TEST(test_sv_ends_with_empty_suffix) {
143 stringv sv = sv_from_cstr("hello");
144 stringv suffix = sv_from_parts(NULL, 0);
145 mu_check(sv_ends_with(sv, suffix));
146}
147
148MU_TEST(test_sv_ends_with_longer_suffix) {
149 stringv sv = sv_from_cstr("hi");
150 stringv suffix = sv_from_cstr("world");
151 mu_check(!sv_ends_with(sv, suffix));
152}
153
154// Macro tests
155MU_TEST(test_countof) {
156 int array[10];
157 mu_assert_int_eq(10, countof(array));
158
159 char str[] = "hello";
160 mu_assert_int_eq(6, countof(str)); // includes null terminator
161}
162
163MU_TEST(test_min_max_clamp) {
164 mu_assert_int_eq(5, MIN(5, 10));
165 mu_assert_int_eq(5, MIN(10, 5));
166
167 mu_assert_int_eq(10, MAX(5, 10));
168 mu_assert_int_eq(10, MAX(10, 5));
169
170 mu_assert_int_eq(5, CLAMP(3, 5, 10));
171 mu_assert_int_eq(7, CLAMP(7, 5, 10));
172 mu_assert_int_eq(10, CLAMP(15, 5, 10));
173}
174
175MU_TEST(test_static_foreach_macro) {
176 int values[] = {1, 2, 3, 4, 5};
177 int sum = 0;
178 int val;
179
180 static_foreach(int, val, values) { sum += val; }
181
182 mu_assert_int_eq(15, sum);
183}
184
185MU_TEST(test_static_foreach_with_strings) {
186 const char *words[] = {"hello", "world", "test"};
187 int count = 0;
188 const char *word;
189
190 static_foreach(const char *, word, words) {
191 count++;
192 mu_check(word != NULL);
193 }
194
195 mu_assert_int_eq(3, count);
196}
197
198MU_TEST(test_static_foreach_single_element) {
199 int single[] = {42};
200 int result = 0;
201 int val;
202
203 static_foreach(int, val, single) { result = val; }
204
205 mu_assert_int_eq(42, result);
206}
207
208MU_TEST(test_static_foreach_modify_counter) {
209 u32 numbers[] = {10, 20, 30, 40};
210 u32 max = 0;
211 u32 num;
212
213 static_foreach(u32, num, numbers) {
214 if (num > max)
215 max = num;
216 }
217
218 mu_assert_int_eq(40, max);
219}
220
221MU_TEST(test_static_foreach_with_structs) {
222 struct Point {
223 int x, y;
224 };
225
226 struct Point pts[] = {{1, 2}, {3, 4}, {5, 6}};
227 int sum_x = 0;
228 int sum_y = 0;
229 struct Point p;
230
231 static_foreach(struct Point, p, pts) {
232 sum_x += p.x;
233 sum_y += p.y;
234 }
235
236 mu_assert_int_eq(9, sum_x); // 1 + 3 + 5
237 mu_assert_int_eq(12, sum_y); // 2 + 4 + 6
238}
239
240MU_TEST(test_alloc_and_free) {
241 int *ptr = ALLOC(int, 10);
242 mu_check(ptr != NULL);
243
244 // Use the memory
245 ptr[0] = 42;
246 ptr[9] = 99;
247 mu_assert_int_eq(42, ptr[0]);
248 mu_assert_int_eq(99, ptr[9]);
249
250 FREE(ptr);
251 mu_check(ptr == NULL);
252}
253
254MU_TEST(test_realloc) {
255 int *ptr = ALLOC(int, 5);
256 mu_check(ptr != NULL);
257
258 ptr[0] = 1;
259 ptr[4] = 5;
260
261 ptr = REALLOC(ptr, int, 10);
262 mu_check(ptr != NULL);
263
264 mu_assert_int_eq(1, ptr[0]);
265 mu_assert_int_eq(5, ptr[4]);
266 ptr[9] = 99;
267 mu_assert_int_eq(99, ptr[9]);
268
269 free(ptr);
270}
271
272MU_TEST(test_typedefs) {
273 // Just verify the types compile and have expected sizes
274 i8 a = -1;
275 u8 b = 255;
276 i16 c = -1000;
277 u16 d = 60000;
278 i32 e = -100000;
279 u32 f = 4000000000U;
280 i64 g = -1000000000LL;
281 u64 h = 10000000000ULL;
282
283 mu_check(a == -1);
284 mu_check(b == 255);
285 mu_check(c == -1000);
286 mu_check(d == 60000);
287 mu_check(e == -100000);
288 mu_check(f == 4000000000U);
289 mu_check(g == -1000000000LL);
290 mu_check(h == 10000000000ULL);
291}
292
293// String builder tests
294MU_TEST(test_sb_init) {
295 stringb sb;
296 sb_init(&sb, 0);
297 mu_check(sb.data != NULL);
298 mu_assert_int_eq(0, sb.length);
299 mu_check(sb.capacity >= 16); // default capacity
300 mu_assert_int_eq('\0', sb.data[0]);
301 sb_free(&sb);
302}
303
304MU_TEST(test_sb_init_with_capacity) {
305 stringb sb;
306 sb_init(&sb, 64);
307 mu_check(sb.data != NULL);
308 mu_assert_int_eq(0, sb.length);
309 mu_assert_int_eq(64, sb.capacity);
310 sb_free(&sb);
311}
312
313MU_TEST(test_sb_append_cstr) {
314 stringb sb;
315 sb_init(&sb, 0);
316 sb_append_cstr(&sb, "hello");
317 mu_assert_int_eq(5, sb.length);
318 mu_check(strcmp(sb.data, "hello") == 0);
319 sb_free(&sb);
320}
321
322MU_TEST(test_sb_append_cstr_multiple) {
323 stringb sb;
324 sb_init(&sb, 0);
325 sb_append_cstr(&sb, "hello");
326 sb_append_cstr(&sb, " ");
327 sb_append_cstr(&sb, "world");
328 mu_assert_int_eq(11, sb.length);
329 mu_check(strcmp(sb.data, "hello world") == 0);
330 sb_free(&sb);
331}
332
333MU_TEST(test_sb_append_cstr_null) {
334 stringb sb;
335 sb_init(&sb, 0);
336 sb_append_cstr(&sb, "test");
337 sb_append_cstr(&sb, NULL);
338 mu_assert_int_eq(4, sb.length);
339 mu_check(strcmp(sb.data, "test") == 0);
340 sb_free(&sb);
341}
342
343MU_TEST(test_sb_append_sv) {
344 stringb sb;
345 sb_init(&sb, 0);
346 stringv sv = sv_from_cstr("hello");
347 sb_append_sv(&sb, sv);
348 mu_assert_int_eq(5, sb.length);
349 mu_check(strcmp(sb.data, "hello") == 0);
350 sb_free(&sb);
351}
352
353MU_TEST(test_sb_append_sv_slice) {
354 stringb sb;
355 sb_init(&sb, 0);
356 stringv full = sv_from_cstr("hello world");
357 stringv slice = sv_slice(full, 6, 11);
358 sb_append_sv(&sb, slice);
359 mu_assert_int_eq(5, sb.length);
360 mu_check(strcmp(sb.data, "world") == 0);
361 sb_free(&sb);
362}
363
364MU_TEST(test_sb_append_sv_empty) {
365 stringb sb;
366 sb_init(&sb, 0);
367 sb_append_cstr(&sb, "test");
368 stringv empty = sv_from_parts(NULL, 0);
369 sb_append_sv(&sb, empty);
370 mu_assert_int_eq(4, sb.length);
371 mu_check(strcmp(sb.data, "test") == 0);
372 sb_free(&sb);
373}
374
375MU_TEST(test_sb_append_char) {
376 stringb sb;
377 sb_init(&sb, 0);
378 sb_append_char(&sb, 'a');
379 sb_append_char(&sb, 'b');
380 sb_append_char(&sb, 'c');
381 mu_assert_int_eq(3, sb.length);
382 mu_check(strcmp(sb.data, "abc") == 0);
383 sb_free(&sb);
384}
385
386MU_TEST(test_sb_mixed_append) {
387 stringb sb;
388 sb_init(&sb, 0);
389 sb_append_cstr(&sb, "Hello");
390 sb_append_char(&sb, ' ');
391 stringv sv = sv_from_cstr("beautiful");
392 sb_append_sv(&sb, sv);
393 sb_append_char(&sb, ' ');
394 sb_append_cstr(&sb, "world!");
395 mu_assert_int_eq(22, sb.length);
396 mu_check(strcmp(sb.data, "Hello beautiful world!") == 0);
397 sb_free(&sb);
398}
399
400MU_TEST(test_sb_as_sv) {
401 stringb sb;
402 sb_init(&sb, 0);
403 sb_append_cstr(&sb, "test string");
404 stringv sv = sb_as_sv(&sb);
405 mu_assert_int_eq(11, sv.length);
406 mu_check(sv.data == sb.data);
407 mu_check(strncmp(sv.data, "test string", sv.length) == 0);
408 sb_free(&sb);
409}
410
411MU_TEST(test_sb_growth) {
412 stringb sb;
413 sb_init(&sb, 4); // very small initial capacity
414 mu_assert_int_eq(4, sb.capacity);
415
416 // Append enough to trigger growth
417 sb_append_cstr(&sb, "this is a long string that will exceed initial capacity");
418 mu_check(sb.capacity > 4); // capacity should have grown
419 mu_check(sb.length == strlen("this is a long string that will exceed initial capacity"));
420 mu_check(strcmp(sb.data, "this is a long string that will exceed initial capacity") == 0);
421 sb_free(&sb);
422}
423
424MU_TEST(test_sb_free) {
425 stringb sb;
426 sb_init(&sb, 32);
427 sb_append_cstr(&sb, "test");
428 sb_free(&sb);
429 mu_check(sb.data == NULL);
430 mu_assert_int_eq(0, sb.length);
431 mu_assert_int_eq(0, sb.capacity);
432}
433
434MU_TEST(test_sb_sv_interop) {
435 // Test that sb can be used to build a string, then viewed with sv
436 stringb sb;
437 sb_init(&sb, 0);
438
439 const char *words[] = {"one", "two", "three"};
440 const char *word;
441 static_foreach(const char *, word, words) {
442 if (sb.length > 0) {
443 sb_append_char(&sb, ',');
444 }
445 sb_append_cstr(&sb, word);
446 }
447
448 stringv result = sb_as_sv(&sb);
449 mu_check(sv_equals(result, sv_from_cstr("one,two,three")));
450
451 // Can use sv operations on the builder's content
452 mu_check(sv_starts_with(result, sv_from_cstr("one")));
453 mu_check(sv_ends_with(result, sv_from_cstr("three")));
454
455 sb_free(&sb);
456}
457
458MU_TEST(test_sb_append_sv_from_sb) {
459 // Test appending a string view of one builder to another
460 stringb sb1, sb2;
461 sb_init(&sb1, 0);
462 sb_init(&sb2, 0);
463
464 sb_append_cstr(&sb1, "hello");
465 sb_append_cstr(&sb2, "world");
466
467 stringv sv1 = sb_as_sv(&sb1);
468 sb_append_char(&sb2, ' ');
469 sb_append_sv(&sb2, sv1);
470
471 mu_check(strcmp(sb2.data, "world hello") == 0);
472
473 sb_free(&sb1);
474 sb_free(&sb2);
475}
476
477// Array tests
478MU_TEST(test_array_init) {
479 array(int) arr;
480 array_init(arr);
481 mu_check(arr.data == NULL);
482 mu_assert_int_eq(0, arr.length);
483 mu_assert_int_eq(0, arr.capacity);
484 array_free(arr);
485}
486
487MU_TEST(test_array_init_cap) {
488 array(int) arr;
489 array_init_cap(arr, 32);
490 mu_check(arr.data != NULL);
491 mu_assert_int_eq(0, arr.length);
492 mu_assert_int_eq(32, arr.capacity);
493 array_free(arr);
494}
495
496MU_TEST(test_array_push) {
497 array(int) arr;
498 array_init(arr);
499 array_push(arr, 10);
500 array_push(arr, 20);
501 array_push(arr, 30);
502
503 mu_assert_int_eq(3, arr.length);
504 mu_check(arr.capacity >= 3);
505 mu_assert_int_eq(10, arr.data[0]);
506 mu_assert_int_eq(20, arr.data[1]);
507 mu_assert_int_eq(30, arr.data[2]);
508
509 array_free(arr);
510}
511
512MU_TEST(test_array_pop) {
513 array(int) arr;
514 array_init(arr);
515 array_push(arr, 10);
516 array_push(arr, 20);
517
518 int val = array_pop(arr);
519 mu_assert_int_eq(20, val);
520 mu_assert_int_eq(1, arr.length);
521
522 val = array_pop(arr);
523 mu_assert_int_eq(10, val);
524 mu_assert_int_eq(0, arr.length);
525
526 array_free(arr);
527}
528
529MU_TEST(test_array_pop_empty) {
530 array(int) arr;
531 array_init(arr);
532 int val = array_pop(arr);
533 mu_assert_int_eq(0, val);
534 mu_assert_int_eq(0, arr.length);
535 array_free(arr);
536}
537
538MU_TEST(test_array_get_set) {
539 array(int) arr;
540 array_init(arr);
541 array_push(arr, 10);
542 array_push(arr, 20);
543
544 mu_assert_int_eq(10, array_get(arr, 0));
545 mu_assert_int_eq(20, array_get(arr, 1));
546
547 array_set(arr, 0, 100);
548 mu_assert_int_eq(100, array_get(arr, 0));
549
550 // Test out of bounds set (should do nothing)
551 array_set(arr, 5, 500);
552 mu_assert_int_eq(2, arr.length);
553
554 array_free(arr);
555}
556
557MU_TEST(test_array_insert) {
558 array(int) arr;
559 array_init(arr);
560 array_push(arr, 10);
561 array_push(arr, 30);
562
563 array_insert(arr, 1, 20); // Insert between 10 and 30
564 mu_assert_int_eq(3, arr.length);
565 mu_assert_int_eq(10, arr.data[0]);
566 mu_assert_int_eq(20, arr.data[1]);
567 mu_assert_int_eq(30, arr.data[2]);
568
569 array_insert(arr, 0, 5); // Insert at start
570 mu_assert_int_eq(5, arr.data[0]);
571 mu_assert_int_eq(10, arr.data[1]);
572
573 array_insert(arr, 4, 40); // Insert at end
574 mu_assert_int_eq(40, arr.data[4]);
575 mu_assert_int_eq(5, arr.length);
576
577 array_free(arr);
578}
579
580MU_TEST(test_array_remove) {
581 array(int) arr;
582 array_init(arr);
583 array_push(arr, 10);
584 array_push(arr, 20);
585 array_push(arr, 30);
586 array_push(arr, 40);
587
588 array_remove(arr, 1); // Remove 20
589 mu_assert_int_eq(3, arr.length);
590 mu_assert_int_eq(10, arr.data[0]);
591 mu_assert_int_eq(30, arr.data[1]);
592 mu_assert_int_eq(40, arr.data[2]);
593
594 array_remove(arr, 0); // Remove 10 (start)
595 mu_assert_int_eq(30, arr.data[0]);
596
597 array_remove(arr, 1); // Remove 40 (end)
598 mu_assert_int_eq(30, arr.data[0]);
599 mu_assert_int_eq(1, arr.length);
600
601 array_free(arr);
602}
603
604MU_TEST(test_array_growth) {
605 array(int) arr;
606 array_init_cap(arr, 4);
607
608 for (int i = 0; i < 20; i++) {
609 array_push(arr, i);
610 }
611
612 mu_assert_int_eq(20, arr.length);
613 mu_check(arr.capacity >= 20);
614 mu_check(arr.capacity > 4);
615
616 for (int i = 0; i < 20; i++) {
617 mu_assert_int_eq(i, arr.data[i]);
618 }
619
620 array_free(arr);
621}
622
623MU_TEST(test_array_reserve) {
624 array(int) arr;
625 array_init(arr);
626
627 array_reserve(arr, 100);
628 mu_assert_int_eq(100, arr.capacity);
629 mu_assert_int_eq(0, arr.length);
630 mu_check(arr.data != NULL);
631
632 array_free(arr);
633}
634
635MU_TEST(test_array_clear) {
636 array(int) arr;
637 array_init(arr);
638 array_push(arr, 1);
639 array_push(arr, 2);
640
641 size_t cap = arr.capacity;
642 array_clear(arr);
643
644 mu_assert_int_eq(0, arr.length);
645 mu_assert_int_eq(cap, arr.capacity); // Capacity stays
646 mu_check(arr.data != NULL);
647
648 array_free(arr);
649}
650
651MU_TEST(test_array_foreach) {
652 array(int) arr;
653 array_init(arr);
654 array_push(arr, 1);
655 array_push(arr, 2);
656 array_push(arr, 3);
657
658 int sum = 0;
659 int val;
660 array_foreach(arr, val) { sum += val; }
661
662 mu_assert_int_eq(6, sum);
663 array_free(arr);
664}
665
666MU_TEST(test_array_foreach_idx) {
667 array(int) arr;
668 array_init(arr);
669 array_push(arr, 10);
670 array_push(arr, 20);
671 array_push(arr, 30);
672
673 int sum_val = 0;
674 size_t sum_idx = 0;
675 int val;
676
677 array_foreach_idx(arr, val, i) {
678 sum_val += val;
679 sum_idx += i;
680 mu_assert_int_eq(val, arr.data[i]);
681 }
682
683 mu_assert_int_eq(60, sum_val);
684 mu_assert_int_eq(3, sum_idx); // 0+1+2
685 array_free(arr);
686}
687
688// Slice Tests
689SLICE_DEF(int);
690
691MU_TEST(test_slice_make) {
692 int nums[] = {1, 2, 3, 4, 5};
693 slice(int) s = make_slice(int, nums, 5);
694
695 mu_assert_int_eq(5, s.length);
696 mu_check(s.data == nums);
697 mu_assert_int_eq(1, s.data[0]);
698 mu_assert_int_eq(5, s.data[4]);
699}
700
701MU_TEST(test_slice_make_partial) {
702 int nums[] = {10, 20, 30, 40, 50};
703 slice(int) s = make_slice(int, nums + 1, 3); // 20, 30, 40
704
705 mu_assert_int_eq(3, s.length);
706 mu_assert_int_eq(20, s.data[0]);
707 mu_assert_int_eq(40, s.data[2]);
708}
709
710MU_TEST(test_slice_from_dynamic_array) {
711 array(int) arr;
712 array_init(arr);
713 array_push(arr, 100);
714 array_push(arr, 200);
715
716 slice(int) s = array_as_slice(int, arr);
717
718 mu_assert_int_eq(2, s.length);
719 mu_check(s.data == arr.data);
720 mu_assert_int_eq(100, s.data[0]);
721
722 array_free(arr);
723}
724
725MU_TEST(test_slice_modification) {
726 int nums[] = {1, 1, 1};
727 slice(int) s = make_slice(int, nums, 3);
728
729 s.data[1] = 99;
730
731 mu_assert_int_eq(99, nums[1]);
732 mu_assert_int_eq(1, nums[0]);
733}
734
735// Arena tests
736MU_TEST(test_arena_basic) {
737 Arena a = arena_make();
738 void *p1 = arena_alloc(&a, 10);
739 void *p2 = arena_alloc(&a, 20);
740
741 mu_check(p1 != NULL);
742 mu_check(p2 != NULL);
743 mu_check(p1 != p2);
744
745 // Basic usage check
746 memset(p1, 1, 10);
747 memset(p2, 2, 20);
748
749 arena_free(&a);
750}
751
752MU_TEST(test_arena_growth) {
753 Arena a = arena_make();
754
755 // Force growth by allocating more than default block size (4096)
756 // or by allocating many small objects.
757 // Let's alloc a big chunk first.
758 void *big = arena_alloc(&a, 5000);
759 mu_check(big != NULL);
760 mu_check(a.blocks.length >= 1);
761
762 // Alloc another big chunk
763 void *big2 = arena_alloc(&a, 5000);
764 mu_check(big2 != NULL);
765 mu_check(big2 != big);
766 mu_check(a.blocks.length >= 2);
767
768 arena_free(&a);
769}
770
771MU_TEST(test_arena_alignment) {
772 Arena a = arena_make();
773
774 void *p1 = arena_alloc(&a, 1); // 1 byte
775 void *p2 = arena_alloc(&a, 1); // 1 byte
776
777 uintptr_t addr1 = (uintptr_t)p1;
778 uintptr_t addr2 = (uintptr_t)p2;
779
780 mu_check(addr1 % sizeof(void *) == 0);
781 mu_check(addr2 % sizeof(void *) == 0);
782 mu_check(addr2 - addr1 >= sizeof(void *));
783
784 arena_free(&a);
785}
786
787MU_TEST(test_arena_safety) {
788 Arena a = arena_make();
789
790 // Test 1: Integer overflow in allocation should be caught by safe_alloc
791 // Requesting 2 items of size (SIZE_MAX/2 + 1) should fail
792 // This tests ns_safe_malloc directly or via macro if we exposed it,
793 // but arena_alloc calls ensure/grow which calls ALLOC(char, size).
794 // To test ALLOC overflow: ALLOC(char, SIZE_MAX) is just 1*SIZE_MAX.
795 // We need a way to trigger safe_alloc failure.
796
797 // Let's test the underlying safe allocator directly since we exposed it in nonstd.h
798 void *p = safe_malloc(SIZE_MAX / 2 + 100, 2);
799 mu_check(p == NULL);
800
801 // Test 2: Ensure normal allocations still work
802 void *p2 = arena_alloc(&a, 100);
803 mu_check(p2 != NULL);
804
805 arena_free(&a);
806}
807
808// File I/O tests
809MU_TEST(test_file_io_basic) {
810 const char *filename = "test_io_basic.txt";
811 const char *content = "Hello, file!";
812 size_t len = strlen(content);
813
814 // Write
815 mu_check(write_entire_file(filename, content, len));
816
817 // Read
818 size_t read_len;
819 char *read_content = read_entire_file(filename, &read_len);
820 mu_check(read_content != NULL);
821 mu_assert_int_eq(len, read_len);
822 mu_check(strncmp(content, read_content, len) == 0);
823
824 FREE(read_content);
825 remove(filename);
826}
827
828MU_TEST(test_file_io_sb) {
829 const char *filename = "test_io_sb.txt";
830 stringb sb;
831 sb_init(&sb, 0);
832 sb_append_cstr(&sb, "Hello from sb!");
833
834 // Write
835 mu_check(write_file_sb(filename, &sb));
836
837 // Read
838 stringb read_sb = read_entire_file_sb(filename);
839 mu_check(read_sb.data != NULL);
840 mu_assert_int_eq(sb.length, read_sb.length);
841 mu_check(strcmp(sb.data, read_sb.data) == 0);
842
843 sb_free(&sb);
844 sb_free(&read_sb);
845 remove(filename);
846}
847
848MU_TEST(test_file_io_read_missing) {
849 size_t len;
850 char *content = read_entire_file("non_existent_file.txt", &len);
851 mu_check(content == NULL);
852}
853
854MU_TEST(test_file_io_read_missing_sb) {
855 stringb sb = read_entire_file_sb("non_existent_file.txt");
856 mu_check(sb.data == NULL);
857 mu_assert_int_eq(0, sb.length);
858}
859
860// Logging tests
861MU_TEST(test_logging_level_filtering) {
862 FILE *tmp = tmpfile();
863 mu_check(tmp != NULL);
864
865 set_log_level(LOG_WARN);
866
867 log_message(tmp, LOG_INFO, "Info message"); // Should not be logged
868 log_message(tmp, LOG_ERROR, "Error message"); // Should be logged
869
870 rewind(tmp);
871 char buffer[1024];
872 size_t read = fread(buffer, 1, sizeof(buffer), tmp);
873 buffer[read] = '\0';
874
875 mu_check(strstr(buffer, "Info message") == NULL);
876 mu_check(strstr(buffer, "Error message") != NULL);
877 mu_check(strstr(buffer, "[ERROR]") != NULL);
878
879 fclose(tmp);
880}
881
882MU_TEST(test_logging_env_level) {
883 // Enum: ERROR=0, WARN=1, INFO=2, DEBUG=3
884 setenv("LOG_LEVEL", "0", 1); // ERROR
885 mu_assert_int_eq(LOG_ERROR, get_log_level_from_env());
886
887 setenv("LOG_LEVEL", "3", 1); // DEBUG
888 mu_assert_int_eq(LOG_DEBUG, get_log_level_from_env());
889
890 // Invalid level
891 set_log_level(LOG_INFO); // Reset
892 setenv("LOG_LEVEL", "99", 1);
893 mu_assert_int_eq(LOG_INFO, get_log_level_from_env()); // Should return current max_level (defaults check)
894
895 unsetenv("LOG_LEVEL");
896}
897
898MU_TEST(test_logging_format) {
899 FILE *tmp = tmpfile();
900 mu_check(tmp != NULL);
901
902 set_log_level(LOG_INFO);
903 log_message(tmp, LOG_INFO, "Test %d %s", 123, "format");
904
905 rewind(tmp);
906 char buffer[1024];
907 size_t read = fread(buffer, 1, sizeof(buffer), tmp);
908 buffer[read] = '\0';
909
910 mu_check(strstr(buffer, "Test 123 format") != NULL);
911 mu_check(strstr(buffer, "[INFO ]") != NULL);
912 // Check timestamp format roughly (YYYY-MM-DD)
913 mu_check(strstr(buffer, "20") != NULL);
914
915 fclose(tmp);
916}
917
918// Image tests
919MU_TEST(test_ppm_init_free) {
920 Canvas img = ppm_init(100, 100);
921 mu_assert_int_eq(100, (int)img.width);
922 mu_assert_int_eq(100, (int)img.height);
923 mu_check(img.pixels != NULL);
924 ppm_free(&img);
925 mu_assert_int_eq(0, (int)img.width);
926 mu_assert_int_eq(0, (int)img.height);
927 mu_check(img.pixels == NULL);
928}
929
930MU_TEST(test_ppm_set_get_pixel) {
931 Canvas img = ppm_init(10, 10);
932 Color c = {255, 128, 64};
933 ppm_set_pixel(&img, 5, 5, c);
934 Color got = ppm_get_pixel(&img, 5, 5);
935 mu_assert_int_eq(255, got.r);
936 mu_assert_int_eq(128, got.g);
937 mu_assert_int_eq(64, got.b);
938
939 // Test out of bounds (should return black)
940 got = ppm_get_pixel(&img, 100, 100);
941 mu_assert_int_eq(0, got.r);
942
943 ppm_free(&img);
944}
945
946MU_TEST(test_ppm_save_read) {
947 Canvas img = ppm_init(10, 10);
948 for (u32 y = 0; y < 10; ++y) {
949 for (u32 x = 0; x < 10; ++x) {
950 ppm_set_pixel(&img, x, y, (Color){(u8)(x * 20), (u8)(y * 20), 100});
951 }
952 }
953
954 const char *tmp_ppm = "test_image.ppm";
955 mu_check(ppm_save(&img, tmp_ppm));
956
957 Canvas read = ppm_read(tmp_ppm);
958 mu_assert_int_eq((int)img.width, (int)read.width);
959 mu_assert_int_eq((int)img.height, (int)read.height);
960 mu_check(read.pixels != NULL);
961
962 for (u32 i = 0; i < 100; ++i) {
963 mu_assert_int_eq(img.pixels[i].r, read.pixels[i].r);
964 mu_assert_int_eq(img.pixels[i].g, read.pixels[i].g);
965 mu_assert_int_eq(img.pixels[i].b, read.pixels[i].b);
966 }
967
968 ppm_free(&img);
969 ppm_free(&read);
970 remove(tmp_ppm);
971}
972
973MU_TEST(test_ppm_draw_helpers) {
974 Canvas img = ppm_init(100, 100);
975
976 // Test fill
977 ppm_fill(&img, COLOR_RED);
978 Color c1 = ppm_get_pixel(&img, 0, 0);
979 mu_assert_int_eq(255, c1.r);
980 mu_assert_int_eq(0, c1.g);
981
982 // Test rect
983 ppm_fill(&img, COLOR_BLACK);
984 ppm_draw_rect(&img, 10, 10, 20, 20, COLOR_WHITE);
985 mu_assert_int_eq(255, ppm_get_pixel(&img, 10, 10).r);
986 mu_assert_int_eq(0, ppm_get_pixel(&img, 15, 15).r); // Inside should be black
987
988 // Test line
989 ppm_fill(&img, COLOR_BLACK);
990 ppm_draw_line(&img, 0, 0, 10, 10, COLOR_GREEN);
991 mu_assert_int_eq(255, ppm_get_pixel(&img, 5, 5).g);
992
993 // Test hexagon/color macros
994 Color hex = COLOR_HEX(0x112233);
995 mu_assert_int_eq(0x11, hex.r);
996 mu_assert_int_eq(0x22, hex.g);
997 mu_assert_int_eq(0x33, hex.b);
998
999 ppm_free(&img);
1000}
1001
1002// Test suites
1003MU_TEST_SUITE(test_suite_stringv) {
1004 printf("\n[String View Tests]\n");
1005 RUN_TEST_WITH_NAME(test_sv_from_cstr);
1006 RUN_TEST_WITH_NAME(test_sv_from_cstr_null);
1007 RUN_TEST_WITH_NAME(test_sv_from_parts);
1008 RUN_TEST_WITH_NAME(test_sv_from_cstr_literal);
1009 RUN_TEST_WITH_NAME(test_sv_slice_normal);
1010 RUN_TEST_WITH_NAME(test_sv_slice_middle);
1011 RUN_TEST_WITH_NAME(test_sv_slice_out_of_bounds);
1012 RUN_TEST_WITH_NAME(test_sv_slice_invalid_range);
1013 RUN_TEST_WITH_NAME(test_sv_equals_same);
1014 RUN_TEST_WITH_NAME(test_sv_equals_different);
1015 RUN_TEST_WITH_NAME(test_sv_equals_different_length);
1016 RUN_TEST_WITH_NAME(test_sv_equals_empty);
1017 RUN_TEST_WITH_NAME(test_sv_starts_with_true);
1018 RUN_TEST_WITH_NAME(test_sv_starts_with_false);
1019 RUN_TEST_WITH_NAME(test_sv_starts_with_empty_prefix);
1020 RUN_TEST_WITH_NAME(test_sv_starts_with_longer_prefix);
1021 RUN_TEST_WITH_NAME(test_sv_ends_with_true);
1022 RUN_TEST_WITH_NAME(test_sv_ends_with_false);
1023 RUN_TEST_WITH_NAME(test_sv_ends_with_empty_suffix);
1024 RUN_TEST_WITH_NAME(test_sv_ends_with_longer_suffix);
1025}
1026
1027MU_TEST_SUITE(test_suite_stringb) {
1028 printf("\n[String Builder Tests]\n");
1029 RUN_TEST_WITH_NAME(test_sb_init);
1030 RUN_TEST_WITH_NAME(test_sb_init_with_capacity);
1031 RUN_TEST_WITH_NAME(test_sb_append_cstr);
1032 RUN_TEST_WITH_NAME(test_sb_append_cstr_multiple);
1033 RUN_TEST_WITH_NAME(test_sb_append_cstr_null);
1034 RUN_TEST_WITH_NAME(test_sb_append_sv);
1035 RUN_TEST_WITH_NAME(test_sb_append_sv_slice);
1036 RUN_TEST_WITH_NAME(test_sb_append_sv_empty);
1037 RUN_TEST_WITH_NAME(test_sb_append_char);
1038 RUN_TEST_WITH_NAME(test_sb_mixed_append);
1039 RUN_TEST_WITH_NAME(test_sb_as_sv);
1040 RUN_TEST_WITH_NAME(test_sb_growth);
1041 RUN_TEST_WITH_NAME(test_sb_free);
1042 RUN_TEST_WITH_NAME(test_sb_sv_interop);
1043 RUN_TEST_WITH_NAME(test_sb_append_sv_from_sb);
1044}
1045
1046MU_TEST_SUITE(test_suite_macros) {
1047 printf("\n[Macro Tests]\n");
1048 RUN_TEST_WITH_NAME(test_countof);
1049 RUN_TEST_WITH_NAME(test_min_max_clamp);
1050 RUN_TEST_WITH_NAME(test_static_foreach_macro);
1051 RUN_TEST_WITH_NAME(test_static_foreach_with_strings);
1052 RUN_TEST_WITH_NAME(test_static_foreach_single_element);
1053 RUN_TEST_WITH_NAME(test_static_foreach_modify_counter);
1054 RUN_TEST_WITH_NAME(test_static_foreach_with_structs);
1055 RUN_TEST_WITH_NAME(test_alloc_and_free);
1056 RUN_TEST_WITH_NAME(test_realloc);
1057}
1058
1059MU_TEST_SUITE(test_suite_array) {
1060 printf("\n[Array Tests]\n");
1061 RUN_TEST_WITH_NAME(test_array_init);
1062 RUN_TEST_WITH_NAME(test_array_init_cap);
1063 RUN_TEST_WITH_NAME(test_array_push);
1064 RUN_TEST_WITH_NAME(test_array_pop);
1065 RUN_TEST_WITH_NAME(test_array_pop_empty);
1066 RUN_TEST_WITH_NAME(test_array_get_set);
1067 RUN_TEST_WITH_NAME(test_array_insert);
1068 RUN_TEST_WITH_NAME(test_array_remove);
1069 RUN_TEST_WITH_NAME(test_array_growth);
1070 RUN_TEST_WITH_NAME(test_array_reserve);
1071 RUN_TEST_WITH_NAME(test_array_clear);
1072 RUN_TEST_WITH_NAME(test_array_foreach);
1073 RUN_TEST_WITH_NAME(test_array_foreach_idx);
1074}
1075
1076MU_TEST_SUITE(test_suite_slice) {
1077 printf("\n[Slice Tests]\n");
1078 RUN_TEST_WITH_NAME(test_slice_make);
1079 RUN_TEST_WITH_NAME(test_slice_make_partial);
1080 RUN_TEST_WITH_NAME(test_slice_from_dynamic_array);
1081 RUN_TEST_WITH_NAME(test_slice_modification);
1082}
1083
1084MU_TEST_SUITE(test_suite_types) {
1085 printf("\n[Type Tests]\n");
1086 RUN_TEST_WITH_NAME(test_typedefs);
1087}
1088
1089MU_TEST_SUITE(test_suite_arena) {
1090 printf("\n[Arena Tests]\n");
1091 RUN_TEST_WITH_NAME(test_arena_basic);
1092 RUN_TEST_WITH_NAME(test_arena_growth);
1093 RUN_TEST_WITH_NAME(test_arena_alignment);
1094 RUN_TEST_WITH_NAME(test_arena_safety);
1095}
1096
1097MU_TEST_SUITE(test_suite_files) {
1098 printf("\n[File I/O Tests]\n");
1099 RUN_TEST_WITH_NAME(test_file_io_basic);
1100 RUN_TEST_WITH_NAME(test_file_io_sb);
1101 RUN_TEST_WITH_NAME(test_file_io_read_missing);
1102 RUN_TEST_WITH_NAME(test_file_io_read_missing_sb);
1103}
1104
1105MU_TEST_SUITE(test_suite_logging) {
1106 printf("\n[Logging Tests]\n");
1107 RUN_TEST_WITH_NAME(test_logging_level_filtering);
1108 RUN_TEST_WITH_NAME(test_logging_env_level);
1109 RUN_TEST_WITH_NAME(test_logging_format);
1110}
1111
1112MU_TEST_SUITE(test_suite_image) {
1113 printf("\n[Image Tests]\n");
1114 RUN_TEST_WITH_NAME(test_ppm_init_free);
1115 RUN_TEST_WITH_NAME(test_ppm_set_get_pixel);
1116 RUN_TEST_WITH_NAME(test_ppm_save_read);
1117 RUN_TEST_WITH_NAME(test_ppm_draw_helpers);
1118}
1119
1120int main(int argc, char *argv[]) {
1121 (void)argc;
1122 (void)argv;
1123
1124 MU_RUN_SUITE(test_suite_stringv);
1125 MU_RUN_SUITE(test_suite_stringb);
1126 MU_RUN_SUITE(test_suite_macros);
1127 MU_RUN_SUITE(test_suite_array);
1128 MU_RUN_SUITE(test_suite_slice);
1129 MU_RUN_SUITE(test_suite_types);
1130 MU_RUN_SUITE(test_suite_arena);
1131 MU_RUN_SUITE(test_suite_files);
1132 MU_RUN_SUITE(test_suite_logging);
1133 MU_RUN_SUITE(test_suite_image);
1134
1135 MU_REPORT();
1136
1137 return MU_EXIT_CODE;
1138}