Skip to content

Latest commit

 

History

History
312 lines (223 loc) · 6.2 KB

File metadata and controls

312 lines (223 loc) · 6.2 KB

Testing RustyNES

Table of Contents


Overview

RustyNES employs a comprehensive testing strategy combining unit tests, integration tests, and test ROM validation to ensure cycle-accurate emulation and compatibility.

Testing Goals

  • 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.md is the single source of truth for per-suite pass counts and the mapper-coverage matrix.


Test Suite Structure

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

Running Tests

All Tests

cargo test

Specific Test Suite

cargo test --test cpu_tests
cargo test --test ppu_tests
cargo test --test mapper_tests

Specific Test

cargo test test_adc_immediate
cargo test test_ppu_vblank_timing

Release Mode (Faster)

cargo test --release

With Output

cargo test -- --nocapture

Test ROMs

Core Test ROMs

nestest.nes (CPU validation):

cargo test --test nestest

Expected Result: All 8000+ instructions pass golden log comparison

blargg's Test Suite (CPU, APU, PPU):

cargo test --test blargg_suite

Tests:

  • cpu_exec_space
  • cpu_interrupts_v2
  • cpu_timing_test6
  • apu_test
  • ppu_vbl_nmi

Acquiring Test ROMs

Sources:

Installation:

mkdir -p tests/roms
cd tests/roms
# Download test ROMs
curl -O https://www.nesdev.org/nestest.nes

Unit Testing

CPU Tests

#[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));
}

PPU Tests

#[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));
}

Mapper Tests

#[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);
}

Integration Testing

System Integration

#[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);
}

NMI Timing

#[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);
}

Accuracy Testing

TASVideos Test Suite

Automated validation:

cargo test --test tas_accuracy_suite

Categories:

  • APU Tests (25)
  • CPU Tests (35)
  • PPU Tests (45)
  • Mapper Tests (51)

Regression Testing

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());
}

Continuous Integration

GitHub Actions Workflow

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

The 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), the no_std cross-build, RUSTDOCFLAGS=-D warnings cargo doc --workspace --no-deps, and a markdownlint v0.39.0 pinned gate.


References


Related Documents: