calog/tests/testEngineMyBasic.c

114 lines
3.6 KiB
C

// testEngineMyBasic.c -- my-basic on context threads under the actor model. Verifies a
// host native is serviced on the host thread, and that several my-basic contexts run
// concurrently without corruption -- my-basic keeps process-global state, so the engine
// serializes my-basic work with a lock; this is the test that would race without it.
// Built under ASan+UBSan (make test) and ThreadSanitizer (make tsanmb).
#define _POSIX_C_SOURCE 200809L
#include "calog.h"
#include <stdatomic.h>
#include <stdio.h>
#include <time.h>
#define CHECK(cond, msg) checkImpl((cond), (msg), __FILE__, __LINE__)
#define PUMP_LIMIT 4000
#define CONTEXT_COUNT 3
static CalogT *calog = NULL;
static _Atomic int32_t bumpCount = 0;
static _Atomic uint64_t bumpCtxId = 0xFFFFu;
static int32_t testsRun = 0;
static int32_t testsFailed = 0;
static int32_t bump(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static void checkImpl(bool condition, const char *message, const char *file, int32_t line);
static void testConcurrentContexts(void);
static void testHostNative(void);
static int32_t bump(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
(void)args;
(void)argCount;
(void)userData;
atomic_store(&bumpCtxId, calogCurrentId());
atomic_fetch_add(&bumpCount, 1);
calogValueNil(result);
return calogOkE;
}
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 void testConcurrentContexts(void) {
CalogContextT *ctxs[CONTEXT_COUNT];
struct timespec ts = { 0, 500000 };
int32_t i;
// The scripts allocate (list creation) as they run so the parallel execution paths
// churn my-basic's now-atomic allocation counter concurrently -- the case that
// raced before the vendored patch (see make tsanmb).
atomic_store(&bumpCount, 0);
for (i = 0; i < CONTEXT_COUNT; i++) {
ctxs[i] = calogContextOpen(calog, &calogMyBasicEngine);
calogContextEval(ctxs[i], "a = list(1, 2, 3)\nb = list(4, 5, 6)\nbump()");
}
for (i = 0; i < PUMP_LIMIT && atomic_load(&bumpCount) < CONTEXT_COUNT; i++) {
calogPump(calog);
nanosleep(&ts, NULL);
}
CHECK(atomic_load(&bumpCount) == CONTEXT_COUNT, "several concurrent my-basic contexts all reached the host native");
for (i = 0; i < CONTEXT_COUNT; i++) {
calogContextClose(ctxs[i]);
}
}
static void testHostNative(void) {
CalogContextT *ctx;
struct timespec ts = { 0, 500000 };
int32_t i;
ctx = calogContextOpen(calog, &calogMyBasicEngine);
CHECK(ctx != NULL, "opened a my-basic context");
atomic_store(&bumpCount, 0);
calogContextEval(ctx, "bump()");
for (i = 0; i < PUMP_LIMIT && atomic_load(&bumpCount) < 1; i++) {
calogPump(calog);
nanosleep(&ts, NULL);
}
CHECK(atomic_load(&bumpCount) == 1, "the my-basic script's native call ran");
CHECK(atomic_load(&bumpCtxId) == 0, "the native ran on the host thread (id 0)");
calogContextClose(ctx);
}
int main(void) {
calog = calogCreate();
if (calog == NULL) {
printf("calog create failed\n");
return 1;
}
calogRegister(calog, "bump", bump, NULL);
testHostNative();
testConcurrentContexts();
calogDestroy(calog);
printf("\n%d checks, %d failed\n", testsRun, testsFailed);
fflush(stdout);
if (testsFailed != 0) {
return 1;
}
return 0;
}