Files
packfs/tests/test_index_stress.c
T

180 lines
6.2 KiB
C
Raw Normal View History

/*
* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#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();
}