/* * 989_septest_run — M4 E1 (#79-B) regression gate: `ww test --sep`. * * E1 makes `ww test` work under the separate-compilation producer as an * ADDITIVE opt-in (combined stays default; 910/997 untouched). The synth * 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 --sep` through build_one_sep(is_test=1). This gate is the * STANDING regression for that capability until E2 migrates 997. * * Asserts (all COLD — fresh per-(case,stage) WW_PKGCACHE, scratch wiped): * 1. RUN-EXIT: `ww test --sep ` 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 under sep (a no-op --sep 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 — under sep 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 under sep, not a stray combined fall- * back. * * Light wwstage-driver test (CLAUDE.md rule 14): every intermediate is * `-o`-redirected to /tmp, so it is phase-1 parallel-safe. Models * 989_c6soak_run.c conventions; 989 prefix per the sep-gate precedent. */ #include #include #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 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; fputs(body, f); fclose(f); return 0; } /* 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 /