From ce0dc954d08bbc8c0c500e9284457bc96214f1e9 Mon Sep 17 00:00:00 2001 From: John Breaux Date: Tue, 28 Mar 2023 12:31:56 -0500 Subject: [PATCH] tests: Improve cpu.rs line coverage to >99% --- src/cpu.rs | 66 ++++++++++- src/cpu/tests.rs | 66 ++++++++++- src/cpu/tests/decode.rs | 173 ++++++++++++++++++++++++++++ tests/{struct.rs => integration.rs} | 28 ++++- 4 files changed, 327 insertions(+), 6 deletions(-) create mode 100644 src/cpu/tests/decode.rs rename tests/{struct.rs => integration.rs} (93%) diff --git a/src/cpu.rs b/src/cpu.rs index c213573..0cbf1fe 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -216,16 +216,32 @@ impl CPU { /// # use chirp::prelude::*; /// // Create a new CPU, and set v4 to 0x41 /// let mut cpu = CPU::default(); - /// cpu.set_gpr(0x4, 0x41); + /// cpu.set_v(0x4, 0x41); /// // Dump the CPU registers /// 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) { *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 /// # Examples /// ```rust @@ -240,6 +256,48 @@ impl CPU { 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 /// /// If cpu.flags.monotonic is Some, the cycle count will be @@ -655,8 +713,8 @@ impl CPU { ) }) .collect::(), - self.delay, - self.sound, + self.delay as u8, + self.sound as u8, self.cycle, ); } diff --git a/src/cpu/tests.rs b/src/cpu/tests.rs index 8eea761..90ac6e6 100644 --- a/src/cpu/tests.rs +++ b/src/cpu/tests.rs @@ -18,6 +18,8 @@ pub(self) use crate::{ bus::{Bus, Region::*}, }; +mod decode; + fn setup_environment() -> (CPU, Bus) { ( CPU { @@ -68,12 +70,19 @@ mod unimplemented { bus.write(0x200u16, 0x900fu16); // 0x800f is not an instruction 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 #[test] #[should_panic] fn ins_fxbb() { 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); } } @@ -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); + } + } +} diff --git a/src/cpu/tests/decode.rs b/src/cpu/tests/decode.rs new file mode 100644 index 0000000..f48c85b --- /dev/null +++ b/src/cpu/tests/decode.rs @@ -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"); } +} diff --git a/tests/struct.rs b/tests/integration.rs similarity index 93% rename from tests/struct.rs rename to tests/integration.rs index c7e62ec..2d7a6e0 100644 --- a/tests/struct.rs +++ b/tests/integration.rs @@ -1,4 +1,4 @@ -//! Testing methods on Chirp's structs +//! Testing methods on Chirp's public API use chirp::prelude::*; use std::{collections::hash_map::DefaultHasher, hash::Hash}; @@ -63,6 +63,7 @@ mod bus { mod cpu { use super::*; + #[test] fn set_break() { let mut cpu = CPU::default(); @@ -89,6 +90,31 @@ mod cpu { // Only unset the matching point 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 { use super::*; //#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]