kit

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

ar_test.c (28381B)


      1 /* Round-trip tests for the kit ar reader/writer.
      2  *
      3  * Builds against just include/kit.h + libkit.a and a few libc calls
      4  * (malloc/realloc/free, printf for diagnostics). kit_ar_write itself
      5  * makes no heap allocations, so the test does not need a KitHeap.
      6  *
      7  * Set KIT_AR_TEST_HOST=1 to also dump the produced symbol-index
      8  * archive to /tmp/kit_ar_test.a and run the host's `ar t` and
      9  * `nm --print-armap` on it as a cross-check. */
     10 #include <kit/archive.h>
     11 #include <kit/core.h>
     12 #include <stdio.h>
     13 #include <stdlib.h>
     14 #include <string.h>
     15 
     16 #include "lib/kit_unit.h"
     17 
     18 /* ===== minimal KitWriter over a growing buffer ===== */
     19 
     20 typedef struct BufW {
     21   KitWriter base;
     22   uint8_t* data;
     23   size_t len;
     24   size_t cap;
     25   KitStatus st;
     26 } BufW;
     27 
     28 static KitStatus bufw_write(KitWriter* w, const void* data, size_t n) {
     29   BufW* b = (BufW*)w;
     30   if (b->st != KIT_OK) return b->st;
     31   if (b->len + n > b->cap) {
     32     size_t nc = b->cap ? b->cap * 2 : 256;
     33     while (nc < b->len + n) nc *= 2;
     34     uint8_t* p = (uint8_t*)realloc(b->data, nc);
     35     if (!p) {
     36       b->st = KIT_NOMEM;
     37       return KIT_NOMEM;
     38     }
     39     b->data = p;
     40     b->cap = nc;
     41   }
     42   memcpy(b->data + b->len, data, n);
     43   b->len += n;
     44   return KIT_OK;
     45 }
     46 
     47 static KitStatus bufw_seek(KitWriter* w, uint64_t off) {
     48   (void)w;
     49   (void)off;
     50   return KIT_OK;
     51 }
     52 static uint64_t bufw_tell(KitWriter* w) { return ((BufW*)w)->len; }
     53 static KitStatus bufw_status(KitWriter* w) { return ((BufW*)w)->st; }
     54 static void bufw_close(KitWriter* w) { (void)w; }
     55 
     56 static void bufw_init(BufW* b) {
     57   b->base.write = bufw_write;
     58   b->base.seek = bufw_seek;
     59   b->base.tell = bufw_tell;
     60   b->base.status = bufw_status;
     61   b->base.close = bufw_close;
     62   b->data = NULL;
     63   b->len = 0;
     64   b->cap = 0;
     65   b->st = KIT_OK;
     66 }
     67 
     68 static void bufw_fini(BufW* b) { free(b->data); }
     69 
     70 /* ===== libc heap + context (needed by kit_ar_iter_new) ===== */
     71 
     72 static KitUnit g_u;
     73 
     74 /* ar_test keeps its own context: now=-1 and no diag sink, since its archive
     75  * assertions depend on exact header bytes (the clock) and never expect a
     76  * diagnostic. The heap comes from g_u, filled by kit_unit_init in main. */
     77 static KitContext g_ctx = {.heap = &g_u.heap, .now = -1};
     78 
     79 /* ===== assertion helpers ===== */
     80 
     81 /* EXPECT keeps its per-test early-return-on-fail shape via CU_CHECK_RET. */
     82 #define EXPECT(cond, ...) CU_CHECK_RET(&g_u, cond, __VA_ARGS__)
     83 
     84 static uint32_t be32(const uint8_t* p) {
     85   return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) |
     86          ((uint32_t)p[2] << 8) | (uint32_t)p[3];
     87 }
     88 
     89 /* Decode the ar_size field (10-byte ASCII decimal, space-padded). */
     90 static uint64_t ar_size_field(const uint8_t* hdr) {
     91   uint64_t v = 0;
     92   int j;
     93   for (j = 48; j < 58; ++j) {
     94     char c = (char)hdr[j];
     95     if (c < '0' || c > '9') break;
     96     v = v * 10 + (uint64_t)(c - '0');
     97   }
     98   return v;
     99 }
    100 
    101 /* Decode the ar_date field (12-byte ASCII decimal, space-padded). */
    102 static uint64_t ar_date_field(const uint8_t* hdr) {
    103   uint64_t v = 0;
    104   int j;
    105   for (j = 16; j < 28; ++j) {
    106     char c = (char)hdr[j];
    107     if (c < '0' || c > '9') break;
    108     v = v * 10 + (uint64_t)(c - '0');
    109   }
    110   return v;
    111 }
    112 
    113 /* ===== tests ===== */
    114 
    115 static int test_basic_roundtrip(void) {
    116   KitArInput ms[2];
    117   BufW bw;
    118   KitSlice in;
    119   KitArIter* it = NULL;
    120   KitArMember m;
    121   int rc;
    122 
    123   ms[0].name = KIT_SLICE_LIT("a.o");
    124   ms[0].bytes.data = (const uint8_t*)"AAAA";
    125   ms[0].bytes.len = 4;
    126   ms[1].name = KIT_SLICE_LIT("b.o");
    127   ms[1].bytes.data = (const uint8_t*)"BBBBB";
    128   ms[1].bytes.len = 5;
    129 
    130   bufw_init(&bw);
    131   rc = kit_ar_write(&bw.base, ms, 2, NULL);
    132   EXPECT(rc == 0, "kit_ar_write returned %d", rc);
    133   EXPECT(bw.st == KIT_OK, "writer error");
    134   EXPECT(bw.len >= 8, "archive too short");
    135   EXPECT(memcmp(bw.data, "!<arch>\n", 8) == 0, "magic");
    136 
    137   in.data = bw.data;
    138   in.len = bw.len;
    139   EXPECT(kit_ar_iter_new(&g_ctx, &in, &it) == KIT_OK, "iter_init");
    140 
    141   EXPECT(kit_ar_iter_next(it, &m), "first member");
    142   EXPECT(kit_slice_eq_cstr(m.name, "a.o"), "name 0 = %.*s",
    143          KIT_SLICE_ARG(m.name));
    144   EXPECT(m.size == 4 && memcmp(m.data, "AAAA", 4) == 0, "data 0");
    145 
    146   EXPECT(kit_ar_iter_next(it, &m), "second member");
    147   EXPECT(kit_slice_eq_cstr(m.name, "b.o"), "name 1 = %.*s",
    148          KIT_SLICE_ARG(m.name));
    149   EXPECT(m.size == 5 && memcmp(m.data, "BBBBB", 5) == 0, "data 1");
    150 
    151   EXPECT(!kit_ar_iter_next(it, &m), "iter end");
    152 
    153   kit_ar_iter_free(it);
    154   bufw_fini(&bw);
    155   return 1;
    156 }
    157 
    158 static int test_long_name_table(void) {
    159   /* >15 chars triggers the // long-name table. */
    160   KitArInput ms[2];
    161   BufW bw;
    162   KitSlice in;
    163   KitArIter* it = NULL;
    164   KitArMember m;
    165   KitArWriteOptions opts = {0};
    166   int rc;
    167 
    168   opts.long_names = 1;
    169   ms[0].name = KIT_SLICE_LIT("short.o");
    170   ms[0].bytes.data = (const uint8_t*)"x";
    171   ms[0].bytes.len = 1;
    172   ms[1].name = KIT_SLICE_LIT("this_name_is_long_enough.o");
    173   ms[1].bytes.data = (const uint8_t*)"yy";
    174   ms[1].bytes.len = 2;
    175 
    176   bufw_init(&bw);
    177   rc = kit_ar_write(&bw.base, ms, 2, &opts);
    178   EXPECT(rc == 0, "write rc=%d", rc);
    179 
    180   in.data = bw.data;
    181   in.len = bw.len;
    182   EXPECT(kit_ar_iter_new(&g_ctx, &in, &it) == KIT_OK, "iter_init");
    183   EXPECT(kit_ar_iter_next(it, &m), "first member");
    184   EXPECT(kit_slice_eq_cstr(m.name, "short.o"), "name 0 = %.*s",
    185          KIT_SLICE_ARG(m.name));
    186   EXPECT(kit_ar_iter_next(it, &m), "second member");
    187   EXPECT(kit_slice_eq_cstr(m.name, "this_name_is_long_enough.o"),
    188          "long name = %.*s", KIT_SLICE_ARG(m.name));
    189   EXPECT(!kit_ar_iter_next(it, &m), "iter end");
    190 
    191   kit_ar_iter_free(it);
    192   bufw_fini(&bw);
    193   return 1;
    194 }
    195 
    196 static int test_symbol_index_empty(void) {
    197   KitArInput ms[1];
    198   BufW bw;
    199   KitSlice in;
    200   KitArIter* it = NULL;
    201   KitArMember m;
    202   KitArWriteOptions opts = {0};
    203   int rc;
    204   uint32_t nsyms;
    205 
    206   opts.symbol_index = 1;
    207   ms[0].name = KIT_SLICE_LIT("lonely.o");
    208   ms[0].bytes.data = (const uint8_t*)"P";
    209   ms[0].bytes.len = 1;
    210 
    211   bufw_init(&bw);
    212   rc = kit_ar_write(&bw.base, ms, 1, &opts);
    213   EXPECT(rc == 0, "write rc=%d", rc);
    214 
    215   /* First member after magic must be `/` index with count=0. */
    216   EXPECT(bw.len >= 8 + 60 + 4, "archive too short");
    217   EXPECT(bw.data[8] == '/' && bw.data[9] == ' ', "index name field");
    218   EXPECT(ar_size_field(bw.data + 8) == 4, "index payload size = 4");
    219   nsyms = be32(bw.data + 8 + 60);
    220   EXPECT(nsyms == 0, "nsyms = %u", nsyms);
    221 
    222   /* Iterator should skip the `/` and yield only the user member. */
    223   in.data = bw.data;
    224   in.len = bw.len;
    225   EXPECT(kit_ar_iter_new(&g_ctx, &in, &it) == KIT_OK, "iter_init");
    226   EXPECT(kit_ar_iter_next(it, &m), "first user member");
    227   EXPECT(kit_slice_eq_cstr(m.name, "lonely.o"), "name = %.*s",
    228          KIT_SLICE_ARG(m.name));
    229   EXPECT(!kit_ar_iter_next(it, &m), "iter end");
    230 
    231   kit_ar_iter_free(it);
    232   bufw_fini(&bw);
    233   return 1;
    234 }
    235 
    236 static int test_symbol_index_basic(void) {
    237   /* 2 members with symbol lists; verify count, offsets, and names. */
    238   KitSlice a_syms[] = {KIT_SLICE_LIT("foo"), KIT_SLICE_LIT("bar")};
    239   KitSlice b_syms[] = {KIT_SLICE_LIT("baz")};
    240   KitArMemberSymbols msyms[2];
    241   KitArInput ms[2];
    242   BufW bw;
    243   KitArWriteOptions opts = {0};
    244   int rc;
    245   uint32_t nsyms;
    246   const uint8_t* p;
    247   uint64_t index_payload;
    248   uint32_t off0, off1, off2;
    249   uint64_t a_hdr_off, b_hdr_off;
    250   const char* name;
    251 
    252   opts.symbol_index = 1;
    253   opts.long_names = 1;
    254 
    255   msyms[0].names = a_syms;
    256   msyms[0].count = 2;
    257   msyms[1].names = b_syms;
    258   msyms[1].count = 1;
    259   opts.member_symbols = msyms;
    260 
    261   ms[0].name = KIT_SLICE_LIT("a.o");
    262   ms[0].bytes.data = (const uint8_t*)"AAAA";
    263   ms[0].bytes.len = 4;
    264   ms[1].name = KIT_SLICE_LIT("b.o");
    265   ms[1].bytes.data = (const uint8_t*)"BBBBB";
    266   ms[1].bytes.len = 5;
    267 
    268   bufw_init(&bw);
    269   rc = kit_ar_write(&bw.base, ms, 2, &opts);
    270   EXPECT(rc == 0, "write rc=%d", rc);
    271 
    272   EXPECT(memcmp(bw.data, "!<arch>\n", 8) == 0, "magic");
    273   EXPECT(bw.data[8] == '/' && bw.data[9] == ' ', "index ar_name field");
    274 
    275   index_payload = ar_size_field(bw.data + 8);
    276   /* 4 (count) + 4*3 (offsets) + 3+1 + 3+1 + 3+1 (names "foo\0bar\0baz\0") = 28
    277    */
    278   EXPECT(index_payload == 28, "index payload = %llu",
    279          (unsigned long long)index_payload);
    280 
    281   p = bw.data + 8 + 60;
    282   nsyms = be32(p);
    283   p += 4;
    284   EXPECT(nsyms == 3, "nsyms = %u", nsyms);
    285 
    286   off0 = be32(p);     /* foo → a.o */
    287   off1 = be32(p + 4); /* bar → a.o */
    288   off2 = be32(p + 8); /* baz → b.o */
    289   p += 12;
    290 
    291   /* Compute expected header offsets: index_total = 60+28 (no pad, even).
    292    * No long-name table is emitted (basenames ≤ 15 chars). So:
    293    *   a.o header at offset 8 + 88 = 96
    294    *   b.o header at offset 96 + 60 + 4 (+0 pad) = 160 */
    295   a_hdr_off = 8 + 60 + 28;
    296   b_hdr_off = a_hdr_off + 60 + 4;
    297   EXPECT(off0 == a_hdr_off, "off0 = %u, expected %llu", off0,
    298          (unsigned long long)a_hdr_off);
    299   EXPECT(off1 == a_hdr_off, "off1 = %u, expected %llu", off1,
    300          (unsigned long long)a_hdr_off);
    301   EXPECT(off2 == b_hdr_off, "off2 = %u, expected %llu", off2,
    302          (unsigned long long)b_hdr_off);
    303 
    304   /* Sanity: the member headers must actually live at those offsets. */
    305   EXPECT(memcmp(bw.data + a_hdr_off, "a.o/", 4) == 0, "a.o at offset");
    306   EXPECT(memcmp(bw.data + b_hdr_off, "b.o/", 4) == 0, "b.o at offset");
    307 
    308   /* Names: "foo\0bar\0baz\0" */
    309   name = (const char*)p;
    310   EXPECT(strcmp(name, "foo") == 0, "name 0 = %s", name);
    311   name += strlen(name) + 1;
    312   EXPECT(strcmp(name, "bar") == 0, "name 1 = %s", name);
    313   name += strlen(name) + 1;
    314   EXPECT(strcmp(name, "baz") == 0, "name 2 = %s", name);
    315 
    316   bufw_fini(&bw);
    317   return 1;
    318 }
    319 
    320 static int test_symbol_index_with_long_names(void) {
    321   /* `/` member must come BEFORE `//` long-name table, and offsets must
    322    * still point at correct member-header positions. */
    323   KitSlice syms0[] = {KIT_SLICE_LIT("alpha")};
    324   KitSlice syms1[] = {KIT_SLICE_LIT("beta")};
    325   KitArMemberSymbols msyms[2];
    326   KitArInput ms[2];
    327   BufW bw;
    328   KitSlice in;
    329   KitArIter* it = NULL;
    330   KitArMember m;
    331   KitArWriteOptions opts = {0};
    332   int rc;
    333   uint64_t index_payload, longtab_payload;
    334   uint64_t index_total, longtab_total;
    335   uint64_t m0_hdr, m1_hdr;
    336   uint32_t off0, off1;
    337   const uint8_t* p;
    338 
    339   opts.symbol_index = 1;
    340   opts.long_names = 1;
    341   msyms[0].names = syms0;
    342   msyms[0].count = 1;
    343   msyms[1].names = syms1;
    344   msyms[1].count = 1;
    345   opts.member_symbols = msyms;
    346 
    347   ms[0].name = KIT_SLICE_LIT("this_name_is_long_enough.o"); /* 26 chars → // */
    348   ms[0].bytes.data = (const uint8_t*)"X";
    349   ms[0].bytes.len = 1;
    350   ms[1].name = KIT_SLICE_LIT("short.o");
    351   ms[1].bytes.data = (const uint8_t*)"YY";
    352   ms[1].bytes.len = 2;
    353 
    354   bufw_init(&bw);
    355   rc = kit_ar_write(&bw.base, ms, 2, &opts);
    356   EXPECT(rc == 0, "write rc=%d", rc);
    357 
    358   /* Layout: magic(8) | / index member | // long-name member | members. */
    359   EXPECT(bw.data[8] == '/' && bw.data[9] == ' ', "/ first");
    360   index_payload = ar_size_field(bw.data + 8);
    361   /* 4 (count) + 4*2 (offsets) + 6 ("alpha\0") + 5 ("beta\0") = 23 → odd → pad 1
    362    */
    363   EXPECT(index_payload == 23, "index_payload = %llu",
    364          (unsigned long long)index_payload);
    365   index_total = 60 + index_payload + (index_payload & 1);
    366 
    367   /* Verify // header sits at 8 + index_total. */
    368   EXPECT(bw.data[8 + index_total] == '/', "// pos byte 0");
    369   EXPECT(bw.data[8 + index_total + 1] == '/', "// pos byte 1");
    370   longtab_payload = ar_size_field(bw.data + 8 + index_total);
    371   /* "this_name_is_long_enough.o/\n" = 28 bytes → even → no pad */
    372   EXPECT(longtab_payload == 28, "longtab payload = %llu",
    373          (unsigned long long)longtab_payload);
    374   longtab_total = 60 + longtab_payload + (longtab_payload & 1);
    375 
    376   m0_hdr = 8 + index_total + longtab_total;
    377   m1_hdr = m0_hdr + 60 + 1 + 1 /* parity pad for odd len 1 */;
    378 
    379   p = bw.data + 8 + 60;
    380   EXPECT(be32(p) == 2, "nsyms = %u", be32(p));
    381   p += 4;
    382   off0 = be32(p);
    383   off1 = be32(p + 4);
    384   EXPECT(off0 == m0_hdr, "off0 = %u, expected %llu", off0,
    385          (unsigned long long)m0_hdr);
    386   EXPECT(off1 == m1_hdr, "off1 = %u, expected %llu", off1,
    387          (unsigned long long)m1_hdr);
    388   /* Spot-check m1 starts with "short.o/" for sanity. */
    389   EXPECT(memcmp(bw.data + m1_hdr, "short.o/", 8) == 0, "m1 hdr");
    390 
    391   /* Iterator should walk past both /, // and yield 2 members. */
    392   in.data = bw.data;
    393   in.len = bw.len;
    394   EXPECT(kit_ar_iter_new(&g_ctx, &in, &it) == KIT_OK, "iter_init");
    395   EXPECT(kit_ar_iter_next(it, &m), "m0");
    396   EXPECT(kit_slice_eq_cstr(m.name, "this_name_is_long_enough.o"),
    397          "m0 name = %.*s", KIT_SLICE_ARG(m.name));
    398   EXPECT(kit_ar_iter_next(it, &m), "m1");
    399   EXPECT(kit_slice_eq_cstr(m.name, "short.o"), "m1 name = %.*s",
    400          KIT_SLICE_ARG(m.name));
    401   EXPECT(!kit_ar_iter_next(it, &m), "iter end");
    402 
    403   /* Optional host cross-check. */
    404   if (getenv("KIT_AR_TEST_HOST")) {
    405     FILE* f = fopen("/tmp/kit_ar_test.a", "wb");
    406     if (f) {
    407       fwrite(bw.data, 1, bw.len, f);
    408       fclose(f);
    409       fprintf(stderr, "host cross-check: ar t /tmp/kit_ar_test.a\n");
    410       (void)!system("ar t /tmp/kit_ar_test.a");
    411       fprintf(stderr,
    412               "host cross-check: nm --print-armap /tmp/kit_ar_test.a\n");
    413       (void)!system("nm --print-armap /tmp/kit_ar_test.a 2>&1 || true");
    414     }
    415   }
    416 
    417   kit_ar_iter_free(it);
    418   bufw_fini(&bw);
    419   return 1;
    420 }
    421 
    422 static int test_iter_skips_index(void) {
    423   /* Make sure the iterator never surfaces the `/` member as a user member. */
    424   KitSlice s[] = {KIT_SLICE_LIT("only_sym")};
    425   KitArMemberSymbols msyms[1];
    426   KitArInput ms[1];
    427   KitSlice in;
    428   KitArIter* it = NULL;
    429   KitArMember m;
    430   BufW bw;
    431   KitArWriteOptions opts = {0};
    432   int seen = 0;
    433 
    434   opts.symbol_index = 1;
    435   msyms[0].names = s;
    436   msyms[0].count = 1;
    437   opts.member_symbols = msyms;
    438   ms[0].name = KIT_SLICE_LIT("only.o");
    439   ms[0].bytes.data = (const uint8_t*)"Z";
    440   ms[0].bytes.len = 1;
    441 
    442   bufw_init(&bw);
    443   EXPECT(kit_ar_write(&bw.base, ms, 1, &opts) == 0, "write");
    444 
    445   in.data = bw.data;
    446   in.len = bw.len;
    447   EXPECT(kit_ar_iter_new(&g_ctx, &in, &it) == KIT_OK, "iter_init");
    448   while (kit_ar_iter_next(it, &m)) {
    449     EXPECT(!kit_slice_eq_cstr(m.name, "/"), "iter surfaced raw `/` member");
    450     seen++;
    451   }
    452   EXPECT(seen == 1, "saw %d members", seen);
    453 
    454   kit_ar_iter_free(it);
    455   bufw_fini(&bw);
    456   return 1;
    457 }
    458 
    459 static int test_empty_archive(void) {
    460   /* nmembers == 0 with NULL members should produce a magic-only archive. */
    461   BufW bw;
    462   KitSlice in;
    463   KitArIter* it = NULL;
    464   KitArMember m;
    465   int rc;
    466 
    467   bufw_init(&bw);
    468   rc = kit_ar_write(&bw.base, NULL, 0, NULL);
    469   EXPECT(rc == 0, "write rc=%d", rc);
    470   EXPECT(bw.len == 8, "size = %zu", bw.len);
    471   EXPECT(memcmp(bw.data, "!<arch>\n", 8) == 0, "magic only");
    472 
    473   in.data = bw.data;
    474   in.len = bw.len;
    475   EXPECT(kit_ar_iter_new(&g_ctx, &in, &it) == KIT_OK, "iter_init");
    476   EXPECT(!kit_ar_iter_next(it, &m), "no members");
    477 
    478   kit_ar_iter_free(it);
    479   bufw_fini(&bw);
    480   return 1;
    481 }
    482 
    483 static int test_epoch_field(void) {
    484   /* opts.epoch is written into ar_date for every member. */
    485   KitArInput ms[1];
    486   BufW bw;
    487   KitArWriteOptions opts = {0};
    488   int rc;
    489 
    490   ms[0].name = KIT_SLICE_LIT("x.o");
    491   ms[0].bytes.data = (const uint8_t*)"q";
    492   ms[0].bytes.len = 1;
    493 
    494   opts.epoch = 1234567890u;
    495   bufw_init(&bw);
    496   rc = kit_ar_write(&bw.base, ms, 1, &opts);
    497   EXPECT(rc == 0, "write rc=%d", rc);
    498   EXPECT(ar_date_field(bw.data + 8) == 1234567890u, "ar_date = %llu",
    499          (unsigned long long)ar_date_field(bw.data + 8));
    500   bufw_fini(&bw);
    501 
    502   /* Default (epoch=0): single '0' followed by spaces. */
    503   opts.epoch = 0;
    504   bufw_init(&bw);
    505   rc = kit_ar_write(&bw.base, ms, 1, &opts);
    506   EXPECT(rc == 0, "write rc=%d", rc);
    507   EXPECT(bw.data[8 + 16] == '0', "epoch default first byte");
    508   EXPECT(bw.data[8 + 17] == ' ', "epoch default second byte = 0x%02x",
    509          bw.data[8 + 17]);
    510   bufw_fini(&bw);
    511   return 1;
    512 }
    513 
    514 static int test_path_basename(void) {
    515   /* Member name with path components is stored as basename only. */
    516   KitArInput ms[1];
    517   BufW bw;
    518   KitSlice in;
    519   KitArIter* it = NULL;
    520   KitArMember m;
    521 
    522   ms[0].name = KIT_SLICE_LIT("src/sub/foo.o");
    523   ms[0].bytes.data = (const uint8_t*)"D";
    524   ms[0].bytes.len = 1;
    525 
    526   bufw_init(&bw);
    527   EXPECT(kit_ar_write(&bw.base, ms, 1, NULL) == 0, "write");
    528 
    529   in.data = bw.data;
    530   in.len = bw.len;
    531   EXPECT(kit_ar_iter_new(&g_ctx, &in, &it) == KIT_OK, "iter_init");
    532   EXPECT(kit_ar_iter_next(it, &m), "first");
    533   EXPECT(kit_slice_eq_cstr(m.name, "foo.o"), "basename = %.*s",
    534          KIT_SLICE_ARG(m.name));
    535 
    536   kit_ar_iter_free(it);
    537   bufw_fini(&bw);
    538   return 1;
    539 }
    540 
    541 static int test_truncate_when_long_names_off(void) {
    542   /* >15 chars without long_names: name is truncated to 15. */
    543   KitArInput ms[1];
    544   BufW bw;
    545   KitSlice in;
    546   KitArIter* it = NULL;
    547   KitArMember m;
    548 
    549   ms[0].name = KIT_SLICE_LIT("abcdefghijklmnopqrst.o"); /* 22 chars */
    550   ms[0].bytes.data = (const uint8_t*)"D";
    551   ms[0].bytes.len = 1;
    552 
    553   bufw_init(&bw);
    554   EXPECT(kit_ar_write(&bw.base, ms, 1, NULL) == 0, "write");
    555 
    556   in.data = bw.data;
    557   in.len = bw.len;
    558   EXPECT(kit_ar_iter_new(&g_ctx, &in, &it) == KIT_OK, "iter_init");
    559   EXPECT(kit_ar_iter_next(it, &m), "first");
    560   EXPECT(kit_slice_eq_cstr(m.name, "abcdefghijklmno"), "truncated = %.*s",
    561          KIT_SLICE_ARG(m.name));
    562 
    563   kit_ar_iter_free(it);
    564   bufw_fini(&bw);
    565   return 1;
    566 }
    567 
    568 static int test_name_15_char_boundary(void) {
    569   /* Exactly 15 chars: fits in-header even with long_names enabled. */
    570   KitArInput ms[1];
    571   BufW bw;
    572   KitSlice in;
    573   KitArIter* it = NULL;
    574   KitArMember m;
    575   KitArWriteOptions opts = {0};
    576 
    577   opts.long_names = 1;
    578   ms[0].name = KIT_SLICE_LIT("abcdefghijklmno"); /* 15 chars */
    579   ms[0].bytes.data = (const uint8_t*)"X";
    580   ms[0].bytes.len = 1;
    581 
    582   bufw_init(&bw);
    583   EXPECT(kit_ar_write(&bw.base, ms, 1, &opts) == 0, "write");
    584 
    585   /* No `//` long-name table: first member sits right after magic. */
    586   EXPECT(memcmp(bw.data + 8, "abcdefghijklmno/", 16) == 0, "name field = %.16s",
    587          (const char*)(bw.data + 8));
    588 
    589   in.data = bw.data;
    590   in.len = bw.len;
    591   EXPECT(kit_ar_iter_new(&g_ctx, &in, &it) == KIT_OK, "iter_init");
    592   EXPECT(kit_ar_iter_next(it, &m), "first");
    593   EXPECT(kit_slice_eq_cstr(m.name, "abcdefghijklmno"), "name = %.*s",
    594          KIT_SLICE_ARG(m.name));
    595 
    596   kit_ar_iter_free(it);
    597   bufw_fini(&bw);
    598   return 1;
    599 }
    600 
    601 static int test_name_16_char_boundary(void) {
    602   /* Exactly 16 chars: triggers // long-name table. */
    603   KitArInput ms[1];
    604   BufW bw;
    605   KitSlice in;
    606   KitArIter* it = NULL;
    607   KitArMember m;
    608   KitArWriteOptions opts = {0};
    609 
    610   opts.long_names = 1;
    611   ms[0].name = KIT_SLICE_LIT("abcdefghijklmnop"); /* 16 chars */
    612   ms[0].bytes.data = (const uint8_t*)"Y";
    613   ms[0].bytes.len = 1;
    614 
    615   bufw_init(&bw);
    616   EXPECT(kit_ar_write(&bw.base, ms, 1, &opts) == 0, "write");
    617   EXPECT(bw.data[8] == '/' && bw.data[9] == '/', "// header");
    618 
    619   in.data = bw.data;
    620   in.len = bw.len;
    621   EXPECT(kit_ar_iter_new(&g_ctx, &in, &it) == KIT_OK, "iter_init");
    622   EXPECT(kit_ar_iter_next(it, &m), "first");
    623   EXPECT(kit_slice_eq_cstr(m.name, "abcdefghijklmnop"), "name = %.*s",
    624          KIT_SLICE_ARG(m.name));
    625 
    626   kit_ar_iter_free(it);
    627   bufw_fini(&bw);
    628   return 1;
    629 }
    630 
    631 static int test_empty_member_payload(void) {
    632   /* len=0 (data=NULL): header only, no pad; followed by the next member. */
    633   KitArInput ms[2];
    634   BufW bw;
    635   KitSlice in;
    636   KitArIter* it = NULL;
    637   KitArMember m;
    638 
    639   ms[0].name = KIT_SLICE_LIT("empty.o");
    640   ms[0].bytes.data = NULL;
    641   ms[0].bytes.len = 0;
    642   ms[1].name = KIT_SLICE_LIT("next.o");
    643   ms[1].bytes.data = (const uint8_t*)"N";
    644   ms[1].bytes.len = 1;
    645 
    646   bufw_init(&bw);
    647   EXPECT(kit_ar_write(&bw.base, ms, 2, NULL) == 0, "write");
    648   /* magic(8) + hdr(60) + 0 + hdr(60) + 1 + pad(1) = 130 */
    649   EXPECT(bw.len == 130, "size = %zu", bw.len);
    650 
    651   in.data = bw.data;
    652   in.len = bw.len;
    653   EXPECT(kit_ar_iter_new(&g_ctx, &in, &it) == KIT_OK, "iter_init");
    654   EXPECT(kit_ar_iter_next(it, &m), "first");
    655   EXPECT(kit_slice_eq_cstr(m.name, "empty.o") && m.size == 0,
    656          "empty.o size=%zu", m.size);
    657   EXPECT(kit_ar_iter_next(it, &m), "second");
    658   EXPECT(kit_slice_eq_cstr(m.name, "next.o") && m.size == 1 && m.data[0] == 'N',
    659          "next.o");
    660   EXPECT(!kit_ar_iter_next(it, &m), "iter end");
    661 
    662   kit_ar_iter_free(it);
    663   bufw_fini(&bw);
    664   return 1;
    665 }
    666 
    667 static int test_odd_payload_pad(void) {
    668   /* Odd-length payloads add a '\n' parity pad; even lengths do not. */
    669   KitArInput ms[3];
    670   BufW bw;
    671 
    672   ms[0].name = KIT_SLICE_LIT("a.o");
    673   ms[0].bytes.data = (const uint8_t*)"x";
    674   ms[0].bytes.len = 1;
    675   ms[1].name = KIT_SLICE_LIT("b.o");
    676   ms[1].bytes.data = (const uint8_t*)"yy";
    677   ms[1].bytes.len = 2;
    678   ms[2].name = KIT_SLICE_LIT("c.o");
    679   ms[2].bytes.data = (const uint8_t*)"z";
    680   ms[2].bytes.len = 1;
    681 
    682   bufw_init(&bw);
    683   EXPECT(kit_ar_write(&bw.base, ms, 3, NULL) == 0, "write");
    684   /* 8 + (60+1+1) + (60+2) + (60+1+1) = 194 */
    685   EXPECT(bw.len == 194, "size = %zu", bw.len);
    686   EXPECT(bw.data[8 + 60 + 1] == '\n', "pad after a.o = 0x%02x",
    687          bw.data[8 + 60 + 1]);
    688   /* No pad after b.o (even): next header begins immediately. */
    689   EXPECT(bw.data[8 + 62 + 60 + 2] == 'c', "c.o name follows b.o without pad");
    690 
    691   bufw_fini(&bw);
    692   return 1;
    693 }
    694 
    695 static int test_ar_list_output(void) {
    696   /* kit_ar_list emits one user member per line, skipping / and //. */
    697   KitArInput ms[3];
    698   BufW bw, lw;
    699   KitSlice in;
    700   KitArWriteOptions opts = {0};
    701   const char* expected = "a.o\nlong_name_member.o\nb.o\n";
    702 
    703   opts.symbol_index = 1;
    704   opts.long_names = 1;
    705   ms[0].name = KIT_SLICE_LIT("a.o");
    706   ms[0].bytes.data = (const uint8_t*)"A";
    707   ms[0].bytes.len = 1;
    708   ms[1].name = KIT_SLICE_LIT("long_name_member.o");
    709   ms[1].bytes.data = (const uint8_t*)"B";
    710   ms[1].bytes.len = 1;
    711   ms[2].name = KIT_SLICE_LIT("b.o");
    712   ms[2].bytes.data = (const uint8_t*)"C";
    713   ms[2].bytes.len = 1;
    714 
    715   bufw_init(&bw);
    716   EXPECT(kit_ar_write(&bw.base, ms, 3, &opts) == 0, "write");
    717 
    718   in.data = bw.data;
    719   in.len = bw.len;
    720   bufw_init(&lw);
    721   EXPECT(kit_ar_list(&in, &lw.base) == 0, "list");
    722   EXPECT(lw.len == strlen(expected), "list len = %zu, want %zu", lw.len,
    723          strlen(expected));
    724   EXPECT(memcmp(lw.data, expected, lw.len) == 0, "list = %.*s", (int)lw.len,
    725          (const char*)lw.data);
    726 
    727   bufw_fini(&lw);
    728   bufw_fini(&bw);
    729   return 1;
    730 }
    731 
    732 static int test_iter_bad_magic(void) {
    733   /* iter_init must reject non-ar inputs. */
    734   static const uint8_t bad[] = "NOT-AN-AR";
    735   KitSlice in;
    736   KitArIter* it = NULL;
    737 
    738   in.data = bad;
    739   in.len = sizeof(bad) - 1;
    740   EXPECT(kit_ar_iter_new(&g_ctx, &in, &it) != KIT_OK, "rejects bad magic");
    741 
    742   in.data = NULL;
    743   in.len = 0;
    744   EXPECT(kit_ar_iter_new(&g_ctx, &in, &it) != KIT_OK, "rejects empty");
    745 
    746   in.data = bad;
    747   in.len = 4; /* too short */
    748   EXPECT(kit_ar_iter_new(&g_ctx, &in, &it) != KIT_OK, "rejects short");
    749 
    750   return 1;
    751 }
    752 
    753 static int test_write_invalid_args(void) {
    754   /* Bad argument combinations must return 1 from kit_ar_write. */
    755   KitArInput ms[1];
    756   BufW bw;
    757   KitArWriteOptions opts = {0};
    758   KitSlice bad_syms[1];
    759   KitArMemberSymbols msyms[1];
    760 
    761   bufw_init(&bw);
    762 
    763   EXPECT(kit_ar_write(NULL, NULL, 0, NULL) != KIT_OK, "NULL writer rejected");
    764   EXPECT(kit_ar_write(&bw.base, NULL, 1, NULL) != KIT_OK,
    765          "NULL members with nmembers>0 rejected");
    766 
    767   ms[0].name = KIT_SLICE_NULL;
    768   ms[0].bytes.data = (const uint8_t*)"X";
    769   ms[0].bytes.len = 1;
    770   EXPECT(kit_ar_write(&bw.base, ms, 1, NULL) != KIT_OK, "NULL name rejected");
    771 
    772   /* NULL symbol-name with nonzero count. */
    773   bad_syms[0] = KIT_SLICE_NULL;
    774   msyms[0].names = bad_syms;
    775   msyms[0].count = 1;
    776   opts.symbol_index = 1;
    777   opts.member_symbols = msyms;
    778   ms[0].name = KIT_SLICE_LIT("ok.o");
    779   EXPECT(kit_ar_write(&bw.base, ms, 1, &opts) != KIT_OK,
    780          "NULL symbol name rejected");
    781 
    782   bufw_fini(&bw);
    783   return 1;
    784 }
    785 
    786 static int test_iter_skips_bsd_symdef(void) {
    787   /* Hand-craft an archive containing a BSD __.SYMDEF SORTED member; the
    788    * iterator must not surface it. (kit_ar_write never emits BSD indexes,
    789    * but the iterator is documented to handle them on read.) */
    790   BufW bw;
    791   KitSlice in;
    792   KitArIter* it = NULL;
    793   KitArMember m;
    794   char hdr[60];
    795   size_t j;
    796   int seen = 0;
    797 
    798   bufw_init(&bw);
    799   bw.base.write(&bw.base, "!<arch>\n", 8);
    800 
    801   /* __.SYMDEF SORTED with a 4-byte payload. */
    802   for (j = 0; j < 60; ++j) hdr[j] = ' ';
    803   {
    804     const char* nm = "__.SYMDEF SORTED";
    805     for (j = 0; j < 16 && nm[j]; ++j) hdr[j] = nm[j];
    806   }
    807   hdr[16] = '0';
    808   hdr[28] = '0';
    809   hdr[34] = '0';
    810   hdr[40] = '6';
    811   hdr[41] = '4';
    812   hdr[42] = '4';
    813   hdr[48] = '4'; /* size = 4 */
    814   hdr[58] = '`';
    815   hdr[59] = '\n';
    816   bw.base.write(&bw.base, hdr, 60);
    817   bw.base.write(&bw.base, "ZZZZ", 4);
    818 
    819   /* User member u.o size=1 + parity pad. */
    820   for (j = 0; j < 60; ++j) hdr[j] = ' ';
    821   hdr[0] = 'u';
    822   hdr[1] = '.';
    823   hdr[2] = 'o';
    824   hdr[3] = '/';
    825   hdr[16] = '0';
    826   hdr[28] = '0';
    827   hdr[34] = '0';
    828   hdr[40] = '6';
    829   hdr[41] = '4';
    830   hdr[42] = '4';
    831   hdr[48] = '1';
    832   hdr[58] = '`';
    833   hdr[59] = '\n';
    834   bw.base.write(&bw.base, hdr, 60);
    835   bw.base.write(&bw.base, "U\n", 2);
    836 
    837   in.data = bw.data;
    838   in.len = bw.len;
    839   EXPECT(kit_ar_iter_new(&g_ctx, &in, &it) == KIT_OK, "iter_init");
    840   while (kit_ar_iter_next(it, &m)) {
    841     EXPECT(kit_slice_eq_cstr(m.name, "u.o"), "unexpected name = %.*s",
    842            KIT_SLICE_ARG(m.name));
    843     seen++;
    844   }
    845   EXPECT(seen == 1, "saw %d members", seen);
    846 
    847   kit_ar_iter_free(it);
    848   bufw_fini(&bw);
    849   return 1;
    850 }
    851 
    852 static int test_iter_data_aliases_archive(void) {
    853   /* KitArMember.data must point into the archive's own bytes. */
    854   KitArInput ms[1];
    855   BufW bw;
    856   KitSlice in;
    857   KitArIter* it = NULL;
    858   KitArMember m;
    859 
    860   ms[0].name = KIT_SLICE_LIT("a.o");
    861   ms[0].bytes.data = (const uint8_t*)"PAYLOAD";
    862   ms[0].bytes.len = 7;
    863 
    864   bufw_init(&bw);
    865   EXPECT(kit_ar_write(&bw.base, ms, 1, NULL) == 0, "write");
    866 
    867   in.data = bw.data;
    868   in.len = bw.len;
    869   EXPECT(kit_ar_iter_new(&g_ctx, &in, &it) == KIT_OK, "iter_init");
    870   EXPECT(kit_ar_iter_next(it, &m), "first");
    871   EXPECT(m.data >= bw.data && m.data + m.size <= bw.data + bw.len,
    872          "data aliases archive bytes");
    873   EXPECT(memcmp(m.data, "PAYLOAD", 7) == 0, "payload");
    874 
    875   kit_ar_iter_free(it);
    876   bufw_fini(&bw);
    877   return 1;
    878 }
    879 
    880 static int test_symbol_index_partial_members(void) {
    881   /* Members with 0 symbols mid-list: cur_offset must still advance for
    882    * every member so later offsets land on the right header. */
    883   KitSlice mid_syms[] = {KIT_SLICE_LIT("midsym")};
    884   KitArMemberSymbols msyms[3];
    885   KitArInput ms[3];
    886   BufW bw;
    887   KitArWriteOptions opts = {0};
    888   uint32_t nsyms, off;
    889   uint64_t expected_b_hdr;
    890 
    891   opts.symbol_index = 1;
    892   msyms[0].names = NULL;
    893   msyms[0].count = 0;
    894   msyms[1].names = mid_syms;
    895   msyms[1].count = 1;
    896   msyms[2].names = NULL;
    897   msyms[2].count = 0;
    898   opts.member_symbols = msyms;
    899 
    900   ms[0].name = KIT_SLICE_LIT("a.o");
    901   ms[0].bytes.data = (const uint8_t*)"AA";
    902   ms[0].bytes.len = 2;
    903   ms[1].name = KIT_SLICE_LIT("b.o");
    904   ms[1].bytes.data = (const uint8_t*)"BB";
    905   ms[1].bytes.len = 2;
    906   ms[2].name = KIT_SLICE_LIT("c.o");
    907   ms[2].bytes.data = (const uint8_t*)"CC";
    908   ms[2].bytes.len = 2;
    909 
    910   bufw_init(&bw);
    911   EXPECT(kit_ar_write(&bw.base, ms, 3, &opts) == 0, "write");
    912 
    913   /* Index payload: 4(count) + 4(offset) + 7("midsym\0") = 15 → odd → +1 pad. */
    914   EXPECT(ar_size_field(bw.data + 8) == 15, "index payload = %llu",
    915          (unsigned long long)ar_size_field(bw.data + 8));
    916 
    917   nsyms = be32(bw.data + 8 + 60);
    918   EXPECT(nsyms == 1, "nsyms = %u", nsyms);
    919 
    920   off = be32(bw.data + 8 + 60 + 4);
    921   /* magic(8) + index(60+15+1) + a.o(60+2) = 146 */
    922   expected_b_hdr = 8 + 60 + 15 + 1 + 60 + 2;
    923   EXPECT(off == expected_b_hdr, "off = %u, expected %llu", off,
    924          (unsigned long long)expected_b_hdr);
    925   EXPECT(memcmp(bw.data + off, "b.o/", 4) == 0, "b.o at offset");
    926 
    927   bufw_fini(&bw);
    928   return 1;
    929 }
    930 
    931 int main(void) {
    932   int passes = 0;
    933   int total = 0;
    934 #define RUN(t)       \
    935   do {               \
    936     total++;         \
    937     passes += (t)(); \
    938   } while (0)
    939 
    940   kit_unit_init(&g_u);
    941   RUN(test_basic_roundtrip);
    942   RUN(test_long_name_table);
    943   RUN(test_symbol_index_empty);
    944   RUN(test_symbol_index_basic);
    945   RUN(test_symbol_index_with_long_names);
    946   RUN(test_iter_skips_index);
    947   RUN(test_empty_archive);
    948   RUN(test_epoch_field);
    949   RUN(test_path_basename);
    950   RUN(test_truncate_when_long_names_off);
    951   RUN(test_name_15_char_boundary);
    952   RUN(test_name_16_char_boundary);
    953   RUN(test_empty_member_payload);
    954   RUN(test_odd_payload_pad);
    955   RUN(test_ar_list_output);
    956   RUN(test_iter_bad_magic);
    957   RUN(test_write_invalid_args);
    958   RUN(test_iter_skips_bsd_symdef);
    959   RUN(test_iter_data_aliases_archive);
    960   RUN(test_symbol_index_partial_members);
    961 
    962   if (g_u.fails) {
    963     fprintf(stderr, "ar_test: %d failure(s) (%d/%d passed)\n", g_u.fails,
    964             passes, total);
    965     return 1;
    966   }
    967   printf("ar_test: OK (%d/%d)\n", passes, total);
    968   return 0;
    969 }