204 lines
7.2 KiB
Bash
Executable file
204 lines
7.2 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# test-agi.sh - JoeyAGI's host gate: the whole interpreter, built for the
|
|
# build machine on JoeyLib's headless BLANK port (tests/hostPort.c), plays
|
|
# scripted sessions and every frame it is asked for must hash as expected.
|
|
#
|
|
# 1. testResources: every resource of every game in games/ loads, every
|
|
# PICTURE decodes and every VIEW parses; KQ3's pictures (visual and
|
|
# priority) must hash as tests/expected/resources-kq3.txt says --
|
|
# those frames were checked pixel for pixel against Sierra's
|
|
# interpreter.
|
|
# 2. The probes: small games tests/probes.py builds from scratch, one per
|
|
# subsystem (pictures, windows, motion, control lines, priority,
|
|
# parser, menus, inventory, sound timing, ...), each played by its
|
|
# own script. Expected hashes in tests/expected/probes/.
|
|
# 3. With games/kq3 present, the KQ3 sessions in tests/kq3/*.scr (the
|
|
# opening, walking, typing, menus, debug teleports). Expected hashes
|
|
# in tests/expected/kq3/.
|
|
#
|
|
# Time inside a run is virtual (one frame per jlWaitVBL), so a run is
|
|
# exactly reproducible. Every run leaves its frames as PPMs in
|
|
# build/tests/out/NAME/.
|
|
#
|
|
# Usage: scripts/test-agi.sh [--update] [NAME...]
|
|
# NAME is a probe, kq3-SESSION or resources-GAME; --update
|
|
# rewrites the expected hashes from this run (check the frames
|
|
# first, e.g. with scripts/oracle.sh).
|
|
# Env: JOEYLIB the JoeyLib checkout (default ../joeylib)
|
|
# Exit: 0 pass, 1 fail, 2 skipped (no JoeyLib / host compiler / python3)
|
|
set -uo pipefail
|
|
|
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
|
JOEYLIB=${JOEYLIB:-$repo/../joeylib}
|
|
[[ -f $JOEYLIB/include/joey/joey.h ]] || { echo "test-agi: no JoeyLib checkout at $JOEYLIB (set JOEYLIB)" >&2; exit 2; }
|
|
command -v cc >/dev/null || { echo "test-agi: no host C compiler" >&2; exit 2; }
|
|
command -v python3 >/dev/null || { echo "test-agi: no python3" >&2; exit 2; }
|
|
|
|
update=0
|
|
if [[ ${1:-} == --update ]]; then
|
|
update=1
|
|
shift
|
|
fi
|
|
only=" $* "
|
|
|
|
build=$repo/build/tests
|
|
obj=$build/obj
|
|
out=$build/out
|
|
expected=$repo/tests/expected
|
|
mkdir -p "$obj" "$out"
|
|
|
|
# ----- Build: JoeyLib's core and generic code with the host port -----
|
|
|
|
jlFlags=(-std=c99 -O1 -DJOEYLIB_PLATFORM_BLANK -I "$JOEYLIB/include" -I "$JOEYLIB/include/joey" -I "$JOEYLIB/src/core" -I "$JOEYLIB/src/codegen")
|
|
agiFlags=("${jlFlags[@]}" -Wall -Wextra -Werror -I "$repo" -I "$repo/tests")
|
|
engine=(agi agiRes agiPic agiView agiGfx agiObj agiText agiFont agiInput agiSound agiSave agiVm)
|
|
|
|
# compile SRC OBJ FLAGS...: when the source, or any header, is newer.
|
|
compile() {
|
|
local src=$1 o=$2
|
|
shift 2
|
|
if [[ -f $o && ! $src -nt $o ]] && [[ -z $(find "$repo" "$repo/tests" "$JOEYLIB/include" "$JOEYLIB/src/core" -maxdepth 2 -name '*.h' -newer "$o" -print -quit) ]]; then
|
|
return 0
|
|
fi
|
|
cc "$@" -c "$src" -o "$o"
|
|
}
|
|
|
|
echo "Building the host interpreter..."
|
|
ok=1
|
|
jlObjs=()
|
|
for f in "$JOEYLIB"/src/core/*.c "$JOEYLIB"/src/generic/*.c; do
|
|
o=$obj/jl_$(basename "${f%.c}").o
|
|
compile "$f" "$o" "${jlFlags[@]}" -w || ok=0
|
|
jlObjs+=("$o")
|
|
done
|
|
agiObjs=()
|
|
for f in "${engine[@]}"; do
|
|
o=$obj/$f.o
|
|
if [[ $f == agi ]]; then
|
|
compile "$repo/$f.c" "$o" "${agiFlags[@]}" -Dmain=agiMain || ok=0
|
|
else
|
|
compile "$repo/$f.c" "$o" "${agiFlags[@]}" || ok=0
|
|
fi
|
|
agiObjs+=("$o")
|
|
done
|
|
for f in hostPort agiHost testResources; do
|
|
compile "$repo/tests/$f.c" "$obj/$f.o" "${agiFlags[@]}" || ok=0
|
|
done
|
|
[[ $ok == 1 ]] || { echo "test-agi: FAIL (build)" >&2; exit 1; }
|
|
cc -o "$build/agiHost" "$obj/agiHost.o" "$obj/hostPort.o" "${agiObjs[@]}" "${jlObjs[@]}" -lm || exit 1
|
|
cc -o "$build/testResources" "$obj/testResources.o" "$obj/hostPort.o" "${agiObjs[@]}" "${jlObjs[@]}" -lm || exit 1
|
|
|
|
passed=0
|
|
failed=0
|
|
skipped=0
|
|
|
|
# check NAME EXPECTFILE ACTUALFILE: compare, or with --update record.
|
|
check() {
|
|
local name=$1 want=$2 got=$3
|
|
if [[ $update == 1 ]]; then
|
|
mkdir -p "$(dirname "$want")"
|
|
cp "$got" "$want"
|
|
echo " $name: recorded $(wc -l < "$got") lines"
|
|
passed=$((passed + 1))
|
|
return
|
|
fi
|
|
if [[ ! -f $want ]]; then
|
|
echo " $name: FAIL (no $want; record it with --update once the frames are checked)"
|
|
failed=$((failed + 1))
|
|
return
|
|
fi
|
|
if diff -q "$want" "$got" >/dev/null; then
|
|
echo " $name: PASS ($(wc -l < "$got") checked)"
|
|
passed=$((passed + 1))
|
|
else
|
|
echo " $name: FAIL -- what differs (< expected, > got); frames in $out/$name/"
|
|
diff "$want" "$got" | grep '^[<>]' | head -20 | sed 's/^/ /'
|
|
failed=$((failed + 1))
|
|
fi
|
|
}
|
|
|
|
# selected NAME: was it asked for (everything is when nothing was named)?
|
|
selected() {
|
|
[[ $only == " " || $only == *" $1 "* ]]
|
|
}
|
|
|
|
# play NAME GAMEDIR SCRIPT EXPECTFILE: one scripted session.
|
|
play() {
|
|
local name=$1 game=$2 script=$3 want=$4 run
|
|
selected "$name" || return 0
|
|
run=$(mktemp -d "$build/run.XXXXXX")
|
|
ln -s "$game" "$run/DATA"
|
|
# The host port cannot make directories; saving needs SAVES/ there.
|
|
mkdir "$run/SAVES"
|
|
rm -rf "${out:?}/$name"
|
|
mkdir -p "$out/$name"
|
|
(cd "$run" && timeout -s KILL 600 "$build/agiHost" "$script" "$out/$name") > "$out/$name/log.txt" 2>&1
|
|
rm -rf "$run"
|
|
sed -n 's/^\[ *[0-9.]*\] shot \([^ ]*\) \([0-9a-f]*\)$/\1 \2/p' "$out/$name/log.txt" > "$out/$name/hashes.txt"
|
|
if ! grep -q 'script done' "$out/$name/log.txt"; then
|
|
echo " $name: FAIL (the run stopped before its script ended; see $out/$name/log.txt)"
|
|
failed=$((failed + 1))
|
|
return
|
|
fi
|
|
check "$name" "$want" "$out/$name/hashes.txt"
|
|
}
|
|
|
|
# ----- 1. Resources -----
|
|
|
|
echo
|
|
echo "--- resources ---"
|
|
for dir in "$repo"/games/*/; do
|
|
[[ -f $dir/LOGDIR ]] || continue
|
|
game=$(basename "$dir")
|
|
name=resources-$game
|
|
selected "$name" || continue
|
|
run=$(mktemp -d "$build/run.XXXXXX")
|
|
ln -s "$dir" "$run/DATA"
|
|
mkdir -p "$out/$name"
|
|
(cd "$run" && timeout -s KILL 600 "$build/testResources") > "$out/$name/hashes.txt" 2> "$out/$name/log.txt"
|
|
status=$?
|
|
rm -rf "$run"
|
|
if [[ $status != 0 ]]; then
|
|
echo " $name: FAIL ($(tail -1 "$out/$name/log.txt"))"
|
|
failed=$((failed + 1))
|
|
elif [[ -f $expected/$name.txt || $update == 1 ]]; then
|
|
check "$name" "$expected/$name.txt" "$out/$name/hashes.txt"
|
|
else
|
|
echo " $name: PASS ($(tail -1 "$out/$name/log.txt"); no expected picture hashes for this game)"
|
|
passed=$((passed + 1))
|
|
fi
|
|
done
|
|
|
|
# ----- 2. Probes -----
|
|
|
|
echo
|
|
echo "--- probes ---"
|
|
python3 "$repo/tests/probes.py" build "$build/probes" || { echo "test-agi: FAIL (probes.py)" >&2; exit 1; }
|
|
for name in $(python3 "$repo/tests/probes.py" list); do
|
|
play "$name" "$build/probes/$name" "$build/probes/$name/probe.scr" "$expected/probes/$name.txt"
|
|
done
|
|
|
|
# ----- 3. King's Quest III -----
|
|
|
|
echo
|
|
echo "--- kq3 ---"
|
|
if [[ -f $repo/games/kq3/LOGDIR ]]; then
|
|
for scr in "$repo"/tests/kq3/*.scr; do
|
|
[[ -f $scr ]] || continue
|
|
session=$(basename "${scr%.scr}")
|
|
play "kq3-$session" "$repo/games/kq3" "$scr" "$expected/kq3/$session.txt"
|
|
done
|
|
else
|
|
echo " skipped: no games/kq3"
|
|
skipped=1
|
|
fi
|
|
|
|
echo
|
|
echo "===== test-agi: $passed passed, $failed failed ====="
|
|
if [[ $failed -gt 0 ]]; then
|
|
exit 1
|
|
fi
|
|
if [[ $skipped -gt 0 && $passed -eq 0 ]]; then
|
|
exit 2
|
|
fi
|
|
exit 0
|