// testFs.c -- exercises the filesystem library: write/read round-trip (binary-safe), append, // exists transitions, stat (size + file/dir kind), directory listing, remove, and a catchable // error, all driven from a Lua context in a private temp directory. #define _POSIX_C_SOURCE 200809L #include "calog.h" #include "calogFs.h" #include #include #include #include #define CHECK(cond, msg) checkImpl((cond), (msg), __FILE__, __LINE__) #define PUMP_LIMIT 4000 #define RESULT_SLOTS 12 static CalogT *calog = NULL; static _Atomic int64_t results[RESULT_SLOTS]; static _Atomic int32_t doneCount = 0; static _Atomic int32_t errorCount = 0; static int32_t testsRun = 0; static int32_t testsFailed = 0; static int64_t asInt(const CalogValueT *value); static void checkImpl(bool condition, const char *message, const char *file, int32_t line); static int32_t nativeDone(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static void onError(uint64_t contextId, const char *message, void *userData); static void pumpUntilDone(int32_t target); static int64_t asInt(const CalogValueT *value) { if (value->type == calogIntE) { return value->as.i; } return (int64_t)value->as.r; } static void checkImpl(bool condition, const char *message, const char *file, int32_t line) { testsRun++; if (!condition) { testsFailed++; printf("FAIL %s:%d %s\n", file, line, message); } } static int32_t nativeDone(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { (void)args; (void)argCount; (void)userData; atomic_fetch_add(&doneCount, 1); calogValueNil(result); return calogOkE; } static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { int64_t tag; (void)userData; calogValueNil(result); if (argCount != 2 || (args[0].type != calogIntE && args[0].type != calogRealE)) { return calogFail(result, calogErrArgE, "report expects (tag, value)"); } tag = asInt(&args[0]); if (tag < 0 || tag >= RESULT_SLOTS) { return calogFail(result, calogErrArgE, "report: tag out of range"); } atomic_store(&results[tag], asInt(&args[1])); return calogOkE; } static void onError(uint64_t contextId, const char *message, void *userData) { (void)contextId; (void)userData; fprintf(stderr, " [script error] %s\n", (message != NULL) ? message : "(null)"); atomic_fetch_add(&errorCount, 1); } static void pumpUntilDone(int32_t target) { struct timespec ts = { 0, 500000 }; int i; for (i = 0; i < PUMP_LIMIT; i++) { calogPump(calog); if (atomic_load(&doneCount) >= target) { calogPump(calog); return; } nanosleep(&ts, NULL); } } int main(void) { CalogContextT *ctx; char script[4096]; char dir[64]; int32_t i; calog = calogCreate(); if (calog == NULL) { printf("calog create failed\n"); return 1; } calogSetErrorHandler(calog, onError, NULL); calogRegister(calog, "report", nativeReport, NULL); calogRegister(calog, "done", nativeDone, NULL); calogFsRegister(calog); for (i = 0; i < RESULT_SLOTS; i++) { atomic_store(&results[i], -1); } // A private per-process temp dir keeps the test hermetic and re-runnable. snprintf(dir, sizeof(dir), "/tmp/calogFsTest_%ld", (long)getpid()); snprintf(script, sizeof(script), "local dir = '%s'\n" "fsMkdir(dir)\n" "local path = dir .. '/data.bin'\n" "local data = 'hello\\0world'\n" // 11 bytes, embedded NUL "fsWrite(path, data)\n" "report(1, fsExists(path) and 1 or 0)\n" // 1, the file now exists "local back = fsRead(path)\n" "report(2, back == data and 1 or 0)\n" // 1, binary-safe round-trip "report(3, #back)\n" // 11, whole file read by size "local st = fsStat(path)\n" "report(4, st.size)\n" // 11, stat size "report(5, (st.isFile and not st.isDir) and 1 or 0)\n" // 1, it is a regular file "fsAppend(path, '!')\n" "report(6, #fsRead(path))\n" // 12, append added a byte "local found = 0\n" "for _, name in ipairs(fsList(dir)) do\n" " if name == 'data.bin' then found = 1 end\n" "end\n" "report(7, found)\n" // 1, listing sees the file "report(8, (fsStat(dir).isDir and not fsStat(dir).isFile) and 1 or 0)\n" // 1, directory stat "local ok = pcall(function() fsRead(dir .. '/nope.dat') end)\n" "report(9, ok and 0 or 1)\n" // 1, missing read is catchable "fsRemove(path)\n" "report(10, fsExists(path) and 0 or 1)\n" // 1, exists went false "report(11, fsStat(path) == nil and 1 or 0)\n" // 1, stat of a missing path is nil "fsRemove(dir)\n" "done()", dir); ctx = calogContextOpen(calog, &calogLuaEngine); calogContextEval(ctx, script); pumpUntilDone(1); CHECK(atomic_load(&results[1]) == 1, "fsWrite then fsExists sees the new file"); CHECK(atomic_load(&results[2]) == 1, "fsRead round-trips binary data with an embedded NUL"); CHECK(atomic_load(&results[3]) == 11, "fsRead returns the whole file by byte length"); CHECK(atomic_load(&results[4]) == 11, "fsStat.size matches the bytes written"); CHECK(atomic_load(&results[5]) == 1, "fsStat reports a regular file (isFile, not isDir)"); CHECK(atomic_load(&results[6]) == 12, "fsAppend grew the file by one byte"); CHECK(atomic_load(&results[7]) == 1, "fsList contains the written file"); CHECK(atomic_load(&results[8]) == 1, "fsStat reports a directory (isDir, not isFile)"); CHECK(atomic_load(&results[9]) == 1, "reading a missing file raises a catchable error"); CHECK(atomic_load(&results[10]) == 1, "fsRemove deletes the file (fsExists transitions to false)"); CHECK(atomic_load(&results[11]) == 1, "fsStat of a missing path returns nil"); CHECK(atomic_load(&errorCount) == 0, "no uncaught errors"); calogContextClose(ctx); calogDestroy(calog); printf("\n%d checks, %d failed\n", testsRun, testsFailed); fflush(stdout); if (testsFailed != 0) { return 1; } return 0; }