// testSquirrel.c -- single-threaded Squirrel adapter tests for closure export. // // Mirrors testLua's export coverage: a Squirrel closure is pinned as a refcounted // CalogFnT and invoked from C through the broker, a closure passed as a native // argument round-trips and is called back, and the export error paths are checked. // Single-threaded on purpose -- a CalogFnT must be invoked on its owner's VM // thread, so there is no actor layer here (the threaded engine path is // testEngineSquirrel). Built under ASan+UBSan. #include "calogInternal.h" #include "squirrelAdapter.h" #include #include #include #define CHECK(cond, msg) checkImpl((cond), (msg), __FILE__, __LINE__) #define DOUBLER_INPUT 21 #define DOUBLER_EXPECTED 42 #define ADDER_EXPECTED 6 static CalogT *broker = NULL; static CalogSquirrelT *sqctx = NULL; static CalogValueT recorded; 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 nativeApplyTo5(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static int32_t nativeRecord(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static void testExportErrors(void); static void testExportInvoke(void); static void testFunctionValueArg(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 nativeApplyTo5(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { CalogValueT arg; CalogValueT inner; int32_t status; (void)userData; if (argCount != 1 || args[0].type != calogFnE) { return calogFail(result, calogErrArgE, "applyTo5 expects one function"); } // Call the passed-in Squirrel closure back (it was marshalled in as a CalogFnT). calogValueInt(&arg, 5); status = calogFnInvoke(args[0].as.fn, &arg, 1, &inner); calogValueFree(&arg); calogValueMove(result, &inner); return status; } static int32_t nativeRecord(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { int32_t status; (void)userData; calogValueFree(&recorded); calogValueNil(&recorded); calogValueNil(result); if (argCount != 1) { return calogFail(result, calogErrArgE, "record expects one argument"); } status = calogValueCopy(&recorded, &args[0]); if (status != calogOkE) { return calogFail(result, status, "record failed to copy argument"); } return calogOkE; } static void testExportErrors(void) { CalogFnT *callable; int32_t status; status = calogSquirrelExport(sqctx, "noSuchGlobal", &callable); CHECK(status == calogErrNotFoundE && callable == NULL, "exporting an undefined name fails as not-found"); status = calogSquirrelRun(sqctx, "myNumber <- 5"); CHECK(status == calogOkE, "define a non-closure global"); status = calogSquirrelExport(sqctx, "myNumber", &callable); CHECK(status == calogErrTypeE && callable == NULL, "exporting a non-closure fails as a type error"); } static void testExportInvoke(void) { CalogFnT *doubler; CalogValueT arg; CalogValueT result; int32_t status; status = calogSquirrelRun(sqctx, "function doubler(x) { return x * 2; }"); CHECK(status == calogOkE, "define doubler"); status = calogSquirrelExport(sqctx, "doubler", &doubler); CHECK(status == calogOkE, "export doubler as a CalogFnT"); calogValueInt(&arg, DOUBLER_INPUT); status = calogFnInvoke(doubler, &arg, 1, &result); CHECK(status == calogOkE && result.type == calogIntE && result.as.i == DOUBLER_EXPECTED, "invoke exported Squirrel closure from C"); calogValueFree(&result); calogValueFree(&arg); calogFnRelease(doubler); } static void testFunctionValueArg(void) { int32_t status; // The anonymous closure crosses into the native as a CalogFnT, is invoked with // 5, and the result (6) is recorded. The CalogFnT is released when the native // call's args are freed -- all on this one thread. status = calogSquirrelRun(sqctx, "record(applyTo5(function(x) { return x + 1; }))"); CHECK(status == calogOkE && recorded.type == calogIntE && recorded.as.i == ADDER_EXPECTED, "closure passed as an argument is called back through the broker"); } int main(void) { broker = calogBrokerCreate(); if (broker == NULL) { printf("broker create failed\n"); return 1; } if (calogSquirrelCreate(&sqctx, broker, 0) != calogOkE) { printf("squirrel context create failed\n"); return 1; } calogRegister(broker, "record", nativeRecord, NULL); calogRegister(broker, "applyTo5", nativeApplyTo5, NULL); calogSquirrelExpose(sqctx, "record"); calogSquirrelExpose(sqctx, "applyTo5"); calogValueNil(&recorded); testExportInvoke(); testFunctionValueArg(); testExportErrors(); calogValueFree(&recorded); calogSquirrelDestroy(sqctx); calogBrokerDestroy(broker); printf("\n%d checks, %d failed\n", testsRun, testsFailed); fflush(stdout); if (testsFailed != 0) { return 1; } return 0; }