/* * 910_at_test — drives the compiler's `-T` @test mode (task #15). * * Under `w6c -T`, the checker collects every @test fn and synthesizes * `export fn main() i32 { t0(); t1(); ...; return 0; }`, calling each in * source order; a test that runs to completion passes, an abort/div0/ * nonzero exit FAILS the run. This replaces the old driver-side regex * scan (find_attest) — the @test set now comes from the AST, the single * source of truth (rob #15 ruling). cgen is untouched: the synthesized * entry rides the existing cgfn path. * * Probes: * 1. RUN — `w6c -T` the @test-only fixture, assemble, link, run; the * synthesized entry must exit 0 (every @test passed). Imports are * resolved by `ww build` into a *.combined.ww (the fixture uses * `alloc`, whose rt_malloc symbol the driver concatenates); we then * compile that combined unit with `-T`. * 2. REJECT — `-T` of a fixture that also defines a user `main` must * exit nonzero (the synth entry owns main). * 3. GUARD — `-T` of a fixture whose @test fn is not `fn() void` must * exit nonzero (rule-7 loud, no silent skip/coerce). */ #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 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; } /* run_fixture — ` -T` the @test fixture (import-resolved into a * combined unit by build), assemble, link, run; the synthesized * entry must exit 0. Returns 0 on success. */ static int run_fixture(const char *bin, const char *comp, const char *drv) { int pid = getpid(); char src[256], comb[256], binout[256], asmf[256], obj[256], exe[256]; char rt[1024], cmd[4096]; snprintf(src, sizeof src, "/tmp/at910_%s_%d.ww", comp, pid); snprintf(binout, sizeof binout, "/tmp/at910_%s_%d", comp, pid); snprintf(comb, sizeof comb, "/tmp/at910_%s_%d.combined.ww", comp, pid); snprintf(asmf, sizeof asmf, "/tmp/at910_%s_%d.s", comp, pid); snprintf(obj, sizeof obj, "/tmp/at910_%s_%d.o", comp, pid); snprintf(exe, sizeof exe, "/tmp/at910_%s_%d.exe", comp, pid); snprintf(rt, sizeof rt, "%s/../lib/libwwrt.a", bin); snprintf(cmd, sizeof cmd, "cp test/wcc/data/attest_pass.ww %s", src); if (runwait(cmd) != 0) { fprintf(stderr, "910 FAIL: cp\n"); return 1; } /* `test -c -o ` (compile-only) resolves imports AND auto-bundles * lib/test (#17) into .combined.ww — the synth's run() callee * must link below — WITHOUT running the harness. We depend on the * combined ARTIFACT; `-o` keeps the binary off the CWD. */ snprintf(cmd, sizeof cmd, "%s/%s test -c -o %s %s > /dev/null 2>&1", bin, drv, binout, src); runwait(cmd); if (access(comb, 0) != 0) { fprintf(stderr, "910 FAIL: %s build produced no %s\n", drv, comb); return 1; } snprintf(cmd, sizeof cmd, "%s/%s -T %s -o %s 2>/dev/null", bin, comp, comb, asmf); if (runwait(cmd) != 0) { fprintf(stderr, "910 FAIL: %s -T errored\n", comp); return 1; } snprintf(cmd, sizeof cmd, "%s/w6a -o %s %s 2>/dev/null", bin, obj, asmf); if (runwait(cmd) != 0) { fprintf(stderr, "910 FAIL: w6a\n"); return 1; } snprintf(cmd, sizeof cmd, "%s/w6l -o %s %s %s 2>/dev/null", bin, exe, obj, rt); if (runwait(cmd) != 0) { fprintf(stderr, "910 FAIL: w6l\n"); return 1; } int rc = runwait(exe); if (rc != 0) { fprintf(stderr, "910 FAIL: synth entry exited %d (a @test failed)\n", rc); return 1; } unlink(src); unlink(comb); unlink(asmf); unlink(obj); unlink(exe); unlink(binout); char tmp[300]; snprintf(tmp, sizeof tmp, "%s.s", binout); unlink(tmp); snprintf(tmp, sizeof tmp, "%s.o", binout); unlink(tmp); return 0; } /* reject — ` -T ` must exit nonzero. */ static int reject(const char *bin, const char *comp, const char *fixture, const char *what) { char cmd[4096]; snprintf(cmd, sizeof cmd, "%s/%s -T %s -o /dev/null 2>/dev/null", bin, comp, fixture); if (runwait(cmd) == 0) { fprintf(stderr, "910 FAIL: %s -T accepted %s (expected reject)\n", comp, what); return 1; } return 0; } /* Each row: a fixture `-T` must loud-reject, and why. */ static const struct { const char *fixture; const char *what; } rejects[] = { { "test/wcc/data/attest_userman.ww", "user main" }, { "test/wcc/data/attest_badsig.ww", "non-void @test" }, }; /* * #6: each row asserts whether a symbol's TEXT def appears in the asm * `` emits for attest_nondrop.ww in a given mode. A @test fn is * spliced out of a non-test build (harec ref/harec/src/check.c:3941 — * checked but never emitted) and kept under -T (the synth entry calls * it); a plain fn is always emitted. */ static const struct { const char *sym; /* TEXT label to grep for */ int testmode; /* 1 => -T, 0 => plain build */ int present; /* expected: 1 present, 0 absent */ const char *what; } nondrop_rows[] = { { "data.nondrop_keep", 0, 1, "plain fn kept non-T" }, { "data.nondrop_test_a", 0, 0, "head @test dropped non-T" }, { "data.nondrop_test_b", 0, 0, "mid @test dropped non-T" }, { "data.nondrop_test_c", 0, 0, "consecutive @test dropped non-T" }, { "data.nondrop_keep", 1, 1, "plain fn kept under -T" }, { "data.nondrop_test_a", 1, 1, "head @test kept under -T" }, { "data.nondrop_test_b", 1, 1, "mid @test kept under -T" }, { "data.nondrop_test_c", 1, 1, "consecutive @test kept under -T" }, }; /* nondrop — compile attest_nondrop.ww plain and `-T`, then assert each * row's TEXT def is present/absent. */ static int nondrop(const char *bin, const char *comp) { int pid = getpid(); char plain[256], tee[256], cmd[4096]; snprintf(plain, sizeof plain, "/tmp/at910nd_%s_p_%d.s", comp, pid); snprintf(tee, sizeof tee, "/tmp/at910nd_%s_t_%d.s", comp, pid); snprintf(cmd, sizeof cmd, "%s/%s test/wcc/data/attest_nondrop.ww -o %s 2>/dev/null", bin, comp, plain); if (runwait(cmd) != 0) { fprintf(stderr, "910 FAIL: %s non-T nondrop compile\n", comp); return 1; } /* The -T compile synthesizes a main that calls lib/test's run(), so it * needs the lib/test-inclusive combined `ww test -c` writes (the plain * compile above has no synth, so it stays on the raw fixture). */ char stem[256], comb[300]; snprintf(stem, sizeof stem, "/tmp/at910nd_%s_c_%d", comp, pid); snprintf(comb, sizeof comb, "%s.combined.ww", stem); snprintf(cmd, sizeof cmd, "%s/ww test -c -o %s test/wcc/data/attest_nondrop.ww > /dev/null 2>&1", bin, stem); runwait(cmd); snprintf(cmd, sizeof cmd, "%s/%s -T %s -o %s 2>/dev/null", bin, comp, comb, tee); if (runwait(cmd) != 0) { fprintf(stderr, "910 FAIL: %s -T nondrop compile\n", comp); return 1; } int rc = 0; for (size_t i = 0; i < sizeof nondrop_rows / sizeof nondrop_rows[0]; i++) { const char *f = nondrop_rows[i].testmode ? tee : plain; snprintf(cmd, sizeof cmd, "grep -q '^TEXT %s,' %s", nondrop_rows[i].sym, f); int found = runwait(cmd) == 0; if (found != nondrop_rows[i].present) { fprintf(stderr, "910 FAIL: %s — %s: %s %s (expected %s)\n", comp, nondrop_rows[i].what, nondrop_rows[i].sym, found ? "present" : "absent", nondrop_rows[i].present ? "present" : "absent"); rc = 1; break; } } unlink(plain); unlink(tee); unlink(comb); char tmp[300]; snprintf(tmp, sizeof tmp, "%s.s", stem); unlink(tmp); snprintf(tmp, sizeof tmp, "%s.o", stem); unlink(tmp); unlink(stem); return rc; } /* reject_plain — ` ` (NON-T) must exit nonzero. Pins that * a @test body is type-checked before the #6 splice drops it: an * undefined symbol in the body is caught loud even though the fn never * reaches codegen (harec checks at :3913, drops at :3941). */ static int reject_plain(const char *bin, const char *comp, const char *fixture, const char *what) { char cmd[4096]; snprintf(cmd, sizeof cmd, "%s/%s %s -o /dev/null 2>/dev/null", bin, comp, fixture); if (runwait(cmd) == 0) { fprintf(stderr, "910 FAIL: %s (non-T) accepted %s " "(expected reject)\n", comp, what); return 1; } return 0; } /* linkfail — a plain fn calling a @test fn, built non-T: compile + * assemble succeed, but the link MUST fail (the dropped @test def leaves * the call's symbol dangling — harec-faithful loud failure, never a * silent mis-link). Linker behavior is stage-independent, so cstage * suffices; 997's byte-id proves the wwstage .s is identical. */ static int linkfail(const char *bin, const char *comp) { int pid = getpid(); char asmf[256], obj[256], exe[256], rt[1024], cmd[4096]; snprintf(asmf, sizeof asmf, "/tmp/at910lf_%s_%d.s", comp, pid); snprintf(obj, sizeof obj, "/tmp/at910lf_%s_%d.o", comp, pid); snprintf(exe, sizeof exe, "/tmp/at910lf_%s_%d.exe", comp, pid); snprintf(rt, sizeof rt, "%s/../lib/libwwrt.a", bin); snprintf(cmd, sizeof cmd, "%s/%s test/wcc/data/attest_calldropped.ww -o %s 2>/dev/null", bin, comp, asmf); if (runwait(cmd) != 0) { fprintf(stderr, "910 FAIL: %s non-T calldropped compile\n", comp); return 1; } snprintf(cmd, sizeof cmd, "%s/w6a -o %s %s 2>/dev/null", bin, obj, asmf); if (runwait(cmd) != 0) { fprintf(stderr, "910 FAIL: w6a calldropped\n"); unlink(asmf); return 1; } snprintf(cmd, sizeof cmd, "%s/w6l -o %s %s %s 2>/dev/null", bin, exe, obj, rt); int rc = 0; if (runwait(cmd) == 0) { fprintf(stderr, "910 FAIL: calldropped linked (expected " "undefined-reference to the dropped @test sym)\n"); rc = 1; } unlink(asmf); unlink(obj); unlink(exe); return rc; } /* accept — ` ` (NON-T) must exit zero. The #23 cross-package * legal control: distinct-mod same-leaf decls coexist, so the dup reject * keyed on (name, mod) must NOT fire. */ static int accept(const char *bin, const char *comp, const char *fixture, const char *what) { char cmd[4096]; snprintf(cmd, sizeof cmd, "%s/%s %s -o /dev/null 2>/dev/null", bin, comp, fixture); if (runwait(cmd) != 0) { fprintf(stderr, "910 FAIL: %s rejected %s (expected accept)\n", comp, what); return 1; } return 0; } /* collide_run — a @test unit defining a user `fn run` collides with * lib/test's bound runner once ` test -c` bundles it; ` -T` of * the combined must loud-reject the duplicate (#23). Pre-fix wwstage built * a binary that called the user run and silently skipped every @test. */ static int collide_run(const char *bin, const char *comp, const char *drv) { int pid = getpid(); char stem[256], comb[300], cmd[4096]; snprintf(stem, sizeof stem, "/tmp/at910cr_%s_%d", comp, pid); snprintf(comb, sizeof comb, "%s.combined.ww", stem); snprintf(cmd, sizeof cmd, "%s/%s test -c -o %s test/wcc/data/attest_userrun.ww > /dev/null 2>&1", bin, drv, stem); runwait(cmd); if (access(comb, 0) != 0) { fprintf(stderr, "910 FAIL: %s build produced no %s\n", drv, comb); return 1; } snprintf(cmd, sizeof cmd, "%s/%s -T %s -o /dev/null 2>/dev/null", bin, comp, comb); int rc = 0; if (runwait(cmd) == 0) { fprintf(stderr, "910 FAIL: %s -T accepted user `fn run` collision " "(expected duplicate-fn reject)\n", comp); rc = 1; } unlink(comb); char tmp[320]; snprintf(tmp, sizeof tmp, "%s.s", stem); unlink(tmp); snprintf(tmp, sizeof tmp, "%s.o", stem); unlink(tmp); unlink(stem); return rc; } /* modfn_run — ` run ` and assert exit 6 (#30). The fixture's * main returns `aa() + aa.helper()` = 1 + 5: the bare `aa()` must resolve to * the fn (1) and the qualified `aa.helper()` to the module member (5). A * mis-resolution that still compiled would run to a different code; this * pins the resolved TARGETS, not just acceptance. */ static int modfn_run(const char *bin, const char *drv, const char *fixture, const char *what) { char cmd[4096]; snprintf(cmd, sizeof cmd, "%s/%s run %s > /dev/null 2>&1", bin, drv, fixture); int rc = runwait(cmd); if (rc != 6) { fprintf(stderr, "910 FAIL: %s run %s exited %d (expected 6 — bare fn " "+ qualified module resolution)\n", drv, what, rc); return 1; } return 0; } int main(void) { const char *bin = absbin(); if (!bin) { fprintf(stderr, "910 FAIL: getcwd\n"); return 1; } if (run_fixture(bin, "w6c", "ww") != 0) return 1; for (size_t i = 0; i < sizeof rejects / sizeof rejects[0]; i++) if (reject(bin, "w6c", rejects[i].fixture, rejects[i].what) != 0) return 1; if (nondrop(bin, "w6c") != 0) return 1; if (reject_plain(bin, "w6c", "test/wcc/data/attest_undefbody.ww", "undefined symbol in @test body") != 0) return 1; if (linkfail(bin, "w6c") != 0) return 1; /* #23 — top-level duplicate-decl reject (one row per kind: all four * route through installtop, so each deserves its own pin) + legal * cross-package control + the builtin-redecl carve-out accept. */ if (reject_plain(bin, "w6c", "test/wcc/data/dup_fn.ww", "same-module duplicate fn") != 0) return 1; if (reject_plain(bin, "w6c", "test/wcc/data/dup_type.ww", "same-module duplicate type") != 0) return 1; if (reject_plain(bin, "w6c", "test/wcc/data/dup_def.ww", "same-module duplicate def") != 0) return 1; if (reject_plain(bin, "w6c", "test/wcc/data/dup_let.ww", "same-module duplicate let") != 0) return 1; if (accept(bin, "w6c", "test/wcc/data/dup_xpkg_ok.ww", "cross-package same-name") != 0) return 1; if (accept(bin, "w6c", "test/wcc/data/builtin_redecl_ok.ww", "builtin nomem redecl (carve-out)") != 0) return 1; /* #30 — a top-level fn whose leaf matches an imported module name * coexists with the module bareword (different namespaces). Accept * proves both the bare `aa()` (fn) and qualified `aa.helper()` * (module) resolve; a mis-resolution would fail to compile. Both * decl orders are pinned (use-before-value AND value-before-use): the * order-dependence is exactly what regresses silently — cstage is * order-independent, wwstage closes both directions via the symmetric * promotes in installtop + installdecl's N_USE arm. */ if (accept(bin, "w6c", "test/wcc/data/modfn_coexist_ok.ww", "fn named like imported module (use-before-value)") != 0) return 1; if (accept(bin, "w6c", "test/wcc/data/modfn_coexist_vbu_ok.ww", "fn named like imported module (value-before-use)") != 0) return 1; /* runtime-resolve: both orders run to exit 6 (bare fn=1 + module * member=5), pinning the resolved targets on the cstage driver. */ if (modfn_run(bin, "ww", "test/wcc/data/modfn_coexist_ok.ww", "use-before-value") != 0) return 1; if (modfn_run(bin, "ww", "test/wcc/data/modfn_coexist_vbu_ok.ww", "value-before-use") != 0) return 1; if (collide_run(bin, "w6c", "ww") != 0) return 1; printf("@test -T: run ok + user-main and bad-signature rejected + " "non-T @test drop + checked-body + dangling-call link-fail (#6) + " "dup fn/type/def/let reject + xpkg + builtin-redecl accept + " "modfn coexist (#30) + fn-run collision (#23)\n"); return 0; }