1
0
Fork 0
mirror of https://github.com/imjasonh/nescript synced 2026-07-08 17:06:04 +00:00
nescript/tests/mesen/probe.lua
Claude d075bc2c43
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
2026-04-17 01:03:23 +00:00

45 lines
1.8 KiB
Lua

-- 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)