- Overview
- Test Suite Structure
- Running Tests
- Test ROMs
- Unit Testing
- Integration Testing
- Accuracy Testing
- Continuous Integration
RustyNES employs a comprehensive testing strategy combining unit tests, integration tests, and test ROM validation to ensure cycle-accurate emulation and compatibility.
- AccuracyCoin 139/141 (98.58%) — the two newest upstream PPU tests ("ALE + Read", "Hybrid Addresses") are known gaps; the core held 139/139 byte-identically across every release before the v2.0.1 catalog re-sync grew it to 141 assigned tests
- Unit test coverage for all components
- Integration tests for component interactions
- Regression tests for mapper edge cases (168 mapper families)
- Game compatibility matrix validation
docs/STATUS.mdis the single source of truth for per-suite pass counts and the mapper-coverage matrix.
tests/
├── unit/ # Component-level tests
│ ├── cpu_tests.rs
│ ├── ppu_tests.rs
│ ├── apu_tests.rs
│ └── mapper_tests.rs
├── integration/ # Cross-component tests
│ ├── bus_tests.rs
│ └── system_tests.rs
├── roms/ # Test ROM files
│ ├── nestest.nes
│ ├── blargg/
│ ├── ppu_tests/
│ └── mapper_tests/
└── accuracy/ # Accuracy validation
└── tas_suite.rs
cargo testcargo test --test cpu_tests
cargo test --test ppu_tests
cargo test --test mapper_testscargo test test_adc_immediate
cargo test test_ppu_vblank_timingcargo test --releasecargo test -- --nocapturenestest.nes (CPU validation):
cargo test --test nestestExpected Result: All 8000+ instructions pass golden log comparison
blargg's Test Suite (CPU, APU, PPU):
cargo test --test blargg_suiteTests:
- cpu_exec_space
- cpu_interrupts_v2
- cpu_timing_test6
- apu_test
- ppu_vbl_nmi
Sources:
Installation:
mkdir -p tests/roms
cd tests/roms
# Download test ROMs
curl -O https://www.nesdev.org/nestest.nes#[test]
fn test_adc_immediate() {
let mut cpu = Cpu::new();
let mut bus = MockBus::new();
cpu.a = 0x50;
cpu.p.remove(Status::CARRY);
// ADC #$10
bus.write(0x0000, 0x69); // ADC immediate
bus.write(0x0001, 0x10);
cpu.execute_instruction(&mut bus);
assert_eq!(cpu.a, 0x60);
assert!(!cpu.p.contains(Status::CARRY));
assert!(!cpu.p.contains(Status::ZERO));
assert!(!cpu.p.contains(Status::NEGATIVE));
}#[test]
fn test_vblank_flag_set() {
let mut ppu = Ppu::new();
// Run to scanline 241 (VBlank start)
for _ in 0..241 {
for _ in 0..341 {
ppu.step();
}
}
assert!(ppu.status.contains(PpuStatus::VBLANK));
}#[test]
fn test_uxrom_bank_switching() {
let rom = create_test_rom(8, 0); // 8 PRG banks
let mut mapper = UxROM::new(rom, 0);
mapper.write_prg(0x8000, 0x05);
assert_eq!(mapper.prg_bank, 5);
let addr = mapper.map_prg_addr(0x8000);
assert_eq!(addr, 5 * 0x4000);
}#[test]
fn test_cpu_ppu_timing_sync() {
let mut console = Console::new(load_test_rom());
for _ in 0..10 {
console.step(); // Execute one CPU instruction
}
// Verify PPU ran 3x as many cycles
assert_eq!(console.ppu.cycles, console.cpu.cycles * 3);
}#[test]
fn test_nmi_generation() {
let mut console = Console::new(load_test_rom());
// Run until VBlank
console.step_frame();
// NMI should be pending
assert!(console.cpu.nmi_pending);
}Automated validation:
cargo test --test tas_accuracy_suiteCategories:
- APU Tests (25)
- CPU Tests (35)
- PPU Tests (45)
- Mapper Tests (51)
Add new regression test:
#[test]
fn test_regression_sprite_zero_hit_timing() {
// Specific case that previously failed
let mut console = Console::new(load_rom("sprite_hit_test.nes"));
console.step_frame();
assert!(console.ppu.sprite_zero_hit_occurred());
}name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Run tests
# NEVER --all-features: the `scripting` (mlua) and `script-wasm` (piccolo)
# backends are mutually exclusive. Use explicit feature sets.
run: cargo test --workspace
- name: Run test ROMs
run: cargo test --workspace --features test-romsThe real CI workflows live in
.github/workflows/(ci.yml,release.yml,web.yml,android.yml). CI runs the feature-combo clippy gates (scripting,hd-pack,retroachievements, both wasm flavours), theno_stdcross-build,RUSTDOCFLAGS=-D warnings cargo doc --workspace --no-deps, and a markdownlint v0.39.0 pinned gate.
Related Documents:
- BUILD.md - Building the project
- CONTRIBUTING.md - Development workflow
- DEBUGGING.md - Debugging failing tests