Minimalist agentic harness in C
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272/** Tests for lib/vec. Each test aborts on the first failed assert.*/#undef NDEBUG // The asserts are the tests, so they must never be compiled out.#include "../lib/vec.h"#include <assert.h>#include <stdio.h>#include <stdlib.h>#include <string.h>
// Asserts that an int vector holds exactly `expected`.static voidexpect_ints(const vec* v, const int* expected, usize n) { assert(v->length == n); assert(v->length <= v->capacity); assert(memcmp(v->data, expected, n * sizeof(int)) == 0);}
// A new vec is empty and allocates nothing until the first insert.static voidtest_new_is_empty(void) { vec v = vecNew(0, NULL); assert(v.type_size == 1); assert(v.length == 0); assert(v.capacity == 0); assert(v.data == NULL); assert(v.destroy == NULL); assert(vecGet(&v, 0) == NULL); vecDestroy(&v);}
static voidtest_first_insert_allocates(void) { vec v = vecNew(sizeof(int), NULL); assert(vecInsert(&v, 0, (int[]){1, 2, 3}, 3) == OK); assert(v.data != NULL); assert(v.capacity >= 3); expect_ints(&v, (int[]){1, 2, 3}, 3); vecDestroy(&v);}
static voidtest_insert_at_end(void) { vec v = vecNew(sizeof(int), NULL); assert(vecInsert(&v, 0, (int[]){1, 2}, 2) == OK); assert(vecInsert(&v, v.length, (int[]){3}, 1) == OK); expect_ints(&v, (int[]){1, 2, 3}, 3); vecDestroy(&v);}
static voidtest_insert_at_start(void) { vec v = vecNew(sizeof(int), NULL); assert(vecInsert(&v, 0, (int[]){3, 4}, 2) == OK); assert(vecInsert(&v, 0, (int[]){1, 2}, 2) == OK); expect_ints(&v, (int[]){1, 2, 3, 4}, 4); vecDestroy(&v);}
static voidtest_insert_in_middle(void) { vec v = vecNew(sizeof(int), NULL); assert(vecInsert(&v, 0, (int[]){1, 2, 5}, 3) == OK); assert(vecInsert(&v, 2, (int[]){3, 4}, 2) == OK); expect_ints(&v, (int[]){1, 2, 3, 4, 5}, 5); vecDestroy(&v);}
// Filling a vector exactly to its reserved capacity must not grow it.static voidtest_exact_fill_does_not_grow(void) { vec v = vecNew(sizeof(int), NULL); assert(vecExpand(&v, 4) == OK); assert(v.capacity == 4); assert(vecInsert(&v, 0, (int[]){1, 2, 3, 4}, 4) == OK); assert(v.capacity == 4); expect_ints(&v, (int[]){1, 2, 3, 4}, 4); vecDestroy(&v);}
static voidtest_grow_doubles(void) { vec v = vecNew(sizeof(int), NULL); assert(vecExpand(&v, 2) == OK); assert(v.capacity == 2); assert(vecInsert(&v, 0, (int[]){1, 2}, 2) == OK); assert(vecInsert(&v, 2, (int[]){3}, 1) == OK); assert(v.capacity == 4); expect_ints(&v, (int[]){1, 2, 3}, 3); vecDestroy(&v);}
// One insert that needs far more than double, into the middle of existing items.static voidtest_grow_beyond_double(void) { enum { N = 1000 }; int big[N], expected[N + 2]; for (int i = 0; i < N; i++) { big[i] = 100 + i; expected[i + 1] = 100 + i; } expected[0] = 1; expected[N + 1] = 2;
vec v = vecNew(sizeof(int), NULL); assert(vecExpand(&v, 2) == OK); assert(v.capacity == 2); assert(vecInsert(&v, 0, (int[]){1, 2}, 2) == OK); assert(vecInsert(&v, 1, big, N) == OK); assert(v.capacity >= N + 2); expect_ints(&v, expected, N + 2); vecDestroy(&v);}
// Byte-sized elements, the way golf will use it for text buffers.static voidtest_bytes(void) { vec v = vecNew(1, NULL); assert(vecInsert(&v, 0, "world", 5) == OK); assert(vecInsert(&v, 0, "hello, ", 7) == OK); assert(vecInsert(&v, v.length, "", 1) == OK); // the terminator assert(v.length == 13); assert(strcmp((char*)v.data, "hello, world") == 0); vecDestroy(&v);}
// Elements wider than a machine word, to catch byte/element mix-ups.static voidtest_struct_elements(void) { typedef struct { u64 id; char name[24]; } item_t; const item_t a = {1, "one"}, b = {2, "two"}, c = {3, "three"};
vec v = vecNew(sizeof(item_t), NULL); assert(vecInsert(&v, 0, &a, 1) == OK); assert(vecInsert(&v, 1, &c, 1) == OK); assert(vecInsert(&v, 1, &b, 1) == OK);
const item_t* items = (const item_t*)v.data; assert(v.length == 3); assert(items[0].id == 1 && strcmp(items[0].name, "one") == 0); assert(items[1].id == 2 && strcmp(items[1].name, "two") == 0); assert(items[2].id == 3 && strcmp(items[2].name, "three") == 0); vecDestroy(&v);}
// In-range indices point at the element; anything past the end is NULL.static voidtest_get(void) { vec v = vecNew(sizeof(int), NULL); assert(vecGet(&v, 0) == NULL); assert(vecInsert(&v, 0, (int[]){10, 20, 30}, 3) == OK); assert(*(int*)vecGet(&v, 0) == 10); assert(*(int*)vecGet(&v, 2) == 30); assert(vecGet(&v, 1) == v.data + sizeof(int)); assert(vecGet(&v, 3) == NULL); assert(vecGet(&v, 1000) == NULL); vecDestroy(&v);}
static usize destroy_calls;static int destroy_sum;
static voidcount_destroy(void* item) { destroy_calls++; destroy_sum += *(int*)item;}
// The element destructor runs once per element, and only on vecDestroy.static voidtest_destructor_called_per_element(void) { destroy_calls = 0; destroy_sum = 0;
vec v = vecNew(sizeof(int), count_destroy); assert(vecInsert(&v, 0, (int[]){1, 2, 3}, 3) == OK); // grows: no calls assert(vecInsert(&v, 1, (int[]){10}, 1) == OK); // shifts: no calls assert(destroy_calls == 0);
vecDestroy(&v); assert(destroy_calls == 4); assert(destroy_sum == 16);}
static voidtest_destructor_not_called_when_empty(void) { destroy_calls = 0; vec v = vecNew(sizeof(int), count_destroy); vecDestroy(&v); vecDestroy(NULL); assert(destroy_calls == 0);}
typedef struct { char* text; usize length; } owned_t;
static voidowned_destroy(void* item) { free(((owned_t*)item)->text);}
// Elements that own heap memory, like history items will. ASan fails the run// if any string is leaked or freed twice.static voidtest_destructor_frees_owned_memory(void) { vec v = vecNew(sizeof(owned_t), owned_destroy); for (int i = 0; i < 50; i++) { owned_t item = { .text = malloc(16), .length = 16 }; assert(item.text != NULL); memset(item.text, 'a' + i % 26, item.length); assert(vecInsert(&v, v.length / 2, &item, 1) == OK); } assert(v.length == 50); vecDestroy(&v);}
// Destroying leaves an empty vec that keeps its element type and destructor.static voidtest_destroy_resets(void) { vec v = vecNew(sizeof(int), count_destroy); assert(vecInsert(&v, 0, (int[]){1, 2}, 2) == OK); vecDestroy(&v); assert(v.length == 0); assert(v.capacity == 0); assert(v.data == NULL); assert(v.type_size == sizeof(int)); assert(v.destroy == count_destroy);}
// A second destroy must not run destructors again or free the buffer twice.static voidtest_destroy_twice(void) { destroy_calls = 0; vec v = vecNew(sizeof(int), count_destroy); assert(vecInsert(&v, 0, (int[]){1, 2, 3}, 3) == OK); vecDestroy(&v); vecDestroy(&v); assert(destroy_calls == 3);}
static voidtest_reuse_after_destroy(void) { vec v = vecNew(sizeof(int), NULL); assert(vecInsert(&v, 0, (int[]){1, 2}, 2) == OK); vecDestroy(&v); assert(vecInsert(&v, 0, (int[]){3, 4, 5}, 3) == OK); expect_ints(&v, (int[]){3, 4, 5}, 3); vecDestroy(&v);}
intmain(void) { test_new_is_empty(); test_first_insert_allocates(); test_insert_at_end(); test_insert_at_start(); test_insert_in_middle(); test_exact_fill_does_not_grow(); test_grow_doubles(); test_grow_beyond_double(); test_bytes(); test_struct_elements(); test_get(); test_destructor_called_per_element(); test_destructor_not_called_when_empty(); test_destructor_frees_owned_memory(); test_destroy_resets(); test_destroy_twice(); test_reuse_after_destroy(); puts("vec: all tests passed"); return 0;}