tests: Improve cpu.rs line coverage to >99%
This commit is contained in:
		
							
								
								
									
										66
									
								
								src/cpu.rs
									
									
									
									
									
								
							
							
						
						
									
										66
									
								
								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::<String>(), | ||||
|             self.delay, | ||||
|             self.sound, | ||||
|             self.delay as u8, | ||||
|             self.sound as u8, | ||||
|             self.cycle, | ||||
|         ); | ||||
|     } | ||||
|   | ||||
| @@ -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); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										173
									
								
								src/cpu/tests/decode.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								src/cpu/tests/decode.rs
									
									
									
									
									
										Normal 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"); } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user