calog/tests/testKv.c
2026-07-03 02:13:23 -05:00

170 lines
6.2 KiB
C

// testKv.c -- exercises the kv library: set/get scalars and tables, deep-copy independence,
// has/delete, key listing, and rejection of function values, driven from a Lua context.
// calogKvShutdown() runs BEFORE calogDestroy (like testExport).
#define _POSIX_C_SOURCE 200809L
#include "calog.h"
#include "calogKv.h"
#include <stdatomic.h>
#include <stdio.h>
#include <time.h>
#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;
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);
calogKvRegister(calog);
for (i = 0; i < RESULT_SLOTS; i++) {
atomic_store(&results[i], -1);
}
ctx = calogContextOpen(calog, &calogLuaEngine);
calogContextEval(ctx,
"kvSet('n', 42)\n"
"report(1, kvGet('n'))\n" // 42, store + fetch a scalar
"kvSet('x', 1)\n"
"local had = kvHas('x')\n"
"kvDelete('x')\n"
"report(2, (had and not kvHas('x')) and 1 or 0)\n" // 1, present then gone
"kvSet('t', {a=10, b=20})\n"
"local t = kvGet('t')\n"
"report(3, t.a + t.b)\n" // 30, store + fetch a table
"t.a = 999\n"
"local t2 = kvGet('t')\n"
"report(4, t2.a)\n" // 10, get returns an independent copy
"report(5, kvGet('missing') == nil and 1 or 0)\n" // 1, missing key -> nil
"local ok = pcall(function() kvSet('fn', function() return 1 end) end)\n"
"report(6, ok and 0 or 1)\n" // 1, function value is rejected
"report(7, kvHas('fn') and 0 or 1)\n" // 1, nothing was stored under 'fn'
"local okn = pcall(function() kvSet('nested', {cb = function() return 1 end}) end)\n"
"report(10, (okn == false and kvHas('nested') == false) and 1 or 0)\n" // 1, a fn nested in a table is rejected too
"kvSet('k1', 1)\n"
"kvSet('k2', 2)\n"
"kvSet('k3', 3)\n"
"report(8, #kvKeys())\n" // 5, keys: n, t, k1, k2, k3
"local found = 0\n"
"for _, k in ipairs(kvKeys()) do if k == 'n' then found = 1 end end\n"
"report(9, found)\n" // 1, keys come back as strings
"done()");
pumpUntilDone(1);
CHECK(atomic_load(&results[1]) == 42, "kvGet returns a stored scalar");
CHECK(atomic_load(&results[2]) == 1, "kvHas is false after kvDelete");
CHECK(atomic_load(&results[3]) == 30, "kvGet returns a stored table");
CHECK(atomic_load(&results[4]) == 10, "kvGet returns an independent deep copy");
CHECK(atomic_load(&results[5]) == 1, "kvGet on a missing key returns nil");
CHECK(atomic_load(&results[6]) == 1, "kvSet rejects a function value");
CHECK(atomic_load(&results[7]) == 1, "a rejected function value is not stored");
CHECK(atomic_load(&results[8]) == 5, "kvKeys lists every stored key");
CHECK(atomic_load(&results[9]) == 1, "kvKeys returns keys as strings");
CHECK(atomic_load(&results[10]) == 1, "kvSet rejects a function nested inside an aggregate");
CHECK(atomic_load(&errorCount) == 0, "no uncaught errors");
calogKvShutdown();
calogContextClose(ctx);
calogDestroy(calog);
printf("\n%d checks, %d failed\n", testsRun, testsFailed);
fflush(stdout);
if (testsFailed != 0) {
return 1;
}
return 0;
}