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