From 92d5f1cb56c0c3f182b372eecb82721f6a58a1b4 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Sat, 8 Aug 2026 14:08:24 +0900 Subject: [PATCH] test: port the ww-test sep observer to ww; retire 989_septest septest_test.ww keeps all five rows (pass/fail run-exits, the userman + badsig @test rejects, the collide_run user-fn-run coexist) plus the full-sepwork .s/.wwi byte-id sweep and the -T synth TEXT-main / CALL-test.run presence needles, both driver stages. The pass/fail run-exit rows overlap package_test ownership but stay: retiring them would drop audit-listed assertions from this gate. The attest_* data fixtures remain in test/wcc/data (also consumed by the 911_attest_* carriers). --- Makefile | 3 +- test/sep/septest_test.ww | 155 ++++++++++++++++++ test/wcc/989_septest_run.c | 319 ------------------------------------- 3 files changed, 157 insertions(+), 320 deletions(-) create mode 100644 test/sep/septest_test.ww delete mode 100644 test/wcc/989_septest_run.c diff --git a/Makefile b/Makefile index 7fdf88e7..1904c2f5 100644 --- a/Makefile +++ b/Makefile @@ -363,7 +363,8 @@ BYTEID_WW_TARGETS = $(BYTEID_WW_TESTS:%=wwtest/%) # units, link rejects), NOT byteid suites: they run under # test-compiler beside the surviving residual carriers. SEP_WW_TESTS = test/sep/sepbuild_test.ww test/sep/sepimport_test.ww \ - test/sep/seplink_test.ww test/sep/sepscratch_test.ww + test/sep/seplink_test.ww test/sep/sepscratch_test.ww \ + test/sep/septest_test.ww SEP_WW_TARGETS = $(SEP_WW_TESTS:%=wwtest/%) BOOTSTRAP_WRAPPER_SOURCES = test/wcc/950_selfcheck.c \ test/wcc/991_w6a_ww.c \ diff --git a/test/sep/septest_test.ww b/test/sep/septest_test.ww new file mode 100644 index 00000000..0cca1ed7 --- /dev/null +++ b/test/sep/septest_test.ww @@ -0,0 +1,155 @@ +package septest_test; + +// `ww test` sep-driver gate (M4 E1, #79-B). Port of the retired native +// carrier test/wcc/989_septest_run.c; every assertion preserved. +// +// Rows (each drives ` test -o` on BOTH stages): +// pass — two passing inline @tests -> run-exit 0; the byte-id +// + synth anchor. +// fail — one failing inline @test -> run-exit 1: proves the +// @tests genuinely RUN under sep (non-vacuity teeth). +// userman — explicit user `main` collides with the -T synth +// entry -> reject (exit != 0; exact code is a driver +// detail, deliberately unasserted). +// badsig — non-`fn() void` @test signature -> reject. +// collide_run — user `fn run` coexists with lib/test's bound runner +// (distinct units: main.run in __root.s, test.run in +// test.s) -> exit 0; a regressed collision re-mangles +// to duplicate test.run -> w6l dup-symbol -> non-zero. +// +// byteid rows (pass, collide_run): EVERY .s/.wwi in the cs sepwork +// byte-equals the wwstage same-named file, with seen>0 (an empty +// sepwork fails loudly); __root.s carries "TEXT main" AND +// "CALL\ttest.run(SB)" — the qualified -T synth path fired under sep. +// +// The three on-disk attest fixtures stay in test/wcc/data/ (also +// consumed by the 911_attest_* carriers). + +import os; +import os.exec; +import strings; +import testenv; +import time; + +fn fail(label: str, why: str) void = { + let m: str = strings.concat("septest FAIL: ", label, " -- ", why, + "\n"); + os.write(2, m.ptr, m.len: u64); + assert(false); +}; + +fn tmo() time.duration = { + return (180i64 * (time.second: i64)): time.duration; +}; + +fn passsrc() str = { + return strings.concat( + "package septest;\n", + "@test fn t_arith() void = {\n", + " let a: i32 = 2;\n", + " if (a + 3 != 5) { abort(); };\n", + "};\n", + "@test fn t_again() void = {\n", + " let s: str = \"ok\";\n", + " if (len(s) != 2) { abort(); };\n", + "};\n"); +}; + +fn failsrc() str = { + return strings.concat( + "package septest;\n", + "@test fn t_bad() void = {\n", + " if (1 + 1 == 2) { abort(); };\n", + "};\n"); +}; + +// -1 encodes an abnormal (non-EXIT) termination, never a valid code. +fn teststage(td: str, label: str, drv: str, tag: str, fixture: str) i32 = { + let prog: str = strings.concat(td, "/", label, ".", tag, ".bin"); + let av: []str = [testenv.driver(drv), "test", "-o", prog, fixture]; + let co: testenv.commandout; + testenv.runcommand(td, td, strings.concat(label, "_", tag), av, + tmo(), &co); + if (co.termination != exec.termination.EXIT) { return -1; }; + return co.code; +}; + +fn cmpsepwork(label: str, csdir: str, wwdir: str) void = { + if (!testenv.isdir(csdir)) { fail(label, "no cs sepwork"); }; + let names: []str = testenv.listdir(csdir); + let seen: i32 = 0; + let i: i32 = 0; + for (i < names.len) { + if (strings.hassuffix(names[i], ".s") + || strings.hassuffix(names[i], ".wwi")) { + seen += 1; + if (!testenv.same( + testenv.readfile(strings.concat(csdir, "/", names[i])), + testenv.readfile(strings.concat(wwdir, "/", names[i])))) { + fail(label, strings.concat("cs!=ww for ", names[i], + " (rule 10)")); + }; + }; + i += 1; + }; + // an existing-but-empty sepwork would pass the loop vacuously + if (seen == 0) { fail(label, "no .s/.wwi in cs sepwork"); }; +}; + +fn runrow(label: str, src: str, path: str, expect: i32, byteid: bool, + reject: bool) void = { + let td: str = testenv.fresh(); + let fixture: str = ""; + if (path.len != 0) { + fixture = strings.concat(testenv.repo(), "/", path); + } else { + fixture = strings.concat(td, "/", label, ".ww"); + testenv.writefile(fixture, src); + }; + let cs: i32 = teststage(td, label, "ww", "cs", fixture); + let ws: i32 = teststage(td, label, "ww_ww", "ww", fixture); + if (reject) { + if (cs == 0 || ws == 0) { + fail(label, "accepted (expected reject on both stages)"); + }; + } else { + if (cs != expect || ws != expect) { + fail(label, "run-exit differs from expected on a stage"); + }; + }; + if (byteid) { + let csdir: str = strings.concat(td, "/", label, ".cs.bin.sepwork"); + let wwdir: str = strings.concat(td, "/", label, ".ww.bin.sepwork"); + cmpsepwork(label, csdir, wwdir); + let rs: str = testenv.readfile(strings.concat(csdir, "/__root.s")); + if (!testenv.has(rs, "TEXT main")) { + fail(label, "__root.s lacks `TEXT main` (synth -T main missing)"); + }; + if (!testenv.has(rs, "CALL\ttest.run(SB)")) { + fail(label, strings.concat("__root.s lacks `CALL test.run(SB)` ", + "(qualified synth call missing)")); + }; + }; + testenv.clean(td); +}; + +@test fn pass() void = { + runrow("pass", passsrc(), "", 0, true, false); +}; + +@test fn failing() void = { + runrow("fail", failsrc(), "", 1, false, false); +}; + +@test fn userman() void = { + runrow("userman", "", "test/wcc/data/attest_userman.ww", 0, false, true); +}; + +@test fn badsig() void = { + runrow("badsig", "", "test/wcc/data/attest_badsig.ww", 0, false, true); +}; + +@test fn colliderun() void = { + runrow("collide_run", "", "test/wcc/data/attest_userrun.ww", 0, true, + false); +}; diff --git a/test/wcc/989_septest_run.c b/test/wcc/989_septest_run.c deleted file mode 100644 index d40b607d..00000000 --- a/test/wcc/989_septest_run.c +++ /dev/null @@ -1,319 +0,0 @@ -/* - * 989_septest_run — M4 E1 (#79-B) regression gate: `ww test`. - * - * `ww test` uses the separate-compilation producer. The synthesized - * test-main emits `test.run(__wwtests)` (N_DOT, qualified) + injects a - * synthetic `use test;`; the driver injects `test` as a graph dep of the - * root, passes `-T` to the root producer only, and routes a single-file - * `ww test` through build_one_sep(is_test=1). This gate is the standing - * regression for that capability. - * - * Asserts (all COLD — direct per-(case,stage) builds): - * 1. RUN-EXIT: `ww test ` exits with the expected code for - * BOTH driver stages — 0 when every @test passes, non-zero when one - * fails. The failing row is the non-vacuity teeth: it proves the @tests - * genuinely run (a vacuous path that linked an empty main would - * exit 0 on the failing fixture and the gate would catch it). - * This same RUN-EXIT path also hosts the @test user-facing reject + - * coexist legs ported from 910/997 (C2a, #83), which die at the M4 flip - * that deletes 910/997: `userman` (explicit user main collides with the - * -T synth entry) and `badsig` (non-`fn() void` @test) must reject - * (exit != 0) on both stages; `collide_run` (user `fn run` coexists with - * lib/test's bound runner) must accept and run to exit 0 — the - * two land in distinct units, so a regressed collision fails the link. - * 2. cs==ww (rule 10): the cstage `ww` and wwstage `ww_ww` sep-drivers emit - * byte-identical per-package .s/.wwi for the passing fixture — including - * __root.s, which carries the synth `CALL test.run`. - * 3. SYNTH PRESENCE (non-vacuity on the -T path): the passing fixture's - * __root.s defines `TEXT main` and emits `CALL test.run(SB)` — proving - * the qualified-synth -T path fired. - * - * Every intermediate is `-o`-redirected to /tmp for isolation. Models - * 989_c6soak_run.c conventions; 989 prefix per the sep-gate precedent. - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include "wwtestpkg.h" - -static int -runwait(const char *cmd) -{ - int rc = system(cmd); - if (rc == -1) return -1; - if (WIFEXITED(rc)) return WEXITSTATUS(rc); - return 1; -} - -static const char * -absbin(void) -{ - const char *b = getenv("BIN"); - if (!b) b = "out/bin"; - if (b[0] == '/') return b; - static char buf[2048]; - char cwd[1024]; - if (getcwd(cwd, sizeof cwd) == NULL) return NULL; - snprintf(buf, sizeof buf, "%s/%s", cwd, b); - return buf; -} - -static int -slurp(const char *path, char **outbuf, size_t *outlen) -{ - FILE *f = fopen(path, "rb"); - if (!f) return -1; - fseek(f, 0, SEEK_END); - long n = ftell(f); - fseek(f, 0, SEEK_SET); - if (n < 0) { fclose(f); return -1; } - char *b = malloc((size_t)n + 1); - if (!b) { fclose(f); return -1; } - if (fread(b, 1, (size_t)n, f) != (size_t)n) { free(b); fclose(f); return -1; } - b[n] = '\0'; - fclose(f); - *outbuf = b; - *outlen = (size_t)n; - return 0; -} - -static int -files_eq(const char *a, const char *b) -{ - char *ba = NULL, *bb = NULL; - size_t na = 0, nb = 0; - if (slurp(a, &ba, &na) < 0 || slurp(b, &bb, &nb) < 0) { - free(ba); free(bb); - return -1; - } - int eq = (na == nb && memcmp(ba, bb, na) == 0); - free(ba); free(bb); - return eq ? 0 : 1; -} - -/* 0 if `needle` occurs in the file at `path`, 1 if absent, -1 on read err. */ -static int -file_contains(const char *path, const char *needle) -{ - char *b = NULL; - size_t n = 0; - if (slurp(path, &b, &n) < 0) return -1; - int found = (strstr(b, needle) != NULL); - free(b); - return found ? 0 : 1; -} - -static int -write_file(const char *path, const char *body) -{ - FILE *f = fopen(path, "wb"); - if (!f) return -1; - wwtest_fputs(body, f); - return fclose(f); -} - -/* cs==ww over a sep test build's per-package output: every .s/.wwi the - * cstage driver produced in `csdir` must be byte-identical to the wwstage - * driver's same-named file in `wwdir`. Returns the count of mismatches. */ -static int -cmp_sepwork(const char *csdir, const char *wwdir, const char *label) -{ - DIR *d = opendir(csdir); - if (!d) { - fprintf(stderr, "septest FAIL: %s — no cs sepwork %s\n", label, csdir); - return 1; - } - int bad = 0, seen = 0; - struct dirent *ent; - while ((ent = readdir(d)) != NULL) { - const char *nm = ent->d_name; - size_t nl = strlen(nm); - int is_s = (nl > 2 && strcmp(nm + nl - 2, ".s") == 0); - int is_wwi = (nl > 4 && strcmp(nm + nl - 4, ".wwi") == 0); - if (!is_s && !is_wwi) continue; - seen++; - char a[2048], b[2048]; - snprintf(a, sizeof a, "%s/%s", csdir, nm); - snprintf(b, sizeof b, "%s/%s", wwdir, nm); - if (files_eq(a, b) != 0) { - fprintf(stderr, "septest FAIL: %s — cs!=ww for %s (rule 10)\n", - label, nm); - bad++; - } - } - closedir(d); - /* An existing-but-empty sepwork would pass the cs==ww loop vacuously. */ - if (seen == 0) { - fprintf(stderr, "septest FAIL: %s — no .s/.wwi in %s\n", label, csdir); - bad++; - } - return bad; -} - -struct tcase { - const char *label; - const char *src; /* inline fixture written to /