146 lines
5.6 KiB
C
146 lines
5.6 KiB
C
// testUtil.c -- exercises the utility libraries (calogRegex, calogCsv, calogProc) from a Lua
|
|
// context: regex match/search/replace/split with flags and groups, RFC 4180 CSV round-trips
|
|
// including quoting and embedded NULs, and subprocess run capturing stdout/stderr/exit + feeding
|
|
// stdin. A bad regex and a bad CSV delimiter are reported as errors the script can catch.
|
|
|
|
#define _POSIX_C_SOURCE 200809L
|
|
|
|
#include "calog.h"
|
|
#include "calogCsv.h"
|
|
#include "calogProc.h"
|
|
#include "calogRegex.h"
|
|
|
|
#include <stdatomic.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
#define PUMP_LIMIT 4000
|
|
|
|
static CalogT *calog = NULL;
|
|
static _Atomic int32_t checksRun = 0;
|
|
static _Atomic int32_t checksFailed = 0;
|
|
static _Atomic bool scriptDone = false;
|
|
|
|
static int32_t nativeCheck(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
|
|
static int32_t nativeDone(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 const char *SCRIPT =
|
|
// --- regex ---
|
|
"check(table.concat(regexMatch('c(a)(l)og', 'calog'), ',') == 'calog,a,l', 'regexMatch groups')\n"
|
|
"check(regexMatch('zzz', 'calog') == nil, 'regexMatch no match is nil')\n"
|
|
"check(regexMatch('A', 'abc', 'i') ~= nil, 'regexMatch i flag')\n"
|
|
"check(regexReplace('a', 'banana', 'X', 'g') == 'bXnXnX', 'regexReplace global')\n"
|
|
"check(regexReplace('a', 'banana', 'X') == 'bXnana', 'regexReplace first only')\n"
|
|
"check(regexReplace('([a-z]+)@([a-z]+)', 'user@host', '${2}.${1}') == 'host.user', 'regexReplace group refs')\n"
|
|
"check(table.concat(regexSplit(',', 'a,b,c'), '|') == 'a|b|c', 'regexSplit')\n"
|
|
"local s = regexSearch('([0-9]+)', 'abc123def')\n"
|
|
"check(s.match == '123' and s.start == 3 and s['end'] == 6, 'regexSearch offsets')\n"
|
|
"check(s.groups[1] == '123', 'regexSearch capture group')\n"
|
|
"check(not pcall(function() regexMatch('(', 'x') end), 'bad pattern is an error')\n"
|
|
// --- csv ---
|
|
"local rows = csvParse('a,b,c\\n\"x,y\",2,\"has\"\"q\"\\n')\n"
|
|
"check(#rows == 2, 'csvParse row count')\n"
|
|
"check(rows[1][3] == 'c', 'csvParse plain cell')\n"
|
|
"check(rows[2][1] == 'x,y', 'csvParse quoted cell with delimiter')\n"
|
|
"check(rows[2][3] == 'has\"q', 'csvParse escaped quote')\n"
|
|
"local nul = csvParse('a\\0b,c')\n"
|
|
"check(#nul[1][1] == 3, 'csvParse cell is binary-safe')\n"
|
|
"check(csvFormat({{'has,comma', 'x'}}) == '\"has,comma\",x', 'csvFormat quotes a delimiter')\n"
|
|
"check(csvFormat({{1, 2}, {3, 4}}) == '1,2\\r\\n3,4', 'csvFormat ints joined by CRLF')\n"
|
|
"check(not pcall(function() csvParse('a,b', ',,') end), 'bad delimiter is an error')\n"
|
|
// --- subprocess ---
|
|
"local r = procRun({'printf', 'hi there'})\n"
|
|
"check(r.exit == 0 and r.stdout == 'hi there', 'procRun captures stdout')\n"
|
|
"local r2 = procRun({'cat'}, {stdin = 'feed me'})\n"
|
|
"check(r2.stdout == 'feed me', 'procRun feeds stdin')\n"
|
|
"local r3 = procRun({'sh', '-c', 'exit 3'})\n"
|
|
"check(r3.exit == 3, 'procRun reports the exit code')\n"
|
|
"local r4 = procRun({'sh', '-c', 'echo out; echo err 1>&2'})\n"
|
|
"check(r4.exit == 0 and string.find(r4.stdout, 'out') ~= nil and string.find(r4.stderr, 'err') ~= nil, 'procRun splits stdout and stderr')\n"
|
|
"done()\n";
|
|
|
|
|
|
static int32_t nativeCheck(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
|
|
(void)userData;
|
|
calogValueNil(result);
|
|
atomic_fetch_add(&checksRun, 1);
|
|
if (argCount < 1 || args[0].type != calogBoolE) {
|
|
atomic_fetch_add(&checksFailed, 1);
|
|
printf("FAIL check: expected a boolean\n");
|
|
return calogOkE;
|
|
}
|
|
if (!args[0].as.b) {
|
|
atomic_fetch_add(&checksFailed, 1);
|
|
printf("FAIL %s\n", argCount >= 2 && args[1].type == calogStringE ? args[1].as.s.bytes : "(unnamed)");
|
|
}
|
|
return calogOkE;
|
|
}
|
|
|
|
|
|
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 void onError(uint64_t contextId, const char *message, void *userData) {
|
|
(void)contextId;
|
|
(void)userData;
|
|
printf("FAIL script error: %s\n", message);
|
|
atomic_fetch_add(&checksFailed, 1);
|
|
atomic_store(&scriptDone, true);
|
|
}
|
|
|
|
|
|
static void pumpUntilDone(void) {
|
|
struct timespec ts = { 0, 500000 };
|
|
int32_t i;
|
|
|
|
for (i = 0; i < PUMP_LIMIT; i++) {
|
|
calogPump(calog);
|
|
if (atomic_load(&scriptDone)) {
|
|
calogPump(calog);
|
|
return;
|
|
}
|
|
nanosleep(&ts, NULL);
|
|
}
|
|
}
|
|
|
|
|
|
int main(void) {
|
|
CalogContextT *ctx;
|
|
|
|
calog = calogCreate();
|
|
if (calog == NULL) {
|
|
printf("calog create failed\n");
|
|
return 1;
|
|
}
|
|
calogSetErrorHandler(calog, onError, NULL);
|
|
if (calogRegexRegister(calog) != calogOkE || calogCsvRegister(calog) != calogOkE || calogProcRegister(calog) != calogOkE) {
|
|
printf("utility register failed\n");
|
|
return 1;
|
|
}
|
|
calogRegisterInline(calog, "check", nativeCheck, NULL);
|
|
calogRegisterInline(calog, "done", nativeDone, NULL);
|
|
|
|
ctx = calogContextOpen(calog, &calogLuaEngine);
|
|
if (ctx == NULL) {
|
|
printf("context open failed\n");
|
|
return 1;
|
|
}
|
|
calogContextEval(ctx, SCRIPT);
|
|
pumpUntilDone();
|
|
|
|
calogDestroy(calog);
|
|
|
|
printf("\n%d checks, %d failed\n", atomic_load(&checksRun), atomic_load(&checksFailed));
|
|
fflush(stdout);
|
|
return atomic_load(&checksFailed) == 0 ? 0 : 1;
|
|
}
|