From 22999cd3fa5b3e9081bc0af4124eb66710340b93 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Tue, 12 May 2026 03:58:55 +0900 Subject: [PATCH] selfhost: mirror @test runner via ww_ww (997_at_test_ww) @test parsing already works under ww_ww (parseattrs lives in the shared lib/ww/parse/decl.ww, picked up by both Cstage and wwstage), so this is the test-side parity: 997 is 910 with `ww run` swapped for `ww_ww run`, exercising the selfhost driver+compiler+assembler+ linker end-to-end on the same attest_pass.ww fixture. Suite is now 22 tests. --- Makefile | 6 +- test/wcc/997_at_test_ww.c | 170 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 test/wcc/997_at_test_ww.c diff --git a/Makefile b/Makefile index e5b3797e..b07e3caf 100644 --- a/Makefile +++ b/Makefile @@ -215,7 +215,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_at_test \ $(BIN)/test_selfhost $(BIN)/test_w6a_ww $(BIN)/test_w6l_ww \ $(BIN)/test_w6c_ww $(BIN)/test_ww_ww $(BIN)/test_self_rebuild \ - $(BIN)/test_dyn_ww $(BIN)/test_selfcheck + $(BIN)/test_dyn_ww $(BIN)/test_selfcheck $(BIN)/test_at_test_ww $(BIN)/test_smoke: test/wcc/000_smoke.c $(LIB)/libwcc.a | $(BIN) $(CC) $(CFLAGS) $(INCS) -o $@ $< -L$(LIB) -lwcc @@ -290,6 +290,10 @@ $(BIN)/test_dyn_ww: test/wcc/996_dyn_ww.c $(BIN)/ww $(BIN)/w6l $(BIN)/w6l_ww \ $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< +$(BIN)/test_at_test_ww: test/wcc/997_at_test_ww.c $(BIN)/ww_ww $(BIN)/w6c_ww \ + $(BIN)/w6a_ww $(BIN)/w6l_ww $(LIB)/libwwrt.a | $(BIN) + $(CC) $(CFLAGS) -o $@ $< + test: all $(TESTS) @WW=$(BIN)/ww BIN=$(BIN) sh test/run diff --git a/test/wcc/997_at_test_ww.c b/test/wcc/997_at_test_ww.c new file mode 100644 index 00000000..b0ab6c6b --- /dev/null +++ b/test/wcc/997_at_test_ww.c @@ -0,0 +1,170 @@ +/* + * 997_at_test_ww — same as 910_at_test, driven through `ww_ww` + * (the selfhost driver) instead of `ww` (the C driver). Confirms + * the ww-built toolchain parses, compiles, and runs an @test + * fixture end-to-end. + * + * Scanning is regex-free, matching the literal sequence + * `\n@test\s+fn\s+(IDENT)`. Same parser shape as ww accepts. + */ +#include +#include +#include +#include +#include + +static int +runwait(const char *cmd) +{ + int rc = system(cmd); + if (rc == -1) return -1; + if (WIFEXITED(rc)) return WEXITSTATUS(rc); + return 1; +} + +static int +isws(int c) +{ + return c == ' ' || c == '\t' || c == '\n' || c == '\r'; +} + +static int +isident(int c) +{ + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || c == '_'; +} + +/* find_attest — scan src for `@test\s+fn\s+(ident)`; on a match, + * write the identifier into `out` (NUL-terminated) and return the + * scan position past the matched ident. Returns -1 when no more. */ +static long +find_attest(const char *src, long pos, char *out, size_t outsz) +{ + long n = (long)strlen(src); + while (pos < n) { + const char *q = strstr(src + pos, "@test"); + if (q == NULL) return -1; + long off = q - src; + /* must be a top-level marker: previous non-space char is + * '\n' or we're at the file start. */ + long pre = off - 1; + while (pre >= 0 && (src[pre] == ' ' || src[pre] == '\t')) pre--; + if (pre >= 0 && src[pre] != '\n') { + pos = off + 1; + continue; + } + long c = off + 5; /* past "@test" */ + while (c < n && isws(src[c])) c++; + if (c + 2 > n || strncmp(src + c, "fn", 2) != 0) { + pos = off + 1; + continue; + } + c += 2; + if (c < n && isident(src[c])) { + pos = off + 1; + continue; + } + while (c < n && isws(src[c])) c++; + long ids = c; + while (c < n && isident(src[c])) c++; + if (c == ids) { + pos = off + 1; + continue; + } + size_t len = (size_t)(c - ids); + if (len + 1 > outsz) return -1; + memcpy(out, src + ids, len); + out[len] = '\0'; + return c; + } + return -1; +} + +static char * +slurp(const char *path) +{ + FILE *f = fopen(path, "rb"); + if (!f) return NULL; + fseek(f, 0, SEEK_END); + long n = ftell(f); + fseek(f, 0, SEEK_SET); + char *b = malloc((size_t)n + 1); + if (!b) { fclose(f); return NULL; } + if (fread(b, 1, (size_t)n, f) != (size_t)n) { + free(b); fclose(f); return NULL; + } + b[n] = '\0'; + fclose(f); + return b; +} + +static int +runtests(const char *path, const char *bin) +{ + char *src = slurp(path); + if (!src) { + fprintf(stderr, "997 FAIL: cannot read %s\n", path); + return -1; + } + + /* Build a synthetic test driver: original source + a main() + * that calls each @test fn. Each test that runs to completion + * (no abort/exit) counts as a pass. */ + char tmpsrc[256]; + snprintf(tmpsrc, sizeof tmpsrc, "/tmp/wwd_attest_ww_%d.ww", getpid()); + FILE *out = fopen(tmpsrc, "wb"); + if (!out) { free(src); return -1; } + fputs(src, out); + fputs("\nexport fn main() i32 = {\n", out); + + long pos = 0; + int count = 0; + for (;;) { + char name[128]; + long next = find_attest(src, pos, name, sizeof name); + if (next < 0) break; + fprintf(out, "\t%s();\n", name); + pos = next; + count++; + } + fputs("\treturn 0;\n};\n", out); + fclose(out); + free(src); + + if (count == 0) { + fprintf(stderr, "997 FAIL: no @test fns found in %s\n", path); + unlink(tmpsrc); + return -1; + } + + char cmd[2048]; + snprintf(cmd, sizeof cmd, "%s/ww_ww run %s 2>/dev/null", bin, tmpsrc); + int rc = runwait(cmd); + unlink(tmpsrc); + if (rc != 0) { + fprintf(stderr, "997 FAIL: %s: %d @test fn(s), driver exited %d\n", + path, count, rc); + return -1; + } + return count; +} + +int +main(void) +{ + const char *bin = getenv("BIN"); + if (!bin) bin = "out/bin"; + const char *files[] = { + "test/wcc/data/attest_pass.ww", + NULL, + }; + int total = 0; + for (int i = 0; files[i]; i++) { + int n = runtests(files[i], bin); + if (n < 0) return 1; + total += n; + } + printf("@test (ww_ww): %d test(s) ran ok\n", total); + return 0; +}