Files
ww/test/run
Hojun-Cho f272068940 test/run: missing test binary is a loud FAIL, not a silent SKIP
A test/wcc/NNN_*.c with no Makefile-wired binary used to write a SKIP
status, count toward "all N tests passed", and leave MAKE_TEST_EXIT=0.
Two live instances: 953_arrlit_slice_run skipped under a green gate for
weeks (committed unwired at bf1037d, wired in the previous commit), and
a 938 hit the same hole mid-gate. The runner now treats a missing binary
as FAIL naming the file and the target to add, touches the .fail marker,
and so flips the summary + exit code. Closed by construction: make test
builds every $(TESTS) target before the runner walks test/wcc/*.c, so
the missing-binary path is reachable only by an unwired file — there is
no legitimate missing-binary SKIP to preserve. Intentional skips keep
their existing visible forms (UNIT-mode non-enumeration; in-test per-row
skip messages), neither of which folds into the pass count.

Count math: the summary N is unchanged (a SKIP already incremented ran);
what changes is honesty — pre-fix "all N passed" could include silent
skips, post-fix every counted test actually executed. A bare sh test/run
without the make-built binaries now fails loud instead of green-skipping
the whole suite.
2026-06-05 03:01:32 +09:00

150 lines
5.2 KiB
Bash
Executable File

#!/bin/sh
# test/run — driver for `make test`. Plan 9 rc-flavoured but plain sh.
#
# Walks test/wcc/<NNN>_<name>.c → out/bin/test_<name> binary, fans out
# across $JOBS (default $(nproc)) workers via xargs -P. Each worker
# writes its status to a per-job file; the collector enumerates them in
# lexicographic order so output stays deterministic across runs even
# though execution interleaves. Exit non-zero if any worker failed.
BIN=${BIN:-out/bin}
WW=${WW:-$BIN/ww}
# Worker mode: invoked once per test by xargs. Argv: --one <prefix> <test.c>.
# Writes <prefix>.status; touches <prefix>.fail on a non-zero binary exit.
if [ "$1" = "--one" ]; then
prefix=$2
t=$3
name=${t##*/}
name=${name%.c}
short=${name#[0-9][0-9][0-9]_}
bin=$BIN/test_$short
if [ ! -x "$bin" ]; then
# A missing binary means an unwired test/wcc file: `make test`
# builds every $(TESTS) target before this runs, so the only way
# to get here is a .c with no Makefile rule. That used to SKIP
# and still count toward "all N tests passed" — 953_arrlit_slice
# sat dark for weeks under a green gate. Fail loud instead.
{
printf 'FAIL %s (no binary %s)\n' "$name" "$bin"
printf 'unwired test: add $(BIN)/test_%s to TESTS + a build rule in Makefile\n' "$short"
} > "$prefix.status"
: > "$prefix.fail"
exit 0
fi
if "$bin" > "$prefix.out" 2> "$prefix.err"; then
printf 'ok %s\n' "$name" > "$prefix.status"
else
rc=$?
{
printf 'FAIL %s (rc=%d)\n' "$name" "$rc"
echo '--- stdout ---'
cat "$prefix.out"
echo '--- stderr ---'
cat "$prefix.err"
} > "$prefix.status"
: > "$prefix.fail"
fi
exit 0
fi
set -e
RESULTS=$(mktemp -d "${TMPDIR:-/tmp}/wwtest.XXXXXX")
trap 'rm -rf "$RESULTS"' EXIT
JOBS=${JOBS:-$(nproc 2>/dev/null || echo 4)}
# Phase 2 runs the wwstage byte-id gates (990-997) + 950_selfcheck.
# ww_ww writes intermediates next to every traversed source (task #15),
# so 991/992/994's reads of selfhost/cmd/<tool>/main.{s,o,combined.ww}
# race against any concurrent driver-using test that emits a sibling
# .combined.ww (task #16). Phase 1 stays parallel for unit coverage; the
# wwstage-driver tests stay out of phase 1 to keep those reads honest.
for t in test/wcc/*.c; do
[ -f "$t" ] || continue
case ${t##*/} in
950_*|990_*|991_*|992_*|993_*|994_*|995_*|996_*|997_*) continue ;;
esac
name=${t##*/}; name=${name%.c}
printf '%s/%s\0%s\0' "$RESULTS" "$name" "$t"
done | xargs -0 -n2 -P "$JOBS" "$0" --one || true
# UNIT=1 (make test-unit) is the inner-loop short path: skip the wwstage
# byte-id gates (990-997) and 950_selfcheck entirely. Pre-push uses
# `make test` for the full suite.
if [ "${UNIT:-0}" != "1" ]; then
# Phase 2 is partitioned (#3): the pure-reader byte-id gates run in
# parallel; the source-tree writers run in a serial tail afterward.
#
# PARALLEL GROUP — 990/991/992/994/996/997. Each reads only committed
# or make-all-produced files and writes only /tmp or a disjoint tracked
# stem (990→out smoke.*, 996→examples/mandelbrot/*, 997→/tmp fixture
# copy). No group member writes a stem another member reads (per-gate FS
# footprint audited race-free, #3), so they fan out via the same
# xargs -P "$JOBS" machinery as phase 1.
for t in test/wcc/990_*.c test/wcc/991_*.c test/wcc/992_*.c \
test/wcc/994_*.c test/wcc/996_*.c test/wcc/997_*.c; do
[ -f "$t" ] || continue
name=${t##*/}; name=${name%.c}
printf '%s/%s\0%s\0' "$RESULTS" "$name" "$t"
done | xargs -0 -n2 -P "$JOBS" "$0" --one || true
# SERIAL TAIL — 993/995/950, the only source-tree writers. 993 & 995
# both write cmd/wwdump/main.* (995 writes all 5 cmd/*/main.*), so they
# run sequentially w.r.t. each other AND after the parallel group, so
# the group's readers see stable make-all output, never a torn write.
for t in test/wcc/993_*.c test/wcc/995_*.c test/wcc/950_*.c; do
[ -f "$t" ] || continue
name=${t##*/}; name=${name%.c}
"$0" --one "$RESULTS/$name" "$t" || true
done
# combined.ww freshness gate (#110): 990/995 above unconditionally
# regenerate every tracked combined.ww amalgamation, so a diff vs HEAD
# here means an embedded source changed without its derived artifact
# being regenerated + committed (the #28 staleness hole the per-file
# byte-id gates were blind to). git ls-files keeps new combined.ww
# auto-covered (rule-13 SSoT). Lives inside the UNIT guard because
# test-unit skips 990/995 and thus the regen.
name=combined_ww_fresh
if git diff --exit-code -- $(git ls-files '*.combined.ww') > /dev/null 2>&1; then
printf 'ok %s\n' "$name" > "$RESULTS/$name.status"
else
{
printf 'FAIL %s (stale; regen + commit the derived amalgamation)\n' "$name"
echo '--- stale combined.ww ---'
git diff --name-only -- $(git ls-files '*.combined.ww')
} > "$RESULTS/$name.status"
: > "$RESULTS/$name.fail"
fi
fi
fail=0
ran=0
for s in "$RESULTS"/*.status; do
[ -f "$s" ] || continue
cat "$s"
ran=$((ran + 1))
prefix=${s%.status}
[ -f "$prefix.fail" ] && fail=$((fail + 1))
done
# ---- ww source-level tests ----------------------------------------------
if [ -d test/lang ]; then
for f in test/lang/*/*.ww; do
[ -f "$f" ] || continue
echo "skip $f (ww run not online yet)"
done
fi
if [ $ran -eq 0 ]; then
echo "no tests were run"
exit 1
fi
if [ $fail -gt 0 ]; then
echo "$fail test(s) failed"
exit 1
fi
echo "all $ran tests passed"