sots-re/tools/gate.sh

83 lines
5.3 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
# One gate at a time. Lane DW saw up to FOUR `gate.sh --fresh` runs concurrently, all rsyncing
# into the same remote tree and rm -rf'ing its build dirs -- rule 24's shape with no lock, and a
# way to report a build that is half someone else's. Every lane runs from this host, so a local
# flock is sufficient. Waits up to 30 min, then fails loudly rather than running unlocked.
if [ -z "${SOTS_GATE_LOCKED:-}" ]; then
exec env SOTS_GATE_LOCKED=1 flock -w 1800 /tmp/sots-gate.lock "$0" "$@" \
|| { echo "GATE FAIL could not acquire /tmp/sots-gate.lock within 30 min"; exit 2; }
fi
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