tests: Improve cpu.rs line coverage to >99%

This commit is contained in:
John 2023-03-28 12:31:56 -05:00
parent 83c178413d
commit ce0dc954d0
4 changed files with 327 additions and 6 deletions

View File

@ -216,16 +216,32 @@ impl CPU {
/// # use chirp::prelude::*; /// # use chirp::prelude::*;
/// // Create a new CPU, and set v4 to 0x41 /// // Create a new CPU, and set v4 to 0x41
/// let mut cpu = CPU::default(); /// let mut cpu = CPU::default();
/// cpu.set_gpr(0x4, 0x41); /// cpu.set_v(0x4, 0x41);
/// // Dump the CPU registers /// // Dump the CPU registers
/// cpu.dump(); /// cpu.dump();
/// ``` /// ```
pub fn set_gpr(&mut self, gpr: Reg, value: u8) { pub fn set_v(&mut self, gpr: Reg, value: u8) {
if let Some(gpr) = self.v.get_mut(gpr) { if let Some(gpr) = self.v.get_mut(gpr) {
*gpr = value; *gpr = value;
} }
} }
/// Gets a slice of the entire general purpose register field
/// # Examples
/// ```rust
/// # use chirp::prelude::*;
/// // Create a new CPU, and set v4 to 0x41
/// let mut cpu = CPU::default();
/// cpu.set_v(0x0, 0x41);
/// assert_eq!(
/// cpu.v(),
/// [0x41, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
/// )
/// ```
pub fn v(&self) -> &[u8] {
self.v.as_slice()
}
/// Gets the program counter /// Gets the program counter
/// # Examples /// # Examples
/// ```rust /// ```rust
@ -240,6 +256,48 @@ impl CPU {
self.pc self.pc
} }
/// Gets the I register
/// # Examples
/// ```rust
///# use chirp::prelude::*;
///# fn main() -> Result<()> {
/// let mut cpu = CPU::default();
/// assert_eq!(0, cpu.i());
///# Ok(())
///# }
/// ```
pub fn i(&self) -> Adr {
self.i
}
/// Gets the value in the Sound Timer register
/// # Examples
/// ```rust
///# use chirp::prelude::*;
///# fn main() -> Result<()> {
/// let mut cpu = CPU::default();
/// assert_eq!(0, cpu.sound());
///# Ok(())
///# }
/// ```
pub fn sound(&self) -> u8 {
self.sound as u8
}
/// Gets the value in the Delay Timer register
/// # Examples
/// ```rust
///# use chirp::prelude::*;
///# fn main() -> Result<()> {
/// let mut cpu = CPU::default();
/// assert_eq!(0, cpu.delay());
///# Ok(())
///# }
/// ```
pub fn delay(&self) -> u8 {
self.delay as u8
}
/// Gets the number of cycles the CPU has executed /// Gets the number of cycles the CPU has executed
/// ///
/// If cpu.flags.monotonic is Some, the cycle count will be /// If cpu.flags.monotonic is Some, the cycle count will be
@ -655,8 +713,8 @@ impl CPU {
) )
}) })
.collect::<String>(), .collect::<String>(),
self.delay, self.delay as u8,
self.sound, self.sound as u8,
self.cycle, self.cycle,
); );
} }

View File

@ -18,6 +18,8 @@ pub(self) use crate::{
bus::{Bus, Region::*}, bus::{Bus, Region::*},
}; };
mod decode;
fn setup_environment() -> (CPU, Bus) { fn setup_environment() -> (CPU, Bus) {
( (
CPU { CPU {
@ -68,12 +70,19 @@ mod unimplemented {
bus.write(0x200u16, 0x900fu16); // 0x800f is not an instruction bus.write(0x200u16, 0x900fu16); // 0x800f is not an instruction
cpu.tick(&mut bus); cpu.tick(&mut bus);
} }
#[test]
#[should_panic]
fn ins_exbb() {
let (mut cpu, mut bus) = setup_environment();
bus.write(0x200u16, 0xe00fu16); // 0xe00f is not an instruction
cpu.tick(&mut bus);
}
// Fxbb // Fxbb
#[test] #[test]
#[should_panic] #[should_panic]
fn ins_fxbb() { fn ins_fxbb() {
let (mut cpu, mut bus) = setup_environment(); let (mut cpu, mut bus) = setup_environment();
bus.write(0x200u16, 0xffffu16); // 0xffff is not an instruction bus.write(0x200u16, 0xf00fu16); // 0xf00f is not an instruction
cpu.tick(&mut bus); cpu.tick(&mut bus);
} }
} }
@ -917,3 +926,58 @@ mod io {
} }
} }
} }
mod behavior {
use super::*;
mod realtime {
use super::*;
use std::time::Duration;
#[test]
fn delay() {
let (mut cpu, mut bus) = setup_environment();
cpu.flags.monotonic = None;
cpu.delay = 10.0;
for _ in 0..2 {
cpu.multistep(&mut bus, 8);
std::thread::sleep(Duration::from_secs_f64(1.0 / 60.0));
}
// time is within 1 frame deviance over a theoretical 2 frame pause
assert!(7.0 <= cpu.delay && cpu.delay <= 9.0);
}
#[test]
fn sound() {
let (mut cpu, mut bus) = setup_environment();
cpu.flags.monotonic = None; // disable monotonic timing
cpu.sound = 10.0;
for _ in 0..2 {
cpu.multistep(&mut bus, 8);
std::thread::sleep(Duration::from_secs_f64(1.0 / 60.0));
}
// time is within 1 frame deviance over a theoretical 2 frame pause
assert!(7.0 <= cpu.sound && cpu.sound <= 9.0);
}
#[test]
fn vbi_wait() {
let (mut cpu, mut bus) = setup_environment();
cpu.flags.monotonic = None; // disable monotonic timing
cpu.flags.vbi_wait = true;
for _ in 0..2 {
cpu.multistep(&mut bus, 8);
std::thread::sleep(Duration::from_secs_f64(1.0 / 60.0));
}
// Display wait is disabled after a 1 frame pause
assert_eq!(false, cpu.flags.vbi_wait);
}
}
mod breakpoint {
use super::*;
#[test]
fn hit_break() {
let (mut cpu, mut bus) = setup_environment();
cpu.set_break(0x202);
cpu.multistep(&mut bus, 10);
assert_eq!(true, cpu.flags.pause);
assert_eq!(0x202, cpu.pc);
}
}
}

173
src/cpu/tests/decode.rs Normal file
View File

@ -0,0 +1,173 @@
//! Exercises the instruction decode logic.
use super::*;
const INDX: &[u8; 16] = b"\0\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f";
/// runs one arbitrary operation on a brand new CPU
/// returns the CPU for inspection
fn run_single_op(op: &[u8]) -> CPU {
let (mut cpu, mut bus) = (
CPU::default(),
bus! {
Program[0x200..0x240] = op,
},
);
cpu.v = *INDX;
cpu.flags.quirks = Quirks::from(true);
cpu.tick(&mut bus); // will panic if unimplemented
cpu
}
#[rustfmt::skip]
mod sys {
use super::*;
#[test] fn cls() { run_single_op(b"\x00\xe0"); }
#[test] fn ret() { run_single_op(b"\x00\xee"); }
#[test] #[should_panic] fn u0420() { run_single_op(b"\x04\x20"); }
}
#[rustfmt::skip]
mod jump {
use super::*;
#[test] fn aligned() { assert_eq!(0x230, run_single_op(b"\x12\x30").pc); }
#[test] fn unaligned() { assert_eq!(0x231, run_single_op(b"\x12\x31").pc); }
}
#[rustfmt::skip]
mod call {
use super::*;
#[test] fn aligned() { assert_eq!(0x230, run_single_op(b"\x22\x30").pc); }
#[test] fn unaligned() { assert_eq!(0x231, run_single_op(b"\x22\x31").pc); }
}
#[rustfmt::skip]
mod skeb {
use super::*;
#[test] fn skip() { assert_eq!(0x204, run_single_op(b"\x30\x00").pc); }
#[test] fn no_skip() { assert_eq!(0x202, run_single_op(b"\x30\x01").pc); }
}
#[rustfmt::skip]
mod sneb {
use super::*;
#[test] fn skip() { assert_eq!(0x204, run_single_op(b"\x40\x01").pc); }
#[test] fn noskip() { assert_eq!(0x202, run_single_op(b"\x40\x00").pc); }
}
#[rustfmt::skip]
mod se {
use super::*;
#[test] fn skip() { assert_eq!(0x204, run_single_op(b"\x50\x00").pc); }
#[test] fn noskip() { assert_eq!(0x202, run_single_op(b"\x50\x10").pc); }
#[test] #[should_panic] fn u5ff1() { run_single_op(b"\x5f\xf1"); }
#[test] #[should_panic] fn u5ff2() { run_single_op(b"\x5f\xf2"); }
#[test] #[should_panic] fn u5ff3() { run_single_op(b"\x5f\xf3"); }
#[test] #[should_panic] fn u5ff4() { run_single_op(b"\x5f\xf4"); }
#[test] #[should_panic] fn u5ff5() { run_single_op(b"\x5f\xf5"); }
#[test] #[should_panic] fn u5ff6() { run_single_op(b"\x5f\xf6"); }
#[test] #[should_panic] fn u5ff7() { run_single_op(b"\x5f\xf7"); }
#[test] #[should_panic] fn u5ff8() { run_single_op(b"\x5f\xf8"); }
#[test] #[should_panic] fn u5ff9() { run_single_op(b"\x5f\xf9"); }
#[test] #[should_panic] fn u5ffa() { run_single_op(b"\x5f\xfa"); }
#[test] #[should_panic] fn u5ffb() { run_single_op(b"\x5f\xfb"); }
#[test] #[should_panic] fn u5ffc() { run_single_op(b"\x5f\xfc"); }
#[test] #[should_panic] fn u5ffd() { run_single_op(b"\x5f\xfd"); }
#[test] #[should_panic] fn u5ffe() { run_single_op(b"\x5f\xfe"); }
#[test] #[should_panic] fn u5fff() { run_single_op(b"\x5f\xff"); }
}
#[rustfmt::skip]
mod mov {
use super::*;
#[test] fn w00() { assert_eq!(0x00, run_single_op(b"\x61\x00").v[1]); }
#[test] fn wc5() { assert_eq!(0xc5, run_single_op(b"\x62\xc5").v[2]); }
#[test] fn wff() { assert_eq!(0xff, run_single_op(b"\x63\xff").v[3]); }
}
#[rustfmt::skip]
mod add {
use super::*;
#[test] fn p00() { assert_eq!(0x01, run_single_op(b"\x71\x00").v[1]); }
#[test] fn pc5() { assert_eq!(0xc7, run_single_op(b"\x72\xc5").v[2]); }
#[test] fn pff() { assert_eq!(0x02, run_single_op(b"\x73\xff").v[3]); }
}
#[rustfmt::skip]
mod alu {
use super::*;
#[test] fn mov() { assert_eq!(0x02, run_single_op(b"\x81\x20").v[1]); }
#[test] fn or() { assert_eq!(0x03, run_single_op(b"\x81\x21").v[1]); }
#[test] fn and() { assert_eq!(0x00, run_single_op(b"\x81\x22").v[1]); }
#[test] fn xor() { assert_eq!(0x03, run_single_op(b"\x81\x23").v[1]); }
#[test] fn add() { assert_eq!(0x03, run_single_op(b"\x81\x24").v[1]); }
#[test] fn sub() { assert_eq!(0xff, run_single_op(b"\x81\x25").v[1]); }
#[test] fn shr() { assert_eq!(0x01, run_single_op(b"\x81\x26").v[1]); }
#[test] fn bsub() { assert_eq!(0x01, run_single_op(b"\x81\x27").v[1]); }
#[test] #[should_panic] fn u8128() { run_single_op(b"\x81\x28"); }
#[test] #[should_panic] fn u8129() { run_single_op(b"\x81\x29"); }
#[test] #[should_panic] fn u812a() { run_single_op(b"\x81\x2a"); }
#[test] #[should_panic] fn u812b() { run_single_op(b"\x81\x2b"); }
#[test] #[should_panic] fn u812c() { run_single_op(b"\x81\x2c"); }
#[test] #[should_panic] fn u812d() { run_single_op(b"\x81\x2d"); }
#[test] fn shl() { assert_eq!(0x04, run_single_op(b"\x81\x2e").v[1]); }
#[test] #[should_panic] fn u812f() { run_single_op(b"\x81\x2f"); }
}
#[rustfmt::skip]
mod sne {
use super::*;
#[test] fn skip() { assert_eq!(0x204, run_single_op(b"\x90\x10").pc); }
#[test] fn no_skip() { assert_eq!(0x202, run_single_op(b"\x90\x00").pc); }
#[test] #[should_panic] fn u9ff1() { run_single_op(b"\x9f\xf1"); }
#[test] #[should_panic] fn u9ff2() { run_single_op(b"\x9f\xf2"); }
#[test] #[should_panic] fn u9ff3() { run_single_op(b"\x9f\xf3"); }
#[test] #[should_panic] fn u9ff4() { run_single_op(b"\x9f\xf4"); }
#[test] #[should_panic] fn u9ff5() { run_single_op(b"\x9f\xf5"); }
#[test] #[should_panic] fn u9ff6() { run_single_op(b"\x9f\xf6"); }
#[test] #[should_panic] fn u9ff7() { run_single_op(b"\x9f\xf7"); }
#[test] #[should_panic] fn u9ff8() { run_single_op(b"\x9f\xf8"); }
#[test] #[should_panic] fn u9ff9() { run_single_op(b"\x9f\xf9"); }
#[test] #[should_panic] fn u9ffa() { run_single_op(b"\x9f\xfa"); }
#[test] #[should_panic] fn u9ffb() { run_single_op(b"\x9f\xfb"); }
#[test] #[should_panic] fn u9ffc() { run_single_op(b"\x9f\xfc"); }
#[test] #[should_panic] fn u9ffd() { run_single_op(b"\x9f\xfd"); }
#[test] #[should_panic] fn u9ffe() { run_single_op(b"\x9f\xfe"); }
#[test] #[should_panic] fn u9fff() { run_single_op(b"\x9f\xff"); }
}
#[rustfmt::skip]
mod movi {
use super::*;
#[test] fn aligned() { assert_eq!(0x230, run_single_op(b"\xa2\x30").i()); }
#[test] fn unaligned() { assert_eq!(0x231, run_single_op(b"\xa2\x31").i()); }
}
#[rustfmt::skip]
mod jmpr {
use super::*;
#[test] fn aligned() { assert_eq!(0x230, run_single_op(b"\xb2\x30").pc); }
#[test] fn unaligned() { assert_eq!(0x231, run_single_op(b"\xb2\x31").pc); }
}
#[rustfmt::skip]
mod rand {
use super::*;
// for exhaustive testing, see src/cpu/tests.rs
#[test] fn rand() { assert!(run_single_op(b"\xc0\x01").v[0] <= 1); }
}
#[rustfmt::skip]
mod draw {
use super::*;
#[test] fn draw() { run_single_op(b"\xd0\x0f"); }
}
#[rustfmt::skip]
mod key {
use super::*;
#[test] fn skip_key_equals() { assert_eq!(0x202, run_single_op(b"\xe0\x9e").pc); }
#[test] fn skip_key_not_equals() { assert_eq!(0x204, run_single_op(b"\xe0\xa1").pc); }
#[test] #[should_panic] fn uefff() { run_single_op(b"\xef\xff"); }
}
#[rustfmt::skip]
mod io {
use super::*;
#[test] fn load_delay_timer() { assert_eq!(0x0, run_single_op(b"\xf7\x07").v[7]); }
#[test] fn wait_for_key() { assert!(run_single_op(b"\xf0\x0a").flags.keypause); }
#[test] fn store_delay_timer() { assert_eq!(0xf, run_single_op(b"\xff\x15").delay()); }
#[test] fn store_sound_timer() { assert_eq!(0xf, run_single_op(b"\xff\x18").sound()); }
#[test] fn add_i() { assert_eq!(0x0, run_single_op(b"\xf0\x1e").i); }
#[test] fn load_sprite() { assert_eq!(0x50, run_single_op(b"\xf0\x29").i); }
#[test] fn bcd_convert() { run_single_op(b"\xf0\x33"); /* nothing to check */ }
#[test] fn store_dma() { assert_eq!(INDX, run_single_op(b"\xff\x55").v()); }
#[test] fn load_dma() { assert_eq!([0;16], run_single_op(b"\xff\x65").v()); }
// unimplemented
#[test] #[should_panic] fn uffff() { run_single_op(b"\xff\xff"); }
}

View File

@ -1,4 +1,4 @@
//! Testing methods on Chirp's structs //! Testing methods on Chirp's public API
use chirp::prelude::*; use chirp::prelude::*;
use std::{collections::hash_map::DefaultHasher, hash::Hash}; use std::{collections::hash_map::DefaultHasher, hash::Hash};
@ -63,6 +63,7 @@ mod bus {
mod cpu { mod cpu {
use super::*; use super::*;
#[test] #[test]
fn set_break() { fn set_break() {
let mut cpu = CPU::default(); let mut cpu = CPU::default();
@ -89,6 +90,31 @@ mod cpu {
// Only unset the matching point // Only unset the matching point
assert_eq!(cpu.breakpoints(), &[point + 1]); assert_eq!(cpu.breakpoints(), &[point + 1]);
} }
#[test]
fn press_invalid_key() {
let mut cpu = CPU::default();
let cpu2 = cpu.clone();
cpu.press(0x21345134);
// no change has been made
assert_eq!(cpu, cpu2);
}
#[test]
fn release_invalid_key() {
let mut cpu = CPU::default();
let cpu2 = cpu.clone();
cpu.release(0x21345134);
// no change has been made
assert_eq!(cpu, cpu2);
}
#[test]
fn set_invalid_gpr() {
let mut cpu = CPU::default();
let cpu2 = cpu.clone();
cpu.set_v(0x21345134, 0xff);
// no change has been made
assert_eq!(cpu, cpu2);
}
mod controlflags { mod controlflags {
use super::*; use super::*;
//#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] //#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]