joeylib2/scripts/test-agi.sh

200 lines
6.9 KiB
Bash
Executable file

#!/usr/bin/env bash
# Host-side regression tests for the AGI port.
#
# Compiles tests/agi/testAgiRes.c + examples/agi/agiRes.c with the
# host gcc and runs it against every game directory in
# examples/agi/gamedata/. Each game is exercised in isolation; the
# script aggregates pass/fail counts and exits non-zero if any test
# fails.
#
# Why a host build instead of running the emulator binaries: the AGI
# resource loader is plain stdio C with no platform dependencies, so
# the parsing logic is identical on every JoeyLib target. Validating
# it on the build host means each iteration takes a second instead
# of the 15-30s of an emulator boot, and reproducible failures don't
# depend on capturing video / log output through DOSBox or MAME.
#
# Usage:
# scripts/test-agi.sh # test every subdir of gamedata/
# scripts/test-agi.sh kq3 agidemo # test only the named subdirs
set -euo pipefail
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
gamedata=$repo/examples/agi/gamedata
build=$repo/build/tests
testRes=$build/testAgiRes
testPic=$build/testAgiPic
testView=$build/testAgiView
testVm=$build/testAgiVm
testPipeline=$build/testAgiPipeline
mkdir -p "$build"
# Shared host compile flags. agiPic.c calls jlDrawPixel in the blit
# step, which the host test never invokes; we stub it as an empty
# function via the link-line shim below so the host build can resolve
# the symbol without dragging in the JoeyLib runtime.
# Pick a JoeyLib platform for the host build. None of the platform-
# specific code paths are exercised by the host test (it only touches
# the AGI parsers, never the JoeyLib HAL), but joey/platform.h #errors
# unless exactly one is defined. DOS is the closest fit for a Linux
# host (chunky, little-endian, x86 lineage).
hostCflags=(-std=c99 -Wall -Wextra -Werror -O2 -DJOEYLIB_PLATFORM_DOS -I "$repo/include" -I "$repo/examples/agi" -I "$repo/src/core")
stubSrc=$build/hostStubs.c
cat > "$stubSrc" <<'EOF'
#include "joey/draw.h"
#include "joey/core.h"
#include "joey/file.h"
#include "surfaceInternal.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
// Host jlDataOpen: the real one (assetLoad.c) forces a DATA/ prefix, but
// the test gamedata dirs hold their files flat. Open <AGI_DATA_DIR>/<name>
// so agiRes.c's bare-name jlDataOpen calls resolve against the game dir the
// harness points us at.
FILE *jlDataOpen(const char *name, const char *mode) {
const char *base = getenv("AGI_DATA_DIR");
char path[1024];
if (base == NULL) { base = "."; }
if ((size_t)snprintf(path, sizeof(path), "%s/%s", base, name) >= sizeof(path)) { return NULL; }
return fopen(path, mode);
}
void jlDrawPixel(jlSurfaceT *s, int16_t x, int16_t y, uint8_t c) { (void)s; (void)x; (void)y; (void)c; }
void jlFillRect(jlSurfaceT *s, int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t c) { (void)s; (void)x; (void)y; (void)w; (void)h; (void)c; }
uint32_t jlRandom(void) { return (uint32_t)rand(); }
uint16_t jlRandomRange(uint16_t bound) { return (bound == 0u) ? 0u : (uint16_t)((unsigned)rand() % bound); }
void jlRandomSeed(uint32_t seed) { srand((unsigned)seed); }
void *jlAlloc(uint32_t bytes) { return malloc((size_t)bytes); }
void jlFree(void *p) { free(p); }
void jlLogF(const char *fmt, ...) { (void)fmt; }
void jlLogFlush(void) { }
void jlLog(const char *msg) { (void)msg; }
/* surfaceMarkDirtyRect is a MACRO since the Phase-6 mark rework -- the
* host TUs (agiPic/agiView) expand it inline, so the stubs provide the
* band state it references instead of a function. gStage stays NULL,
* so every expansion takes the s != gStage no-op branch; the symbols
* only need to exist to link. */
jlSurfaceT *gStage = 0;
uint8_t gStageMinWord[SURFACE_HEIGHT];
uint8_t gStageMaxWord[SURFACE_HEIGHT];
void surfaceMarkDirtyRows(uint16_t y, uint16_t yEnd, uint8_t minWord, uint8_t maxWord) { (void)y; (void)yEnd; (void)minWord; (void)maxWord; }
void surfaceMarkDirtyAll(const jlSurfaceT *s) { (void)s; }
EOF
echo "Building host tests..."
gcc "${hostCflags[@]}" \
"$repo/tests/agi/testAgiRes.c" \
"$repo/examples/agi/agiRes.c" \
"$stubSrc" \
-o "$testRes"
gcc "${hostCflags[@]}" \
"$repo/tests/agi/testAgiPic.c" \
"$repo/examples/agi/agiRes.c" \
"$repo/examples/agi/agiPic.c" \
"$stubSrc" \
-o "$testPic"
gcc "${hostCflags[@]}" \
"$repo/tests/agi/testAgiView.c" \
"$repo/examples/agi/agiRes.c" \
"$repo/examples/agi/agiPic.c" \
"$repo/examples/agi/agiView.c" \
"$stubSrc" \
-o "$testView"
gcc "${hostCflags[@]}" \
"$repo/tests/agi/testAgiVm.c" \
"$repo/examples/agi/agiRes.c" \
"$repo/examples/agi/agiVm.c" \
"$repo/examples/agi/agiObj.c" \
"$stubSrc" \
-o "$testVm"
gcc "${hostCflags[@]}" \
"$repo/tests/agi/testAgiPipeline.c" \
"$repo/examples/agi/agiRes.c" \
"$repo/examples/agi/agiVm.c" \
"$repo/examples/agi/agiObj.c" \
"$stubSrc" \
-o "$testPipeline"
# Pick the game directories to exercise. With no args, every
# subdirectory of examples/agi/gamedata that contains LOGDIR is a
# candidate (skips stray README files etc).
if [[ $# -gt 0 ]]; then
games=("$@")
else
games=()
if [[ -d $gamedata ]]; then
for dir in "$gamedata"/*/; do
[[ -d $dir ]] || continue
name=$(basename "$dir")
if [[ -f $dir/LOGDIR ]]; then
games+=("$name")
fi
done
fi
fi
if [[ ${#games[@]} -eq 0 ]]; then
echo
echo "No AGI game directories found under $gamedata"
echo "Drop an AGI v2 game in examples/agi/gamedata/<name>/ and rerun."
echo "See examples/agi/gamedata/README.md for the required files."
exit 0
fi
passed=0
failed=0
echo
for game in "${games[@]}"; do
dir=$gamedata/$game
echo "===== $game ====="
if [[ ! -d $dir ]]; then
echo " FAIL: $dir does not exist"
failed=$((failed + 1))
continue
fi
# agiRes.c opens data by bare name via jlDataOpen; the host stub
# resolves those against AGI_DATA_DIR (see stubSrc above).
export AGI_DATA_DIR="$dir"
gameOk=1
echo "--- resource loader ---"
if ! "$testRes" "$dir"; then
gameOk=0
fi
echo "--- PIC decoder ---"
if ! "$testPic" "$dir"; then
gameOk=0
fi
echo "--- VIEW decoder ---"
if ! "$testView" "$dir"; then
gameOk=0
fi
echo "--- LOGIC VM (logic.0) ---"
if ! "$testVm" "$dir"; then
gameOk=0
fi
echo "--- VM pipeline (callbacks + room transitions) ---"
if ! "$testPipeline" "$dir"; then
gameOk=0
fi
if [[ $gameOk -eq 1 ]]; then
passed=$((passed + 1))
else
failed=$((failed + 1))
fi
echo
done
total=$((passed + failed))
echo "===== summary: $passed/$total passed ====="
if [[ $failed -gt 0 ]]; then
exit 1
fi
exit 0