reznez 0.0.0

The high accuracy NES Emulator
Documentation
extern crate reznez;

use std::collections::{BTreeMap, BTreeSet};
use std::ffi::OsStr;
use std::fs;
use std::fs::File;
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};

use rayon::prelude::*;
use sscanf;
use walkdir::WalkDir;

use reznez::config::{Config, GuiType, Opt};
use reznez::nes::Nes;
use reznez::ppu::render::frame_rate::TargetFrameRate;
use reznez::ppu::render::ppm::Ppm;
use reznez::util::hash_util::calculate_hash;

#[test]
fn framematch() {
    let frame_directories: BTreeSet<_> = WalkDir::new("tests/expected_frames")
        .into_iter()
        .map(|entry| entry.unwrap().path().to_path_buf())
        .filter(|path| path.extension() == Some(OsStr::new("ppm")))
        .map(|path| path.parent().unwrap().to_path_buf())
        .collect();

    let failed = Arc::new(AtomicBool::new(false));
    frame_directories.par_iter().for_each(|frame_directory| {
        let mut rom_path_vec: Vec<_> = frame_directory.into_iter().collect();
        rom_path_vec[1] = OsStr::new("roms");
        let mut rom_path: PathBuf = rom_path_vec.into_iter().collect();
        rom_path.set_extension("nes");
        if File::open(rom_path.clone()).is_err() {
            // Some ROMs aren't committed due to copyright.
            return;
        }

        let mut frame_hashes = BTreeMap::new();
        for ppm_entry in fs::read_dir(frame_directory.clone()).unwrap() {
            let ppm_path = ppm_entry.unwrap().path();
            let ppm_file_name = ppm_path.file_name().unwrap().to_str().unwrap();
            let frame_index = sscanf::scanf!(ppm_file_name, "frame{}.ppm", u16);

            if let Some(frame_index) = frame_index {
                let ppm = Ppm::from_bytes(&fs::read(ppm_path).unwrap()).unwrap();
                let hash = calculate_hash(&ppm);
                frame_hashes.insert(frame_index, hash);
            }
        }

        if frame_hashes.is_empty() {
            return;
        }

        let opt = Opt {
            rom_path,
            gui: GuiType::NoGui,
            stop_frame: None,
            target_frame_rate: TargetFrameRate::Unbounded,
            disable_audio: true,
            log_frames: false,
            log_cpu_all: false,
            log_ppu_all: false,
            log_apu_all: false,
            log_cpu_instructions: false,
            log_cpu_flow_control: false,
            log_cpu_steps: false,
            log_ppu_stages: false,
            log_ppu_flags: false,
            log_ppu_steps: false,
            log_oam_addr: false,
            log_apu_cycles: false,
            log_apu_events: false,
            log_timings: false,
            frame_dump: false,
            analysis: false,
            disable_controllers: false,
        };

        let mut nes = Nes::new(&Config::new(&opt));
        nes.mute();
        let rom_name = frame_directory
            .file_stem()
            .unwrap()
            .to_str()
            .unwrap()
            .to_string();

        let max_frame_index = frame_hashes.keys().last().unwrap();
        for frame_index in 0..=*max_frame_index {
            nes.step_frame();
            if let Some(expected_hash) = frame_hashes.get(&frame_index) {
                let mask = nes.memory_mut().as_ppu_memory().regs().mask();
                let actual_ppm = &nes.frame().to_ppm(mask);
                let actual_hash = calculate_hash(&actual_ppm);
                if actual_hash != *expected_hash {
                    failed.store(true, Ordering::Relaxed);

                    let directory: PathBuf = frame_directory.components().skip(2).collect();
                    fs::create_dir_all(format!("tests/actual_frames/{}/", directory.display())).unwrap();
                    let actual_ppm_path =
                        format!("tests/actual_frames/{}/frame{:03}.ppm", directory.display(), frame_index);
                    fs::write(actual_ppm_path.clone(), actual_ppm.to_bytes()).unwrap();
                    println!(
                        "\t\tROM {} didn't match expected hash at frame {}. See '{}' .",
                        rom_name, frame_index, actual_ppm_path,
                    );
                }
            }
        }
    });

    assert!(!failed.load(Ordering::Relaxed));
}