calog/tests/testEngineSquirrel.c
2026-06-30 20:55:01 -05:00

187 lines
6.9 KiB
C

// testEngineSquirrel.c -- a real Squirrel VM running on a CalogContextT thread.
//
// The third engine, validating that the CalogEngineT vtable + canonical CalogValueT make a
// new engine drop-in: the same actor core (calogContextCreate/calogContextEval) drives a
// Squirrel VM exactly as it drives Lua. The script calls a thread-agnostic native
// (report), a native owned by a different context (doubleIt, routed cross-thread
// by the exposed-native trampoline), and exercises string and array marshalling
// (reportStr, sumArr). Built under ASan+UBSan; the Squirrel core is linked in
// un-sanitized but its heap use is still tracked across the boundary.
#include "calog.h"
#include <stdatomic.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define CHECK(cond, msg) checkImpl((cond), (msg), __FILE__, __LINE__)
#define DOUBLE_EXPECTED 42
#define SUM_EXPECTED 42
#define REPORTSTR_CAP 64
static CalogT *broker = NULL;
static CalogContextT *squirrelCtx = NULL;
static CalogContextT *workerCtx = NULL;
static _Atomic int64_t reportedValue = 0;
static _Atomic uint32_t reportedCtxId = 0;
static _Atomic uint32_t doubleItCtxId = 0;
static char reportedStr[REPORTSTR_CAP];
static int32_t testsRun = 0;
static int32_t testsFailed = 0;
static void checkImpl(bool condition, const char *message, const char *file, int32_t line);
static int32_t nativeDoubleIt(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeReportStr(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeSumArr(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static void testCrossContextFromScript(void);
static void testMarshalRoundTrip(void);
static void testScriptError(void);
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 nativeDoubleIt(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
(void)userData;
atomic_store(&doubleItCtxId, calogCurrentId());
if (argCount != 1 || args[0].type != calogIntE) {
return calogFail(result, calogErrArgE, "doubleIt expects one integer");
}
calogValueInt(result, args[0].as.i * 2);
return calogOkE;
}
static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
(void)userData;
if (argCount != 1 || args[0].type != calogIntE) {
return calogFail(result, calogErrArgE, "report expects one integer");
}
atomic_store(&reportedCtxId, calogCurrentId());
atomic_store(&reportedValue, args[0].as.i);
calogValueNil(result);
return calogOkE;
}
static int32_t nativeReportStr(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
(void)userData;
if (argCount != 1 || args[0].type != calogStringE) {
return calogFail(result, calogErrArgE, "reportStr expects one string");
}
// Truncate defensively; the test strings are short. Synchronization with the
// reader is via the eval reply, which establishes happens-before.
snprintf(reportedStr, sizeof(reportedStr), "%s", args[0].as.s.bytes);
calogValueNil(result);
return calogOkE;
}
static int32_t nativeSumArr(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
CalogAggT *aggregate;
int64_t sum;
int64_t index;
(void)userData;
if (argCount != 1 || args[0].type != calogAggE) {
return calogFail(result, calogErrArgE, "sumArr expects one array");
}
aggregate = args[0].as.agg;
sum = 0;
for (index = 0; index < aggregate->arrayCount; index++) {
if (aggregate->array[index].type != calogIntE) {
return calogFail(result, calogErrTypeE, "sumArr expects integer elements");
}
sum += aggregate->array[index].as.i;
}
calogValueInt(result, sum);
return calogOkE;
}
static void testCrossContextFromScript(void) {
CalogValueT result;
int32_t status;
status = calogContextEval(squirrelCtx, "report(doubleIt(21))", &result);
CHECK(status == calogOkE, "squirrel script with a cross-context call ran without error");
CHECK(atomic_load(&reportedValue) == DOUBLE_EXPECTED, "cross-context doubleIt result flowed back into the script");
CHECK(atomic_load(&doubleItCtxId) == calogContextId(workerCtx), "doubleIt executed on the worker context's thread");
CHECK(atomic_load(&reportedCtxId) == calogContextId(squirrelCtx), "report executed on the Squirrel context's own thread");
calogValueFree(&result);
}
static void testMarshalRoundTrip(void) {
CalogValueT result;
int32_t status;
status = calogContextEval(squirrelCtx, "reportStr(\"squirrel\")", &result);
CHECK(status == calogOkE && strcmp(reportedStr, "squirrel") == 0, "string marshalled from Squirrel into a native");
calogValueFree(&result);
status = calogContextEval(squirrelCtx, "report(sumArr([10, 20, 12]))", &result);
CHECK(status == calogOkE && atomic_load(&reportedValue) == SUM_EXPECTED, "array marshalled from Squirrel into the hybrid aggregate");
calogValueFree(&result);
}
static void testScriptError(void) {
CalogValueT result;
int32_t status;
status = calogContextEval(squirrelCtx, "@@@ not valid squirrel @@@", &result);
CHECK(status != calogOkE && result.type == calogStringE, "script error surfaced through result, not a crash");
calogValueFree(&result);
}
int main(void) {
const char *exposeNames[4];
CalogConfigT squirrelConfig;
broker = calogCreate();
if (broker == NULL) {
printf("broker create failed\n");
return 1;
}
calogContextCreate(broker, NULL, NULL, &workerCtx);
calogRegister(broker, "doubleIt", nativeDoubleIt, NULL, calogContextId(workerCtx));
calogRegister(broker, "report", nativeReport, NULL, 0);
calogRegister(broker, "reportStr", nativeReportStr, NULL, 0);
calogRegister(broker, "sumArr", nativeSumArr, NULL, 0);
exposeNames[0] = "doubleIt";
exposeNames[1] = "report";
exposeNames[2] = "reportStr";
exposeNames[3] = "sumArr";
squirrelConfig.exposeNames = exposeNames;
squirrelConfig.exposeCount = 4;
calogContextCreate(broker, &calogSquirrelEngine, &squirrelConfig, &squirrelCtx);
calogContextStart(workerCtx);
calogContextStart(squirrelCtx);
testCrossContextFromScript();
testMarshalRoundTrip();
testScriptError();
calogDestroy(broker);
printf("\n%d checks, %d failed\n", testsRun, testsFailed);
fflush(stdout);
if (testsFailed != 0) {
return 1;
}
return 0;
}