test-framework: Add headless ROM regression suite - #19
Conversation
Adds a test harness that boots ROMs through the emulator core with no SDL,
no X server and no audio device, and turns each run into a PASS/FAIL/SKIP
verdict. Intended for bulk regression testing while fixing mappers and
timing: a full 790-ROM sweep takes a few minutes and reports exactly which
titles changed state.
harness/ rnes_headless.c plus a null HAL, linking the emulator core
from ../src without the SDL front end
rnestest/ ROM list handling, iNES/NES 2.0 header decoding, the verdict
engine and JSON/JUnit/GitHub reporters
tests/ unit tests for the verdict engine and the header decoder
A ROM fails on definitive signals only -- crash, hang, CPU jam, a blank
screen, or a CPU stuck in a tight loop -- each of which must hold across the
whole sample window, so a screen that animates even once passes. Freeze
detection is opt-in because plenty of working games hold a static title
screen for several seconds.
"Blank" is measured by content coverage, the share of pixels differing from
the dominant colour, rather than by counting distinct colours. Copyright
cards and story text are white-on-black -- exactly two colours -- yet
perfectly healthy; counting colours alone marked ~28 working titles blank.
roms/ is empty by design: only license-clean ROMs belong there, so the CI
job stays green and does nothing until some are added.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Renê de Souza Pinto <rene@renesp.com.br>
|
Claude finished @rene's task —— View job Code Review
Overall this is a well-structured addition. The headless harness is cleanly separated from the SDL front-end, the verdict heuristics are thoughtfully designed to minimise false positives, and the Python layer is properly layered. A few issues worth addressing before merging:
|
sbus_init() takes the 2 KiB of CPU RAM from malloc() and never initializes
it, so the emulator powers on with whatever the heap held. Real hardware is
arbitrary at power-on too, but a regression suite cannot be: games that read
RAM before writing it take a different path every run. Joust swung between
1.27M and 5.67M instructions over the same 300 frames and flipped PASS/FAIL
between two otherwise identical sweeps. Pinning the heap with MALLOC_PERTURB_
made runs bit-identical, confirming the cause.
Fill CPU RAM with a fixed byte before reset, in the harness rather than the
core -- the emulator is entitled to its arbitrary power-on state. The fill
goes through the public sbus_write(), so no core change is needed. --ram-fill
overrides it, so a title can be re-run under a different power-on state on
purpose; a ROM whose verdict moves is reading uninitialized RAM, which is a
property of the game worth knowing rather than noise in the suite. The value
used is reported as ram_fill in the JSON so a result says which power-on
state produced it.
Default 0x00 leaves the suite where it was: 687 PASS / 7 FAIL / 96 SKIP over
790 ROMs, identical to the pre-change sweep, and 0xFF gives the same verdicts
for every ROM.
Also from review feedback on the pull request:
- parse_buttons() silently truncated a --buttons list longer than 63 bytes,
splitting a name mid-token and dropping it. Detect truncation, and treat
an unknown name or more buttons than MAX_BUTTONS as errors too; validate
during argument parsing so a bad list is a usage error rather than a
silently empty input script.
- _parse_report() accepted any line that looked like a JSON object, so core
tracing on the shared stdout could be mistaken for the report. Require
the harness's "status" key, and keep scanning past an unparsable line
instead of giving up on it.
- Add `make strict` (-Wall -Wextra -Werror over harness/ only; the core has
pre-existing warnings) and `make sanitize` (ASan + UBSan). Run strict and
the unit tests in CI, which previously ran neither.
- Document the frame-count conversion on the hang path, the ht_used
validity-bit invariant in the colour histogram, and the _exit() pairing
the audio drain thread depends on.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Renê de Souza Pinto <rene@renesp.com.br>
Description
Adds a test harness that boots ROMs through the emulator core with no SDL, no X server and no audio device, and turns each run into a PASS/FAIL/SKIP verdict. Intended for bulk regression testing while fixing mappers and timing: a full 790-ROM sweep takes a few minutes and reports exactly which titles changed state.
harness/ rnes_headless.c plus a null HAL, linking the emulator core
from ../src without the SDL front end
rnestest/ ROM list handling, iNES/NES 2.0 header decoding, the verdict
engine and JSON/JUnit/GitHub reporters
tests/ unit tests for the verdict engine and the header decoder
A ROM fails on definitive signals only -- crash, hang, CPU jam, a blank screen, or a CPU stuck in a tight loop -- each of which must hold across the whole sample window, so a screen that animates even once passes. Freeze detection is opt-in because plenty of working games hold a static title screen for several seconds.
"Blank" is measured by content coverage, the share of pixels differing from the dominant colour, rather than by counting distinct colours. Copyright cards and story text are white-on-black -- exactly two colours -- yet perfectly healthy; counting colours alone marked ~28 working titles blank.
roms/ is empty by design: only license-clean ROMs belong there, so the CI job stays green and does nothing until some are added.