sots-re/tools/gate.sh
alex 1df4ffb463 lane DW: the writer is byte-exact on all 43 saves; make the gate honest
Track 0 items 0a and 0b.  The finding has the full account; the two things
that matter for anyone reading the gate afterwards:

gate.sh was taking TAIL's exit code from `ctest ... | tail -15`, so it printed
"GATE ok host-ctest" directly above "2 tests failed out of 59".  The same
masking was on the host build and on the shim cross-build.  All three now
capture the real status and trim the output afterwards.

The corpus-skip check counted every "unset, skipped" line, two of which are not
corpus tests, so it could never reach zero.  It is now scoped to the tests gated
on the corpus, SOTS_DATA_DIR is set so game_design_census actually runs against
it (43 saves, 2728 designs, 0 mismatched -- a test that had been skipping), and
every remaining skip is printed by name so none can be invisible again.
2026-09-09 10:10:40 -04:00

74 lines
4.7 KiB
Bash
Executable file

#!/usr/bin/env bash
# The engine gate, as a script instead of as the integrator's memory.
#
# Why this exists: for a whole day the gate reported 59/59 while the two tests that read
# the save corpus were SKIPPING, because SOTS_SAVES_DIR was never set on CT111. The corpus
# had grown from 22 to 43 saves and the coverage ratchet had broken exactly as rule 27 says
# it should -- and a writer defect had appeared on 12 of them -- and the gate could not see
# either. A green verdict on a harness that is comparing nothing (rule 1), delivered by the
# person whose job was to notice it.
#
# Each check is its own command and its own exit code. Nothing here is &&-chained across
# checks, because a chain that short-circuits reports a check that never ran (rule 22's
# cousin, paid for once already).
#
# Usage: tools/gate.sh [--fresh] --fresh: rm -rf the remote build dirs first (rule 24)
set -u
ENGINE="${ENGINE:-$HOME/sots-engine}"
RE="${RE:-$HOME/sots-re}"
CT=111
REMOTE_TREE=/srv/re-lab/build/sots-engine
REMOTE_CORPUS=/srv/re-lab/saves-corpus
REMOTE_DATA=/srv/re-lab/gob-extract # the extracted asset tree; game_design_census needs it
FRESH=0
[ "${1:-}" = "--fresh" ] && FRESH=1
ct() { ssh spicy "pct exec $CT -- bash -lc '$*'"; }
status=0
report() { # name rc
if [ "$2" -eq 0 ]; then echo "GATE ok $1"; else echo "GATE FAIL $1 (exit $2)"; status=1; fi
}
echo "== gate: $ENGINE @ $(git -C "$ENGINE" rev-parse --short HEAD), corpus $(ls "$RE"/verify/results/saves/*.sav | wc -l) saves, fresh=$FRESH"
# 1. clean-room, host
( cd "$ENGINE" && bash tools/clean_room_check.sh ); report clean-room $?
# 2. shim config structure, host
( cd "$ENGINE" && uv run --quiet python3 tools/check_shim_configs.py ); report shim-configs $?
# 3. sync the tree -- BOTH halves of rule 24: exclude local build dirs, and clear the remote ones
rsync -a --delete --exclude '.git' --exclude 'build*' -e ssh "$ENGINE"/ spicy:/tmp/eng-gate/; report sync-to-host $?
if [ $FRESH -eq 1 ]; then ct "rm -rf $REMOTE_TREE/build-host $REMOTE_TREE/build-shim"; report clear-remote-builds $?; fi
ssh spicy "tar -C /tmp/eng-gate -cf - . | pct exec $CT -- tar -C $REMOTE_TREE -xf -"; report sync-to-ct $?
# 4. sync the corpus -- the tests are only a gate if they run against it
rsync -a --delete --include='*.sav' --exclude='*' -e ssh "$RE"/verify/results/saves/ spicy:/tmp/sots-corpus/; report corpus-to-host $?
ssh spicy "pct exec $CT -- mkdir -p $REMOTE_CORPUS && tar -C /tmp/sots-corpus -cf - . | pct exec $CT -- tar -C $REMOTE_CORPUS -xf -"; report corpus-to-ct $?
NCORPUS=$(ct "ls $REMOTE_CORPUS | grep -c .sav")
echo "GATE info corpus on CT111: $NCORPUS saves"
# 5. host build + tests, WITH the corpus AND the extracted data tree
#
# `cmd | tail -1` returns TAIL's status, not the command's -- so for a day this
# script printed "GATE ok host-ctest" over a run with two FAILING tests, which is
# rule 1's failure wearing this script's own clothes. Every step below captures
# the real exit code first and only then trims the output.
ct "cd $REMOTE_TREE && cmake -S . -B build-host -DCMAKE_BUILD_TYPE=Release >/tmp/gate-cfg.log 2>&1 && cmake --build build-host -j8 >/tmp/gate-build.log 2>&1; rc=\$?; tail -1 /tmp/gate-build.log; exit \$rc"; report host-build $?
ct "cd $REMOTE_TREE/build-host && SOTS_SAVES_DIR=$REMOTE_CORPUS SOTS_DATA_DIR=$REMOTE_DATA ctest --output-on-failure >/tmp/gate-ctest.log 2>&1; rc=\$?; tail -15 /tmp/gate-ctest.log; exit \$rc"; report host-ctest $?
# and prove the corpus tests RAN rather than skipped. The check is scoped to the
# tests gated on the CORPUS (they name SOTS_SAVES_DIR when they skip); every other
# skip is listed below it by name, because a skip nobody reads is how this started.
SKIPPED=$(ct "cd $REMOTE_TREE/build-host && SOTS_SAVES_DIR=$REMOTE_CORPUS SOTS_DATA_DIR=$REMOTE_DATA ctest -V 2>&1 | grep -c SOTS_SAVES_DIR")
if [ "${SKIPPED:-1}" -ne 0 ]; then echo "GATE FAIL corpus tests skipped ($SKIPPED) -- the gate is hollow"; status=1; else echo "GATE ok corpus tests ran (0 skipped)"; fi
ct "cd $REMOTE_TREE/build-host && SOTS_SAVES_DIR=$REMOTE_CORPUS SOTS_DATA_DIR=$REMOTE_DATA ctest -V 2>&1 | grep skipped" | sed 's/^/GATE info skip: /'
# 6. shim cross-build -- same exit-code discipline as step 5. The `ls` at the end
# is a second, independent check (the artefact exists), not the build's verdict:
# without --fresh a DLL from an earlier run would satisfy it on its own.
ct "cd $REMOTE_TREE && cmake -S . -B build-shim -DCMAKE_TOOLCHAIN_FILE=cmake/toolchain-mingw-i686.cmake -DCMAKE_BUILD_TYPE=Release >/tmp/gate-shim-cfg.log 2>&1; rc=\$?; cmake --build build-shim -j8 >/tmp/gate-shim.log 2>&1 || rc=1; tail -1 /tmp/gate-shim.log; ls -la build-shim/binkw32.dll || rc=1; exit \$rc"; report shim-cross-build $?
echo "== gate: $([ $status -eq 0 ] && echo GREEN || echo RED)"
exit $status