// testXml.c -- exercises the XML library: parse tags/attributes/nested + mixed content, entity // decode and re-escape, self-closing elements, round-trip, and error handling, driven from Lua. #define _POSIX_C_SOURCE 200809L #include "calog.h" #include "calogXml.h" #include #include #include #define CHECK(cond, msg) checkImpl((cond), (msg), __FILE__, __LINE__) #define PUMP_LIMIT 4000 #define RESULT_SLOTS 16 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 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)) { 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 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 *ctx; 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); calogXmlRegister(calog); for (i = 0; i < RESULT_SLOTS; i++) { atomic_store(&results[i], -1); } ctx = calogContextOpen(calog, &calogLuaEngine); calogContextEval(ctx, "local e = xmlParse('hello')\n" "report(1, (e.tag == 'greeting' and e.children[1] == 'hello') and 1 or 0)\n" // 1, tag + text "local it = xmlParse('')\n" "report(2, (it.attr.id == '42' and it.attr.name == 'widget' and #it.children == 0) and 1 or 0)\n" // 1, attrs + self-close "local r = xmlParse('12')\n" "report(3, tonumber(r.children[1].children[1]) + tonumber(r.children[2].children[1]))\n" // 3, nested navigation "local x = '

text

'\n" "report(4, xmlStringify(xmlParse(x)) == x and 1 or 0)\n" // 1, lossless round-trip "local t = xmlParse('a & b < c')\n" "report(5, t.children[1] == 'a & b < c' and 1 or 0)\n" // 1, entities decode "report(6, xmlStringify(t) == 'a & b < c' and 1 or 0)\n" // 1, text re-escaped "local ok = pcall(function() xmlParse('') end)\n" "report(7, ok and 0 or 1)\n" // 1, malformed is catchable "local p = xmlParse('

beforeboldafter

')\n" "report(8, (p.children[1]=='before' and p.children[2].tag=='b' and p.children[2].children[1]=='bold' and p.children[3]=='after') and 1 or 0)\n" // 1, mixed content "local a = xmlParse('')\n" "report(9, a.attr.t == 'x & y' and 1 or 0)\n" // 1, attribute entity decode "report(10, xmlStringify(a) == '' and 1 or 0)\n" // 1, attribute re-escaped "local w = xmlParse('p q')\n" "local w2 = xmlParse(xmlStringify(w))\n" "report(11, w2.attr.t == 'x\\ty\\nz\\rw' and 1 or 0)\n" // 1, TAB/LF/CR in attr survive round-trip "report(12, w2.children[1] == 'p\\rq' and 1 or 0)\n" // 1, CR in text survives round-trip "done()"); pumpUntilDone(1); CHECK(atomic_load(&results[1]) == 1, "parse a tag with a text child"); CHECK(atomic_load(&results[2]) == 1, "parse attributes on a self-closing element"); CHECK(atomic_load(&results[3]) == 3, "navigate nested elements"); CHECK(atomic_load(&results[4]) == 1, "stringify + parse is lossless for compact XML"); CHECK(atomic_load(&results[5]) == 1, "predefined entities decode to text"); CHECK(atomic_load(&results[6]) == 1, "text is re-escaped on stringify"); CHECK(atomic_load(&results[7]) == 1, "malformed XML raises a catchable error"); CHECK(atomic_load(&results[8]) == 1, "mixed content preserves text and element order"); CHECK(atomic_load(&results[9]) == 1, "attribute entities decode"); CHECK(atomic_load(&results[10]) == 1, "attribute values are re-escaped on stringify"); CHECK(atomic_load(&results[11]) == 1, "TAB/LF/CR in an attribute survive a round-trip"); CHECK(atomic_load(&results[12]) == 1, "CR in text survives a round-trip"); CHECK(atomic_load(&errorCount) == 0, "no uncaught errors"); calogDestroy(calog); printf("\n%d checks, %d failed\n", testsRun, testsFailed); fflush(stdout); if (testsFailed != 0) { return 1; } return 0; }