22#
33# Headless regression test for virtualjaguar-libretro
44#
5- # Builds miniretro, runs test ROMs for N frames, dumps screenshots,
6- # and compares the last screenshot's checksum against a known baseline .
5+ # Runs test ROMs via miniretro, compares screenshots against reference
6+ # images, and generates visual diffs on failure .
77#
88# Usage: ./test/regression_test.sh <core_path>
99# Example: ./test/regression_test.sh ./virtualjaguar_libretro.so
1010#
1111# Set MINIRETRO_BIN env var to skip building miniretro from source.
12+ # Set DIFF_DIR env var to specify where diff images are saved.
1213#
1314set -euo pipefail
1415
@@ -17,6 +18,7 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
1718WORK_DIR=" $( mktemp -d) "
1819BASELINE_DIR=" ${SCRIPT_DIR} /baselines"
1920ROM_DIR=" ${SCRIPT_DIR} /roms"
21+ DIFF_DIR=" ${DIFF_DIR:- ${WORK_DIR} / diffs} "
2022# 600 frames (~10 seconds at 60fps) to get past BIOS boot
2123FRAMES=600
2224DUMP_EVERY=100
5557fi
5658
5759echo " ==> Baselines: ${BASELINE_DIR} "
60+ echo " ==> Diff output: ${DIFF_DIR} "
61+ mkdir -p " ${DIFF_DIR} "
5862
5963# --- Resolve core to absolute path ---
6064CORE=" $( cd " $( dirname " ${CORE} " ) " && pwd) /$( basename " ${CORE} " ) "
@@ -63,6 +67,7 @@ CORE="$(cd "$(dirname "${CORE}")" && pwd)/$(basename "${CORE}")"
6367PASS=0
6468FAIL=0
6569NEW=0
70+ SUMMARY=" "
6671
6772for rom in " ${ROM_DIR} " /* .j64 " ${ROM_DIR} " /* .rom; do
6873 [ -f " ${rom} " ] || continue
@@ -86,41 +91,95 @@ for rom in "${ROM_DIR}"/*.j64 "${ROM_DIR}"/*.rom; do
8691 if [ -z " ${frame_file} " ]; then
8792 echo " WARNING: No frame dumped for ${rom_name} "
8893 FAIL=$(( FAIL + 1 ))
94+ SUMMARY=" ${SUMMARY} | ${rom_name} | :x: FAIL | No frame output | - |\n"
8995 continue
9096 fi
9197
9298 echo " Using frame: $( basename " ${frame_file} " ) "
9399
94- # Compute checksum
95- if command -v md5sum & > /dev/null; then
96- hash=$( md5sum " ${frame_file} " | awk ' {print $1}' )
97- else
98- hash=$( md5 -q " ${frame_file} " )
99- fi
100-
101- baseline_file=" ${BASELINE_DIR} /${rom_name} .md5"
102-
103- if [ -f " ${baseline_file} " ]; then
104- expected=$( cat " ${baseline_file} " )
105- if [ " ${hash} " = " ${expected} " ]; then
106- echo " PASS: ${rom_name} (${hash} )"
107- PASS=$(( PASS + 1 ))
100+ # Copy current screenshot to diff dir for reference
101+ cp " ${frame_file} " " ${DIFF_DIR} /${rom_name} _current.png"
102+
103+ baseline_png=" ${BASELINE_DIR} /${rom_name} .png"
104+
105+ if [ -f " ${baseline_png} " ]; then
106+ # Compare against reference screenshot
107+ if command -v compare & > /dev/null; then
108+ # ImageMagick compare: generate diff image and get metric
109+ set +e
110+ metric_raw=$( compare -metric AE " ${baseline_png} " " ${frame_file} " \
111+ " ${DIFF_DIR} /${rom_name} _diff.png" 2>&1 )
112+ compare_status=$?
113+ set -e
114+
115+ if [ " ${compare_status} " -le 1 ]; then
116+ # Extract just the integer pixel count (ImageMagick may output "0 (0)")
117+ metric=$( printf ' %s\n' " ${metric_raw} " | awk ' NR==1 {print $1}' )
118+
119+ if [[ " ${metric} " =~ ^[0-9]+$ ]] && [ " ${metric} " = " 0" ]; then
120+ echo " PASS: ${rom_name} (0 pixels differ)"
121+ PASS=$(( PASS + 1 ))
122+ SUMMARY=" ${SUMMARY} | ${rom_name} | :white_check_mark: PASS | 0 pixels differ | - |\n"
123+ # Clean up diff artifacts on pass
124+ rm -f " ${DIFF_DIR} /${rom_name} _diff.png" " ${DIFF_DIR} /${rom_name} _current.png"
125+ elif [[ " ${metric} " =~ ^[0-9]+$ ]]; then
126+ echo " FAIL: ${rom_name} (${metric} pixels differ)"
127+ # Also generate a side-by-side comparison
128+ if command -v montage & > /dev/null; then
129+ montage " ${baseline_png} " " ${frame_file} " " ${DIFF_DIR} /${rom_name} _diff.png" \
130+ -tile 3x1 -geometry +4+4 -label ' %f' \
131+ " ${DIFF_DIR} /${rom_name} _sidebyside.png" 2> /dev/null || true
132+ fi
133+ cp " ${baseline_png} " " ${DIFF_DIR} /${rom_name} _expected.png"
134+ FAIL=$(( FAIL + 1 ))
135+ SUMMARY=" ${SUMMARY} | ${rom_name} | :x: FAIL | ${metric} pixels differ | See artifacts |\n"
136+ else
137+ echo " FAIL: ${rom_name} (compare error: ${metric_raw} )"
138+ cp " ${baseline_png} " " ${DIFF_DIR} /${rom_name} _expected.png"
139+ FAIL=$(( FAIL + 1 ))
140+ SUMMARY=" ${SUMMARY} | ${rom_name} | :x: FAIL | compare error | See artifacts |\n"
141+ fi
142+ else
143+ echo " FAIL: ${rom_name} (compare failed: ${metric_raw} )"
144+ cp " ${baseline_png} " " ${DIFF_DIR} /${rom_name} _expected.png"
145+ FAIL=$(( FAIL + 1 ))
146+ SUMMARY=" ${SUMMARY} | ${rom_name} | :x: FAIL | compare error | See artifacts |\n"
147+ fi
108148 else
109- echo " FAIL: ${rom_name} "
110- echo " expected: ${expected} "
111- echo " got: ${hash} "
112- FAIL=$(( FAIL + 1 ))
149+ # Fallback: byte-level comparison
150+ if cmp -s " ${baseline_png} " " ${frame_file} " ; then
151+ echo " PASS: ${rom_name} (identical)"
152+ PASS=$(( PASS + 1 ))
153+ SUMMARY=" ${SUMMARY} | ${rom_name} | :white_check_mark: PASS | identical | - |\n"
154+ else
155+ echo " FAIL: ${rom_name} (screenshots differ)"
156+ cp " ${baseline_png} " " ${DIFF_DIR} /${rom_name} _expected.png"
157+ FAIL=$(( FAIL + 1 ))
158+ SUMMARY=" ${SUMMARY} | ${rom_name} | :x: FAIL | screenshots differ | See artifacts |\n"
159+ fi
113160 fi
114161 else
115- echo " NEW: ${rom_name} — no baseline yet ( ${hash} ) "
116- echo " Run: echo ' ${hash} ' > ${baseline_file }"
162+ echo " NEW: ${rom_name} — no baseline yet"
163+ echo " To create: cp ${frame_file} ${baseline_png }"
117164 NEW=$(( NEW + 1 ))
165+ SUMMARY=" ${SUMMARY} | ${rom_name} | :new: NEW | no baseline | - |\n"
118166 fi
119167done
120168
121169echo " "
122170echo " ==> Results: ${PASS} passed, ${FAIL} failed, ${NEW} new (no baseline)"
123171
172+ # Write summary for CI to pick up
173+ cat > " ${DIFF_DIR} /summary.md" << EOSUMMARY
174+ ## Regression Test Results
175+
176+ | ROM | Status | Details | Diff |
177+ |-----|--------|---------|------|
178+ $( echo -e " ${SUMMARY} " )
179+
180+ **Platform:** $( uname -s) $( uname -m)
181+ EOSUMMARY
182+
124183if [ " ${FAIL} " -gt 0 ]; then
125184 exit 1
126185fi
0 commit comments