// 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 #include #include #include #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; }