@test was parsed then dropped (no consumer); `ww test` needed a hand-written
main listing each test by hand, so adding a @test and forgetting the call
silently skipped it. -T makes the checker collect @test N_FNDECLs in source
order, loud-reject a user main, and append a synthetic
`export fn main() i32 { t0(); ...; return 0; }` at the install->body-check seam;
the existing cgfn emits it (cgen untouched) -> byte-identical by construction.
Mirrors harec's checker-side is_test placement.
Plan-9-lean reduction (user-sanctioned, reinstatable post-CSP): sequential,
abort/nonzero=fail; no setjmp isolation, no fnmatch filter, no file:line.
910/997 rewired from a regex scanner to driving `w6c -T` directly (thin trusted
drivers; the @test content stays ww), with a cross-stage byte-id assert on the
-T output. attest_userman/attest_badsig pin the user-main and bad-signature
rejects.
138 lines
4.7 KiB
C
138 lines
4.7 KiB
C
/*
|
|
* 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 <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <sys/wait.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;
|
|
}
|
|
|
|
/* run_fixture — `<comp> -T` the @test fixture (import-resolved into a
|
|
* combined unit by <drv> 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], asmf[256], obj[256], exe[256];
|
|
char rt[1024], cmd[4096];
|
|
snprintf(src, sizeof src, "/tmp/at910_%s_%d.ww", 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; }
|
|
|
|
/* Driver resolves `import rt` into <src>.combined.ww. The build
|
|
* itself exits nonzero (the @test-only fixture has no main — that
|
|
* is exactly what -T synthesizes), but the combined unit is written
|
|
* before any compile step, so we depend on the ARTIFACT, not the
|
|
* exit code. */
|
|
snprintf(cmd, sizeof cmd, "%s/%s build %s > /dev/null 2>&1", bin, drv, 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);
|
|
return 0;
|
|
}
|
|
|
|
/* reject — `<comp> -T <fixture>` 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" },
|
|
};
|
|
|
|
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;
|
|
|
|
printf("@test -T: run ok + user-main and bad-signature rejected\n");
|
|
return 0;
|
|
}
|