Naming a pad file that is not there printed the error and then said the machine had STARTED. MACHINE_OK is nought and the code returned nought, so the front end ran a machine whose clock had never been set up and divided by it: a typo in a path came out as a floating point exception and a core dump. The trap is two functions in one file with opposite conventions - machineStart returns MACHINE_OK for worked, machineRestart thirty lines up returns 1 for worked - and this copied the nearer one. Both of the returns I added last week had it. Checked now for all three files the replay suite is about, because the same mistake fits all of them, and re-broken to be sure: the check comes back exit 136, which is a signal 8, which is the crash. Found by somebody typing a path that was not there, which is the fourth thing this week that no test would have reached. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
148 lines
6.6 KiB
Bash
Executable File
148 lines
6.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Checks that what the machine records is what it plays back.
|
|
#
|
|
# --record-pad writes what a controller held, one byte a frame, in exactly the format --pad
|
|
# reads. That symmetry is the whole feature and it is the whole of what is checked here: a
|
|
# recording is only worth having if playing it back does what was recorded.
|
|
#
|
|
# ---- Why it exists ----
|
|
#
|
|
# Some inputs cannot sensibly be written by hand. Flying a lander from one base to another is
|
|
# a few hundred frames of steering that has to arrive somewhere eight cells wide, and several
|
|
# attempts at hand-authoring one got within two columns and no closer. That is a piloting
|
|
# exercise rather than a test. Playing it once and keeping what happened is the answer, and
|
|
# this is what says the keeping works.
|
|
#
|
|
# Written by Anachronaut
|
|
|
|
set -u
|
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
BUILD="$ROOT/Tests/build/replay"
|
|
ASM="$ROOT/Assembler"
|
|
EMU="$ROOT/SplitBit"
|
|
|
|
for tool in "$ASM" "$EMU"; do
|
|
[ -x "$tool" ] || { echo "$(basename "$tool") is not built."; exit 1; }
|
|
done
|
|
|
|
rm -rf "$BUILD"; mkdir -p "$BUILD"
|
|
|
|
PASS=0
|
|
FAIL=0
|
|
FAILED_NAMES=()
|
|
|
|
GREEN=$'\033[32m'; RED=$'\033[31m'; RESET=$'\033[0m'
|
|
[ -t 1 ] || { GREEN=""; RED=""; RESET=""; }
|
|
|
|
result() {
|
|
if [ "$1" = "ok" ]; then
|
|
PASS=$((PASS + 1)); printf " [%sok %s] %-40s %s\n" "$GREEN" "$RESET" "$2" "$3"
|
|
else
|
|
FAIL=$((FAIL + 1)); FAILED_NAMES+=("$2")
|
|
printf " [%sFAIL%s] %-40s %s\n" "$RED" "$RESET" "$2" "$3"
|
|
fi
|
|
}
|
|
|
|
echo "Checking that a recording plays back as itself."
|
|
|
|
# A program that reads a pad every frame and stops. It is the one in testPrograms rather than
|
|
# a new one, because what is being checked is the recording and not the reading.
|
|
"$ASM" "$ROOT/Programs/testPrograms/padTest.asm" -o "$BUILD/padTest.bin" > "$BUILD/asm.log" 2>&1 \
|
|
|| { echo "padTest did not assemble."; exit 1; }
|
|
|
|
# ---- Played and recorded at the same time ----
|
|
#
|
|
# The strongest form of the claim: the recording is made OF a playback, so if the two formats
|
|
# ever drift apart the bytes coming out stop matching the bytes going in.
|
|
"$EMU" --fast --pad "$ROOT/Tests/input/padTest.pad" \
|
|
--record-pad "$BUILD/roundtrip.pad" "$BUILD/padTest.bin" > "$BUILD/roundtrip.out" 2>&1
|
|
SAME="$(python3 -c "
|
|
first = open('$ROOT/Tests/input/padTest.pad', 'rb').read()
|
|
again = open('$BUILD/roundtrip.pad', 'rb').read()
|
|
print('yes' if again[:len(first)] == first else 'no')
|
|
" 2>/dev/null || echo error)"
|
|
[ "$SAME" = "yes" ] \
|
|
&& result ok "a recording of a playback is the playback" "byte for byte, in and out" \
|
|
|| result no "a recording of a playback is the playback" "the bytes differ"
|
|
|
|
# ---- And it does not matter which controller it was ----
|
|
#
|
|
# The first recording ever made with this came back 1,766 frames of nothing. It recorded pad
|
|
# NOUGHT and the controller was somewhere else - which pad one lands on is an accident of the
|
|
# host, and the flight had to be flown again for nothing.
|
|
#
|
|
# So every pad is or-ed into the byte. Here the same recording is played on pad ONE, with
|
|
# nought holding nothing, and what comes out has to be what went in.
|
|
python3 -c "open('$BUILD/idle.pad','wb').write(b'\x00' * 64)"
|
|
"$EMU" --fast --pad "$BUILD/idle.pad" --pad "$ROOT/Tests/input/padTest.pad" \
|
|
--record-pad "$BUILD/padone.pad" "$BUILD/padTest.bin" > "$BUILD/padone.out" 2>&1
|
|
ELSEWHERE="$(python3 -c "
|
|
first = open('$ROOT/Tests/input/padTest.pad', 'rb').read()
|
|
again = open('$BUILD/padone.pad', 'rb').read()
|
|
print('yes' if again[:len(first)] == first else 'no')
|
|
" 2>/dev/null || echo error)"
|
|
[ "$ELSEWHERE" = "yes" ] \
|
|
&& result ok "a controller on any pad is recorded" "flown on pad one, written all the same" \
|
|
|| result no "a controller on any pad is recorded" "the bytes differ"
|
|
|
|
# ---- One byte a frame, and the frame is the machine's ----
|
|
#
|
|
# A recording is a TIMELINE. If it were a byte a read, a program that polled twice in one
|
|
# frame would record twice as fast as it flew; if it skipped frames nobody looked at, it would
|
|
# play back faster than it was flown. So the count is the number of frames the machine ran,
|
|
# which at 16,667 cycles a frame is arithmetic rather than a guess.
|
|
"$EMU" --fast --cycles 500000 --record-pad "$BUILD/timeline.pad" \
|
|
"$BUILD/padTest.bin" > "$BUILD/timeline.out" 2>&1
|
|
WROTE="$(wc -c < "$BUILD/timeline.pad" | tr -d ' ')"
|
|
# The machine halts of its own accord well before the limit, so what is checked is that the
|
|
# count is frames-of-something rather than bytes-of-nothing: more than none, and fewer than
|
|
# the limit could possibly hold.
|
|
[ "$WROTE" -gt 0 ] && [ "$WROTE" -le 30 ] \
|
|
&& result ok "a byte for every frame and no more" "$WROTE frames of it" \
|
|
|| result no "a byte for every frame and no more" "$WROTE bytes, which is not a frame count"
|
|
|
|
# ---- And a machine with no controller records that honestly ----
|
|
#
|
|
# Nothing held is nought, which is what a pad nobody is touching reports and what a machine
|
|
# with no pad at all reports. A recorder that wrote something else would make a fixture that
|
|
# pressed buttons nobody pressed.
|
|
QUIET="$(python3 -c "
|
|
data = open('$BUILD/timeline.pad', 'rb').read()
|
|
print('yes' if set(data) <= {0} else 'no')
|
|
" 2>/dev/null || echo error)"
|
|
[ "$QUIET" = "yes" ] \
|
|
&& result ok "and nothing held is written as nothing" "no buttons nobody pressed" \
|
|
|| result no "and nothing held is written as nothing" "the recording holds something"
|
|
|
|
# ---- A file that is not there is an error, not a crash ----
|
|
#
|
|
# Naming a pad file that does not exist used to print the error and then say the machine had
|
|
# STARTED: MACHINE_OK is nought, and the code returned nought. The front end then ran a
|
|
# machine whose clock had never been set up and divided by it, so a typo in a path came out as
|
|
# a floating point exception and a core dump.
|
|
#
|
|
# The trap was two functions in one file with opposite conventions - machineRestart returns 1
|
|
# for worked - and the wrong neighbour being copied. Checked here for all three files this
|
|
# suite is about, because the same mistake fits all of them.
|
|
for missing in "--pad" "--keyboard" "--record-pad"; do
|
|
case "$missing" in
|
|
--record-pad) where="/nowhere/at/all.pad" ;;
|
|
*) where="$BUILD/there-is-no-such-file" ;;
|
|
esac
|
|
"$EMU" $missing "$where" "$BUILD/padTest.bin" > "$BUILD/missing.out" 2>&1
|
|
status=$?
|
|
if [ "$status" = "1" ] && grep -q "Error:" "$BUILD/missing.out"; then
|
|
result ok "$missing with no file stops cleanly" "an error and a status, not a signal"
|
|
else
|
|
result no "$missing with no file stops cleanly" "exit $status"
|
|
fi
|
|
done
|
|
|
|
echo
|
|
if [ "$FAIL" -eq 0 ]; then
|
|
echo "All $PASS replay checks passed."
|
|
exit 0
|
|
fi
|
|
echo "$PASS passed, $FAIL failed: ${FAILED_NAMES[*]}"
|
|
exit 1
|