178 lines
6.4 KiB
C
178 lines
6.4 KiB
C
// testEngineJs.c -- a real Duktape (JavaScript) heap running on a CalogContextT
|
|
// thread, the fourth engine.
|
|
//
|
|
// Proves the CalogEngineT wiring and that the actor core drives JS exactly as it drives
|
|
// Lua/Squirrel: a JS script on jsCtx's thread calls a thread-agnostic native
|
|
// (report), a native owned by a different context (doubleIt, routed cross-thread),
|
|
// and -- exercising design.md sec 10 -- a JS closure captured on jsCtx's thread is
|
|
// invoked and released from the main thread, both marshalled back to the owner.
|
|
// Built under ASan+UBSan.
|
|
|
|
#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 CALLBACK_INPUT 7
|
|
#define CALLBACK_EXPECTED 107
|
|
|
|
static CalogT *broker = NULL;
|
|
static CalogContextT *jsCtx = NULL;
|
|
static CalogContextT *workerCtx = NULL;
|
|
static _Atomic int64_t reportedValue = 0;
|
|
static _Atomic uint32_t reportedCtxId = 0;
|
|
static _Atomic uint32_t doubleItCtxId = 0;
|
|
static CalogFnT *_Atomic storedCb = NULL;
|
|
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 nativeSetCb(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
|
static void testCallbackAcrossContexts(void);
|
|
static void testCrossContextFromScript(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 nativeSetCb(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
|
|
(void)userData;
|
|
calogValueNil(result);
|
|
if (argCount != 1 || args[0].type != calogFnE) {
|
|
return calogFail(result, calogErrArgE, "setCb expects one function");
|
|
}
|
|
// Runs on jsCtx's thread; the closure is owned by jsCtx. Retain it past this call.
|
|
calogFnRetain(args[0].as.fn);
|
|
atomic_store(&storedCb, args[0].as.fn);
|
|
return calogOkE;
|
|
}
|
|
|
|
|
|
static void testCallbackAcrossContexts(void) {
|
|
CalogFnT *callback;
|
|
CalogValueT arg;
|
|
CalogValueT result;
|
|
int32_t status;
|
|
|
|
// Capture a JS closure on jsCtx's thread via setCb (owned by jsCtx).
|
|
status = calogContextEval(jsCtx, "setCb(function(x) { return x + 100; })", &result);
|
|
CHECK(status == calogOkE, "captured a JS closure as a CalogFnT on its owner thread");
|
|
calogValueFree(&result);
|
|
|
|
callback = atomic_load(&storedCb);
|
|
CHECK(callback != NULL, "callback was stored");
|
|
|
|
// Invoke from the MAIN thread: calogFnInvoke must marshal to jsCtx's thread
|
|
// (design.md sec 10), so the closure runs on its owner heap, never here.
|
|
calogValueInt(&arg, CALLBACK_INPUT);
|
|
status = calogFnInvoke(callback, &arg, 1, &result);
|
|
CHECK(status == calogOkE && result.type == calogIntE && result.as.i == CALLBACK_EXPECTED, "cross-thread callable invoke routed to the owner context");
|
|
calogValueFree(&result);
|
|
calogValueFree(&arg);
|
|
|
|
// Release from the main thread: the registry-slot drop must run on jsCtx's heap.
|
|
calogFnRelease(callback);
|
|
}
|
|
|
|
|
|
static void testCrossContextFromScript(void) {
|
|
CalogValueT result;
|
|
int32_t status;
|
|
|
|
status = calogContextEval(jsCtx, "report(doubleIt(21))", &result);
|
|
CHECK(status == calogOkE, "JS 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(jsCtx), "report executed on the JS context's own thread");
|
|
calogValueFree(&result);
|
|
}
|
|
|
|
|
|
static void testScriptError(void) {
|
|
CalogValueT result;
|
|
int32_t status;
|
|
|
|
status = calogContextEval(jsCtx, "@@@ not valid js @@@", &result);
|
|
CHECK(status != calogOkE && result.type == calogStringE, "script error surfaced through result, not a crash");
|
|
calogValueFree(&result);
|
|
}
|
|
|
|
|
|
int main(void) {
|
|
const char *exposeNames[3];
|
|
CalogConfigT jsConfig;
|
|
|
|
broker = calogCreate();
|
|
if (broker == NULL) {
|
|
printf("broker create failed\n");
|
|
return 1;
|
|
}
|
|
|
|
calogContextCreate(broker, NULL, NULL, &workerCtx);
|
|
|
|
exposeNames[0] = "doubleIt";
|
|
exposeNames[1] = "report";
|
|
exposeNames[2] = "setCb";
|
|
jsConfig.exposeNames = exposeNames;
|
|
jsConfig.exposeCount = 3;
|
|
calogContextCreate(broker, &calogJsEngine, &jsConfig, &jsCtx);
|
|
|
|
// setCb is owned by jsCtx, so register it once jsCtx exists (for its id), before
|
|
// calogContextStart exposes the names on the thread.
|
|
calogRegister(broker, "doubleIt", nativeDoubleIt, NULL, calogContextId(workerCtx));
|
|
calogRegister(broker, "report", nativeReport, NULL, 0);
|
|
calogRegister(broker, "setCb", nativeSetCb, NULL, calogContextId(jsCtx));
|
|
|
|
calogContextStart(workerCtx);
|
|
calogContextStart(jsCtx);
|
|
|
|
testCrossContextFromScript();
|
|
testScriptError();
|
|
testCallbackAcrossContexts();
|
|
|
|
calogDestroy(broker);
|
|
|
|
printf("\n%d checks, %d failed\n", testsRun, testsFailed);
|
|
fflush(stdout);
|
|
if (testsFailed != 0) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|