// testDbMysql.c -- MySQL/MariaDB backend test. Given a "key=value" conninfo on argv[1], // drives a create/insert/update/query round-trip from a Lua script through the DB library. // If no server is reachable, dbOpen raises a script error and no row is ever reported; that // case is treated as SKIP (the clean error path is still exercised), so the test is safe to // run with or without a live server. #define _POSIX_C_SOURCE 200809L #include "calog.h" #include "calogDb.h" #include #include #include #include #include #define CHECK(cond, msg) checkImpl((cond), (msg), __FILE__, __LINE__) #define PUMP_LIMIT 4000 static CalogT *calog = NULL; static _Atomic int64_t reportedCount = -1; static _Atomic int64_t reportedId = -1; static _Atomic int64_t execAffected = -1; static _Atomic int64_t reportedOk = -1; static char reportedName[32] = { 0 }; static double reportedScore = 0.0; static _Atomic bool scriptDone = false; static _Atomic int32_t errorCount = 0; 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 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 pumpUntilDone(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 nativeDone(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { (void)args; (void)argCount; (void)userData; atomic_store(&scriptDone, true); calogValueNil(result); return calogOkE; } static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { size_t length; (void)userData; calogValueNil(result); if (argCount != 6 || args[0].type != calogIntE || args[1].type != calogIntE || args[2].type != calogStringE || args[3].type != calogIntE || args[4].type != calogRealE || args[5].type != calogIntE) { return calogFail(result, calogErrArgE, "report expects (count, id, name, affected, score, ok)"); } atomic_store(&reportedCount, args[0].as.i); atomic_store(&reportedId, args[1].as.i); length = (size_t)args[2].as.s.length; if (length >= sizeof(reportedName)) { length = sizeof(reportedName) - 1; } memcpy(reportedName, args[2].as.s.bytes, length); reportedName[length] = '\0'; atomic_store(&execAffected, args[3].as.i); reportedScore = args[4].as.r; atomic_store(&reportedOk, args[5].as.i); 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 pumpUntilDone(void) { struct timespec ts = { 0, 500000 }; int errorsBefore; int i; errorsBefore = atomic_load(&errorCount); for (i = 0; i < PUMP_LIMIT; i++) { calogPump(calog); if (atomic_load(&scriptDone) || atomic_load(&errorCount) != errorsBefore) { calogPump(calog); return; } nanosleep(&ts, NULL); } } int main(int argc, char **argv) { CalogContextT *ctx; char script[2048]; const char *conninfo; if (argc < 2) { printf("usage: testDbMysql \n"); return 2; } conninfo = argv[1]; 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 (calogDbRegister(calog) != calogOkE) { printf("calogDbRegister failed\n"); return 1; } snprintf(script, sizeof(script), "local db = dbOpen('mysql', '%s')\n" "dbExec(db, 'drop table if exists ct')\n" "dbExec(db, 'create table ct (id int, name text, score double, ok tinyint)')\n" "dbExec(db, 'insert into ct values (?, ?, ?, ?)', 1, 'alice', 3.5, true)\n" "dbExec(db, 'insert into ct values (?, ?, ?, ?)', 2, 'bob', 7.25, false)\n" "local n = dbExec(db, 'update ct set name = ? where id = ?', 'bobby', 2)\n" "local rows = dbQuery(db, 'select id, name, score, ok from ct order by id')\n" "report(#rows, rows[2].id, rows[2].name, n, rows[2].score, rows[2].ok)\n" "dbClose(db)\n" "done()", conninfo); ctx = calogContextOpen(calog, &calogLuaEngine); calogContextEval(ctx, script); pumpUntilDone(); if (atomic_load(&reportedCount) < 0) { printf("SKIP: no MySQL server reachable (connection error path exercised, no crash)\n"); calogContextClose(ctx); calogDestroy(calog); calogDbShutdown(); return 0; } CHECK(atomic_load(&reportedCount) == 2, "MySQL dbQuery returned both rows"); CHECK(atomic_load(&reportedId) == 2, "the integer column read back typed"); CHECK(strcmp(reportedName, "bobby") == 0, "a bound-param update took effect; text read back"); CHECK(atomic_load(&execAffected) == 1, "dbExec reported the correct rows-affected"); CHECK(reportedScore == 7.25, "the double column read back as a real"); CHECK(atomic_load(&reportedOk) == 0, "the tinyint(bool) column read back as 0"); calogContextClose(ctx); calogDestroy(calog); calogDbShutdown(); printf("\n%d checks, %d failed\n", testsRun, testsFailed); fflush(stdout); return (testsFailed != 0) ? 1 : 0; }