190 lines
6.3 KiB
C
190 lines
6.3 KiB
C
// testTimer.c -- exercises the timer library from Lua: a one-shot fires once after a delay, a
|
|
// cancelled one-shot never fires, and a repeating timer fires several times before cancelling
|
|
// itself. Each callback runs on its owning context and reports back through host natives, which
|
|
// the main thread services by pumping.
|
|
|
|
#define _POSIX_C_SOURCE 200809L
|
|
|
|
#include "calog.h"
|
|
#include "calogTimer.h"
|
|
|
|
#include <stdatomic.h>
|
|
#include <stdio.h>
|
|
#include <time.h>
|
|
|
|
#define CHECK(cond, msg) checkImpl((cond), (msg), __FILE__, __LINE__)
|
|
|
|
#define PUMP_LIMIT 4000
|
|
#define RESULT_SLOTS 12
|
|
|
|
static CalogT *calog = NULL;
|
|
static _Atomic int64_t results[RESULT_SLOTS];
|
|
static _Atomic int32_t doneCount = 0;
|
|
static _Atomic int32_t errorCount = 0;
|
|
static int32_t testsRun = 0;
|
|
static int32_t testsFailed = 0;
|
|
|
|
static int64_t asInt(const CalogValueT *value);
|
|
static void checkImpl(bool condition, const char *message, const char *file, int32_t line);
|
|
static int32_t nativeDone(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 pumpSettle(void);
|
|
static void pumpUntilDone(int32_t target);
|
|
|
|
|
|
static int64_t asInt(const CalogValueT *value) {
|
|
if (value->type == calogIntE) {
|
|
return value->as.i;
|
|
}
|
|
return (int64_t)value->as.r;
|
|
}
|
|
|
|
|
|
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 nativeDone(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
|
|
(void)args;
|
|
(void)argCount;
|
|
(void)userData;
|
|
atomic_fetch_add(&doneCount, 1);
|
|
calogValueNil(result);
|
|
return calogOkE;
|
|
}
|
|
|
|
|
|
static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
|
|
int64_t tag;
|
|
|
|
(void)userData;
|
|
calogValueNil(result);
|
|
if (argCount != 2 || (args[0].type != calogIntE && args[0].type != calogRealE) || (args[1].type != calogIntE && args[1].type != calogRealE)) {
|
|
return calogFail(result, calogErrArgE, "report expects (tag, value)");
|
|
}
|
|
tag = asInt(&args[0]);
|
|
if (tag < 0 || tag >= RESULT_SLOTS) {
|
|
return calogFail(result, calogErrArgE, "report: tag out of range");
|
|
}
|
|
atomic_store(&results[tag], asInt(&args[1]));
|
|
return calogOkE;
|
|
}
|
|
|
|
|
|
static void onError(uint64_t contextId, const char *message, void *userData) {
|
|
(void)contextId;
|
|
(void)userData;
|
|
fprintf(stderr, " [script error] %s\n", (message != NULL) ? message : "(null)");
|
|
atomic_fetch_add(&errorCount, 1);
|
|
}
|
|
|
|
|
|
static void pumpSettle(void) {
|
|
struct timespec ts = { 0, 500000 };
|
|
int i;
|
|
|
|
// Keep pumping for ~60ms so a broken cancel or a stray extra fire has time to show up.
|
|
for (i = 0; i < 120; i++) {
|
|
calogPump(calog);
|
|
nanosleep(&ts, NULL);
|
|
}
|
|
}
|
|
|
|
|
|
static void pumpUntilDone(int32_t target) {
|
|
struct timespec ts = { 0, 500000 };
|
|
int i;
|
|
|
|
for (i = 0; i < PUMP_LIMIT; i++) {
|
|
calogPump(calog);
|
|
if (atomic_load(&doneCount) >= target) {
|
|
calogPump(calog);
|
|
return;
|
|
}
|
|
nanosleep(&ts, NULL);
|
|
}
|
|
}
|
|
|
|
|
|
int main(void) {
|
|
CalogContextT *ctxAfter;
|
|
CalogContextT *ctxCancel;
|
|
CalogContextT *ctxEvery;
|
|
int32_t i;
|
|
|
|
calog = calogCreate();
|
|
if (calog == NULL) {
|
|
printf("calog create failed\n");
|
|
return 1;
|
|
}
|
|
calogSetErrorHandler(calog, onError, NULL);
|
|
calogRegister(calog, "report", nativeReport, NULL);
|
|
calogRegister(calog, "done", nativeDone, NULL);
|
|
if (calogTimerRegister(calog) != calogOkE) {
|
|
printf("calogTimerRegister failed\n");
|
|
return 1;
|
|
}
|
|
for (i = 0; i < RESULT_SLOTS; i++) {
|
|
atomic_store(&results[i], -1);
|
|
}
|
|
|
|
// A one-shot fires its callback once, ~10ms from now, on its owning context.
|
|
ctxAfter = calogContextOpen(calog, &calogLuaEngine);
|
|
calogContextEval(ctxAfter,
|
|
"timerAfter(10, function() report(1, 42); done() end)");
|
|
|
|
// A one-shot cancelled immediately must NEVER fire (slot 3 stays -1). Slot 4 confirms the
|
|
// script itself ran to completion.
|
|
ctxCancel = calogContextOpen(calog, &calogLuaEngine);
|
|
calogContextEval(ctxCancel,
|
|
"local h = timerAfter(20, function() report(3, 999) end)\n"
|
|
"timerCancel(h)\n"
|
|
"report(4, 1)\n"
|
|
"local okr = pcall(function() timerAfter(1/0, function() end) end)\n"
|
|
"report(5, okr and 0 or 1)\n" // 1, a non-finite delay is rejected, not UB
|
|
"done()");
|
|
|
|
// A repeating timer fires every ~5ms; after the third fire it cancels itself and reports the
|
|
// final count. Slot 2 must settle at exactly 3.
|
|
ctxEvery = calogContextOpen(calog, &calogLuaEngine);
|
|
calogContextEval(ctxEvery,
|
|
"local count = 0\n"
|
|
"local id\n"
|
|
"id = timerEvery(5, function()\n"
|
|
" count = count + 1\n"
|
|
" report(2, count)\n"
|
|
" if count >= 3 then timerCancel(id); done() end\n"
|
|
"end)");
|
|
|
|
pumpUntilDone(3);
|
|
pumpSettle();
|
|
|
|
CHECK(atomic_load(&results[1]) == 42, "timerAfter fires its callback once after the delay");
|
|
CHECK(atomic_load(&results[4]) == 1, "a script that cancels a pending one-shot still runs to completion");
|
|
CHECK(atomic_load(&results[3]) == -1, "timerCancel of a one-shot before it fires prevents the callback");
|
|
CHECK(atomic_load(&results[2]) == 3, "timerEvery fires repeatedly and timerCancel stops it after 3 fires");
|
|
CHECK(atomic_load(&results[5]) == 1, "a non-finite / out-of-range delay is rejected");
|
|
CHECK(atomic_load(&doneCount) >= 3, "all three timer scripts completed");
|
|
CHECK(atomic_load(&errorCount) == 0, "no uncaught errors");
|
|
|
|
// Release pending callbacks + stop the thread while the contexts are still alive.
|
|
calogTimerShutdown();
|
|
|
|
calogContextClose(ctxAfter);
|
|
calogContextClose(ctxCancel);
|
|
calogContextClose(ctxEvery);
|
|
calogDestroy(calog);
|
|
|
|
printf("\n%d checks, %d failed\n", testsRun, testsFailed);
|
|
fflush(stdout);
|
|
if (testsFailed != 0) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|