kit_unit.h (8188B)
1 /* test/lib/kit_unit.h — shared scaffolding for kit C unit tests. 2 * 3 * The unit suite copy-pasted the same prologue into ~18 files: a 4 * malloc/realloc/free shim wrapping the host allocator into a KitHeap, a 5 * diagnostic sink that prints to stderr, an EXPECT/CHECK macro, a 6 * KitTargetSpec + KitTarget + KitContext + kit_compiler_new dance, and a 7 * pass/fail counter checked in main(). This header folds all of that into one 8 * stack-resident KitUnit context. 9 * 10 * Design constraints honored: 11 * - No global mutable state: every test threads a KitUnit it owns 12 * (matches the project's "everything hangs off a context struct" rule). 13 * - Public headers only (<kit/core.h>), so the header compiles 14 * unchanged in BOTH linkage regimes: linking the public archive 15 * (LIB_AR, where ld -r has hidden internal symbols) and linking the raw 16 * objects (LIB_OBJS + -Isrc, internal symbols visible). It never names 17 * an internal symbol, so a test that additionally pulls internal 18 * headers (its own #include "abi/abi.h" etc.) is unaffected. 19 * - Every helper is `static inline` so a TU that uses only part of the 20 * API does not trip -Werror,-Wunused-function (verified: unused 21 * `static inline` is warning-clean; unused bare `static` is not). 22 * 23 * The build rule adds -Itest, so include as: 24 * #include "lib/kit_unit.h" 25 */ 26 27 #ifndef KIT_TEST_LIB_KIT_UNIT_H 28 #define KIT_TEST_LIB_KIT_UNIT_H 29 30 #include <kit/core.h> 31 #include <stdarg.h> 32 #include <stdio.h> 33 #include <stdlib.h> 34 #include <string.h> 35 36 /* One context per test. Zero-initialize with kit_unit_init. The diag sink 37 * captures the most recent message body into last_diag (before any 38 * suppression), which expected-panic tests strstr() against. */ 39 typedef struct KitUnitTargetNode { 40 KitTarget* target; 41 struct KitUnitTargetNode* next; 42 } KitUnitTargetNode; 43 44 typedef struct KitUnit { 45 KitHeap heap; 46 KitDiagSink diag; 47 KitContext ctx; 48 KitUnitTargetNode* targets; 49 char last_diag[256]; 50 int suppress_fatal; /* when set, FATAL diagnostics are captured but not 51 * printed — used by tests that drive a deliberate 52 * panic and assert on last_diag. */ 53 int checks; /* total CU_CHECK/CU_EXPECT evaluations */ 54 int fails; /* subset that failed */ 55 } KitUnit; 56 57 static inline void* kit_unit_alloc(KitHeap* h, size_t n, size_t a) { 58 (void)h; 59 (void)a; 60 return n ? malloc(n) : NULL; 61 } 62 63 static inline void* kit_unit_realloc(KitHeap* h, void* p, size_t old_n, 64 size_t n, size_t a) { 65 (void)h; 66 (void)old_n; 67 (void)a; 68 return realloc(p, n); 69 } 70 71 static inline void kit_unit_free(KitHeap* h, void* p, size_t n) { 72 (void)h; 73 (void)n; 74 free(p); 75 } 76 77 static inline void kit_unit_diag_emit(KitDiagSink* s, KitDiagKind k, 78 KitSrcLoc loc, const char* fmt, 79 va_list ap) { 80 static const char* const names[] = {"note", "warning", "error", "fatal"}; 81 KitUnit* u = (KitUnit*)s->user; 82 va_list copy; 83 (void)loc; 84 /* Capture the message body first so it is available even when suppressed. */ 85 if (u) { 86 va_copy(copy, ap); 87 vsnprintf(u->last_diag, sizeof u->last_diag, fmt, copy); 88 va_end(copy); 89 if (u->suppress_fatal && k == KIT_DIAG_FATAL) return; 90 } 91 fprintf(stderr, "%s: ", (unsigned)k < 4u ? names[k] : "diag"); 92 vfprintf(stderr, fmt, ap); 93 fputc('\n', stderr); 94 } 95 96 /* Wire heap + diag + ctx onto u and zero the counters. ctx.now is set to 0 97 * (the value the memset-to-zero majority of the suite already used); a test 98 * that needs a specific clock may overwrite u->ctx.now afterward. */ 99 static inline void kit_unit_init(KitUnit* u) { 100 memset(u, 0, sizeof *u); 101 u->heap.alloc = kit_unit_alloc; 102 u->heap.realloc = kit_unit_realloc; 103 u->heap.free = kit_unit_free; 104 u->diag.emit = kit_unit_diag_emit; 105 u->diag.user = u; 106 u->ctx.heap = &u->heap; 107 u->ctx.diag = &u->diag; 108 u->ctx.now = 0; 109 } 110 111 static inline KitTargetSpec kit_unit_target(KitArchKind arch, KitOSKind os, 112 KitObjFmt obj) { 113 KitTargetSpec t; 114 memset(&t, 0, sizeof t); 115 t.arch = arch; 116 t.os = os; 117 t.obj = obj; 118 t.ptr_size = 8; 119 t.ptr_align = 8; 120 return t; 121 } 122 123 static inline KitStatus kit_unit_compiler_new(KitUnit* u, KitTargetSpec t, 124 KitCompiler** out) { 125 KitTargetOptions opts; 126 KitTarget* target = NULL; 127 KitUnitTargetNode* node = NULL; 128 KitStatus st; 129 if (out) *out = NULL; 130 memset(&opts, 0, sizeof opts); 131 opts.spec = t; 132 st = kit_target_new(&u->ctx, &opts, &target); 133 if (st != KIT_OK) return st; 134 node = (KitUnitTargetNode*)kit_unit_alloc(&u->heap, sizeof(*node), 135 _Alignof(KitUnitTargetNode)); 136 if (!node) { 137 kit_target_free(target); 138 return KIT_NOMEM; 139 } 140 st = kit_compiler_new(target, &u->ctx, out); 141 if (st != KIT_OK) { 142 kit_unit_free(&u->heap, node, sizeof(*node)); 143 kit_target_free(target); 144 return st; 145 } 146 node->target = target; 147 node->next = u->targets; 148 u->targets = node; 149 return KIT_OK; 150 } 151 152 static inline void kit_unit_fini(KitUnit* u) { 153 KitUnitTargetNode* n = u ? u->targets : NULL; 154 while (n) { 155 KitUnitTargetNode* next = n->next; 156 kit_target_free(n->target); 157 kit_unit_free(&u->heap, n, sizeof(*n)); 158 n = next; 159 } 160 if (u) u->targets = NULL; 161 } 162 163 /* Byte-substring search, for tests asserting emitted machine code contains a 164 * given encoding. Returns 1 if pat occurs in data (empty pat matches). */ 165 static inline int kit_unit_contains(const uint8_t* data, size_t len, 166 const uint8_t* pat, size_t pat_len) { 167 size_t i; 168 if (pat_len == 0) return 1; 169 if (len < pat_len) return 0; 170 for (i = 0; i <= len - pat_len; ++i) { 171 if (memcmp(data + i, pat, pat_len) == 0) return 1; 172 } 173 return 0; 174 } 175 176 static inline void kit_unit_summary(KitUnit* u, const char* name) { 177 if (u->fails) { 178 fprintf(stderr, "%s: %d/%d checks failed\n", name, u->fails, u->checks); 179 } else { 180 printf("%s: %d checks, 0 failures\n", name, u->checks); 181 } 182 } 183 184 static inline int kit_unit_status(KitUnit* u) { 185 int rc = u->fails ? 1 : 0; 186 kit_unit_fini(u); 187 return rc; 188 } 189 190 /* Record one check. `cond` is evaluated EXACTLY ONCE — it may have side 191 * effects (e.g. EXPECT(kit_ar_iter_next(it, &m), ...) advances an 192 * iterator). Always bumps u->checks; on a miss bumps u->fails and prints a 193 * located FAIL line. Threads the context explicitly (no hidden global). */ 194 #define CU_CHECK(u, cond, ...) \ 195 do { \ 196 int cu_ok_ = (cond) ? 1 : 0; \ 197 ++(u)->checks; \ 198 if (!cu_ok_) { \ 199 ++(u)->fails; \ 200 fprintf(stderr, "FAIL %s:%d: ", __FILE__, __LINE__); \ 201 fprintf(stderr, __VA_ARGS__); \ 202 fputc('\n', stderr); \ 203 } \ 204 } while (0) 205 206 /* Spelling preferred by most existing call sites. */ 207 #define CU_EXPECT(u, cond, ...) CU_CHECK(u, cond, __VA_ARGS__) 208 209 /* CU_CHECK then `return 0;` on a miss — also evaluates `cond` exactly once. 210 * Preserves the per-test-function early-return shape used by table-of-tests 211 * harnesses (e.g. ar_test). */ 212 #define CU_CHECK_RET(u, cond, ...) \ 213 do { \ 214 int cu_ok_ = (cond) ? 1 : 0; \ 215 ++(u)->checks; \ 216 if (!cu_ok_) { \ 217 ++(u)->fails; \ 218 fprintf(stderr, "FAIL %s:%d: ", __FILE__, __LINE__); \ 219 fprintf(stderr, __VA_ARGS__); \ 220 fputc('\n', stderr); \ 221 return 0; \ 222 } \ 223 } while (0) 224 225 #endif /* KIT_TEST_LIB_KIT_UNIT_H */