commit fa542273abf56197707d1ce2db72563bdd9daa12
parent 445f751aceb58da4012431734742011229962bea
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Mon, 27 Apr 2026 10:23:27 -0700
scripts: collapse test runner to one container per arch
The host runner used to re-enter the container per fixture (and
per build/run within a fixture). Move all suite logic into
scripts/boot-run-tests.sh so a whole arch's suite is one podman
invocation; the host now just dispatches and aggregates totals.
m1pp's lint preflight stays on the host because lint.sh runs python.
Diffstat:
3 files changed, 512 insertions(+), 507 deletions(-)
diff --git a/Makefile b/Makefile
@@ -225,9 +225,10 @@ build/native-tools/m1pp: scripts/build-native-tools.sh M1pp/M1pp.c
#
# `make test` runs every suite. SUITE selects one; ARCH restricts to one
# arch. Make ensures every dependency (images, tools, tables, m1pp
-# expanders) exists before scripts/run-tests.sh is invoked. The runner
-# itself only loops fixtures, builds per-fixture binaries via the boot-*
-# scripts, runs them in the container, and diffs.
+# expanders) exists before scripts/run-tests.sh is invoked. The host
+# runner starts one container per arch and hands off to
+# scripts/boot-run-tests.sh, which loops the fixtures, builds
+# per-fixture binaries via the boot-* scripts, runs them, and diffs.
SUITE ?=
diff --git a/scripts/boot-run-tests.sh b/scripts/boot-run-tests.sh
@@ -0,0 +1,431 @@
+#!/bin/sh
+## boot-run-tests.sh — in-container suite runner.
+##
+## One invocation handles every requested fixture in a suite for $ARCH,
+## instead of the host re-entering the container per fixture. The host
+## runner (scripts/run-tests.sh) starts one podman process per arch and
+## lets this script do all the build / execute / diff work.
+##
+## PASS/FAIL lines stream to stdout in the same format the host expects;
+## the host greps them to update its totals, then prints the final
+## summary itself. Fixture-name discovery happens here when no names
+## are passed (so the host can stay agnostic about each suite's layout),
+## except for m1pp: scripts/lint.sh runs python on the host, so the
+## host preflights lint and passes the explicit kept list down.
+##
+## Env: ARCH=aarch64|amd64|riscv64
+## Usage: boot-run-tests.sh --suite=<m1pp|p1|scheme1|cc-util|cc-lex|cc-pp|cc-cg|cc-parse|cc-e2e> [name ...]
+
+set -eu
+
+: "${ARCH:?ARCH must be set}"
+
+SUITE=
+NAMES=
+
+while [ "$#" -gt 0 ]; do
+ case "$1" in
+ --suite) shift; SUITE=$1 ;;
+ --suite=*) SUITE=${1#--suite=} ;;
+ --) shift; while [ "$#" -gt 0 ]; do NAMES="$NAMES $1"; shift; done; break ;;
+ -*) echo "$0: unknown flag '$1'" >&2; exit 2 ;;
+ *) NAMES="$NAMES $1" ;;
+ esac
+ shift
+done
+
+case "$SUITE" in
+ m1pp|p1|scheme1|cc-util|cc-lex|cc-pp|cc-cg|cc-parse|cc-e2e) ;;
+ "") echo "$0: --suite required" >&2; exit 2 ;;
+ *) echo "$0: unknown suite '$SUITE'" >&2; exit 2 ;;
+esac
+
+discover() {
+ dir=$1; ext=$2
+ ls "$dir" 2>/dev/null \
+ | sed -n "s/^\\([^_][^.]*\\)\\.$ext\$/\\1/p" \
+ | sort -u
+}
+
+report() {
+ label=$1; status=$2
+ echo " $status $label"
+}
+
+show_diff() {
+ expected=$1; actual=$2
+ echo " --- expected ---"
+ printf '%s\n' "$expected" | sed 's/^/ /'
+ echo " --- actual ---"
+ printf '%s\n' "$actual" | sed 's/^/ /'
+}
+
+## --- m1pp suite ---------------------------------------------------------
+
+run_m1pp_suite() {
+ if [ -z "$NAMES" ]; then
+ m1=$(discover tests/M1pp M1)
+ m1pp=$(discover tests/M1pp M1pp)
+ NAMES=$(printf '%s\n%s\n' "$m1" "$m1pp" | sort -u | tr '\n' ' ')
+ fi
+ for name in $NAMES; do
+ expected=tests/M1pp/$name.expected
+ m1_src=tests/M1pp/$name.M1
+ m1pp_src=tests/M1pp/$name.M1pp
+
+ if [ ! -e "$expected" ]; then
+ echo " SKIP $name (no .expected)"
+ continue
+ fi
+ expected_content=$(cat "$expected")
+
+ label="[$ARCH] $name"
+ if [ -e "$m1pp_src" ]; then
+ outfile=build/$ARCH/m1pp-out/$name
+ mkdir -p "$(dirname "$outfile")"
+ rm -f "$outfile"
+ "./build/$ARCH/m1pp" "$m1pp_src" "$outfile" >/dev/null 2>&1 || true
+ if [ -e "$outfile" ]; then
+ actual=$(cat "$outfile")
+ else
+ actual=
+ fi
+ elif [ -e "$m1_src" ]; then
+ bin=build/$ARCH/m1pp-tests/$name
+ if ! sh scripts/boot-build-p1.sh "$m1_src" "$bin" >/dev/null 2>&1; then
+ report "$label" FAIL
+ sh scripts/boot-build-p1.sh "$m1_src" "$bin" 2>&1 \
+ | sed 's/^/ /' >&2 || true
+ continue
+ fi
+ actual=$("./$bin" 2>&1 || true)
+ else
+ echo " SKIP $name (no .M1 or .M1pp)"
+ continue
+ fi
+
+ if [ "$actual" = "$expected_content" ]; then
+ report "$label" PASS
+ else
+ report "$label" FAIL
+ show_diff "$expected_content" "$actual"
+ fi
+ done
+}
+
+## --- p1 suite -----------------------------------------------------------
+
+run_p1_suite() {
+ if [ -z "$NAMES" ]; then
+ NAMES=$(discover tests/P1 P1pp)
+ fi
+ for name in $NAMES; do
+ fixture=tests/P1/$name.P1pp
+ expected=tests/P1/$name.expected
+ if [ ! -e "$fixture" ]; then echo " SKIP $name (no .P1pp)"; continue; fi
+ if [ ! -e "$expected" ]; then echo " SKIP $name (no .expected)"; continue; fi
+ expected_content=$(cat "$expected")
+
+ label="[$ARCH] $name"
+ bin=build/$ARCH/p1-tests/$name
+ if ! sh scripts/boot-build-p1pp.sh "$fixture" "$bin" >/dev/null 2>&1; then
+ report "$label" FAIL
+ sh scripts/boot-build-p1pp.sh "$fixture" "$bin" 2>&1 \
+ | sed 's/^/ /' >&2 || true
+ continue
+ fi
+ actual=$("./$bin" 2>&1 || true)
+ if [ "$actual" = "$expected_content" ]; then
+ report "$label" PASS
+ else
+ report "$label" FAIL
+ show_diff "$expected_content" "$actual"
+ fi
+ done
+}
+
+## --- scheme1 suite ------------------------------------------------------
+
+run_scheme1_suite() {
+ if [ -z "$NAMES" ]; then
+ NAMES=$(discover tests/scheme1 scm)
+ fi
+ for name in $NAMES; do
+ fixture=tests/scheme1/$name.scm
+ expected_stdout_file=tests/scheme1/$name.expected
+ expected_exit_file=tests/scheme1/$name.expected-exit
+
+ if [ ! -e "$fixture" ]; then echo " SKIP $name (no .scm)"; continue; fi
+ if [ -e "$expected_stdout_file" ]; then
+ expected_stdout=$(cat "$expected_stdout_file")
+ else
+ expected_stdout=
+ fi
+ if [ -e "$expected_exit_file" ]; then
+ expected_exit=$(cat "$expected_exit_file")
+ else
+ expected_exit=0
+ fi
+
+ label="[$ARCH] $name"
+ bin=build/$ARCH/scheme1
+ if [ ! -x "$bin" ]; then
+ report "$label" FAIL
+ echo " (missing $bin -- run 'make scheme1 ARCH=$ARCH')" >&2
+ continue
+ fi
+
+ tmp_stdout=$(mktemp)
+ if sh scripts/boot-run-scheme1.sh "$fixture" >"$tmp_stdout" 2>&1; then
+ actual_exit=0
+ else
+ actual_exit=$?
+ fi
+ actual_stdout=$(cat "$tmp_stdout")
+ rm -f "$tmp_stdout"
+
+ if [ "$actual_stdout" = "$expected_stdout" ] \
+ && [ "$actual_exit" = "$expected_exit" ]; then
+ report "$label" PASS
+ else
+ report "$label" FAIL
+ if [ "$actual_stdout" != "$expected_stdout" ]; then
+ show_diff "$expected_stdout" "$actual_stdout"
+ fi
+ if [ "$actual_exit" != "$expected_exit" ]; then
+ echo " exit: expected $expected_exit, got $actual_exit"
+ fi
+ fi
+ done
+}
+
+## --- cc-* suites --------------------------------------------------------
+
+_cc_check() {
+ lbl=$1; exp_out=$2; exp_exit=$3; act_out=$4; act_exit=$5
+ if [ "$act_out" = "$exp_out" ] && [ "$act_exit" = "$exp_exit" ]; then
+ report "$lbl" PASS
+ else
+ report "$lbl" FAIL
+ if [ "$act_out" != "$exp_out" ]; then show_diff "$exp_out" "$act_out"; fi
+ if [ "$act_exit" != "$exp_exit" ]; then
+ echo " exit: expected $exp_exit, got $act_exit"
+ fi
+ fi
+}
+
+# _cc_unit_suite <suite-name> <expected-ext> <layer-list>
+_cc_unit_suite() {
+ suite=$1; ext=$2; layers=$3
+ [ -n "$NAMES" ] || NAMES=$(discover tests/$suite scm)
+ for name in $NAMES; do
+ fixture=tests/$suite/$name.scm
+ [ -e "$fixture" ] || { echo " SKIP $name (no .scm)"; continue; }
+ if [ -e "tests/$suite/$name.$ext" ]; then
+ expout=$(cat "tests/$suite/$name.$ext")
+ else
+ expout=
+ fi
+ if [ -e "tests/$suite/$name.expected-exit" ]; then
+ expexit=$(cat "tests/$suite/$name.expected-exit")
+ else
+ expexit=0
+ fi
+ tmp=$(mktemp)
+ # Wrap in `sh -c` so catm failure doesn't abort under set -e and
+ # scheme1 still runs (matches host runner's prior behavior).
+ if sh -c "
+ build/$ARCH/tools/catm /tmp/cc-test.scm $layers $fixture
+ exec build/$ARCH/scheme1 /tmp/cc-test.scm
+ " >"$tmp" 2>&1; then
+ act_exit=0
+ else
+ act_exit=$?
+ fi
+ act_out=$(cat "$tmp"); rm -f "$tmp"
+ _cc_check "[$ARCH] $suite/$name" "$expout" "$expexit" "$act_out" "$act_exit"
+ done
+}
+
+run_cc_util_suite() {
+ _cc_unit_suite cc-util expected "scheme1/prelude.scm cc/util.scm"
+}
+
+# _cc_pipeline_suite <suite-name> <expected-ext> <layers>
+_cc_pipeline_suite() {
+ suite=$1; ext=$2; layers=$3
+ [ -n "$NAMES" ] || NAMES=$(discover tests/$suite c)
+ for name in $NAMES; do
+ fixture=tests/$suite/$name.c
+ [ -e "$fixture" ] || { echo " SKIP $name (no .c)"; continue; }
+ if [ -e "tests/$suite/$name.$ext" ]; then
+ expout=$(grep -v '^;;' "tests/$suite/$name.$ext" || true)
+ else
+ expout=
+ fi
+ if [ -e "tests/$suite/$name.expected-exit" ]; then
+ expexit=$(cat "tests/$suite/$name.expected-exit")
+ else
+ expexit=0
+ fi
+ tmp=$(mktemp)
+ if sh -c "
+ build/$ARCH/tools/catm /tmp/cc-test.scm $layers
+ exec build/$ARCH/scheme1 /tmp/cc-test.scm $fixture
+ " >"$tmp" 2>&1; then
+ act_exit=0
+ else
+ act_exit=$?
+ fi
+ if [ "$expexit" != "0" ]; then
+ act_out=
+ else
+ act_out=$(cat "$tmp")
+ fi
+ rm -f "$tmp"
+ _cc_check "[$ARCH] $suite/$name" "$expout" "$expexit" "$act_out" "$act_exit"
+ done
+}
+
+run_cc_lex_suite() {
+ _cc_pipeline_suite cc-lex expected-toks \
+ "scheme1/prelude.scm cc/util.scm cc/data.scm cc/lex.scm tests/cc-lex/_run-lex.scm"
+}
+
+# Two passes: .c fixtures via the lex+pp pipeline; the lone .scm fixture
+# (22-initial-defines) covers the -D mechanism the driver doesn't expose,
+# so it stays on the unit-suite path. NAMES is restored between passes
+# because _cc_pipeline_suite may have populated it via discovery.
+run_cc_pp_suite() {
+ saved=$NAMES
+ _cc_pipeline_suite cc-pp expected-toks \
+ "scheme1/prelude.scm cc/util.scm cc/data.scm cc/lex.scm cc/pp.scm tests/cc-pp/_run-pp.scm"
+ NAMES=$saved
+ _cc_unit_suite cc-pp expected \
+ "scheme1/prelude.scm cc/util.scm cc/data.scm cc/pp.scm"
+}
+
+# _cc_runtime_suite <suite-name> <fixture-ext> <layers> [<fixture-as-arg?>]
+_cc_runtime_suite() {
+ suite=$1; fext=$2; layers=$3; arg_pass=${4:-0}
+ [ -n "$NAMES" ] || NAMES=$(discover tests/$suite "$fext")
+ for name in $NAMES; do
+ fixture=tests/$suite/$name.$fext
+ [ -e "$fixture" ] || { echo " SKIP $name (no .$fext)"; continue; }
+
+ if [ -e tests/$suite/$name.expected ]; then
+ expout=$(cat tests/$suite/$name.expected)
+ else
+ expout=
+ fi
+ if [ -e tests/$suite/$name.expected-exit ]; then
+ expexit=$(cat tests/$suite/$name.expected-exit)
+ else
+ expexit=0
+ fi
+
+ outdir=build/$ARCH/$suite/$name
+ p1pp=$outdir/$name.P1pp
+ elf=$outdir/$name
+ mkdir -p "$outdir"
+
+ if [ "$arg_pass" = "1" ]; then
+ cmd="
+ build/$ARCH/tools/catm /tmp/cc-test.scm $layers
+ exec build/$ARCH/scheme1 /tmp/cc-test.scm $fixture
+ "
+ else
+ cmd="
+ build/$ARCH/tools/catm /tmp/cc-test.scm $layers $fixture
+ exec build/$ARCH/scheme1 /tmp/cc-test.scm
+ "
+ fi
+ if ! sh -c "$cmd" >"$p1pp" 2>/dev/null; then
+ report "[$ARCH] $suite/$name" FAIL
+ echo " cg emission failed:"
+ sh -c "$cmd" 2>&1 >/dev/null | sed 's/^/ /' >&2 || true
+ continue
+ fi
+
+ if ! sh scripts/boot-build-p1pp.sh "$p1pp" "$elf" >/dev/null 2>&1; then
+ report "[$ARCH] $suite/$name" FAIL
+ echo " P1pp assemble failed:"
+ sh scripts/boot-build-p1pp.sh "$p1pp" "$elf" 2>&1 \
+ | sed 's/^/ /' >&2 || true
+ continue
+ fi
+
+ tmp=$(mktemp)
+ if "./$elf" >"$tmp" 2>&1; then
+ act_exit=0
+ else
+ act_exit=$?
+ fi
+ act_out=$(cat "$tmp"); rm -f "$tmp"
+ _cc_check "[$ARCH] $suite/$name" "$expout" "$expexit" "$act_out" "$act_exit"
+ done
+}
+
+run_cc_cg_suite() {
+ _cc_runtime_suite cc-cg scm \
+ "scheme1/prelude.scm cc/util.scm cc/data.scm cc/cg.scm" 0
+}
+
+run_cc_parse_suite() {
+ _cc_runtime_suite cc-parse c \
+ "scheme1/prelude.scm cc/util.scm cc/data.scm cc/lex.scm cc/pp.scm cc/cg.scm cc/parse.scm tests/cc-parse/_run-parse.scm" 1
+}
+
+run_cc_e2e_suite() {
+ [ -n "$NAMES" ] || NAMES=$(discover tests/cc-e2e c)
+ for name in $NAMES; do
+ src=tests/cc-e2e/$name.c
+ [ -e "$src" ] || { echo " SKIP $name (no .c)"; continue; }
+ if [ -e tests/cc-e2e/$name.expected ]; then
+ expout=$(cat tests/cc-e2e/$name.expected)
+ else
+ expout=
+ fi
+ if [ -e tests/cc-e2e/$name.expected-exit ]; then
+ expexit=$(cat tests/cc-e2e/$name.expected-exit)
+ else
+ expexit=0
+ fi
+ outdir=build/$ARCH/cc-e2e/$name
+ p1pp=$outdir/$name.P1pp
+ elf=$outdir/$name
+ mkdir -p "$outdir"
+ if ! build/$ARCH/scheme1 build/$ARCH/cc/cc.scm "$src" "$p1pp" \
+ >/dev/null 2>&1; then
+ report "[$ARCH] cc-e2e/$name" FAIL
+ echo " cc compile failed"
+ continue
+ fi
+ if ! sh scripts/boot-build-p1pp.sh "$p1pp" "$elf" >/dev/null 2>&1; then
+ report "[$ARCH] cc-e2e/$name" FAIL
+ sh scripts/boot-build-p1pp.sh "$p1pp" "$elf" 2>&1 \
+ | sed 's/^/ /' >&2 || true
+ continue
+ fi
+ tmp=$(mktemp)
+ if "./$elf" >"$tmp" 2>&1; then
+ act_exit=0
+ else
+ act_exit=$?
+ fi
+ act_out=$(cat "$tmp"); rm -f "$tmp"
+ _cc_check "[$ARCH] cc-e2e/$name" "$expout" "$expexit" "$act_out" "$act_exit"
+ done
+}
+
+case "$SUITE" in
+ m1pp) run_m1pp_suite ;;
+ p1) run_p1_suite ;;
+ scheme1) run_scheme1_suite ;;
+ cc-util) run_cc_util_suite ;;
+ cc-lex) run_cc_lex_suite ;;
+ cc-pp) run_cc_pp_suite ;;
+ cc-cg) run_cc_cg_suite ;;
+ cc-parse) run_cc_parse_suite ;;
+ cc-e2e) run_cc_e2e_suite ;;
+esac
diff --git a/scripts/run-tests.sh b/scripts/run-tests.sh
@@ -1,31 +1,35 @@
#!/bin/sh
-## run-tests.sh — unified test runner for the m1pp, p1, and scheme1 suites.
+## run-tests.sh — host-side dispatcher for the unified test runner.
##
-## Each suite is a directory of `<name>.<ext>` fixtures with sibling
-## `<name>.expected` files. The runner builds each fixture, runs it inside
-## the matching busybox container (`boot2-busybox:<arch>`), and diffs
-## actual against expected output. Filenames starting with `_` are skipped
-## (parked, ad-hoc debugging).
+## All build/run/diff work happens inside the container via
+## scripts/boot-run-tests.sh: this script just starts one podman
+## process per requested arch and aggregates the per-arch
+## PASS/FAIL totals. Prior versions of this runner re-entered the
+## container per fixture (and per build/run within a fixture); now
+## a whole arch's suite is one podman invocation.
+##
+## The one bit of work that stays on the host is the m1pp lint
+## preflight: scripts/lint.sh runs python, which the busybox
+## container doesn't carry. Names that fail lint are reported here
+## (FAIL + diagnostic) and excluded from the in-container batch.
##
## Suites:
-## m1pp tests/M1pp/<name>.M1 — P1 program built via build-p1.sh
-## for each requested arch, run in
-## container, stdout diffed.
-## tests/M1pp/<name>.M1pp — m1pp expander parity test: per-arch
-## m1pp binary consumes the fixture
-## and writes <out>; diffed.
-## p1 tests/P1/<name>.P1pp — P1pp program built via build-p1pp.sh
-## for each requested arch, run in
-## container, stdout diffed.
-## scheme1 tests/scheme1/<name>.scm — Scheme source run by the per-arch
-## scheme1 binary. stdout diffed
-## against <name>.expected (default
-## empty); exit code diffed against
-## <name>.expected-exit (default 0).
+## m1pp tests/M1pp/<name>.M1 — built via boot-build-p1.sh
+## and run; stdout diffed.
+## tests/M1pp/<name>.M1pp — m1pp expander parity test.
+## p1 tests/P1/<name>.P1pp — built via boot-build-p1pp.sh
+## and run; stdout diffed.
+## scheme1 tests/scheme1/<name>.scm — run by per-arch scheme1.
+## cc-util tests/cc-util/<name>.scm — scheme1 prelude+util byte-diff.
+## cc-lex tests/cc-lex/<name>.c — lex pipeline byte-diff.
+## cc-pp tests/cc-pp/<name>.c — pp pipeline byte-diff (+ .scm).
+## cc-cg tests/cc-cg/<name>.scm — cg emit -> P1pp -> ELF -> run.
+## cc-parse tests/cc-parse/<name>.c — full pipeline -> ELF -> run.
+## cc-e2e tests/cc-e2e/<name>.c — cc -> P1pp -> ELF -> run.
##
## All three arches by default; --arch restricts to one.
##
-## Usage: scripts/run-tests.sh --suite <m1pp|p1|scheme1> [--arch ARCH] [name ...]
+## Usage: scripts/run-tests.sh --suite <suite> [--arch ARCH] [name ...]
set -eu
@@ -73,504 +77,73 @@ run_in_container() {
"boot2-busybox:$arch" "$@"
}
-discover() {
- dir=$1; ext=$2
- ls "$dir" 2>/dev/null \
- | sed -n "s/^\\([^_][^.]*\\)\\.$ext\$/\\1/p" \
- | sort -u
-}
+if [ -z "$ARCH" ]; then
+ ARCHES="aarch64 amd64 riscv64"
+else
+ ARCHES=$ARCH
+fi
PASS=0
FAIL=0
-report() {
- label=$1; status=$2
- case "$status" in
- PASS) PASS=$((PASS + 1));;
- FAIL) FAIL=$((FAIL + 1));;
- esac
- echo " $status $label"
-}
-
-show_diff() {
- expected=$1; actual=$2
- echo " --- expected ---"
- printf '%s\n' "$expected" | sed 's/^/ /'
- echo " --- actual ---"
- printf '%s\n' "$actual" | sed 's/^/ /'
-}
-
-## --- m1pp suite ---------------------------------------------------------
-##
-## Caller (Make) ensures build/<arch>/m1pp expanders, build/<arch>/tools/M0,
-## the per-arch image, and P1/P1-<arch>.M1 tables exist before this runs.
-## .M1 fixtures are still built per-fixture inline via boot-build-p1.sh
-## (per-fixture build is test work, not infrastructure).
-
-run_m1pp_suite() {
- if [ -z "$ARCH" ]; then
- ARCHES="aarch64 amd64 riscv64"
- else
- ARCHES=$ARCH
- fi
- if [ -z "$NAMES" ]; then
- m1=$(discover tests/M1pp M1)
- m1pp=$(discover tests/M1pp M1pp)
- NAMES=$(printf '%s\n%s\n' "$m1" "$m1pp" | sort -u | tr '\n' ' ')
- fi
- for name in $NAMES; do
- expected=tests/M1pp/$name.expected
- m1_src=tests/M1pp/$name.M1
- m1pp_src=tests/M1pp/$name.M1pp
-
- if [ ! -e "$expected" ]; then
- echo " SKIP $name (no .expected)"
- continue
- fi
- expected_content=$(cat "$expected")
-
- for arch in $ARCHES; do
- label="[$arch] $name"
- if [ -e "$m1pp_src" ]; then
- outfile=build/$arch/m1pp-out/$name
- mkdir -p "$(dirname "$outfile")"
- rm -f "$outfile"
- run_in_container "$arch" "./build/$arch/m1pp" "$m1pp_src" "$outfile" \
- >/dev/null 2>&1 || true
- actual=$([ -e "$outfile" ] && cat "$outfile" || echo "")
- elif [ -e "$m1_src" ]; then
- bin=build/$arch/m1pp-tests/$name
- if ! ARCH=$arch sh scripts/lint.sh "$m1_src" >/dev/null 2>&1 \
- || ! run_in_container "$arch" sh scripts/boot-build-p1.sh "$m1_src" "$bin" \
- >/dev/null 2>&1; then
- report "$label" FAIL
- ARCH=$arch sh scripts/lint.sh "$m1_src" 2>&1 | sed 's/^/ /' >&2 || true
- run_in_container "$arch" sh scripts/boot-build-p1.sh "$m1_src" "$bin" \
- 2>&1 | sed 's/^/ /' >&2 || true
- continue
- fi
- actual=$(run_in_container "$arch" "./$bin" 2>&1 || true)
- else
- echo " SKIP $name (no .M1 or .M1pp)"
- break
- fi
-
- if [ "$actual" = "$expected_content" ]; then
- report "$label" PASS
- else
- report "$label" FAIL
- show_diff "$expected_content" "$actual"
- fi
- done
- done
-}
-
-## --- p1 suite -----------------------------------------------------------
-
-run_p1_suite() {
- if [ -z "$ARCH" ]; then
- ARCHES="aarch64 amd64 riscv64"
- else
- ARCHES=$ARCH
- fi
+# m1pp lint preflight: lint.sh uses python (host-only). Discover the
+# fixture set if --names was empty, lint each .M1 fixture, emit a
+# host-side FAIL + diagnostic for any miss, write the kept name list
+# to $keep_file. FAIL line goes to stdout to interleave with the
+# container's PASS/FAIL output; the FAIL counter updates in-scope.
+m1pp_preflight() {
+ arch=$1; keep_file=$2
+ : > "$keep_file"
if [ -z "$NAMES" ]; then
- NAMES=$(discover tests/P1 P1pp)
- fi
- for name in $NAMES; do
- fixture=tests/P1/$name.P1pp
- expected=tests/P1/$name.expected
- if [ ! -e "$fixture" ]; then
- echo " SKIP $name (no .P1pp)"; continue
- fi
- if [ ! -e "$expected" ]; then
- echo " SKIP $name (no .expected)"; continue
- fi
- expected_content=$(cat "$expected")
-
- for arch in $ARCHES; do
- label="[$arch] $name"
- bin=build/$arch/p1-tests/$name
- if ! run_in_container "$arch" sh scripts/boot-build-p1pp.sh "$fixture" "$bin" \
- >/dev/null 2>&1; then
- report "$label" FAIL
- run_in_container "$arch" sh scripts/boot-build-p1pp.sh "$fixture" "$bin" \
- 2>&1 | sed 's/^/ /' >&2 || true
- continue
- fi
- actual=$(run_in_container "$arch" "./$bin" 2>&1 || true)
- if [ "$actual" = "$expected_content" ]; then
- report "$label" PASS
- else
- report "$label" FAIL
- show_diff "$expected_content" "$actual"
- fi
- done
- done
-}
-
-## --- scheme1 suite ------------------------------------------------------
-##
-## Caller (Make) ensures build/<arch>/scheme1 already exists. The runner
-## invokes scripts/boot-run-scheme1.sh inside the container, which catm's
-## scheme1/prelude.scm in front of the .scm fixture before handing the
-## combined file to the binary. stdout is diffed against <name>.expected
-## (defaults to empty if absent); the exit status is diffed against
-## <name>.expected-exit (defaults to 0 if absent).
-
-run_scheme1_suite() {
- if [ -z "$ARCH" ]; then
- ARCHES="aarch64 amd64 riscv64"
+ m1=$(ls tests/M1pp 2>/dev/null \
+ | sed -n 's/^\([^_][^.]*\)\.M1$/\1/p')
+ m1pp_n=$(ls tests/M1pp 2>/dev/null \
+ | sed -n 's/^\([^_][^.]*\)\.M1pp$/\1/p')
+ all=$(printf '%s\n%s\n' "$m1" "$m1pp_n" | sort -u | tr '\n' ' ')
else
- ARCHES=$ARCH
+ all=$NAMES
fi
- if [ -z "$NAMES" ]; then
- NAMES=$(discover tests/scheme1 scm)
- fi
- for name in $NAMES; do
- fixture=tests/scheme1/$name.scm
- expected_stdout_file=tests/scheme1/$name.expected
- expected_exit_file=tests/scheme1/$name.expected-exit
-
- if [ ! -e "$fixture" ]; then
- echo " SKIP $name (no .scm)"; continue
- fi
- if [ -e "$expected_stdout_file" ]; then
- expected_stdout=$(cat "$expected_stdout_file")
- else
- expected_stdout=
- fi
- if [ -e "$expected_exit_file" ]; then
- expected_exit=$(cat "$expected_exit_file")
+ for name in $all; do
+ m1_src=tests/M1pp/$name.M1
+ if [ -e "$m1_src" ] \
+ && ! ARCH=$arch sh scripts/lint.sh "$m1_src" >/dev/null 2>&1; then
+ echo " FAIL [$arch] $name"
+ ARCH=$arch sh scripts/lint.sh "$m1_src" 2>&1 \
+ | sed 's/^/ /' >&2 || true
+ FAIL=$((FAIL + 1))
else
- expected_exit=0
+ printf '%s ' "$name" >> "$keep_file"
fi
-
- for arch in $ARCHES; do
- label="[$arch] $name"
- bin=build/$arch/scheme1
- if [ ! -x "$bin" ]; then
- report "$label" FAIL
- echo " (missing $bin -- run 'make scheme1 ARCH=$arch')" >&2
- continue
- fi
-
- tmp_stdout=$(mktemp)
- if run_in_container "$arch" sh scripts/boot-run-scheme1.sh "$fixture" \
- >"$tmp_stdout" 2>&1; then
- actual_exit=0
- else
- actual_exit=$?
- fi
- actual_stdout=$(cat "$tmp_stdout")
- rm -f "$tmp_stdout"
-
- if [ "$actual_stdout" = "$expected_stdout" ] \
- && [ "$actual_exit" = "$expected_exit" ]; then
- report "$label" PASS
- else
- report "$label" FAIL
- if [ "$actual_stdout" != "$expected_stdout" ]; then
- show_diff "$expected_stdout" "$actual_stdout"
- fi
- if [ "$actual_exit" != "$expected_exit" ]; then
- echo " exit: expected $expected_exit, got $actual_exit"
- fi
- fi
- done
done
}
-## --- cc-* suites --------------------------------------------------------
-##
-## Six suites, all sharing the same shape: catm a list of layer files
-## into a per-fixture combined .scm, run scheme1 on the result, diff
-## merged stdout+stderr against an expected-output file, diff exit
-## status against .expected-exit (default 0).
-##
-## Caller (Make) ensures build/<arch>/scheme1 and build/<arch>/cc/cc.scm
-## already exist. The cc-e2e suite additionally requires the P1pp
-## toolchain (m1pp + tools), which the Make deps already cover.
-##
-## Filenames starting with `_` are skipped by `discover()` — that's how
-## suite-internal driver files (_run-lex.scm, _run-pp.scm,
-## _run-parse.scm) avoid being picked up as fixtures.
-
-# _cc_check <label> <expected-stdout> <expected-exit> <actual-stdout> <actual-exit>
-_cc_check() {
- lbl=$1; exp_out=$2; exp_exit=$3; act_out=$4; act_exit=$5
- if [ "$act_out" = "$exp_out" ] && [ "$act_exit" = "$exp_exit" ]; then
- report "$lbl" PASS
- else
- report "$lbl" FAIL
- if [ "$act_out" != "$exp_out" ]; then show_diff "$exp_out" "$act_out"; fi
- if [ "$act_exit" != "$exp_exit" ]; then
- echo " exit: expected $exp_exit, got $act_exit"
+for arch in $ARCHES; do
+ if [ "$SUITE" = "m1pp" ]; then
+ keep_file=$(mktemp)
+ m1pp_preflight "$arch" "$keep_file"
+ names=$(cat "$keep_file")
+ rm -f "$keep_file"
+ # Skip the container call only when the user gave names AND
+ # all of them failed lint. With no names, an empty kept set
+ # would mean nothing to run anyway.
+ names_trimmed=$(echo "$names" | tr -d ' \t\n')
+ if [ -z "$names_trimmed" ]; then
+ continue
fi
+ else
+ names=$NAMES
fi
-}
-
-# _cc_arches: aarch64 amd64 riscv64 by default; honors --arch=...
-_cc_arches() {
- if [ -z "$ARCH" ]; then echo "aarch64 amd64 riscv64"
- else echo "$ARCH"; fi
-}
-
-# _cc_unit_suite <suite-name> <expected-ext> <layer-list>
-# Generic byte-diff unit-suite runner for cc-util and cc-pp's .scm
-# form. Each fixture is `tests/<suite>/<name>.scm`; expected stdout
-# in `tests/<suite>/<name>.<expected-ext>` (default empty); exit in
-# `tests/<suite>/<name>.expected-exit` (default 0).
-# cc-cg and cc-parse use _cc_runtime_suite instead — they
-# compile-and-run, asserting runtime behavior.
-_cc_unit_suite() {
- suite=$1; ext=$2; layers=$3
- [ -n "$NAMES" ] || NAMES=$(discover tests/$suite scm)
- for arch in $(_cc_arches); do
- for name in $NAMES; do
- fixture=tests/$suite/$name.scm
- [ -e "$fixture" ] || { echo " SKIP $name (no .scm)"; continue; }
- if [ -e "tests/$suite/$name.$ext" ]; then
- expout=$(cat "tests/$suite/$name.$ext")
- else
- expout=
- fi
- if [ -e "tests/$suite/$name.expected-exit" ]; then
- expexit=$(cat "tests/$suite/$name.expected-exit")
- else
- expexit=0
- fi
- tmp=$(mktemp)
- if run_in_container "$arch" sh -c "
- build/$arch/tools/catm /tmp/cc-test.scm $layers $fixture
- exec build/$arch/scheme1 /tmp/cc-test.scm
- " >"$tmp" 2>&1; then
- act_exit=0
- else
- act_exit=$?
- fi
- act_out=$(cat "$tmp"); rm -f "$tmp"
- _cc_check "[$arch] $suite/$name" "$expout" "$expexit" "$act_out" "$act_exit"
- done
- done
-}
-
-# cc-util: scheme1 prelude + util only.
-run_cc_util_suite() {
- _cc_unit_suite cc-util expected "scheme1/prelude.scm cc/util.scm"
-}
-
-# _cc_pipeline_suite <suite-name> <expected-ext> <layers>
-# Byte-diff pipeline runner for cc-lex and cc-pp's .c form.
-# Each fixture is tests/<suite>/<name>.c. `<layers>` ends with the
-# suite-specific _run-<phase>.scm driver, which the test runner passes
-# the fixture path to as argv[2]. Expected stdout in
-# tests/<suite>/<name>.<expected-ext> ("|| true" filters the negative-
-# fixture `;;` notes); exit in <name>.expected-exit (default 0).
-# Negative fixtures (expected-exit != 0) only check the exit code.
-_cc_pipeline_suite() {
- suite=$1; ext=$2; layers=$3
- [ -n "$NAMES" ] || NAMES=$(discover tests/$suite c)
- for arch in $(_cc_arches); do
- for name in $NAMES; do
- fixture=tests/$suite/$name.c
- [ -e "$fixture" ] || { echo " SKIP $name (no .c)"; continue; }
- if [ -e "tests/$suite/$name.$ext" ]; then
- expout=$(grep -v '^;;' "tests/$suite/$name.$ext" || true)
- else
- expout=
- fi
- if [ -e "tests/$suite/$name.expected-exit" ]; then
- expexit=$(cat "tests/$suite/$name.expected-exit")
- else
- expexit=0
- fi
- tmp=$(mktemp)
- if run_in_container "$arch" sh -c "
- build/$arch/tools/catm /tmp/cc-test.scm $layers
- exec build/$arch/scheme1 /tmp/cc-test.scm $fixture
- " >"$tmp" 2>&1; then
- act_exit=0
- else
- act_exit=$?
- fi
- if [ "$expexit" != "0" ]; then
- act_out=
- else
- act_out=$(cat "$tmp")
- fi
- rm -f "$tmp"
- _cc_check "[$arch] $suite/$name" "$expout" "$expexit" "$act_out" "$act_exit"
- done
- done
-}
-
-# cc-lex: prelude + util + data + lex + run-lex driver.
-run_cc_lex_suite() {
- _cc_pipeline_suite cc-lex expected-toks \
- "scheme1/prelude.scm cc/util.scm cc/data.scm cc/lex.scm tests/cc-lex/_run-lex.scm"
-}
-
-# cc-pp: prelude + util + data + lex + pp + run-pp driver.
-# Two passes: .c fixtures via the pipeline (real lex+pp); the lone .scm
-# fixture (22-initial-defines) covers the -D mechanism the driver doesn't
-# expose, so it stays on the unit-suite path.
-run_cc_pp_suite() {
- saved=$NAMES
- NAMES=$saved
- _cc_pipeline_suite cc-pp expected-toks \
- "scheme1/prelude.scm cc/util.scm cc/data.scm cc/lex.scm cc/pp.scm tests/cc-pp/_run-pp.scm"
- NAMES=$saved
- _cc_unit_suite cc-pp expected \
- "scheme1/prelude.scm cc/util.scm cc/data.scm cc/pp.scm"
-}
-
-# _cc_runtime_suite <suite-name> <fixture-ext> <layers> [<fixture-as-arg?>]
-# Runtime-validating runner for cc-cg and cc-parse.
-#
-# Each fixture is `tests/<suite>/<name>.<fixture-ext>`. The runner:
-# 1. catms <layers> + (cg-driver) into a combined .scm in the
-# container's tmpfs.
-# 2. Runs scheme1 on it; captures stdout to a per-fixture .P1pp file.
-# For cc-parse the .c fixture is passed as argv[2] to the driver
-# (fixture-as-arg=1); for cc-cg the .scm fixture *is* the last
-# layer (fixture-as-arg=0).
-# 3. Assembles that .P1pp through boot-build-p1pp.sh into a native
-# ELF.
-# 4. Runs the ELF in the container; diffs exit code against
-# <name>.expected-exit (default 0) and stdout against
-# <name>.expected (default empty).
-#
-# Any pipeline failure (compile, assemble, run) is a FAIL with the
-# first failing stage's stderr surfaced.
-_cc_runtime_suite() {
- suite=$1; fext=$2; layers=$3; arg_pass=${4:-0}
- [ -n "$NAMES" ] || NAMES=$(discover tests/$suite "$fext")
- for arch in $(_cc_arches); do
- for name in $NAMES; do
- fixture=tests/$suite/$name.$fext
- [ -e "$fixture" ] || { echo " SKIP $name (no .$fext)"; continue; }
-
- expout=$([ -e tests/$suite/$name.expected ] \
- && cat tests/$suite/$name.expected || echo "")
- expexit=$([ -e tests/$suite/$name.expected-exit ] \
- && cat tests/$suite/$name.expected-exit || echo 0)
-
- outdir=build/$arch/$suite/$name
- p1pp=$outdir/$name.P1pp
- elf=$outdir/$name
- mkdir -p "$outdir"
-
- # Stage 1+2: catm + scheme1 → P1pp file.
- if [ "$arg_pass" = "1" ]; then
- # cc-parse: layers end with the driver; .c fixture is argv[2].
- cmd="
- build/$arch/tools/catm /tmp/cc-test.scm $layers
- exec build/$arch/scheme1 /tmp/cc-test.scm $fixture
- "
- else
- # cc-cg: the .scm fixture itself is the last layer.
- cmd="
- build/$arch/tools/catm /tmp/cc-test.scm $layers $fixture
- exec build/$arch/scheme1 /tmp/cc-test.scm
- "
- fi
- if ! run_in_container "$arch" sh -c "$cmd" >"$p1pp" 2>/dev/null; then
- report "[$arch] $suite/$name" FAIL
- echo " cg emission failed:"
- run_in_container "$arch" sh -c "$cmd" 2>&1 >/dev/null \
- | sed 's/^/ /' >&2 || true
- continue
- fi
-
- # Stage 3: P1pp → ELF.
- if ! run_in_container "$arch" sh scripts/boot-build-p1pp.sh \
- "$p1pp" "$elf" >/dev/null 2>&1; then
- report "[$arch] $suite/$name" FAIL
- echo " P1pp assemble failed:"
- run_in_container "$arch" sh scripts/boot-build-p1pp.sh \
- "$p1pp" "$elf" 2>&1 | sed 's/^/ /' >&2 || true
- continue
- fi
-
- # Stage 4: run + diff.
- tmp=$(mktemp)
- if run_in_container "$arch" "./$elf" >"$tmp" 2>&1; then
- act_exit=0
- else
- act_exit=$?
- fi
- act_out=$(cat "$tmp"); rm -f "$tmp"
- _cc_check "[$arch] $suite/$name" "$expout" "$expexit" "$act_out" "$act_exit"
- done
- done
-}
-# cc-cg: prelude + util + data + cg + fixture; emit P1pp; build; run.
-run_cc_cg_suite() {
- _cc_runtime_suite cc-cg scm \
- "scheme1/prelude.scm cc/util.scm cc/data.scm cc/cg.scm" 0
-}
-
-# cc-parse: full pipeline through real cg + driver; emit P1pp; build; run.
-run_cc_parse_suite() {
- _cc_runtime_suite cc-parse c \
- "scheme1/prelude.scm cc/util.scm cc/data.scm cc/lex.scm cc/pp.scm cc/cg.scm cc/parse.scm tests/cc-parse/_run-parse.scm" 1
-}
-
-# cc-e2e: compile a .c through cc, assemble to ELF, run.
-# Fixture: <name>.c; expected stdout in <name>.expected (default empty);
-# exit in <name>.expected-exit (default 0).
-run_cc_e2e_suite() {
- [ -n "$NAMES" ] || NAMES=$(discover tests/cc-e2e c)
- for arch in $(_cc_arches); do
- for name in $NAMES; do
- src=tests/cc-e2e/$name.c
- [ -e "$src" ] || { echo " SKIP $name (no .c)"; continue; }
- expout=$([ -e tests/cc-e2e/$name.expected ] \
- && cat tests/cc-e2e/$name.expected || echo "")
- expexit=$([ -e tests/cc-e2e/$name.expected-exit ] \
- && cat tests/cc-e2e/$name.expected-exit || echo 0)
- outdir=build/$arch/cc-e2e/$name
- p1pp=$outdir/$name.P1pp
- elf=$outdir/$name
- mkdir -p "$outdir"
- if ! run_in_container "$arch" sh -c \
- "build/$arch/scheme1 build/$arch/cc/cc.scm $src $p1pp" \
- >/dev/null 2>&1; then
- report "[$arch] cc-e2e/$name" FAIL
- echo " cc compile failed"
- continue
- fi
- if ! run_in_container "$arch" sh scripts/boot-build-p1pp.sh \
- "$p1pp" "$elf" >/dev/null 2>&1; then
- report "[$arch] cc-e2e/$name" FAIL
- run_in_container "$arch" sh scripts/boot-build-p1pp.sh \
- "$p1pp" "$elf" 2>&1 | sed 's/^/ /' >&2 || true
- continue
- fi
- tmp=$(mktemp)
- if run_in_container "$arch" "./$elf" >"$tmp" 2>&1; then
- act_exit=0
- else
- act_exit=$?
- fi
- act_out=$(cat "$tmp"); rm -f "$tmp"
- _cc_check "[$arch] cc-e2e/$name" "$expout" "$expexit" "$act_out" "$act_exit"
- done
- done
-}
-
-case "$SUITE" in
- m1pp) run_m1pp_suite ;;
- p1) run_p1_suite ;;
- scheme1) run_scheme1_suite ;;
- cc-util) run_cc_util_suite ;;
- cc-lex) run_cc_lex_suite ;;
- cc-pp) run_cc_pp_suite ;;
- cc-cg) run_cc_cg_suite ;;
- cc-parse) run_cc_parse_suite ;;
- cc-e2e) run_cc_e2e_suite ;;
-esac
+ out=$(mktemp)
+ # shellcheck disable=SC2086 # $names is intentionally word-split.
+ run_in_container "$arch" sh scripts/boot-run-tests.sh \
+ --suite="$SUITE" $names | tee "$out"
+ p=$(grep -c '^ PASS ' "$out" 2>/dev/null || true)
+ f=$(grep -c '^ FAIL ' "$out" 2>/dev/null || true)
+ PASS=$((PASS + ${p:-0}))
+ FAIL=$((FAIL + ${f:-0}))
+ rm -f "$out"
+done
echo "$PASS passed, $FAIL failed"
[ "$FAIL" -eq 0 ]