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.
This commit is contained in:
2026-08-07 23:02:47 +09:00
parent b2899dd8d3
commit a95a7a316b
132 changed files with 7573 additions and 6172 deletions

View File

@@ -1,14 +1,14 @@
/*
* 737_direnum — driver-level sentinel for task #22 directory-as-
* module enumeration. Two row families pinning the contract:
* 737_direnum — driver-level sentinel for directory-package loading.
* Existing imported-directory fixtures plus a private dynamic table pin:
*
* ok — multi-file dir is concatenated by both stages; the entry
* reads cross-pkg bare-leaf fns from sibling files. Build
* must succeed for both C-built `ww` and ww-built `ww_ww`.
* bad — multi-file dir with mismatched `package <name>;` decls
* triggers the strict-same-package error (task #25 subset
* bundled with #22 because the failure mode is dir-enum's
* own; cross-stage symmetric).
* bad — multi-file dirs with conflicting `package <name>;` clauses;
* root — direct multi-file directory builds, deterministic source order,
* missing/invalid clauses, and root conflicts;
* leaf — an imported directory with one consistent but wrong package name.
*
* Asm-presence isn't checked separately — 968_utf8_run, 966_strings_
* run, 995_self_rebuild already exercise dir-enum end-to-end at
@@ -18,6 +18,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
@@ -43,6 +44,49 @@ stderr_contains(const char *path, const char *needle)
return strstr(buf, needle) != NULL;
}
static int
write_file(const char *path, const char *body)
{
FILE *f = fopen(path, "wb");
if (!f) return -1;
int failed = fputs(body, f) == EOF;
if (fclose(f) != 0) failed = 1;
return failed ? -1 : 0;
}
static int
files_equal(const char *a, const char *b)
{
FILE *fa = fopen(a, "rb"), *fb = fopen(b, "rb");
if (!fa || !fb) {
if (fa) fclose(fa);
if (fb) fclose(fb);
return 0;
}
int ca, cb;
do {
ca = fgetc(fa);
cb = fgetc(fb);
} while (ca == cb && ca != EOF);
fclose(fa);
fclose(fb);
return ca == cb;
}
static int
file_orders(const char *path, const char *first, const char *second)
{
FILE *f = fopen(path, "rb");
if (!f) return 0;
char buf[16384];
size_t n = fread(buf, 1, sizeof buf - 1, f);
fclose(f);
buf[n] = '\0';
char *a = strstr(buf, first);
char *b = strstr(buf, second);
return a != NULL && b != NULL && a < b;
}
int
main(void)
{
@@ -61,11 +105,21 @@ main(void)
snprintf(ww_ww, sizeof ww_ww, "%s/ww_ww", bin);
int total = 0, fail = 0;
char errp[64];
int have_owned = 0, have_rootok = 0, have_rootbad = 0;
int have_importbad = 0, have_wanted = 0;
int have_missing = 0, have_invalid = 0;
char owner[128];
snprintf(owner, sizeof owner, "/tmp/direnum_owner_%d", getpid());
if (mkdir(owner, 0755) != 0) {
fprintf(stderr, "737[setup]: cannot acquire temp directory: %s\n",
strerror(errno));
return 1;
}
char errp[512];
/* ok: cross-pkg bare-leaf via dir-enum, both stages. `ww run` routes
* its scratch through /tmp/ww_run_<pid>.sepwork and removes it itself
* (do_run keepscratch 0), so no per-invocation tmpdir is needed here. */
/* ok: cross-pkg bare-leaf via dir-enum, both stages. `ww run` owns
* /tmp/ww_run_<pid>/{main,main.sepwork} and removes both itself, so no
* per-invocation build directory is needed here. */
{
const char *src = "test/wcc/data/direnum/entry.ww";
char cmd[2048];
@@ -88,114 +142,365 @@ main(void)
}
}
/* bad: mismatched package decls in same dir → import-path mismatch
* error. E3-C1: the amalgamator's strict-same-package "differs from"
* check is deleted; under sep w6c rejects the composed unit with
* "package <p> does not match import path <i>" (wwstage is terser per
* #68, so match the shared "does not match import path" substring).
* Both stages must surface it in stderr. */
/* bad: the package loader validates the complete stored source list
* conflict-first, before checking the import-path leaf. Both stages must
* surface the same directory-level conflict. */
{
const char *src = "test/wcc/data/direnum/bad_entry.ww";
snprintf(errp, sizeof errp, "/tmp/direnum_%d_bad.err", getpid());
/* CLASS B: build INPUT stays the in-repo fixture, but -o points the
* output stem (and thus its <stem>.sepwork scratch) at a unique /tmp
* tmpdir so nothing leaks into the tracked tree — even on the
* expected build failure, since scratch is mkdir'd before the reject.
* rm -rf the tmpdir on every exit path. */
char td[64], rmcmd[128];
/* Build INPUT stays in-repo. Output, diagnostic, and the retained
* exact <stem>.sepwork tree stay beneath this invocation's root. */
char out[512];
char cmd[2048];
snprintf(td, sizeof td, "/tmp/direnum_%d_bad_c", getpid());
mkdir(td, 0755);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", td);
snprintf(cmd, sizeof cmd, "%s build -o %s/out %s 2>%s >/dev/null",
ww, td, src, errp);
snprintf(out, sizeof out, "%s/bad-c", owner);
snprintf(errp, sizeof errp, "%s/bad-c.err", owner);
snprintf(cmd, sizeof cmd, "%s build -o '%s' %s 2>'%s' >/dev/null",
ww, out, src, errp);
int rc = runwait(cmd);
runwait(rmcmd);
total++;
if (rc == 0) {
fprintf(stderr, "737[bad-cstage]: expected build failure, succeeded\n");
fail++;
} else if (!stderr_contains(errp, "does not match import path")) {
fprintf(stderr, "737[bad-cstage]: stderr missing 'does not match import path'\n");
} else if (!stderr_contains(errp, "conflicting package names")) {
fprintf(stderr, "737[bad-cstage]: stderr missing 'conflicting package names'\n");
fail++;
}
unlink(errp);
if (access(ww_ww, X_OK) == 0) {
snprintf(td, sizeof td, "/tmp/direnum_%d_bad_w", getpid());
mkdir(td, 0755);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", td);
snprintf(cmd, sizeof cmd, "%s build -o %s/out %s 2>%s >/dev/null",
ww_ww, td, src, errp);
snprintf(out, sizeof out, "%s/bad-w", owner);
snprintf(errp, sizeof errp, "%s/bad-w.err", owner);
snprintf(cmd, sizeof cmd,
"%s build -o '%s' %s 2>'%s' >/dev/null",
ww_ww, out, src, errp);
rc = runwait(cmd);
runwait(rmcmd);
total++;
if (rc == 0) {
fprintf(stderr, "737[bad-wwstage]: expected build failure, succeeded\n");
fail++;
} else if (!stderr_contains(errp, "does not match import path")) {
fprintf(stderr, "737[bad-wwstage]: stderr missing 'does not match import path'\n");
} else if (!stderr_contains(errp, "conflicting package names")) {
fprintf(stderr, "737[bad-wwstage]: stderr missing 'conflicting package names'\n");
fail++;
}
unlink(errp);
}
}
/* bad-deep: same mismatch, but one file's `package` decl sits behind
* a >2048-byte comment header. Pins the PREP-peek whole-file read
* (#16): the old 2048-capped peek missed that decl, so the strict-
* same-package check skipped the file and the mismatch went
* undetected (build wrongly succeeded); the whole-file peek finds it
* → "does not match import path". Both stages. (reviewer-batcha pin.) */
/* bad-deep: same conflict, but one clause sits behind a >2048-byte
* comment header. The loader's one whole-source header/import read must
* still validate it in both stages. */
{
const char *src = "test/wcc/data/direnum/bad_deep_entry.ww";
snprintf(errp, sizeof errp, "/tmp/direnum_%d_baddeep.err", getpid());
/* CLASS B (see bad block): -o into a unique /tmp tmpdir keeps the
* <stem>.sepwork scratch out of the tracked tree on the expected
* reject; rm -rf on every exit path. */
char td[64], rmcmd[128];
/* Same owned-root layout as the shallow conflict above. */
char out[512];
char cmd[2048];
snprintf(td, sizeof td, "/tmp/direnum_%d_baddeep_c", getpid());
mkdir(td, 0755);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", td);
snprintf(cmd, sizeof cmd, "%s build -o %s/out %s 2>%s >/dev/null",
ww, td, src, errp);
snprintf(out, sizeof out, "%s/baddeep-c", owner);
snprintf(errp, sizeof errp, "%s/baddeep-c.err", owner);
snprintf(cmd, sizeof cmd, "%s build -o '%s' %s 2>'%s' >/dev/null",
ww, out, src, errp);
int rc = runwait(cmd);
runwait(rmcmd);
total++;
if (rc == 0) {
fprintf(stderr, "737[bad-deep-cstage]: expected build failure, succeeded\n");
fail++;
} else if (!stderr_contains(errp, "does not match import path")) {
fprintf(stderr, "737[bad-deep-cstage]: stderr missing 'does not match import path'\n");
} else if (!stderr_contains(errp, "conflicting package names")) {
fprintf(stderr, "737[bad-deep-cstage]: stderr missing 'conflicting package names'\n");
fail++;
}
unlink(errp);
if (access(ww_ww, X_OK) == 0) {
snprintf(td, sizeof td, "/tmp/direnum_%d_baddeep_w", getpid());
mkdir(td, 0755);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", td);
snprintf(cmd, sizeof cmd, "%s build -o %s/out %s 2>%s >/dev/null",
ww_ww, td, src, errp);
snprintf(out, sizeof out, "%s/baddeep-w", owner);
snprintf(errp, sizeof errp, "%s/baddeep-w.err", owner);
snprintf(cmd, sizeof cmd,
"%s build -o '%s' %s 2>'%s' >/dev/null",
ww_ww, out, src, errp);
rc = runwait(cmd);
runwait(rmcmd);
total++;
if (rc == 0) {
fprintf(stderr, "737[bad-deep-wwstage]: expected build failure, succeeded\n");
fail++;
} else if (!stderr_contains(errp, "does not match import path")) {
fprintf(stderr, "737[bad-deep-wwstage]: stderr missing 'does not match import path'\n");
} else if (!stderr_contains(errp, "conflicting package names")) {
fprintf(stderr, "737[bad-deep-wwstage]: stderr missing 'conflicting package names'\n");
fail++;
}
unlink(errp);
}
}
/* Authoritative Package rows. All inputs, outputs, and retained build
* scratch live beneath the invocation-owned root and are removed below. */
{
char td[512], rootok[512], rootbad[512], importbad[512];
char wanted[512], missing[512], invalid[512];
char path[1024], cmd[4096];
snprintf(td, sizeof td, "%s/owned", owner);
snprintf(rootok, sizeof rootok, "%s/rootok", td);
snprintf(rootbad, sizeof rootbad, "%s/rootbad", td);
snprintf(importbad, sizeof importbad, "%s/importbad", td);
snprintf(wanted, sizeof wanted, "%s/wanted", importbad);
snprintf(missing, sizeof missing, "%s/missing", td);
snprintf(invalid, sizeof invalid, "%s/invalid", td);
int setup = 0;
if (mkdir(td, 0755) != 0) setup = 1;
else have_owned = 1;
if (!setup && mkdir(rootok, 0755) != 0) setup = 1;
else if (!setup) have_rootok = 1;
if (!setup && mkdir(rootbad, 0755) != 0) setup = 1;
else if (!setup) have_rootbad = 1;
if (!setup && mkdir(importbad, 0755) != 0) setup = 1;
else if (!setup) have_importbad = 1;
if (!setup && mkdir(wanted, 0755) != 0) setup = 1;
else if (!setup) have_wanted = 1;
if (!setup && mkdir(missing, 0755) != 0) setup = 1;
else if (!setup) have_missing = 1;
if (!setup && mkdir(invalid, 0755) != 0) setup = 1;
else if (!setup) have_invalid = 1;
if (!setup) snprintf(path, sizeof path, "%s/z.ww", rootok);
if (!setup) setup = write_file(path,
"package rootok;\n// ORDER-Z\n"
"fn value() i32 = { return 17; };\n");
if (!setup) snprintf(path, sizeof path, "%s/a.ww", rootok);
if (!setup) setup = write_file(path,
"// leading comment is part of the loader grammar\n"
"package rootok;\n// ORDER-A\n"
"fn main() i32 = { return value(); };\n");
if (!setup) snprintf(path, sizeof path, "%s/a.ww", rootbad);
if (!setup) setup = write_file(path,
"package rootbad;\nfn main() i32 = { return 0; };\n");
if (!setup) snprintf(path, sizeof path, "%s/b.ww", rootbad);
if (!setup) setup = write_file(path,
"package other;\nfn spare() i32 = { return 0; };\n");
if (!setup) snprintf(path, sizeof path, "%s/entry.ww", importbad);
if (!setup) setup = write_file(path,
"package main;\nimport wanted;\n"
"fn main() i32 = { return wanted.value(); };\n");
if (!setup) snprintf(path, sizeof path, "%s/a.ww", wanted);
if (!setup) setup = write_file(path,
"package other;\nexport fn value() i32 = { return 0; };\n");
if (!setup) snprintf(path, sizeof path, "%s/a.ww", missing);
if (!setup) setup = write_file(path,
"fn main() i32 = { return 0; };\n");
if (!setup) snprintf(path, sizeof path, "%s/a.ww", invalid);
if (!setup) setup = write_file(path,
"package 7bad;\nfn main() i32 = { return 0; };\n");
if (setup) {
fprintf(stderr, "737[owned-setup]: cannot create private fixtures\n");
fail++;
total++;
} else {
const char *drivers[] = { ww, ww_ww };
const char *stages[] = { "cstage", "wwstage" };
char units[2][512];
int built[2] = {0, 0};
for (int s = 0; s < 2; s++) {
if (access(drivers[s], X_OK) != 0) continue;
char out[512], err[512];
snprintf(out, sizeof out, "%s/root-%d", td, s);
snprintf(err, sizeof err, "%s/root-%d.err", td, s);
snprintf(cmd, sizeof cmd,
"%s build -o '%s' '%s' "
">/dev/null 2>'%s'", drivers[s], out,
rootok, err);
int rc = runwait(cmd);
total++;
if (rc != 0 || runwait(out) != 17) {
fprintf(stderr,
"737[root-%s]: build/run failed (build=%d)\n",
stages[s], rc);
fail++;
continue;
}
snprintf(units[s], sizeof units[s],
"%s.sepwork/__root.unit.ww", out);
if (!file_orders(units[s], "ORDER-A", "ORDER-Z")) {
fprintf(stderr,
"737[root-%s]: stored sources are not byte-sorted\n",
stages[s]);
fail++;
} else {
built[s] = 1;
}
}
if (built[0] && built[1]) {
total++;
if (!files_equal(units[0], units[1])) {
fprintf(stderr,
"737[root-parity]: C/WW units differ\n");
fail++;
}
}
struct rejectrow {
const char *tag;
const char *target;
const char *needle;
} rows[] = {
{ "root-conflict", rootbad, "conflicting package names" },
{ "import-leaf", NULL, "does not match import path" },
{ "root-missing", missing, "invalid or missing package clause" },
{ "root-invalid", invalid, "invalid or missing package clause" },
};
char entry[512];
snprintf(entry, sizeof entry, "%s/entry.ww", importbad);
rows[1].target = entry;
for (int r = 0; r < 4; r++) {
char errors[2][512];
int ran[2] = {0, 0};
for (int s = 0; s < 2; s++) {
if (access(drivers[s], X_OK) != 0) continue;
char out[512];
snprintf(out, sizeof out, "%s/%s-%d", td,
rows[r].tag, s);
snprintf(errors[s], sizeof errors[s], "%s/%s-%d.err",
td, rows[r].tag, s);
snprintf(cmd, sizeof cmd,
"%s build -o '%s' '%s' "
">/dev/null 2>'%s'", drivers[s], out,
rows[r].target, errors[s]);
int rc = runwait(cmd);
total++;
ran[s] = 1;
if (rc == 0
|| !stderr_contains(errors[s], rows[r].needle)) {
fprintf(stderr,
"737[%s-%s]: reject/diagnostic mismatch\n",
rows[r].tag, stages[s]);
fail++;
}
}
if (ran[0] && ran[1]) {
total++;
if (!files_equal(errors[0], errors[1])) {
fprintf(stderr,
"737[%s-parity]: C/WW diagnostics differ\n",
rows[r].tag);
fail++;
}
}
}
}
}
int cleanup_fail = 0;
char path[1024], cmd[2048];
const char *top_stems[] = { "bad-c", "bad-w", "baddeep-c", "baddeep-w" };
const char *suffixes[] = { "", ".err" };
for (size_t i = 0; i < sizeof top_stems / sizeof top_stems[0]; i++) {
snprintf(path, sizeof path, "%s/%s.sepwork", owner, top_stems[i]);
snprintf(cmd, sizeof cmd, "rm -rf -- '%s'", path);
if (runwait(cmd) != 0) {
fprintf(stderr, "737[cleanup]: remove %s failed\n", path);
cleanup_fail = 1;
}
}
const char *reject_tags[] = {
"root-conflict", "import-leaf", "root-missing", "root-invalid"
};
if (have_owned) {
for (int s = 0; s < 2; s++) {
snprintf(path, sizeof path, "%s/owned/root-%d.sepwork", owner, s);
snprintf(cmd, sizeof cmd, "rm -rf -- '%s'", path);
if (runwait(cmd) != 0) {
fprintf(stderr, "737[cleanup]: remove %s failed\n", path);
cleanup_fail = 1;
}
for (size_t r = 0; r < sizeof reject_tags / sizeof reject_tags[0]; r++) {
snprintf(path, sizeof path, "%s/owned/%s-%d.sepwork",
owner, reject_tags[r], s);
snprintf(cmd, sizeof cmd, "rm -rf -- '%s'", path);
if (runwait(cmd) != 0) {
fprintf(stderr, "737[cleanup]: remove %s failed\n", path);
cleanup_fail = 1;
}
}
}
}
for (size_t i = 0; i < sizeof top_stems / sizeof top_stems[0]; i++) {
for (size_t k = 0; k < sizeof suffixes / sizeof suffixes[0]; k++) {
snprintf(path, sizeof path, "%s/%s%s", owner,
top_stems[i], suffixes[k]);
if (unlink(path) != 0 && errno != ENOENT) {
fprintf(stderr, "737[cleanup]: unlink %s: %s\n",
path, strerror(errno));
cleanup_fail = 1;
}
}
}
if (have_owned) {
for (int s = 0; s < 2; s++) {
for (size_t k = 0; k < sizeof suffixes / sizeof suffixes[0]; k++) {
snprintf(path, sizeof path, "%s/owned/root-%d%s",
owner, s, suffixes[k]);
if (unlink(path) != 0 && errno != ENOENT) {
fprintf(stderr, "737[cleanup]: unlink %s: %s\n",
path, strerror(errno));
cleanup_fail = 1;
}
}
for (size_t r = 0; r < sizeof reject_tags / sizeof reject_tags[0]; r++) {
for (size_t k = 0; k < sizeof suffixes / sizeof suffixes[0]; k++) {
snprintf(path, sizeof path, "%s/owned/%s-%d%s",
owner, reject_tags[r], s, suffixes[k]);
if (unlink(path) != 0 && errno != ENOENT) {
fprintf(stderr, "737[cleanup]: unlink %s: %s\n",
path, strerror(errno));
cleanup_fail = 1;
}
}
}
}
}
struct owned_path {
const char *name;
int owned;
} fixture_files[] = {
{ "owned/rootok/z.ww", have_rootok },
{ "owned/rootok/a.ww", have_rootok },
{ "owned/rootbad/a.ww", have_rootbad },
{ "owned/rootbad/b.ww", have_rootbad },
{ "owned/importbad/entry.ww", have_importbad },
{ "owned/importbad/wanted/a.ww", have_wanted },
{ "owned/missing/a.ww", have_missing },
{ "owned/invalid/a.ww", have_invalid },
};
for (size_t i = 0; i < sizeof fixture_files / sizeof fixture_files[0]; i++) {
if (!fixture_files[i].owned) continue;
snprintf(path, sizeof path, "%s/%s", owner, fixture_files[i].name);
if (unlink(path) != 0 && errno != ENOENT) {
fprintf(stderr, "737[cleanup]: unlink %s: %s\n",
path, strerror(errno));
cleanup_fail = 1;
}
}
struct owned_path fixture_dirs[] = {
{ "owned/importbad/wanted", have_wanted },
{ "owned/rootok", have_rootok },
{ "owned/rootbad", have_rootbad },
{ "owned/importbad", have_importbad },
{ "owned/missing", have_missing },
{ "owned/invalid", have_invalid },
{ "owned", have_owned },
};
for (size_t i = 0; i < sizeof fixture_dirs / sizeof fixture_dirs[0]; i++) {
if (!fixture_dirs[i].owned) continue;
snprintf(path, sizeof path, "%s/%s", owner, fixture_dirs[i].name);
if (rmdir(path) != 0 && errno != ENOENT) {
fprintf(stderr, "737[cleanup]: rmdir %s: %s\n",
path, strerror(errno));
cleanup_fail = 1;
}
}
if (rmdir(owner) != 0) {
fprintf(stderr, "737[cleanup]: rmdir %s: %s\n",
owner, strerror(errno));
cleanup_fail = 1;
}
if (cleanup_fail) fail++;
if (fail) {
fprintf(stderr, "737_direnum: %d/%d fixtures failed\n", fail, total);
return 1;