Files
ww/test/wcc/989_separchive_run.c
Hojun-Cho a95a7a316b test/wcc: carrier ownership repair and driver-contract adaptation
Every surviving carrier now owns its artifacts: checked mkdir/mkdtemp/
fopen acquisition, one all-exit cleanup funnel per carrier, ENOENT-
tolerant checked unlinks, exact-path deletion (rm -rf only for an
owned pid-keyed dir or a .sepwork beneath one), and cleanup failure
fails a passing carrier without overwriting its diagnostic. In the
same pass the carriers adapt to the driver contract this branch lands:
--sep and WW_PKGCACHE are gone, -S and the /tmp/ww_run_<pid> scratch
contract are asserted, and rows whose runtime or reject coverage moved
to test/wcc/data fixtures or test/lang @test owners are trimmed to the
byte/artifact/diagnostic observations only they can make.

Repair and adaptation ride together because most files interleave both
in the same hunks; splitting would manufacture intermediate carrier
states that never existed and cannot run against either driver.
2026-08-07 23:21:04 +09:00

417 lines
15 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 989_separchive_run — M3-tail commit-5a gate (#46, task #62): the
* per-package `.a` substrate + its archive-path #31 dup-detect, both
* stages, COLD. Commit 5a makes `ww build` wrap each DEP package's
* `.o` in a deterministic single-member `.a` (cstage archive_o /
* wwstage archiveo) and link the ROOT as a positional `.o` (force-loaded)
* + dep `.a` reverse-topo + libwwrt.a. The #31 dup the selective pull
* would mask is caught by a post-pull PASS 3 in w6l/w6l_ww load_archive.
*
* Legs (fresh output stems under an invocation-owned root; retained
* `<stem>.sepwork` is inspected, then removed by final carrier cleanup):
* 1. POSITIVE: root→helper builds + runs exit 7, BOTH stages, through
* the `.a` link path (root `.o` + helper `.a`).
* 2. ★ DETERMINISM (ken, load-bearing): re-archive the same package 3×
* → byte-identical `.a` (proves zeroed mtime/uid/gid + fixed mode +
* fixed member name; a floating md5 would poison the 5b cache key).
* 3. ★ cs `.a` == ww `.a` (rule 10, the NEW byte-id substrate ken binds):
* the dep `.a` from `ww` is byte-identical to the one from
* `ww_ww`.
* 4. ★ #31 dup THROUGH the `.a` path (ken #263 + D3, the leg that proves
* PASS 3 closed the selective-pull hole): two DEP packages each export
* the same link symbol via `@symbol("dup_sym")`, referenced by root.
* One dep's member is pulled; the OTHER lands UNPULLED and defines an
* already-`defined` name — exactly what selective-pull skips. BOTH
* stages must (exit≠0) ∧ (stderr contains "duplicate symbol") ∧ (no
* partial binary). NON-VACUITY flip: give the second dep a DISTINCT
* symbol → both stages build + run (exit 3), proving the leg actually
* discriminates (without PASS 3 the dup leg would WRONGLY pass green).
* 5. ACHIEVABLE-parity posture (same as c4's 989_sepcycle_dup, because
* #61 is a separate commit): cs vs ww is `both exit≠0 ∧ both stderrs
* contain "duplicate symbol"` — NOT exact-stderr-equal (cstage names
* `<path>: duplicate symbol <sym>`, wwstage the bare form).
*
* Light wwstage-driver test (CLAUDE.md rule 14): all fixtures + scratch
* live under /tmp, COLD each run. Models 989_sepbuild_run.c conventions.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <errno.h>
#define EXPECT_EXIT 7
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;
}
static int
file_has(const char *path, const char *needle)
{
char *b = NULL;
size_t n = 0;
if (slurp(path, &b, &n) < 0) return 0;
int found = (strstr(b, needle) != NULL);
free(b);
return found;
}
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;
}
int
main(void)
{
const char *bin = absbin();
if (!bin) return 1;
char td[64], cmd[8192];
int fail = 0, have_helpdir = 0, have_pkgad = 0, have_pkgbd = 0;
snprintf(td, sizeof td, "/tmp/wwar_%d", getpid());
if (mkdir(td, 0755) != 0) {
fprintf(stderr, "separchive FAIL: cannot acquire %s\n", td);
return 1;
}
/* ---- root → helper fixture (one dep `.a`) ------------------------ */
char helpdir[1024], helpww[1100], rootww[1100];
snprintf(helpdir, sizeof helpdir, "%s/helper", td);
if (mkdir(helpdir, 0755) != 0) {
fprintf(stderr, "separchive FAIL: cannot create %s\n", helpdir);
fail++; goto out;
}
have_helpdir = 1;
snprintf(helpww, sizeof helpww, "%s/h.ww", helpdir);
snprintf(rootww, sizeof rootww, "%s/root.ww", td);
if (write_file(helpww,
"package helper;\n"
"export fn val() i32 = { return 7; };\n") ||
write_file(rootww,
"package main;\n"
"import helper;\n"
"fn main() i32 = { return helper.val(); };\n")) {
fail++; goto out;
}
struct { const char *drv, *tag; char prog[1024]; }
stg[] = { { "ww", "cs", {0} }, { "ww_ww", "ww", {0} } };
/* Leg 1: POSITIVE build + run through the `.a` path, both stages. */
for (int s = 0; s < 2; s++) {
snprintf(stg[s].prog, sizeof stg[s].prog, "%s/prog.%s", td, stg[s].tag);
snprintf(cmd, sizeof cmd,
"timeout 240 %s/%s build -I %s -o %s %s >/dev/null 2>&1",
bin, stg[s].drv, td, stg[s].prog, rootww);
if (runwait(cmd) != 0) {
fprintf(stderr, "separchive FAIL: %s build\n", stg[s].drv);
fail++;
continue;
}
int rc = runwait(stg[s].prog);
if (rc != EXPECT_EXIT) {
fprintf(stderr, "separchive FAIL: %s prog exit=%d expected %d\n",
stg[s].drv, rc, EXPECT_EXIT);
fail++;
}
}
/* Root is a positional `.o` (force-loaded), so NO __root.a exists;
* the dep is the only `.a`. Assert the layout the driver produced. */
{
char roota[1100], rooto[1100], helpa[1100];
snprintf(roota, sizeof roota, "%s/prog.cs.sepwork/__root.a", td);
snprintf(rooto, sizeof rooto, "%s/prog.cs.sepwork/__root.o", td);
snprintf(helpa, sizeof helpa, "%s/prog.cs.sepwork/helper.a", td);
if (access(roota, 0) == 0) {
fprintf(stderr, "separchive FAIL: root wrapped in .a "
"(should stay positional .o)\n");
fail++;
}
if (access(rooto, 0) != 0) {
fprintf(stderr, "separchive FAIL: missing root .o\n");
fail++;
}
if (access(helpa, 0) != 0) {
fprintf(stderr, "separchive FAIL: missing dep helper.a\n");
fail++;
}
}
/* Leg 3: cs `.a` == ww `.a` (rule 10, the new byte-id substrate). */
{
char a[1100], b[1100];
snprintf(a, sizeof a, "%s/prog.cs.sepwork/helper.a", td);
snprintf(b, sizeof b, "%s/prog.ww.sepwork/helper.a", td);
if (files_eq(a, b) != 0) {
fprintf(stderr, "separchive FAIL: cs helper.a != ww helper.a "
"(rule 10 .a byte-id)\n");
fail++;
}
}
/* Leg 2: DETERMINISM — re-archive the same package 3× (cold each),
* the `.a` must be byte-identical (zeroed mtime/uid/gid, fixed mode,
* fixed member name). Copy each build's helper.a aside, compare. */
{
char det[3][1100];
int ok = 1;
for (int i = 0; i < 3; i++) {
char prog[1100];
snprintf(prog, sizeof prog, "%s/det%d", td, i);
snprintf(cmd, sizeof cmd,
"timeout 240 %s/ww build -I %s -o %s %s >/dev/null 2>&1",
bin, td, prog, rootww);
if (runwait(cmd) != 0) { ok = 0; break; }
snprintf(det[i], sizeof det[i], "%s/det%d.sepwork/helper.a", td, i);
}
if (!ok) {
fprintf(stderr, "separchive FAIL: determinism build\n");
fail++;
} else if (files_eq(det[0], det[1]) != 0 ||
files_eq(det[1], det[2]) != 0) {
fprintf(stderr, "separchive FAIL: .a not deterministic across "
"3 builds (floating md5 → poisons 5b cache)\n");
fail++;
}
}
/* ---- Leg 4: #31 dup THROUGH the `.a` path (PASS 3) --------------- */
/* Two DEP packages each export the SAME link symbol via @symbol;
* root references both. One member is pulled, the other lands
* UNPULLED defining an already-`defined` name → the masked dup PASS 3
* catches (#53 path-qualifies normal exports, so @symbol is the
* cleanest forced cross-package clash). */
char pkgad[1024], pkgbd[1024], paww[1100], pbww[1100], droot[1100];
snprintf(pkgad, sizeof pkgad, "%s/pkga", td);
snprintf(pkgbd, sizeof pkgbd, "%s/pkgb", td);
if (mkdir(pkgad, 0755) != 0) {
fprintf(stderr, "separchive FAIL: cannot create %s\n", pkgad);
fail++; goto out;
}
have_pkgad = 1;
if (mkdir(pkgbd, 0755) != 0) {
fprintf(stderr, "separchive FAIL: cannot create %s\n", pkgbd);
fail++; goto out;
}
have_pkgbd = 1;
snprintf(paww, sizeof paww, "%s/a.ww", pkgad);
snprintf(pbww, sizeof pbww, "%s/b.ww", pkgbd);
snprintf(droot, sizeof droot, "%s/droot.ww", td);
if (write_file(paww,
"package pkga;\n"
"@symbol(\"dup_sym\") export fn afn() i32 = { return 1; };\n") ||
write_file(pbww,
"package pkgb;\n"
"@symbol(\"dup_sym\") export fn bfn() i32 = { return 2; };\n") ||
write_file(droot,
"package main;\n"
"import pkga;\n"
"import pkgb;\n"
"fn main() i32 = { return pkga.afn() + pkgb.bfn(); };\n")) {
fail++; goto out;
}
for (int s = 0; s < 2; s++) {
char prog[1100], errf[1100];
snprintf(prog, sizeof prog, "%s/dup.%s", td, stg[s].tag);
snprintf(errf, sizeof errf, "%s/dup.%s.err", td, stg[s].tag);
snprintf(cmd, sizeof cmd,
"timeout 240 %s/%s build -I %s -o %s %s >/dev/null 2>%s",
bin, stg[s].drv, td, prog, droot, errf);
int rc = runwait(cmd);
if (rc == 0) {
fprintf(stderr, "separchive FAIL: %s accepted a masked "
"cross-pkg dup through .a (exit 0 — PASS 3 missing?)\n",
stg[s].drv);
fail++;
}
if (!file_has(errf, "duplicate symbol")) {
fprintf(stderr, "separchive FAIL: %s missing 'duplicate "
"symbol' on the .a dup path\n", stg[s].drv);
fail++;
}
if (access(prog, 0) == 0) {
fprintf(stderr, "separchive FAIL: %s produced a partial "
"binary on the dup reject\n", stg[s].drv);
fail++;
}
}
/* Leg 4 NON-VACUITY: give pkgb a DISTINCT symbol → no collision →
* both stages build + run (exit 3 = 1 + 2). Proves the dup leg
* actually discriminates (not a vacuous always-fail). */
if (write_file(pbww,
"package pkgb;\n"
"@symbol(\"uniq_sym\") export fn bfn() i32 = { return 2; };\n")) {
fail++; goto out;
}
for (int s = 0; s < 2; s++) {
char prog[1100];
snprintf(prog, sizeof prog, "%s/ok.%s", td, stg[s].tag);
snprintf(cmd, sizeof cmd,
"timeout 240 %s/%s build -I %s -o %s %s >/dev/null 2>&1",
bin, stg[s].drv, td, prog, droot);
if (runwait(cmd) != 0) {
fprintf(stderr, "separchive FAIL(non-vacuity): %s could not "
"build the distinct-symbol graph\n", stg[s].drv);
fail++;
continue;
}
int rc = runwait(prog);
if (rc != 3) {
fprintf(stderr, "separchive FAIL(non-vacuity): %s prog "
"exit=%d expected 3\n", stg[s].drv, rc);
fail++;
}
}
out:
snprintf(cmd, sizeof cmd,
"rm -rf -- '%s/prog.cs.sepwork' '%s/prog.ww.sepwork' "
"'%s/det0.sepwork' '%s/det1.sepwork' '%s/det2.sepwork' "
"'%s/dup.cs.sepwork' '%s/dup.ww.sepwork' "
"'%s/ok.cs.sepwork' '%s/ok.ww.sepwork'",
td, td, td, td, td, td, td, td, td);
if (runwait(cmd) != 0) {
fprintf(stderr, "separchive FAIL: cannot clean .sepwork trees\n");
fail++;
}
{
char path[1200];
snprintf(path, sizeof path, "%s/root.ww", td);
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "separchive FAIL: cannot remove %s\n", path); fail++; }
snprintf(path, sizeof path, "%s/droot.ww", td);
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "separchive FAIL: cannot remove %s\n", path); fail++; }
snprintf(path, sizeof path, "%s/prog.cs", td);
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "separchive FAIL: cannot remove %s\n", path); fail++; }
snprintf(path, sizeof path, "%s/prog.ww", td);
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "separchive FAIL: cannot remove %s\n", path); fail++; }
snprintf(path, sizeof path, "%s/det0", td);
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "separchive FAIL: cannot remove %s\n", path); fail++; }
snprintf(path, sizeof path, "%s/det1", td);
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "separchive FAIL: cannot remove %s\n", path); fail++; }
snprintf(path, sizeof path, "%s/det2", td);
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "separchive FAIL: cannot remove %s\n", path); fail++; }
snprintf(path, sizeof path, "%s/dup.cs", td);
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "separchive FAIL: cannot remove %s\n", path); fail++; }
snprintf(path, sizeof path, "%s/dup.ww", td);
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "separchive FAIL: cannot remove %s\n", path); fail++; }
snprintf(path, sizeof path, "%s/dup.cs.err", td);
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "separchive FAIL: cannot remove %s\n", path); fail++; }
snprintf(path, sizeof path, "%s/dup.ww.err", td);
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "separchive FAIL: cannot remove %s\n", path); fail++; }
snprintf(path, sizeof path, "%s/ok.cs", td);
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "separchive FAIL: cannot remove %s\n", path); fail++; }
snprintf(path, sizeof path, "%s/ok.ww", td);
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "separchive FAIL: cannot remove %s\n", path); fail++; }
}
if (have_helpdir) {
if (unlink(helpww) != 0 && errno != ENOENT) {
fprintf(stderr, "separchive FAIL: cannot remove %s\n", helpww);
fail++;
}
}
if (have_pkgad) {
char path[1200];
snprintf(path, sizeof path, "%s/a.ww", pkgad);
if (unlink(path) != 0 && errno != ENOENT) {
fprintf(stderr, "separchive FAIL: cannot remove %s\n", path);
fail++;
}
}
if (have_pkgbd) {
char path[1200];
snprintf(path, sizeof path, "%s/b.ww", pkgbd);
if (unlink(path) != 0 && errno != ENOENT) {
fprintf(stderr, "separchive FAIL: cannot remove %s\n", path);
fail++;
}
}
if (have_helpdir && rmdir(helpdir) != 0 && errno != ENOENT) {
fprintf(stderr, "separchive FAIL: cannot remove %s\n", helpdir);
fail++;
}
if (have_pkgad && rmdir(pkgad) != 0 && errno != ENOENT) {
fprintf(stderr, "separchive FAIL: cannot remove %s\n", pkgad);
fail++;
}
if (have_pkgbd && rmdir(pkgbd) != 0 && errno != ENOENT) {
fprintf(stderr, "separchive FAIL: cannot remove %s\n", pkgbd);
fail++;
}
if (rmdir(td) != 0 && errno != ENOENT) {
fprintf(stderr, "separchive FAIL: cannot remove %s\n", td);
fail++;
}
if (fail) {
fprintf(stderr, "separchive: %d check(s) failed\n", fail);
return 1;
}
printf("separchive: per-pkg .a (root=.o force-load, deps=.a) build+run "
"(exit %d) + .a determinism (3x identical) + cs.a==ww.a (rule 10) + "
"#31 masked-dup reject THROUGH .a (PASS 3, both stages loud+non-zero, "
"non-vacuity flip exit 3)\n", EXPECT_EXIT);
return 0;
}