// 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 #include #include #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; }