diff options
| author | Ethan Werner <mail@ethanwerner.com> | 2026-09-19 06:09:54 +0000 |
|---|---|---|
| committer | Ethan Werner <mail@ethanwerner.com> | 2026-09-19 06:09:54 +0000 |
| commit | bf19b9938508f70011395f4dc283ca758ec6a8d3 (patch) | |
| tree | 46dc4c442ed8d8d43040433c6a4794e76f8e618a /tests/test_bin.c | |
| parent | 96ecf29e8be977f51f351308ef2c7c492bb829aa (diff) | |
Diffstat (limited to 'tests/test_bin.c')
| -rw-r--r-- | tests/test_bin.c | 273 |
1 files changed, 273 insertions, 0 deletions
diff --git a/tests/test_bin.c b/tests/test_bin.c new file mode 100644 index 0000000..f12f336 --- /dev/null +++ b/tests/test_bin.c @@ -0,0 +1,273 @@ +// Tests for bin.h - flat file block storage operations +#include <stdint.h> +typedef int32_t bin_key_t; + +#define BIN_IMPLEMENTATION +#include "../include/bin.h" + +#include <math.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#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; +} |