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
120
.github/workflows/mesen.yml
vendored
Normal file
120
.github/workflows/mesen.yml
vendored
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
name: Mesen .dbg Smoke Test
|
||||
|
||||
# Validates that the `.dbg` debug-info file `nescript build --dbg` emits
|
||||
# is actually consumable by Mesen2 — the same emulator NES developers
|
||||
# use for source-level debugging. The job:
|
||||
# 1. Builds the compiler.
|
||||
# 2. Builds an example ROM with `--dbg`.
|
||||
# 3. Launches the Mesen2 release binary in `--testRunner` mode.
|
||||
# 4. Mesen auto-loads the co-located .dbg, runs `tests/mesen/probe.lua`,
|
||||
# and exits with a status code the script picks.
|
||||
# 5. CI fails if any code label we promised is missing or unresolved.
|
||||
#
|
||||
# This is a separate workflow from `ci.yml` because it depends on the
|
||||
# Mesen2 binary download (40 MB) + xvfb + a couple of Mesen-specific
|
||||
# environment workarounds that aren't worth threading through the main
|
||||
# CI matrix.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTFLAGS: "-D warnings"
|
||||
# Pin the Mesen2 release we test against. Bump this when the upstream
|
||||
# binary's behaviour changes in a way that affects our probe (new
|
||||
# label-name normalisation, .dbg format additions, etc.). See
|
||||
# https://github.com/SourMesen/Mesen2/releases for the source URLs.
|
||||
MESEN_VERSION: "2.1.1"
|
||||
|
||||
jobs:
|
||||
mesen-dbg:
|
||||
name: Mesen2 .dbg validation
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Install Mesen runtime deps
|
||||
# libsdl2-2.0-0: Mesen links to it for audio/video even though
|
||||
# we're running headless. Without it MesenCore.so fails to
|
||||
# load with `cannot open shared object file: libSDL2-2.0.so.0`.
|
||||
# xvfb: Mesen's Avalonia UI initialises an X11 connection at
|
||||
# startup before checking `--testRunner`, so even the headless
|
||||
# path needs *some* display.
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends \
|
||||
libsdl2-2.0-0 \
|
||||
xvfb
|
||||
|
||||
- name: Cache Mesen2 binary
|
||||
id: mesen-cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: mesen2
|
||||
key: mesen2-${{ env.MESEN_VERSION }}-linux-x64
|
||||
|
||||
- name: Download Mesen2
|
||||
if: steps.mesen-cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
mkdir -p mesen2
|
||||
cd mesen2
|
||||
curl -sL -o Mesen.zip \
|
||||
"https://github.com/SourMesen/Mesen2/releases/download/${MESEN_VERSION}/Mesen_${MESEN_VERSION}_Linux_x64.zip"
|
||||
unzip -q Mesen.zip
|
||||
chmod +x Mesen
|
||||
rm Mesen.zip
|
||||
|
||||
- name: Build NEScript
|
||||
run: cargo build --release
|
||||
|
||||
- name: Build test ROM with .dbg
|
||||
# `hello_sprite` is the smallest example with a state handler,
|
||||
# so its .dbg exercises every record kind render_dbg emits
|
||||
# (file, mod, seg, scope, span, line, sym) without a long
|
||||
# compile.
|
||||
run: |
|
||||
./target/release/nescript build examples/hello_sprite.ne \
|
||||
--dbg /tmp/hello.dbg \
|
||||
--output /tmp/hello.nes
|
||||
|
||||
- name: Validate .dbg via Mesen2 testRunner
|
||||
# Three workarounds are baked into this single command — see the
|
||||
# comments inside for *why* each is needed. Removing any of them
|
||||
# turns this into a multi-hour debugging exercise (it took
|
||||
# several hours of investigation to find the GLOBALIZATION
|
||||
# workaround in particular; see commit message of the change
|
||||
# that added this workflow).
|
||||
run: |
|
||||
# 1. `touch settings.json`: Mesen's first-run check
|
||||
# (`Program.cs:56`) launches the GUI setup wizard
|
||||
# *before* the `--testRunner` dispatch when no settings
|
||||
# file exists. The wizard never closes in headless CI,
|
||||
# blocking forever. The file's *contents* don't matter:
|
||||
# Configuration.Deserialize catches any JSON parse error
|
||||
# and falls back to defaults, so a 0-byte file is fine.
|
||||
touch mesen2/settings.json
|
||||
|
||||
# 2. `DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1`: .NET's
|
||||
# globalization layer pulls in libicuuc, which loads
|
||||
# system libstdc++. MesenCore.so is built with a
|
||||
# statically-bundled libstdc++; on Ubuntu 24.04 the
|
||||
# interposition causes a `std::bad_cast` from a static
|
||||
# regex initialiser in `Base6502Assembler.cpp` long
|
||||
# before any user code runs. Invariant mode skips ICU
|
||||
# entirely, which means system libstdc++ is never
|
||||
# loaded and MesenCore's bundled copy wins.
|
||||
#
|
||||
# 3. `xvfb-run -a`: Mesen's Avalonia UI runs `XOpenDisplay`
|
||||
# during AppBuilder setup before the `--testRunner`
|
||||
# dispatch in `Program.cs:74`. xvfb-run gives it a
|
||||
# throwaway X server; -a auto-allocates a display number.
|
||||
DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 \
|
||||
xvfb-run -a ./mesen2/Mesen \
|
||||
--testRunner /tmp/hello.nes tests/mesen/probe.lua \
|
||||
--timeout=15
|
||||
Loading…
Add table
Add a link
Reference in a new issue