Files
improvise/scripts/checkers-test.sh
T
Edward Langley 9a0d1ba77a Merge gh/main (220abc61) into tooling/dak5-ci-suites
#297's rulings kept: no bash floor, no find_modern_bash probe in
checkers.sh (it runs suites under the dev shell's $BASH), and no
toolchain-policing step in the build job. This branch's CI table kept:
ci_suites, --ci, --would-run, and the --ci steps in ci.yml (build, js, kb),
which replace gh/main's hand-listed kb, --self-test and remaining-checkers
steps. The kb row already runs every suite those steps named.
check-ci-graph passes on the merged ci.yml.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
2026-09-24 02:00:44 -07:00

184 lines
7.0 KiB
Bash
Executable File

#!/usr/bin/env bash
# check_the_checker for scripts/checkers.sh. Every case injects a fault that
# a bare `./scripts/X-test.sh` cannot see, and asserts the runner does.
# See context/test-evidence/tooling.md.
set -uo pipefail
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
RUNNER="$here/checkers.sh"
work="$(mktemp -d)"
trap 'rm -rf "$work"' EXIT
pass=0; fail=0
ok() { printf ' ok %s\n' "$1"; pass=$((pass+1)); }
bad() { printf ' FAIL %s (%s)\n' "$1" "$2"; fail=$((fail+1)); }
# A sandbox is a copy of the runner beside fixture suites, so `$HERE` inside it
# is the sandbox: the shebang scan and the suite list are both the fixtures'.
sandbox() {
local d="$work/$1"
mkdir -p "$d"
cp "$RUNNER" "$d/checkers.sh"
chmod +x "$d/checkers.sh"
printf '%s' "$d"
}
# A suite is only discovered when the thing it checks is present, so every
# fixture gets a stub subject beside it.
suite() {
local d="$1" name="$2"
cat > "$d/$name-test.sh"
chmod +x "$d/$name-test.sh"
printf '#!/usr/bin/env bash\nexit 0\n' > "$d/$name.sh"
chmod +x "$d/$name.sh"
}
# $1 name, $2 expected exit, rest: the command. Nothing follows the runner in
# its own pipeline, which is the rule its own header states.
expect() {
local what="$1" want="$2"; shift 2
"$@" > "$work/out" 2>&1
local got=$?
[ "$got" = "$want" ] && ok "$what" || bad "$what" "exit $got, wanted $want"
}
# ---------------------------------------------------------------------------
# 1. The fault this runner exists for. `nix develop` exports `name`; a suite
# that reads an unset variable under `set -u` is therefore SILENT in the dev
# shell and fatal in CI. That is improvise-f9qf, reproduced exactly.
d="$(sandbox setu)"
suite "$d" masked <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
echo "read: $name"
EOF
export name=nix-shell-env
expect "a suite reading an inherited dev-shell variable fails under the runner" \
1 "$d/checkers.sh" masked
# The same suite run the way the old gate ran it PASSES, which is what makes
# the case above discriminating rather than a test of `set -u`.
"$d/masked-test.sh" > "$work/out" 2>&1
direct=$?
[ "$direct" = 0 ] \
&& ok "and passes when run directly with that variable exported" \
|| bad "and passes when run directly with that variable exported" \
"exit $direct, wanted 0"
unset name
# ---------------------------------------------------------------------------
# 2. The shebang rule. `#!/bin/sh` is dash in CI and bash-in-sh-mode on macOS.
d="$(sandbox shebang)"
suite "$d" fine <<'EOF'
#!/usr/bin/env bash
exit 0
EOF
printf '#!/bin/sh\nexit 0\n' > "$d/legacy.sh"
chmod +x "$d/legacy.sh"
expect "a #!/bin/sh script in the scripts directory is refused" 1 \
"$d/checkers.sh" fine
grep -q 'wanted #!/usr/bin/env bash' "$work/out" \
&& ok "and the refusal names the shebang it wanted" \
|| bad "and the refusal names the shebang it wanted" "no such message"
# Without the injected file the same sandbox passes, so the case above is not
# passing on some other complaint.
rm -f "$d/legacy.sh"
expect "and the same sandbox passes once it is removed" 0 "$d/checkers.sh" fine
# ---------------------------------------------------------------------------
# 3. A failing suite is reported, and a passing one is not.
d="$(sandbox rc)"
suite "$d" red <<'EOF'
#!/usr/bin/env bash
exit 1
EOF
expect "a failing suite fails the runner" 1 "$d/checkers.sh" red
d="$(sandbox green)"
suite "$d" blue <<'EOF'
#!/usr/bin/env bash
exit 0
EOF
expect "a passing suite passes the runner" 0 "$d/checkers.sh" blue
# ---------------------------------------------------------------------------
# 4. A named suite that does not exist is a usage error, not a silent no-op.
d="$(sandbox missing)"
suite "$d" present <<'EOF'
#!/usr/bin/env bash
exit 0
EOF
expect "an unknown suite name is exit 4" 4 "$d/checkers.sh" absent
# ---------------------------------------------------------------------------
# 5. --list names every suite in the directory and nothing else.
d="$(sandbox listing)"
suite "$d" alpha <<'EOF'
#!/usr/bin/env bash
exit 0
EOF
suite "$d" beta <<'EOF'
#!/usr/bin/env bash
exit 0
EOF
"$d/checkers.sh" --list > "$work/out" 2>&1
listed="$(tr '\n' ' ' < "$work/out")"
[ "$listed" = "alpha beta " ] && ok "--list names exactly the suites present" \
|| bad "--list names exactly the suites present" "got [$listed]"
# A test RUNNER also ends in -test.sh. It is not a suite, and running it as
# one is how `scripts/node-test.sh` printed its usage inside a green gate.
printf '#!/usr/bin/env bash\nexit 4\n' > "$d/gamma-test.sh"
chmod +x "$d/gamma-test.sh"
"$d/checkers.sh" --list > "$work/out" 2>&1
listed="$(tr '\n' ' ' < "$work/out")"
[ "$listed" = "alpha beta " ] \
&& ok "an X-test.sh with no X beside it is not a suite" \
|| bad "an X-test.sh with no X beside it is not a suite" "got [$listed]"
# ---------------------------------------------------------------------------
# 8. --would-run reports what the rest of the arguments run, and runs nothing.
# check-ci-graph.py asks it, so each argument rule below is answered HERE.
d="$(sandbox wouldrun)"
for n in alpha beta; do
suite "$d" "$n" <<EOF
#!/usr/bin/env bash
touch "$work/ran-$n"
EOF
done
rm -f "$work"/ran-*
"$d/checkers.sh" --would-run > "$work/out" 2>&1
got="$(tr '\n' ' ' < "$work/out")"
[ "$got" = "would-run: alpha would-run: beta " ] && [ ! -e "$work/ran-alpha" ] \
&& ok "--would-run with no names reports every suite and runs none" \
|| bad "--would-run with no names reports every suite and runs none" "got [$got]"
"$d/checkers.sh" --would-run --self-test alpha > "$work/out" 2>&1
got="$(tr '\n' ' ' < "$work/out")"
[ "$got" = "missing: checkers " ] \
&& ok "--would-run --self-test reports checkers and discards the names" \
|| bad "--would-run --self-test reports checkers and discards the names" "got [$got]"
"$d/checkers.sh" --would-run --list alpha > "$work/out" 2>&1
grep -q '^would-run:' "$work/out" \
&& bad "--would-run --list reports nothing as run" "$(tr '\n' ' ' < "$work/out")" \
|| ok "--would-run --list reports nothing as run"
expect "--would-run anywhere but first is a usage error" 4 "$d/checkers.sh" alpha --would-run
[ ! -e "$work/ran-alpha" ] && ok "and it runs nothing" \
|| bad "and it runs nothing" "alpha ran"
# ---------------------------------------------------------------------------
# 9. --ci <job> runs that job's row of the CI table and nothing else. The
# sandbox has none of the real suites, so a row reads back as `missing:`.
"$d/checkers.sh" --would-run --ci js > "$work/out" 2>&1
got="$(tr '\n' ' ' < "$work/out")"
[ "$got" = "missing: node-test " ] && ok "--ci reads that job's row of the table" \
|| bad "--ci reads that job's row of the table" "got [$got]"
expect "--ci with a job the table does not have is exit 4" 4 "$d/checkers.sh" --would-run --ci nope
expect "--ci with nothing after it is exit 4" 4 "$d/checkers.sh" --would-run --ci
expect "--ci beside a suite name is exit 4, not a union" 4 \
"$d/checkers.sh" --would-run --ci js alpha
expect "--ci beside --self-test is exit 4" 4 "$d/checkers.sh" --would-run --self-test --ci js
printf '\ncheckers-test: %d passed, %d failed\n' "$pass" "$fail"
[ "$fail" -eq 0 ]