187 lines
6.9 KiB
C
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;
|
|
}
|