mirror of
https://github.com/imjasonh/nescript
synced 2026-07-08 08:55:38 +00:00
ci: add headless Mesen2 .dbg validation workflow
Run Mesen2's `--testRunner` mode in CI, point it at a NEScript-built
ROM + auto-loaded .dbg, and assert via Lua that the four entry-point
labels (`nmi`, `irq`, `Main_frame`, `main_loop`) we promised to emit
actually resolve. Failures encode which assertion broke into the exit
code so CI can report it without stdout (Mesen's emu.log is internal).
The setup needed three workarounds, each documented inline in the
workflow:
* `touch settings.json` to skip Mesen's first-run GUI wizard, which
blocks the --testRunner dispatch in Program.cs:74. Contents don't
matter — Configuration.Deserialize falls back to defaults on parse
error.
* `DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1` to keep .NET from loading
system libstdc++ via libicuuc. On Ubuntu 24.04 the dual libstdc++
presence (system + MesenCore.so's bundled static copy) crashes
MesenCore's static regex initialiser with std::bad_cast before any
user code runs.
* `xvfb-run` because Mesen's Avalonia UI calls XOpenDisplay before
--testRunner is dispatched.
This is a separate workflow file from ci.yml because it depends on the
40 MB Mesen2 binary download + xvfb + sdl2, none of which the existing
jobs need. Cached by Mesen version so reruns are fast.
https://claude.ai/code/session_01DfN3pKJLryr7vvNFBpcqmC
This commit is contained in:
parent
e4751df143
commit
d075bc2c43
2 changed files with 165 additions and 0 deletions
45
tests/mesen/probe.lua
Normal file
45
tests/mesen/probe.lua
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
-- Mesen2 .dbg validation probe.
|
||||
--
|
||||
-- Run via:
|
||||
-- Mesen --testRunner <rom.nes> probe.lua --timeout=15
|
||||
--
|
||||
-- Mesen auto-loads <rom>.dbg from the same directory as <rom.nes>;
|
||||
-- this script then queries that label table via emu.getLabelAddress.
|
||||
-- Communication with the CI wrapper is via the process exit code
|
||||
-- (Mesen's emu.log writes to an internal buffer, not the process
|
||||
-- stdout, so exit codes are the only reliable cross-process signal).
|
||||
--
|
||||
-- Exit codes:
|
||||
-- 0 = all checks passed
|
||||
-- 1 = `nmi` not found (runtime entry-point missing → linker bug)
|
||||
-- 2 = `nmi` address is 0 (label resolved to nothing → .dbg parse bug)
|
||||
-- 3 = `Main_frame` not found (state-handler label missing → analyzer/linker bug)
|
||||
-- 4 = `Main_frame` address is 0
|
||||
-- 5 = `main_loop` not found (main-loop entry missing → runtime gating bug)
|
||||
-- 6 = `main_loop` address is 0
|
||||
-- 7 = `irq` not found (IRQ vector label missing → runtime bug)
|
||||
-- 8 = `irq` address is 0
|
||||
--
|
||||
-- Any other non-zero exit indicates Mesen crashed before the probe
|
||||
-- finished — typically the GLOBALIZATION_INVARIANT/libstdc++
|
||||
-- collision (fixed by setting DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
|
||||
-- on Linux) or a missing libsdl2 dependency.
|
||||
|
||||
local function check(label, missing_code, zero_code)
|
||||
local info = emu.getLabelAddress(label)
|
||||
if info == nil then emu.stop(missing_code) end
|
||||
if (info.address or 0) == 0 then emu.stop(zero_code) end
|
||||
end
|
||||
|
||||
check("nmi", 1, 2)
|
||||
check("Main_frame", 3, 4)
|
||||
check("main_loop", 5, 6)
|
||||
check("irq", 7, 8)
|
||||
|
||||
-- All four labels resolved to non-zero addresses. That covers:
|
||||
-- * Segment record parsed (CODE seg at $C000)
|
||||
-- * Sym records parsed (the four labels above are emitted by
|
||||
-- `linker::render_dbg` for every NEScript ROM)
|
||||
-- * Mesen's label name normalization matches our
|
||||
-- `mlb_symbol_name` filter
|
||||
emu.stop(0)
|
||||
Loading…
Add table
Add a link
Reference in a new issue