calog/tests/testTrace.c

135 lines
4.3 KiB
C

// testTrace.c -- cross-boundary stack traces. A Lua context exports a function that errors; a
// JavaScript context calls it by name. The failure crosses the context/engine boundary, and the
// error delivered to the handler carries the boundary tag ("[lua ctx N] ...") -- so a cross-engine
// failure is readable without changing the error handler's signature.
#define _POSIX_C_SOURCE 200809L
#include "calog.h"
#include "calogExport.h"
#include <stdatomic.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#define PUMP_LIMIT 4000
static CalogT *calog = NULL;
static _Atomic bool ready = false;
static _Atomic int32_t errorCount = 0;
static char lastMessage[512] = { 0 };
static int32_t testsRun = 0;
static int32_t testsFailed = 0;
static _Atomic bool reported = false;
static void checkImpl(bool condition, const char *message, int32_t line);
static int32_t nativeReady(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static void onError(uint64_t contextId, const char *message, void *userData);
static void pumpUntil(_Atomic bool *flag);
#define CHECK(cond, msg) checkImpl((cond), (msg), __LINE__)
static void checkImpl(bool condition, const char *message, int32_t line) {
testsRun++;
if (!condition) {
testsFailed++;
printf("FAIL testTrace.c:%d %s\n", line, message);
}
}
static int32_t nativeReady(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
(void)args;
(void)argCount;
(void)userData;
atomic_store(&ready, true);
calogValueNil(result);
return calogOkE;
}
static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
(void)userData;
calogValueNil(result);
if (argCount == 1 && args[0].type == calogStringE) {
size_t len;
len = (size_t)args[0].as.s.length;
if (len >= sizeof(lastMessage)) {
len = sizeof(lastMessage) - 1;
}
memcpy(lastMessage, args[0].as.s.bytes, len);
lastMessage[len] = '\0';
atomic_store(&reported, true);
}
return calogOkE;
}
static void onError(uint64_t contextId, const char *message, void *userData) {
(void)contextId;
(void)message;
(void)userData;
atomic_fetch_add(&errorCount, 1);
}
static void pumpUntil(_Atomic bool *flag) {
struct timespec ts = { 0, 500000 };
int32_t i;
for (i = 0; i < PUMP_LIMIT; i++) {
calogPump(calog);
if (atomic_load(flag)) {
calogPump(calog);
return;
}
nanosleep(&ts, NULL);
}
}
int main(void) {
CalogContextT *lua;
CalogContextT *js;
_Atomic bool errored = false;
int32_t before;
calog = calogCreate();
if (calog == NULL) {
printf("calog create failed\n");
return 1;
}
calogSetErrorHandler(calog, onError, NULL);
calogExportRegister(calog);
calogRegister(calog, "ready", nativeReady, NULL);
calogRegister(calog, "report", nativeReport, NULL);
// A Lua context exports a function that raises.
lua = calogContextOpen(calog, &calogLuaEngine);
calogContextEval(lua, "calogExport('boom', function() error('kaboom in lua') end); ready()");
pumpUntil(&ready);
CHECK(atomic_load(&ready), "lua context published the export");
// A JavaScript context calls the Lua export by name and catches the failure; the caught error
// is the cross-boundary-tagged message.
before = atomic_load(&errorCount);
js = calogContextOpen(calog, &calogJsEngine);
calogContextEval(js, "try { calogCall('boom'); } catch (e) { report(String(e)); }");
pumpUntil(&reported);
(void)errored;
(void)before;
CHECK(atomic_load(&reported), "the cross-engine call failed and JS caught it");
CHECK(strstr(lastMessage, "kaboom in lua") != NULL, "the original engine error survived the crossing");
CHECK(strstr(lastMessage, "lua ctx") != NULL, "the error is tagged with the boundary it unwound through");
calogDestroy(calog);
printf("last message: %s\n", lastMessage);
printf("\n%d checks, %d failed\n", testsRun, testsFailed);
fflush(stdout);
return testsFailed == 0 ? 0 : 1;
}