/* * test_index_stress.c — correctness of the persistent treap index * (Section 9.1's sort order; see CLAUDE.md's "Known performance * characteristics" and BENCH.md for why it replaced a flat array) under * heavy, randomized structural churn, cross-checked against an * independent reference model rather than just "did it not crash." * * This exercises exactly the code path BENCH.md's O(n^2) finding and its * fix live in: upper.c's snapshot_upsert/snapshot_remove and the treap * (split3/merge, node refcounting) beneath them, at a scale (thousands * of entries, non-sequential insertion/deletion order, nested * directories, renames) the other test files don't reach. */ #include #include #include #include "packfs.h" #include "test_harness.h" #define N 8000 static int g_alive[N]; static void name_for(int id, char *buf, size_t cap) { /* deliberately nested, to exercise readdir's prefix-range logic at * more than one level, not just a flat root directory */ snprintf(buf, cap, "/d%03d/f%06d.dat", id % 50, id); } static void shuffle(int *order, int n) { for (int i = n - 1; i > 0; i--) { int j = rand() % (i + 1); int t = order[i]; order[i] = order[j]; order[j] = t; } } static void create_one(Vfs *v, int id) { char path[64], parent[16]; name_for(id, path, sizeof(path)); snprintf(parent, sizeof(parent), "/d%03d", id % 50); int err = 0; if (vfs_stat(v, parent, &(VfsStat){0}) != VFS_OK) vfs_mkdir(v, parent); VfsFile *f = vfs_open(v, path, VFS_O_WRONLY | VFS_O_CREAT | VFS_O_TRUNC, &err); CHECK(f != NULL); if (!f) return; char payload[32]; int n = snprintf(payload, sizeof(payload), "id=%d", id); CHECK_EQ_INT(vfs_write(f, payload, (pfs_usize)n), n); vfs_close(f); g_alive[id] = 1; } static void delete_one(Vfs *v, int id) { char path[64]; name_for(id, path, sizeof(path)); CHECK_EQ_INT(vfs_unlink(v, path), VFS_OK); g_alive[id] = 0; } /* Cross-checks the live tree against g_alive[]: every alive id must * stat successfully with the right content; every dead id must NOENT; * and the total count reachable via nested readdir must match the * reference model's count exactly (not just "at least"). */ static void verify_consistency(Vfs *v) { int expected_total = 0; for (int id = 0; id < N; id++) { char path[64]; name_for(id, path, sizeof(path)); VfsStat st; int rc = vfs_stat(v, path, &st); if (g_alive[id]) { expected_total++; CHECK_EQ_INT(rc, VFS_OK); if (rc == VFS_OK) { CHECK_EQ_INT(st.kind, VFS_KIND_FILE); int err = 0; VfsFile *f = vfs_open(v, path, VFS_O_RDONLY, &err); CHECK(f != NULL); if (f) { char buf[32] = {0}, expect[32]; vfs_read(f, buf, sizeof(buf) - 1); snprintf(expect, sizeof(expect), "id=%d", id); CHECK_STR_EQ(buf, expect); vfs_close(f); } } } else { CHECK_EQ_INT(rc, VFS_ERR_NOENT); } } /* Full nested readdir sweep: sum of children across every /dNNN that * still has any live file must equal the reference model's count. * This exercises upper_visit_range's prefix pruning at every * directory, not just the root. */ int counted = 0; for (int d = 0; d < 50; d++) { char dirpath[16]; snprintf(dirpath, sizeof(dirpath), "/d%03d", d); VfsDir dir; if (vfs_readdir(v, dirpath, &dir) == VFS_OK) { counted += (int)dir.count; vfs_dir_free(&dir); } } CHECK_EQ_INT(counted, expected_total); } int main(void) { srand(20260914); Vfs *v = vfs_new(); Backend *mem = backend_mem_new(); CHECK_EQ_INT(vfs_mount(v, "/", mem), VFS_OK); int order[N]; for (int i = 0; i < N; i++) order[i] = i; /* round 1: create everything, in randomized (non-sequential) order — * the exact pattern that made the flat array O(n^2); the treap must * both stay correct and (implicitly, via this test completing in * reasonable time under sanitizers) stay fast. */ shuffle(order, N); for (int i = 0; i < N; i++) create_one(v, order[i]); verify_consistency(v); /* round 2: delete a random 40%, in a different random order */ shuffle(order, N); for (int i = 0; i < N * 2 / 5; i++) delete_one(v, order[i]); verify_consistency(v); /* round 3: recreate half of what was just deleted, delete a * different random slice of what's still alive, interleaved by * iterating one combined shuffled order over "toggle this id" */ shuffle(order, N); for (int i = 0; i < N; i++) { int id = order[i]; if (id % 3 == 0) { if (g_alive[id]) delete_one(v, id); else create_one(v, id); } } verify_consistency(v); /* renames: move a sample of live files to a fresh, previously-unused * path, verify old path gone / new path present with right content */ for (int id = 0; id < N; id += 37) { if (!g_alive[id]) continue; char from[64], to[80]; name_for(id, from, sizeof(from)); snprintf(to, sizeof(to), "%s.moved", from); CHECK_EQ_INT(vfs_rename(v, from, to), VFS_OK); VfsStat st; CHECK_EQ_INT(vfs_stat(v, from, &st), VFS_ERR_NOENT); CHECK_EQ_INT(vfs_stat(v, to, &st), VFS_OK); CHECK_EQ_INT(vfs_rename(v, to, from), VFS_OK); /* move it back so verify_consistency's model still holds */ } verify_consistency(v); /* final: delete everything still alive, in yet another random order; * the tree must end up empty and every readdir empty. */ shuffle(order, N); for (int i = 0; i < N; i++) if (g_alive[order[i]]) delete_one(v, order[i]); verify_consistency(v); for (int d = 0; d < 50; d++) { char dirpath[16]; snprintf(dirpath, sizeof(dirpath), "/d%03d", d); VfsDir dir; if (vfs_readdir(v, dirpath, &dir) == VFS_OK) { CHECK_EQ_INT(dir.count, 0); vfs_dir_free(&dir); } } vfs_unmount(v, "/"); backend_free(mem); vfs_free(v); TEST_MAIN_END(); }