The clock fix (INSTRUCTIONS_PER_MS 10_000 -> 1_000_000, commit 645feb8) moves
the worker-hub +66 ms render gate from ~660k to ~66M instructions, so the old
-n 50M anchor is now pre-render (swaps=0) and no longer guards boot rendering.
Re-anchor sylpheed_n50m -> sylpheed_n200m: at -n 200M the fixed build renders
steadily (draws=3165, swaps=895) and the stable digest is deterministic across
repeated inline lockstep runs. Renames the golden, updates the test's -n and
doc comment, and refreshes tests/golden/README.md. Per the README's re-baseline
policy (intentional digest move -> separate commit).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
114 lines
4.9 KiB
Rust
114 lines
4.9 KiB
Rust
//! Sylpheed boot-sequence regression oracles.
|
|
//!
|
|
//! These goldens trigger `xenia-rs check` against the Project Sylpheed ISO and
|
|
//! compare the resulting digest to a checked-in JSON file via `--stable-digest`,
|
|
//! which excludes timing-sensitive counters (`packets`, `interrupts_*`,
|
|
//! `resolves`, `texture_decodes`). The remaining fields are deterministic in
|
|
//! lockstep at a fixed instruction budget — verified empirically across 3
|
|
//! consecutive runs.
|
|
//!
|
|
//! Goldens are CIRCULAR per ORACBUG-001/002/003: they were captured by running
|
|
//! the same code they validate. Treat them as **regression anchors** (catch
|
|
//! drift from a known-good snapshot) not **correctness anchors** (no claim
|
|
//! about absolute behavior). When a planned fix intentionally moves the
|
|
//! digest (e.g. swap fix → `swaps` increments; renderer fix → `draws` becomes
|
|
//! non-zero), re-baseline the golden as a separate commit.
|
|
//!
|
|
//! Tests are `#[ignore]`-gated because the runs take ~4 seconds each, which
|
|
//! is unacceptable for the default `cargo test` cycle. Run explicitly:
|
|
//! cargo test --release -p xenia-app --test sylpheed_oracles -- --ignored --nocapture
|
|
//!
|
|
//! ISO path is read from the `SYLPHEED_ISO` env var, falling back to the
|
|
//! repo-relative default. CI/contributors without the ISO will see the test
|
|
//! skip gracefully.
|
|
|
|
use std::process::Command;
|
|
|
|
const ISO_DEFAULT: &str = "/home/fabi/RE - Project Sylpheed/Project Sylpheed - Arc of Deception (USA, Europe) (En,Ja).iso";
|
|
|
|
/// Resolve the Sylpheed ISO, in priority order:
|
|
/// 1. `SYLPHEED_ISO` env var (explicit override),
|
|
/// 2. the repo-root `sylpheed.iso` symlink (the documented per-machine
|
|
/// setup — see HANDOFF §0b), so a standard checkout runs without env vars,
|
|
/// 3. a last-resort absolute default.
|
|
/// Returns the first existing path, else the default string so the caller's
|
|
/// existence check still produces the "SKIPPING" message.
|
|
fn iso_path() -> String {
|
|
if let Ok(p) = std::env::var("SYLPHEED_ISO") {
|
|
return p;
|
|
}
|
|
// Repo root is two levels up from this crate's manifest dir.
|
|
let symlink = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../sylpheed.iso");
|
|
if symlink.exists() {
|
|
return symlink.to_string_lossy().into_owned();
|
|
}
|
|
ISO_DEFAULT.to_string()
|
|
}
|
|
|
|
fn run_oracle(label: &str, max_instr: u64, golden_rel: &str) {
|
|
let bin = env!("CARGO_BIN_EXE_xenia-rs");
|
|
let iso = iso_path();
|
|
if !std::path::Path::new(&iso).exists() {
|
|
eprintln!("{label}: iso not found at {iso}; set SYLPHEED_ISO to override. SKIPPING.");
|
|
return;
|
|
}
|
|
|
|
// Resolve the golden path relative to the test's CARGO_MANIFEST_DIR so the
|
|
// test runs correctly from any cwd.
|
|
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
|
let golden = std::path::Path::new(manifest_dir).join(golden_rel);
|
|
assert!(
|
|
golden.exists(),
|
|
"{label}: golden file missing at {}",
|
|
golden.display()
|
|
);
|
|
|
|
let max_instr_str = max_instr.to_string();
|
|
let golden_str = golden.to_string_lossy().to_string();
|
|
|
|
let out = Command::new(bin)
|
|
.args([
|
|
"check",
|
|
&iso,
|
|
"-n",
|
|
&max_instr_str,
|
|
// Pin the inline (single-threaded) GPU backend. The default
|
|
// threaded backend drains the ring on a separate host thread,
|
|
// so the exact instruction at which a CP interrupt is queued —
|
|
// and therefore when the guest's swap-complete ISR callback runs
|
|
// (iterate-2S armed it via SCRATCH_REG writeback) — varies run to
|
|
// run. Inline draining is instruction-count-deterministic, which
|
|
// is what a regression golden needs. (The threaded path is the
|
|
// documented "GPU thread race" the stable-digest already warns
|
|
// about.)
|
|
"--gpu-inline",
|
|
"--stable-digest",
|
|
"--expect",
|
|
&golden_str,
|
|
])
|
|
.output()
|
|
.expect("failed to spawn xenia-rs");
|
|
|
|
if !out.status.success() {
|
|
eprintln!(
|
|
"{label}: STDOUT:\n{}\nSTDERR:\n{}",
|
|
String::from_utf8_lossy(&out.stdout),
|
|
String::from_utf8_lossy(&out.stderr),
|
|
);
|
|
panic!("{label}: digest mismatch (exit {:?})", out.status.code());
|
|
}
|
|
}
|
|
|
|
/// Sylpheed boot to steady rendering, captured at -n 200M lockstep.
|
|
/// Catches regressions in: addi/addic semantics, kernel HLE for the VdSwap
|
|
/// path, thread spawning, file I/O for sound/config, and boot render
|
|
/// throughput. Re-anchored from 50M to 200M after the intro-video clock fix
|
|
/// (INSTRUCTIONS_PER_MS 10_000 -> 1_000_000): the worker-hub +66 ms render
|
|
/// gate now elapses at ~66M instr, so 50M is pre-render; by 200M boot is
|
|
/// rendering steadily (`draws` 3165, `swaps` 895).
|
|
#[test]
|
|
#[ignore = "long-running; run via `cargo test ... -- --ignored sylpheed_n200m`"]
|
|
fn sylpheed_n200m() {
|
|
run_oracle("sylpheed_n200m", 200_000_000, "tests/golden/sylpheed_n200m.json");
|
|
}
|