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

@@ -4,8 +4,9 @@
*
* Root cause it guards: `lib/ww/syntax/` exports the fns wcc calls, and
* those fns' signatures name frontend types (tok, tinfo, tctx, ...). When
* the `--sep` producer emits syntax's `.wwi`, check_exported_type recurses
* every exported decl — and, for an exported struct, its FIELDS too (the
* the separate-compilation producer emits syntax's `.wwi`,
* check_exported_type recurses every exported decl — and, for an exported
* struct, its FIELDS too (the
* harec STORAGE_STRUCT precedent, wwi.c wwi_check_type N_TSTRUCT). Any
* referenced type that is itself unexported aborts the producer with
* "exported declaration references unexported type". Pre-fix, syntax's
@@ -21,24 +22,24 @@
* (#75). It happens to link + run end-to-end, which also shows the
* qualified-ref path resolves across the sep boundary.
*
* Asserts (all COLD — per-stage WW_PKGCACHE wipes the package cache):
* Asserts (direct builds compile every package):
* 1. Build + run, BOTH stages -> exit EXPECT_EXIT (`syntax.tokname(
* TK_INT)` == "int", len 3). Pre-fix the syntax producer exited 1, so
* the build never produced a binary.
* 2. syntax's `.wwi` is produced and carries the 16 `export type` decls.
* 3. cs==ww (rule 10): syntax's `.wwi`/`.s`/`.unit.ww` AND the final
* binary are byte-identical between `ww --sep` and `ww_ww --sep`.
* binary are byte-identical between `ww` and `ww_ww`.
* 4. NON-VACUITY (the bug, isolated, BOTH compilers): replay the producer
* `w6c -c -I` on the produced `syntax.unit.ww` -> exits 0 (exports
* present). On a copy with `export ` stripped from the type decls ->
* MUST exit non-zero (the export-check fires). Proven for w6c + w6c_ww.
*
* Light wwstage-driver test (CLAUDE.md rule 14): all intermediates are
* `-o`-redirected to /tmp, so it is phase-1 parallel-safe; it only READS
* All intermediates are `-o`-redirected to /tmp; it only reads
* the in-tree `lib/ww/syntax`. Models 989_seproot_export_run.c.
*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
@@ -118,9 +119,8 @@ write_file(const char *path, const char *body)
{
FILE *f = fopen(path, "wb");
if (!f) return -1;
fputs(body, f);
fclose(f);
return 0;
if (fputs(body, f) == EOF) { fclose(f); return -1; }
return fclose(f);
}
int
@@ -131,13 +131,11 @@ main(void)
char root[1024];
if (getcwd(root, sizeof root) == NULL) return 1;
char td[64], cmd[8192];
int fail = 0;
char td[] = "/tmp/wwsynexp_XXXXXX";
char cmd[8192];
int fail = 0, cleanup_fail = 0;
snprintf(td, sizeof td, "/tmp/wwsynexp_%d", getpid());
snprintf(cmd, sizeof cmd, "rm -rf %s", td);
runwait(cmd);
mkdir(td, 0755);
if (mkdtemp(td) == NULL) return 1;
/* Root: QUALIFIED refs into the real syntax package — `syntax.tokname`
* over `syntax.tkind`. The producer must serialize syntax's `.wwi`
@@ -158,15 +156,15 @@ main(void)
for (int s = 0; s < 2; s++) {
snprintf(stg[s].prog, sizeof stg[s].prog, "%s/prog.%s", td, stg[s].tag);
/* COLD: per-stage WW_PKGCACHE -> syntax compiles fresh, so the
* `.wwi`/`.s`/`.unit.ww` this gate inspects are always produced. */
/* Direct builds produce the `.wwi`/`.s`/`.unit.ww` artifacts this
* gate inspects. */
snprintf(cmd, sizeof cmd,
"WW_PKGCACHE='%s/cache.%s' timeout 240 %s/%s build --sep "
"timeout 240 %s/%s build "
"-I %s/lib/ww -o %s %s >%s/build.%s.log 2>&1",
td, stg[s].tag, bin, stg[s].drv, root, stg[s].prog, rootww,
bin, stg[s].drv, root, stg[s].prog, rootww,
td, stg[s].tag);
if (runwait(cmd) != 0) {
fprintf(stderr, "syntaxexport FAIL: %s build --sep (pre-fix: syntax "
fprintf(stderr, "syntaxexport FAIL: %s build (pre-fix: syntax "
"producer rejects export-fn-over-unexported-type)\n", stg[s].drv);
fail++;
continue;
@@ -268,8 +266,34 @@ main(void)
}
out:
snprintf(cmd, sizeof cmd, "rm -rf %s", td);
runwait(cmd);
for (int s = 0; s < 2; s++) {
const char *tag = s == 0 ? "cs" : "ww";
char path[1200];
snprintf(path, sizeof path, "%s/prog.%s.sepwork", td, tag);
snprintf(cmd, sizeof cmd, "rm -rf -- '%s'", path);
if (runwait(cmd) != 0) cleanup_fail = 1;
snprintf(path, sizeof path, "%s/prog.%s", td, tag);
if (unlink(path) != 0 && errno != ENOENT) cleanup_fail = 1;
snprintf(path, sizeof path, "%s/build.%s.log", td, tag);
if (unlink(path) != 0 && errno != ENOENT) cleanup_fail = 1;
}
{
const char *files[] = {
"root.ww", "syntax.noexport.ww",
"nv.w6c.wwi", "nv.w6c.s",
"nv.w6c_ww.wwi", "nv.w6c_ww.s",
};
for (size_t i = 0; i < sizeof files / sizeof files[0]; i++) {
char path[1200];
snprintf(path, sizeof path, "%s/%s", td, files[i]);
if (unlink(path) != 0 && errno != ENOENT) cleanup_fail = 1;
}
}
if (rmdir(td) != 0) cleanup_fail = 1;
if (cleanup_fail) {
fprintf(stderr, "syntaxexport FAIL: cleanup incomplete under %s\n", td);
fail++;
}
if (fail) {
fprintf(stderr, "syntaxexport: %d check(s) failed\n", fail);
return 1;