// Tests for bin.h - flat file block storage operations #include typedef int32_t bin_key_t; #define BIN_IMPLEMENTATION #include "../include/bin.h" #include #include #include #include #define ASSERT(cond, msg) \ do { \ if (!(cond)) { \ fprintf(stderr, "FAIL: %s (test_bin.c:%d)\n", msg, __LINE__); \ failures++; \ } \ } while (0) static int failures = 0; struct record { bin_key_t key; char _pad[4]; double value; }; #define REC(k, v) (&((struct record){.key = (k), .value = (v)})) void cleanup(char const *path) { remove(path); } void test_init_and_close() { FILE *f = bin_init("/tmp/test_bin_init.bin", sizeof(struct record)); ASSERT(f != NULL, "bin_init returns non-NULL"); if (!f) return; ASSERT(bin_length(f) == 0, "initial length is 0"); ASSERT(bin_block_size(f) == sizeof(struct record), "block size"); bin_close(f); cleanup("/tmp/test_bin_init.bin"); } void test_append_and_read() { FILE *f = bin_init("/tmp/test_bin_append.bin", sizeof(struct record)); ASSERT(f != NULL, "bin_init returns non-NULL"); if (!f) return; for (int i = 0; i < 10; i++) { bin_append(f, REC(i, i * 2.5), 1); } ASSERT(bin_length(f) == 10, "length after 10 appends"); struct record buf[10]; ASSERT(bin_read(f, 0, buf, 10) == 10, "read 10 records"); for (int i = 0; i < 10; i++) { ASSERT(buf[i].key == i, "read key"); ASSERT(fabs(buf[i].value - (double)(i * 2.5)) < 1e-9, "read value"); } bin_close(f); cleanup("/tmp/test_bin_append.bin"); } void test_write() { FILE *f = bin_init("/tmp/test_bin_write.bin", sizeof(struct record)); ASSERT(f != NULL, "bin_init returns non-NULL"); if (!f) return; bin_append(f, REC(0, 0), 1); bin_append(f, REC(1, 1), 1); bin_write(f, 0, REC(99, 99), 1); struct record r; ASSERT(bin_read(f, 0, &r, 1) == 1, "read 1 record"); ASSERT(r.key == 99, "write overwrites"); ASSERT(r.value == 99, "write overwrites value"); bin_close(f); cleanup("/tmp/test_bin_write.bin"); } void test_insert() { FILE *f = bin_init("/tmp/test_bin_insert.bin", sizeof(struct record)); ASSERT(f != NULL, "bin_init returns non-NULL"); if (!f) return; bin_append(f, REC(0, 0), 1); bin_append(f, REC(2, 2), 1); bin_insert(f, 1, REC(1, 1), 1); ASSERT(bin_length(f) == 3, "length after insert"); struct record buf[3]; ASSERT(bin_read(f, 0, buf, 3) == 3, "read 3 records"); ASSERT(buf[0].key == 0, "insert at 1 preserves [0]"); ASSERT(buf[1].key == 1, "insert at 1 sets [1]"); ASSERT(buf[2].key == 2, "insert at 1 preserves [2]"); bin_close(f); cleanup("/tmp/test_bin_insert.bin"); } void test_insert_at_end() { FILE *f = bin_init("/tmp/test_bin_insert_end.bin", sizeof(struct record)); ASSERT(f != NULL, "bin_init returns non-NULL"); if (!f) return; bin_append(f, REC(0, 0), 1); bin_insert(f, 1, REC(1, 1), 1); ASSERT(bin_length(f) == 2, "length after insert at end"); struct record buf[2]; ASSERT(bin_read(f, 0, buf, 2) == 2, "read 2 records"); ASSERT(buf[0].key == 0, "insert at end preserves [0]"); ASSERT(buf[1].key == 1, "insert at end sets [1]"); bin_close(f); cleanup("/tmp/test_bin_insert_end.bin"); } void test_insert_at_beginning() { FILE *f = bin_init("/tmp/test_bin_insert_begin.bin", sizeof(struct record)); ASSERT(f != NULL, "bin_init returns non-NULL"); if (!f) return; bin_append(f, REC(1, 1), 1); bin_insert(f, 0, REC(0, 0), 1); struct record buf[2]; ASSERT(bin_read(f, 0, buf, 2) == 2, "read 2 records"); ASSERT(buf[0].key == 0, "insert at 0 sets [0]"); ASSERT(buf[1].key == 1, "insert at 0 preserves [1]"); bin_close(f); cleanup("/tmp/test_bin_insert_begin.bin"); } void test_open() { FILE *f = bin_init("/tmp/test_bin_open.bin", sizeof(struct record)); ASSERT(f != NULL, "bin_init returns non-NULL"); if (!f) { cleanup("/tmp/test_bin_open.bin"); return; } bin_append(f, REC(42, 42), 1); bin_close(f); f = bin_open("/tmp/test_bin_open.bin"); ASSERT(f != NULL, "bin_open returns non-NULL"); if (!f) { cleanup("/tmp/test_bin_open.bin"); return; } ASSERT(bin_length(f) == 1, "opened file has correct length"); struct record r; ASSERT(bin_read(f, 0, &r, 1) == 1, "read 1 record"); ASSERT(r.key == 42, "read from opened file"); bin_close(f); cleanup("/tmp/test_bin_open.bin"); } void test_search() { FILE *f = bin_init("/tmp/test_bin_search.bin", sizeof(struct record)); ASSERT(f != NULL, "bin_init returns non-NULL"); if (!f) return; for (int i = 0; i < 20; i++) { bin_append(f, REC(i * 5, 0), 1); } i64 found = bin_search(f, 25); ASSERT(found == 5, "search finds existing key"); i64 not_found = bin_search(f, 26); ASSERT(not_found < 0, "search returns negative for missing key"); i64 idx = bin_fuzzy_index(not_found); ASSERT(idx == 6, "fuzzy_index returns insertion point"); bin_close(f); cleanup("/tmp/test_bin_search.bin"); } void test_search_empty() { FILE *f = bin_init("/tmp/test_bin_search_empty.bin", sizeof(struct record)); ASSERT(f != NULL, "bin_init returns non-NULL"); if (!f) return; i64 result = bin_search(f, 1); ASSERT(result == -1, "search empty returns -1"); bin_close(f); cleanup("/tmp/test_bin_search_empty.bin"); } void test_append_multiple() { FILE *f = bin_init("/tmp/test_bin_multi.bin", sizeof(struct record)); ASSERT(f != NULL, "bin_init returns non-NULL"); if (!f) return; struct record batch[] = { {.key = 10, .value = 10}, {.key = 20, .value = 20}, {.key = 30, .value = 30}, }; bin_append(f, batch, 3); ASSERT(bin_length(f) == 3, "batch append length"); struct record buf[3]; ASSERT(bin_read(f, 0, buf, 3) == 3, "read 3 records"); ASSERT(buf[0].key == 10, "batch[0]"); ASSERT(buf[1].key == 20, "batch[1]"); ASSERT(buf[2].key == 30, "batch[2]"); bin_close(f); cleanup("/tmp/test_bin_multi.bin"); } void test_fuzzy_index_positive() { u64 idx = bin_fuzzy_index(5); ASSERT(idx == 5, "fuzzy_index passthrough positive"); } void test_ok_variants() { FILE *f = bin_init("/tmp/test_bin_ok.bin", sizeof(struct record)); ASSERT(f != NULL, "bin_init returns non-NULL"); if (!f) return; ASSERT(bin_append_ok(f, REC(1, 1), 1), "append_ok returns 1"); ASSERT(bin_append_ok(f, NULL, 1) == 0, "append_ok NULL data returns 0"); ASSERT(bin_append_ok(NULL, REC(2, 2), 1) == 0, "append_ok NULL file returns 0"); ASSERT(bin_write_ok(f, 0, REC(99, 99), 1), "write_ok returns 1"); ASSERT(bin_write_ok(f, -1, REC(0, 0), 1) == 0, "write_ok negative index returns 0"); ASSERT(bin_write_ok(f, 99, REC(0, 0), 1) == 0, "write_ok out-of-range returns 0"); ASSERT(bin_insert_ok(f, 0, REC(0, 0), 1), "insert_ok returns 1"); ASSERT(bin_insert_ok(f, -1, REC(0, 0), 1) == 0, "insert_ok negative index returns 0"); ASSERT(bin_close(f), "close returns 1"); ASSERT(bin_close(NULL) == 0, "close NULL returns 0"); cleanup("/tmp/test_bin_ok.bin"); } int main() { test_init_and_close(); test_append_and_read(); test_write(); test_insert(); test_insert_at_end(); test_insert_at_beginning(); test_open(); test_search(); test_search_empty(); test_append_multiple(); test_fuzzy_index_positive(); test_ok_variants(); printf("test_bin: %d failures\n", failures); return failures; }