#![cfg(test)]
use super::cartridge::{Cartridge, CartridgeError};
use super::cpu::{Cpu, CpuRegisters, CpuState};
use super::memory::Bus;
use super::GameBoyConfig;
use std::path::Path;
enum ClockType {
InfiniteLoop,
Breakpoint,
FrameCount(usize),
}
macro_rules! gb_tests {
(inf; $($test_name: ident $(for $emu: ident)?, $file_path: expr, $dmg_crc: expr, $cgb_crc: expr;)*) => {
gb_tests!($($test_name $(for $emu)?, $file_path, $dmg_crc, $cgb_crc;)*, super::ClockType::InfiniteLoop);
};
(brk; $($test_name: ident $(for $emu: ident)?, $file_path: expr, $dmg_crc: expr, $cgb_crc: expr;)*) => {
gb_tests!($($test_name $(for $emu)?, $file_path, $dmg_crc, $cgb_crc;)*, super::ClockType::Breakpoint);
};
(frames $n: expr; $($test_name: ident $(for $emu: ident)?, $file_path: expr, $dmg_crc: expr, $cgb_crc: expr;)*) => {
gb_tests!($($test_name $(for $emu)?, $file_path, $dmg_crc, $cgb_crc;)*, super::ClockType::FrameCount($n));
};
($($test_name: ident $(for $emu: ident)?, $file_path: expr, $dmg_crc: expr, $cgb_crc: expr;)*, $clock_type: expr) => {
$(
#[test]
#[allow(unused_mut)]
fn $test_name() {
fn test(file_path: &str, is_dmg: bool, crc_checksum: u64) {
let mut gb = crate::tests::TestingGameBoy::new(file_path, is_dmg).unwrap();
match $clock_type {
super::ClockType::InfiniteLoop => {
gb.clock_until_infinte_loop();
}
super::ClockType::Breakpoint => {
gb.clock_until_breakpoint();
}
super::ClockType::FrameCount(frame_count) => {
for _ in 0..frame_count {
gb.clock_for_frame();
}
}
}
let screen_buffer = gb.raw_screen_buffer();
gb.print_screen_buffer();
assert_eq!(crc::Crc::<u64>::new(&crc::CRC_64_XZ).checksum(screen_buffer), crc_checksum);
}
let file_path = concat!("../test_roms/game-boy-test-roms/", $file_path);
let mut emu = String::new();
$(emu += stringify!($emu);)?
assert!(emu == "" || emu == "dmg" || emu == "cgb",
"emu parameter can only be \"dmg\" or \"cgb\"");
let is_dmg = true && emu != "cgb";
let is_cgb = true && emu != "dmg";
if is_dmg {
test(file_path, true, $dmg_crc);
}
if is_cgb {
test(file_path, false, $cgb_crc);
}
}
)*
};
}
mod blargg_tests;
mod gbmicrotest;
mod mooneye_tests;
mod samesuite_tests;
mod save_state_tests;
mod scribbltests;
mod small_tests;
#[derive(save_state::Savable)]
struct TestingGameBoy {
cpu: Cpu,
bus: Bus,
}
impl TestingGameBoy {
pub fn new<P: AsRef<Path>>(file_path: P, is_dmg: bool) -> Result<Self, CartridgeError> {
let cartridge = Cartridge::from_file::<_, String>(file_path, None, false)?;
let config = GameBoyConfig { is_dmg };
let is_cartridge_color = cartridge.is_cartridge_color();
Ok(Self {
bus: Bus::new_without_boot_rom(cartridge, config),
cpu: Cpu::new_without_boot_rom(config, is_cartridge_color),
})
}
pub fn raw_screen_buffer(&self) -> &[u8] {
self.bus.raw_screen_buffer()
}
pub fn print_screen_buffer(&self) {
let buffer = self.raw_screen_buffer();
const TV_WIDTH: u32 = 160;
const TV_HEIGHT: u32 = 144;
const BRIGHTNESS_ASCII: [char; 10] = ['@', '%', '#', '*', '+', '=', '-', ':', '.', ' '];
let mut i = 0;
let mut j = 0;
for pixel in buffer.chunks(3) {
assert_ne!(j, TV_HEIGHT);
let r = pixel[0] as f32;
let g = pixel[0] as f32;
let b = pixel[0] as f32;
let brightness = 0.2126 * r + 0.7152 * g + 0.0722 * b;
let brightness_index = (brightness / (31.0 / 9.0)).round() as usize;
print!("{}", BRIGHTNESS_ASCII[brightness_index]);
i += 1;
if i == TV_WIDTH {
j += 1;
i = 0;
println!();
}
}
println!();
}
pub fn clock_until_infinte_loop(&mut self) {
while self.cpu.next_instruction(&mut self.bus).unwrap() != CpuState::InfiniteLoop {}
}
pub fn clock_until_breakpoint(&mut self) -> CpuRegisters {
loop {
if let CpuState::Breakpoint(regs) = self.cpu.next_instruction(&mut self.bus).unwrap() {
return regs;
}
}
}
pub fn clock_for_frame(&mut self) {
const PPU_CYCLES_PER_FRAME: u32 = 456 * 154;
let mut cycles = 0u32;
while cycles < PPU_CYCLES_PER_FRAME {
self.cpu.next_instruction(&mut self.bus);
cycles += self.bus.elapsed_ppu_cycles();
}
}
}