test/wcc/995_self_rebuild: stage source in workdir to avoid main.s race

ww_ww build writes its intermediates (.combined.ww/.s/.o/binary)
next to the source path. When 995 invoked `ww_ww build /abs/path/
selfhost/cmd/wwdump/main.ww` in parallel with 991/992/994 (which
read selfhost/cmd/<tool>/main.s as a fixture), the build mid-wrote
main.s and the fixture-reading test saw a partial 520B view of the
1.4MB file → "C 1422712 vs ww 520 bytes" assembler-byte-diff fail.

The race was latent pre-31594e4 too: parallel test/run + serial
995 always finished 995 last (3min wall), so 991 was already done
by the time 995's wwdump build wrote main.s. Internal-parallel 995
finishes in 1m47, opening the window.

Fix: cp the source to <workdir>/build.ww before invoking ww_ww,
build from build.ww. Driver writes intermediates as build.{combined.ww,
s,o} and binary `build` in workdir; canonical selfhost/cmd/<tool>/
main.* are never touched by 995. Imports still resolve via -I, but
those paths are read-only fixtures.

Verified 3 consecutive `make test` runs 132/132 (4:18, 3:22, 3:16).
This commit is contained in:
2026-05-21 17:05:58 +09:00
parent 31594e4cae
commit be363f4451

View File

@@ -82,15 +82,21 @@ struct buildjob {
/* Fork a child that runs the `ww_ww build` for this tool. The child
* inherits no concurrent siblings — system() spawns a fresh /bin/sh -c.
* stderr lands in <workdir>/build.err so concurrent builds don't merge
* their diagnostics. */
* their diagnostics.
*
* The source is staged at <workdir>/build.ww before the build so the
* driver writes its .combined.ww/.s/.o intermediates inside workdir —
* not next to the canonical selfhost/cmd/<tool>/main.ww, where 991/992/
* 994 read main.s as a fixture. Without staging, parallel test/run sees
* 991 race against 995's wwdump-build mid-write of main.s. */
static int
spawn_build(const char *bin, const char *cwd, struct buildjob *j)
{
snprintf(j->workdir, sizeof j->workdir, "/tmp/wwsr_%d_%s",
getpid(), j->tool);
char setup[256];
snprintf(setup, sizeof setup, "rm -rf %s && mkdir -p %s",
j->workdir, j->workdir);
char setup[4096];
snprintf(setup, sizeof setup, "rm -rf %s && mkdir -p %s && cp %s/%s %s/build.ww",
j->workdir, j->workdir, cwd, j->src_rel, j->workdir);
if (runwait(setup) != 0) return -1;
char cmd[4096];
@@ -98,15 +104,15 @@ spawn_build(const char *bin, const char *cwd, struct buildjob *j)
snprintf(cmd, sizeof cmd,
"cd %s && timeout 180 %s/ww_ww build -I %s/%s "
"-I %s/lib/ww -I %s/lib/ww/lex -I %s/lib/ww/parse "
"-I %s/selfhost/cmd/wcc %s/%s >/dev/null 2>%s/build.err",
"-I %s/selfhost/cmd/wcc build.ww >/dev/null 2>%s/build.err",
j->workdir, bin, cwd, j->inc_local,
cwd, cwd, cwd, cwd, cwd, j->src_rel, j->workdir);
cwd, cwd, cwd, cwd, j->workdir);
} else {
snprintf(cmd, sizeof cmd,
"cd %s && timeout 180 %s/ww_ww build "
"-I %s/lib/ww -I %s/lib/ww/lex -I %s/lib/ww/parse "
"-I %s/selfhost/cmd/wcc %s/%s >/dev/null 2>%s/build.err",
j->workdir, bin, cwd, cwd, cwd, cwd, cwd, j->src_rel, j->workdir);
"-I %s/selfhost/cmd/wcc build.ww >/dev/null 2>%s/build.err",
j->workdir, bin, cwd, cwd, cwd, cwd, j->workdir);
}
pid_t p = fork();
@@ -146,7 +152,7 @@ collect_build(const char *bin, struct buildjob *j)
rc = -1;
} else {
char rebuilt[256], canonical[256];
snprintf(rebuilt, sizeof rebuilt, "%s/main", j->workdir);
snprintf(rebuilt, sizeof rebuilt, "%s/build", j->workdir);
snprintf(canonical, sizeof canonical, "%s/%s_ww", bin, j->tool);
rc = slurp_eq(rebuilt, canonical);
if (rc != 0) {