feat(testing): add bounded headless ROM suites and JSON reports
Read strict versioned suite manifests with independent ROM cases, explicit ARM/Thumb checkpoints, register/CPSR/memory assertions, and aggregate step limits. Report every case deterministically and fail on timeouts, STOP, diagnostics, missing files, or mismatches. Keep host JSON dependencies outside the core. Add an original ARM/Thumb/BIOS suite generator, unit and process-level regressions, and schema/usage documentation. Verified on Darwin arm64 with debug/release workspace and core suites, Clippy, rustdoc, native ROM-window tests, all ten demo smoke tests, repeatable release reports, and a deliberate assertion failure. Independent public hardware-test ROM validation remains next.
This commit is contained in:
parent
109c1ffe81
commit
da700671ad
16 changed files with 1162 additions and 3 deletions
78
crates/desktop/examples/write_test_suite.rs
Normal file
78
crates/desktop/examples/write_test_suite.rs
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
//! Export original ROM instructions and a reproducible smoke-test manifest.
|
||||
//! The destination directory must not already exist. No Nintendo content is used.
|
||||
use serde_json::json;
|
||||
use std::{
|
||||
error::Error,
|
||||
fs::{self, OpenOptions},
|
||||
io::{self, Write},
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
fn words(code: &[u32]) -> Vec<u8> {
|
||||
code.iter().flat_map(|word| word.to_le_bytes()).collect()
|
||||
}
|
||||
fn write_new(path: &Path, bytes: &[u8]) -> io::Result<()> {
|
||||
let mut file = OpenOptions::new().write(true).create_new(true).open(path)?;
|
||||
file.write_all(bytes)?;
|
||||
file.flush()
|
||||
}
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let invalid = || {
|
||||
io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"usage: write_test_suite NEW_DIRECTORY",
|
||||
)
|
||||
};
|
||||
let mut args = std::env::args_os().skip(1);
|
||||
let directory = PathBuf::from(
|
||||
args.next()
|
||||
.filter(|path| !path.is_empty())
|
||||
.ok_or_else(invalid)?,
|
||||
);
|
||||
if args.next().is_some() {
|
||||
return Err(invalid().into());
|
||||
}
|
||||
fs::create_dir(&directory)?;
|
||||
write_new(
|
||||
&directory.join("arm.gba"),
|
||||
&words(&[
|
||||
0xe3a0_002a, // MOV r0,#42
|
||||
0xe3a0_1402, // MOV r1,#0x02000000
|
||||
0xe581_0000, // STR r0,[r1]
|
||||
0xeaff_fffe, // completion: B .
|
||||
]),
|
||||
)?;
|
||||
let mut thumb = words(&[0xe59f_1000, 0xe12f_ff11, 0x0800_000d]); // LDR r1,literal; BX r1
|
||||
thumb.extend([0x2a, 0x20, 0xfe, 0xe7]); // Thumb MOV r0,#42; completion: B .
|
||||
write_new(&directory.join("thumb.gba"), &thumb)?;
|
||||
write_new(
|
||||
&directory.join("division.gba"),
|
||||
&words(&[
|
||||
0xe3a0_002a, // MOV r0,#42
|
||||
0xe3a0_1005, // MOV r1,#5
|
||||
0xef06_0000, // SWI Div (original BIOS replacement)
|
||||
0xeaff_fffe, // completion: B .
|
||||
]),
|
||||
)?;
|
||||
let manifest = json!({"version":1,"cases":[
|
||||
{"name":"arm-store","rom":"arm.gba","step_limit":10000,
|
||||
"completion":{"pc":"0x0800000c","instruction_set":"arm"},
|
||||
"checks":[{"kind":"register","index":0,"equals":42},
|
||||
{"kind":"memory32","address":"0x02000000","equals":42},
|
||||
{"kind":"cpsr","equals":"0x1f"}]},
|
||||
{"name":"thumb-move","rom":"thumb.gba","step_limit":10000,
|
||||
"completion":{"pc":"0x0800000e","instruction_set":"thumb"},
|
||||
"checks":[{"kind":"register","index":0,"equals":42}]},
|
||||
{"name":"bios-division","rom":"division.gba","step_limit":10000,
|
||||
"completion":{"pc":"0x0800000c","instruction_set":"arm"},
|
||||
"checks":[{"kind":"register","index":0,"equals":8},
|
||||
{"kind":"register","index":1,"equals":2},
|
||||
{"kind":"register","index":3,"equals":8}]}
|
||||
]});
|
||||
write_new(
|
||||
&directory.join("suite.json"),
|
||||
&serde_json::to_vec_pretty(&manifest)?,
|
||||
)?;
|
||||
println!("Wrote original ARM, Thumb, and BIOS test ROMs with suite.json in {directory:?}");
|
||||
Ok(())
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue