Files
scriptc/packages/runtime/test/test_glob.c
T
2026-10-14 03:54:52 -05:00

163 lines
5.3 KiB
C

/* Oracle test for path.matchesGlob.
* Reads the case file given as argv[1] (line format in gen-glob-cases.mjs:
* curated paths, per-pattern answer rows over them, and fuzz cases) and
* runs every case for both path families with case-sensitive and
* case-insensitive matching, each answer "1", "0", or "E" for a thrown
* error.
*
* Exit 0 = all pass; prints each mismatch (capped) and exits 1 otherwise.
*/
#include "../src/scr_runtime.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
long scr_str_live_count(void); /* provided by scr_string.c */
static int hex_val(char c) {
if (c >= '0' && c <= '9') return c - '0';
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
return -1;
}
/* An escaped case string (printable ASCII, \uXXXX code units) to a string
* in the runtime's WTF-8 storage. */
static ScrStr *decode(const char *text) {
size_t len = strlen(text);
uint16_t *units = malloc((len ? len : 1) * sizeof *units);
size_t n = 0;
for (size_t i = 0; i < len; i++) {
if (text[i] == '\\' && text[i + 1] == 'u') {
const char *h = text + i + 2;
units[n++] = (uint16_t)(hex_val(h[0]) << 12 | hex_val(h[1]) << 8 | hex_val(h[2]) << 4 | hex_val(h[3]));
i += 5;
} else {
units[n++] = (uint8_t)text[i];
}
}
char *out = malloc(3 * n + 1);
size_t w = 0;
bool lone = false;
for (size_t i = 0; i < n; i++) {
uint32_t cp = units[i];
if (cp >= 0xD800 && cp < 0xDC00 && i + 1 < n && units[i + 1] >= 0xDC00 && units[i + 1] < 0xE000) {
cp = 0x10000 + ((cp - 0xD800) << 10) + (units[i + 1] - 0xDC00);
i++;
} else if (cp >= 0xD800 && cp < 0xE000) {
lone = true;
}
if (cp < 0x80) {
out[w++] = (char)cp;
} else if (cp < 0x800) {
out[w++] = (char)(0xC0 | (cp >> 6));
out[w++] = (char)(0x80 | (cp & 0x3F));
} else if (cp < 0x10000) {
out[w++] = (char)(0xE0 | (cp >> 12));
out[w++] = (char)(0x80 | ((cp >> 6) & 0x3F));
out[w++] = (char)(0x80 | (cp & 0x3F));
} else {
out[w++] = (char)(0xF0 | (cp >> 18));
out[w++] = (char)(0x80 | ((cp >> 12) & 0x3F));
out[w++] = (char)(0x80 | ((cp >> 6) & 0x3F));
out[w++] = (char)(0x80 | (cp & 0x3F));
}
}
if (lone) scr_wtf8_note();
ScrStr *s = scr_str_new(out, w);
free(out);
free(units);
return s;
}
/* Splits a line at tabs in place; empty fields are kept. */
static int split(char *line, char **fields, int max) {
int count = 0;
fields[count++] = line;
for (char *c = line; *c; c++)
if (*c == '\t') {
*c = 0;
if (count == max) return -1;
fields[count++] = c + 1;
}
return count;
}
static long total, failed;
/* Checks both families and both case modes against two answer strings. */
static void check(ScrStr *path, ScrStr *pattern, const char *posix, const char *win32, const char *path_text,
const char *pattern_text) {
for (int windows = 0; windows < 2; windows++) {
const char *expected = windows ? win32 : posix;
for (int nocase = 0; nocase < 2; nocase++) {
bool hit = scr_glob_match(path, pattern, windows, nocase);
char got = scr_exc_pending() ? 'E' : hit ? '1' : '0';
if (scr_exc_pending()) scr_exc_clear();
total++;
if (got != expected[nocase]) {
failed++;
if (failed <= 40)
fprintf(stderr, "MISMATCH %s nocase=%d path=\"%s\" pattern=\"%s\" got %c want %c\n",
windows ? "win32" : "posix", nocase, path_text, pattern_text, got, expected[nocase]);
}
}
}
}
int main(int argc, char **argv) {
if (argc < 2) return 2;
FILE *f = fopen(argv[1], "r");
if (!f) return 2;
static char line[1 << 20];
size_t npaths = 0, cap = 0;
ScrStr **paths = NULL;
char **path_texts = NULL;
while (fgets(line, sizeof line, f)) {
size_t len = strlen(line);
if (len && line[len - 1] == '\n') line[--len] = 0;
if (!len) continue;
char *fields[5];
int count = split(line, fields, 5);
if (strcmp(fields[0], "p") == 0 && count == 2) {
if (npaths == cap) {
cap = cap ? 2 * cap : 64;
paths = realloc(paths, cap * sizeof *paths);
path_texts = realloc(path_texts, cap * sizeof *path_texts);
}
path_texts[npaths] = strdup(fields[1]);
paths[npaths++] = decode(fields[1]);
} else if (strcmp(fields[0], "c") == 0 && count == 4 && strlen(fields[2]) == 2 * npaths &&
strlen(fields[3]) == 2 * npaths) {
ScrStr *pattern = decode(fields[1]);
for (size_t i = 0; i < npaths; i++)
check(paths[i], pattern, fields[2] + 2 * i, fields[3] + 2 * i, path_texts[i], fields[1]);
scr_str_release(pattern);
} else if (strcmp(fields[0], "f") == 0 && count == 5 && strlen(fields[3]) == 2 && strlen(fields[4]) == 2) {
ScrStr *path = decode(fields[1]);
ScrStr *pattern = decode(fields[2]);
check(path, pattern, fields[3], fields[4], fields[1], fields[2]);
scr_str_release(path);
scr_str_release(pattern);
} else {
fprintf(stderr, "bad line\n");
return 2;
}
}
fclose(f);
for (size_t i = 0; i < npaths; i++) {
scr_str_release(paths[i]);
free(path_texts[i]);
}
free(paths);
free(path_texts);
#ifdef SCR_RC_AUDIT
if (scr_str_live_count() != 0) {
fprintf(stderr, "live strings at exit: %ld\n", scr_str_live_count());
failed++;
}
#endif
fprintf(stderr, "%ld/%ld cases passed\n", total - failed, total);
return failed == 0 ? 0 : 1;
}