kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

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 */