209 lines
7.5 KiB
C
209 lines
7.5 KiB
C
// testJs.c -- single-threaded Duktape (JavaScript) adapter tests.
|
|
//
|
|
// Mirrors testSquirrel/testLua: scalar/string/array/object marshalling between JS
|
|
// and CalogValueT, a JS function pinned as a CalogFnT and invoked from C through the
|
|
// broker, a closure passed as a native argument and called back, and the export
|
|
// error paths. Single-threaded on purpose (a CalogFnT is invoked on its owner's
|
|
// heap thread); the threaded engine path is testEngineJs. Built under ASan+UBSan.
|
|
|
|
#include "calogInternal.h"
|
|
#include "jsAdapter.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#define CHECK(cond, msg) checkImpl((cond), (msg), __FILE__, __LINE__)
|
|
|
|
#define DOUBLER_INPUT 21
|
|
#define DOUBLER_EXPECTED 42
|
|
#define ADDER_EXPECTED 6
|
|
#define SUM_EXPECTED 42
|
|
|
|
static CalogT *broker = NULL;
|
|
static CalogJsT *jsctx = 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 int32_t nativeSumArr(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
|
static void testExportErrors(void);
|
|
static void testExportInvoke(void);
|
|
static void testFunctionValueArg(void);
|
|
static void testMarshalling(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");
|
|
}
|
|
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 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 testExportErrors(void) {
|
|
CalogFnT *callable;
|
|
int32_t status;
|
|
|
|
status = calogJsExport(jsctx, "noSuchGlobal", &callable);
|
|
CHECK(status == calogErrNotFoundE && callable == NULL, "exporting an undefined name fails as not-found");
|
|
|
|
status = calogJsRun(jsctx, "var myNumber = 5;");
|
|
CHECK(status == calogOkE, "define a non-function global");
|
|
status = calogJsExport(jsctx, "myNumber", &callable);
|
|
CHECK(status == calogErrTypeE && callable == NULL, "exporting a non-function fails as a type error");
|
|
}
|
|
|
|
|
|
static void testExportInvoke(void) {
|
|
CalogFnT *doubler;
|
|
CalogValueT arg;
|
|
CalogValueT result;
|
|
int32_t status;
|
|
|
|
status = calogJsRun(jsctx, "function doubler(x) { return x * 2; }");
|
|
CHECK(status == calogOkE, "define doubler");
|
|
status = calogJsExport(jsctx, "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 JS function 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 = calogJsRun(jsctx, "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");
|
|
}
|
|
|
|
|
|
static void testMarshalling(void) {
|
|
CalogValueT key;
|
|
CalogValueT *got;
|
|
int32_t status;
|
|
|
|
status = calogJsRun(jsctx, "record('hi\\u0000there')");
|
|
CHECK(status == calogOkE && recorded.type == calogStringE && recorded.as.s.length == 8, "binary-safe string marshalled from JS (embedded NUL preserved)");
|
|
|
|
status = calogJsRun(jsctx, "record(sumArr([10, 20, 12]))");
|
|
CHECK(status == calogOkE && recorded.type == calogIntE && recorded.as.i == SUM_EXPECTED, "JS array marshalled into the hybrid aggregate");
|
|
|
|
status = calogJsRun(jsctx, "record({a: 1, b: 2})");
|
|
CHECK(status == calogOkE && recorded.type == calogAggE && recorded.as.agg->pairCount == 2, "JS object marshalled into a map aggregate");
|
|
status = calogValueString(&key, "b", 1);
|
|
CHECK(status == calogOkE, "build lookup key");
|
|
got = (recorded.type == calogAggE) ? calogAggGet(recorded.as.agg, &key) : NULL;
|
|
CHECK(got != NULL && got->type == calogIntE && got->as.i == 2, "object key/value survived the round-trip");
|
|
calogValueFree(&key);
|
|
|
|
// A JS BigInt round-trips to int64 EXACTLY -- the fidelity Duktape (double-only)
|
|
// could not offer: 2^53 + 1 survives, where a double would round it down to 2^53.
|
|
status = calogJsRun(jsctx, "record(9007199254740993n)");
|
|
CHECK(status == calogOkE && recorded.type == calogIntE && recorded.as.i == 9007199254740993LL, "JS BigInt round-trips to int64 exactly (2^53+1 preserved)");
|
|
}
|
|
|
|
|
|
int main(void) {
|
|
broker = calogBrokerCreate();
|
|
if (broker == NULL) {
|
|
printf("broker create failed\n");
|
|
return 1;
|
|
}
|
|
if (calogJsCreate(&jsctx, broker, 0) != calogOkE) {
|
|
printf("js context create failed\n");
|
|
return 1;
|
|
}
|
|
calogRegister(broker, "record", nativeRecord, NULL);
|
|
calogRegister(broker, "applyTo5", nativeApplyTo5, NULL);
|
|
calogRegister(broker, "sumArr", nativeSumArr, NULL);
|
|
calogJsExpose(jsctx, "record");
|
|
calogJsExpose(jsctx, "applyTo5");
|
|
calogJsExpose(jsctx, "sumArr");
|
|
calogValueNil(&recorded);
|
|
|
|
testExportInvoke();
|
|
testFunctionValueArg();
|
|
testMarshalling();
|
|
testExportErrors();
|
|
|
|
calogValueFree(&recorded);
|
|
calogJsDestroy(jsctx);
|
|
calogBrokerDestroy(broker);
|
|
|
|
printf("\n%d checks, %d failed\n", testsRun, testsFailed);
|
|
fflush(stdout);
|
|
if (testsFailed != 0) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|