trevors_chip8_toy/gemma/tests/unit_tests.rs
2025-05-28 11:32:09 -04:00

1598 lines
46 KiB
Rust

use std::fs::File;
use std::io::Read;
use gemma::chip8::computer::Chip8Computer;
use gemma::chip8::cpu_states::Chip8CpuStates::WaitingForInstruction;
use gemma::chip8::delay_timer::DelayTimer;
use gemma::chip8::instructions::Chip8CpuInstructions;
use gemma::chip8::instructions::Chip8CpuInstructions::JPX;
use gemma::chip8::keypad::Keypad;
use gemma::chip8::quirk_modes::QuirkMode::{self, Chip8, SChipModern, XOChip};
use gemma::chip8::registers::Chip8Registers;
use gemma::chip8::sound_timer::SoundTimer;
use gemma::chip8::stack::Chip8Stack;
use gemma::chip8::system_memory::Chip8SystemMemory;
use gemma::chip8::util::InstructionUtil;
use gemma::chip8::video::{Chip8Video, Chip8VideoModes};
use gemma::constants::*;
use log::debug;
use rand::random;
use serde::Serialize;
use gemma::chip8::computer_manager::Chip8ComputerManager;
use crate::test_utils::read_test_result;
mod test_utils;
#[test]
fn smoke() {
assert!(true)
}
#[test]
#[ignore]
fn decoder_test_invalid_instructions() {
let invalid_to_encode = [
0x5ab1, 0x5abf, 0x8ab8, 0x8abd, 0x8abf, 0x9ab1, 0x9abf, 0xea9d, 0xea9f, 0xeaa0, 0xeaa2,
0xf006, 0xf008,
];
for i in invalid_to_encode {
assert_eq!(Chip8CpuInstructions::decode(i, &Chip8).encode(), XXXXERRORINSTRUCTION_ENCODED);
assert!(matches!(
Chip8CpuInstructions::decode(i, &Chip8),
Chip8CpuInstructions::XXXXERRORINSTRUCTION
));
}
}
/// START OF THE EXECUTION TESTS
#[test]
fn instruction_tests() {
// 0x0nnn Exit to System Call
let mut x = Chip8Computer::new();
Chip8CpuInstructions::SYS(0).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0);
let mut x = Chip8Computer::new();
Chip8CpuInstructions::SYS(0xFA0).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0xFA0);
let mut x = Chip8Computer::new();
Chip8CpuInstructions::SYS(0x0AF).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x0AF);
// 0x1nnn Jump to Address
let mut x = Chip8Computer::new();
Chip8CpuInstructions::JPA(0).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0);
let mut x = Chip8Computer::new();
Chip8CpuInstructions::JPA(0xABC).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0xABC);
// 0x6xkk Set Vx = kk
let mut x = Chip8Computer::new();
Chip8CpuInstructions::LDR(1, 0x12).execute(&mut x);
assert_eq!(x.registers.peek(1), 0x12);
let mut x = Chip8Computer::new();
Chip8CpuInstructions::LDR(2, 0x21).execute(&mut x);
assert_eq!(x.registers.peek(2), 0x21);
// 0x3xkk Skip next instruction if Vx = kk.
// The interpreter compares register Vx to kk,
// and if they are equal, increments the program counter by 2.
// test setup: Load value 0x84 into V1
let mut x = Chip8Computer::new();
x.registers.poke(0x1, 0x84);
Chip8CpuInstructions::SEX(1, 0x48).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x202);
let mut x = Chip8Computer::new();
x.registers.poke(0x1, 0x84);
Chip8CpuInstructions::SEX(1, 0x84).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x204);
// 0x4xkk Skip next instruction if Vx != kk
let mut x = Chip8Computer::new();
x.registers.poke(0x01, 0x84);
x.registers.poke(0x2, 0x84);
// skip, compare 0x84 to 0x84
Chip8CpuInstructions::SEY(0x1, 0x2).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x204);
let mut x = Chip8Computer::new();
x.registers.poke(0x01, 0x84);
x.registers.poke(0x2, 0x48);
Chip8CpuInstructions::SEY(0x01, 0x02).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x202);
// 0x8xy0 Set value of Vy in Vx
let mut x = Chip8Computer::new();
x.registers.poke(0x01, 0x01);
x.registers.poke(0x02, 0x02);
Chip8CpuInstructions::LDRY(0x01, 0x02).execute(&mut x);
assert_eq!(x.registers.peek(1), 0x02);
// 0x8xy1 Set Vx = Vx OR Vy
// 0b0101 0000 (0x50)
// | 0b0000 1010 (0x0A)
// 0b0101 1010 (0x5A)
let mut x = Chip8Computer::new();
x.registers.poke(0x01, 0b01010000);
x.registers.poke(0x02, 0b00001010);
Chip8CpuInstructions::OR(1, 2).execute(&mut x);
assert_eq!(x.registers.peek(1), 0b01011010);
// 0x8xy2 Set Vx = Vx AND Vy
// 0b1111 1100 (0xFC)
// & 0b1100 1010 (0xCA)
// 0b1100 1000 (0xC8)
let mut x = Chip8Computer::new();
x.registers.poke(0x01, 0xFC);
x.registers.poke(0x02, 0xCA);
Chip8CpuInstructions::AND(1, 2).execute(&mut x);
assert_eq!(x.registers.peek(1), 0xC8);
// 0x8xy3 Set Vx = Vx XOR Vy
// 0b1111 1100 (0xFC)
// ^ 0b1100 1010 (0xCA)
// 0b0011 0110 (0x36)
let mut x = Chip8Computer::new();
x.registers.poke(0x01, 0b11111100);
x.registers.poke(0x02, 0b11001010);
Chip8CpuInstructions::ORY(1, 2).execute(&mut x);
assert_eq!(x.registers.peek(1), 0b00110110);
// 0x8xy4 Set Vx = Vx + Vy (SET VF on Carry)
// T1 T2: Judgement Test
// 0x01 0xFF
// + 0x01 0x01
// 0x02 F0 0x00 F1
let mut x = Chip8Computer::new();
x.registers.poke(0x0f, 00);
x.registers.poke(0x01, 0x01);
x.registers.poke(0x02, 0x01);
Chip8CpuInstructions::ADDR(0x01, 0x02).execute(&mut x);
assert_eq!(x.registers.peek(0xf), 0x00);
assert_eq!(x.registers.peek(0x01), 0x02);
let mut x = Chip8Computer::new();
x.registers.poke(0x0f, 0x00);
x.registers.poke(0x01, 0xff);
x.registers.poke(0x02, 0x01);
Chip8CpuInstructions::ADDR(1, 2).execute(&mut x);
assert_eq!(x.registers.peek(0xf), 1);
assert_eq!(x.registers.peek(1), 0);
/*
Set Vx = Vx SHR 1.
If the least-significant bit of Vx is 1, then VF is set to 1, otherwise 0. Then Vx is divided by 2.
*/
let mut x = Chip8Computer::new();
x.registers.poke(0x0f, 0x00);
x.registers.poke(0x01, 0b00001000);
x.registers.poke(0x02, 0b00000000);
Chip8CpuInstructions::SHR(0x1, 0x2).execute(&mut x); // 0b0000 0010 (0x02) (Not Set)
assert_eq!(x.registers.peek(1), 0b00000100);
assert_eq!(x.registers.peek(0xf), 0);
x = Chip8Computer::new();
x.registers.poke(0x0f, 0x00);
x.registers.poke(0x01, 0b00001001);
Chip8CpuInstructions::SHR(0x1, 0x2).execute(&mut x);
assert_eq!(x.registers.peek(1), 0b00000100);
assert_eq!(x.registers.peek(0xf), 1);
let mut x = Chip8Computer::new();
Chip8CpuInstructions::LDIA(0x123).execute(&mut x);
assert_eq!(x.registers.peek_i(), 0x123);
assert_eq!(x.registers.peek_pc(), 0x202);
}
#[test]
fn jp_v0addr_test() {
let mut x = Chip8Computer::new();
/// jump to I + nnn
x.registers.poke(0x0, 0xff);
Chip8CpuInstructions::JPI(0x100).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x1FF);
}
#[test]
fn cls_test() {
let mut x = Chip8Computer::new();
Chip8CpuInstructions::CLS.execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x202);
for i in 0..CHIP8_VIDEO_MEMORY {
assert!(!x.video_memory.peek(i as u16));
}
// draw some thing to the video memory
x.video_memory.poke(0x01, true);
x.video_memory.poke(0x03, true);
x.video_memory.poke(0x05, true);
Chip8CpuInstructions::CLS.execute(&mut x);
for i in 0..CHIP8_VIDEO_MEMORY {
assert!(!x.video_memory.peek(i as u16));
}
}
#[test]
fn skip_next_instruction_ne_text() {
let mut x = Chip8Computer::new();
x.registers.poke(0x1, 0xf0);
Chip8CpuInstructions::SNEB(0x1, 0x0f).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x204);
let mut x = Chip8Computer::new();
x.registers.poke(0x1, 0xf0);
Chip8CpuInstructions::SNEB(0x1, 0xf0).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x202);
}
#[test]
fn addivx_test() {
let mut x = Chip8Computer::new();
x.registers.poke_i(0xabc);
x.registers.poke(0x0, 0x10);
Chip8CpuInstructions::ADDI(0x0).execute(&mut x);
assert_eq!(x.registers.peek_i(), 0xacc);
}
#[test]
fn ldstvt_test() {
let mut x = Chip8Computer::new();
x.registers.poke(0x1, 0xf0);
Chip8CpuInstructions::LDIS(0x01).execute(&mut x);
assert_eq!(x.sound_timer.current(), 0xf0);
x.sound_timer.tick();
x.sound_timer.tick();
x.sound_timer.tick();
assert_eq!(x.sound_timer.current(), 0xed);
}
#[test]
fn rnd_vx_byte_text() {
let mut x = Chip8Computer::new();
Chip8CpuInstructions::RND(0x1, 0x0f).execute(&mut x);
let new_value = x.registers.peek(0x1);
assert!(new_value < 0x10);
}
#[test]
fn add_vx_byte_test() {
let mut x = Chip8Computer::new();
// set a value in the register
x.registers.poke(0x01, 0xab);
// add 0x10 to register
Chip8CpuInstructions::ADD(0x1, 0x10).execute(&mut x);
assert_eq!(x.registers.peek(1), 0xbb);
}
#[test]
fn sub_vx_vy_test() {
let mut x = Chip8Computer::new();
// load values in 2 registers
x.registers.poke(0x1, 0x10);
x.registers.poke(0x2, 0x08);
Chip8CpuInstructions::SUB(0x1, 0x02).execute(&mut x);
assert_eq!(x.registers.peek(0xf), 1);
assert_eq!(x.registers.peek(0x1), 0x8);
assert_eq!(x.registers.peek_pc(), 0x202);
}
#[test]
fn sne_vx_vy_test() {
let mut x = Chip8Computer::new();
x.registers.poke(0x1, 0x10);
x.registers.poke(0x2, 0x10);
Chip8CpuInstructions::SNEY(0x1, 0x2).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x202);
let mut x = Chip8Computer::new();
x.registers.poke(0x1, 0x10);
x.registers.poke(0x2, 0x00);
Chip8CpuInstructions::SNEY(0x01, 0x02).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x204)
}
#[test]
fn ld_dt_vx_test() {
let mut x = Chip8Computer::new();
x.registers.poke(0x1, 0x10);
Chip8CpuInstructions::LDD(0x1).execute(&mut x);
assert_eq!(x.delay_timer.current(), 0x10);
for _ in 0..0x20 {
x.delay_timer.tick();
}
assert_eq!(x.delay_timer.current(), 0);
}
#[test]
fn ld_vx_dt_test() {
let mut x = Chip8Computer::new();
x.registers.poke(0x1, 0xf0);
Chip8CpuInstructions::LDD(0x1).execute(&mut x);
x.delay_timer.tick();
x.delay_timer.tick();
x.delay_timer.tick();
assert_eq!(x.delay_timer.current(), 0xed);
}
#[test]
fn subn_vx_vy_test() {
// This instruction subtracts the value in
// register Vx from the value in register Vy and stores the result in register Vx.
// The subtraction is performed as follows: Vx = Vy - Vx. If Vy is less than Vx,
// the result will wrap around (due to the 8-bit nature of the registers).
// The carry flag (VF) is set to 1 if there is no borrow (i.e., Vy is greater
// than or equal to Vx), and it is set to 0 if there is a borrow.
let mut x = Chip8Computer::new();
x.registers.poke(0x1, 0xa0);
x.registers.poke(0x2, 0xab);
Chip8CpuInstructions::SUBC(0x1, 0x2).execute(&mut x);
// expect the result to be 0x0b
assert_eq!(x.registers.peek(0x1), 0x0b);
// expect the vf register to be set to 1 as there was overflow
assert_eq!(x.registers.peek(0xf), 0x1);
let mut x = Chip8Computer::new();
x.registers.poke(0x01, 0xab);
x.registers.poke(0x02, 0xa0);
Chip8CpuInstructions::SUBC(0x1, 0x2).execute(&mut x);
// expect the result to be 11110101, -0xB, -11, 245, 0xF5
assert_eq!(x.registers.peek(0x1), 0xf5);
assert_eq!(x.registers.peek(0xf), 0x0);
// 8xyE - SHL Vx {, Vy}
// Set Vx = Vx SHL 1.
//
// If the most-significant bit of Vx is 1, then VF is set to 1, otherwise to 0. Then Vx is multiplied by 2.
let mut x = Chip8Computer::new();
x.registers.poke(0x1, 0b00100000);
Chip8CpuInstructions::SHL(0x1, 0x1).execute(&mut x);
assert_eq!(x.registers.peek(0x1), 0b01000000);
assert_eq!(x.registers.peek(0xf), 0x0);
let mut x = Chip8Computer::new();
x.registers.poke(0x1, 0b10101010);
Chip8CpuInstructions::SHL(0x1, 0x1).execute(&mut x);
assert_eq!(x.registers.peek(0x1), 0b01010100);
assert_eq!(x.registers.peek(0xf), 0x1);
// Fx29 - LD F, Vx
// Set I = location of sprite for digit Vx.
//
// The value of I is set to the location for the hexadecimal sprite corresponding to the value of Vx. See section 2.4, Display, for more information on the Chip-8 hexadecimal font.
let mut x = Chip8Computer::new();
// target_sprite = 2
// target_offset = 0x5
x.registers.poke(0x1, 0x2);
Chip8CpuInstructions::LDFX(0x1).execute(&mut x);
assert_eq!(x.registers.peek_i(), 10);
let mut x = Chip8Computer::new();
x.registers.poke(0x01, 0x06);
Chip8CpuInstructions::LDFX(0x1).execute(&mut x);
assert_eq!(x.registers.peek_i(), 30);
// Fx33 - LD B, Vx
// Store BCD representation of Vx in memory locations I, I+1, and I+2.
//
// The interpreter takes the decimal value of Vx, and places the hundreds digit
// in memory at location in I, the tens digit at location I+1,
// and the ones digit at location I+2.
let mut x = Chip8Computer::new();
// load the value 123 (0x7b)
x.registers.poke(0x1, 0x7b);
x.registers.poke_i(0x500);
Chip8CpuInstructions::BCD(0x1).execute(&mut x);
assert_eq!(x.memory.peek(0x500), 0x1);
assert_eq!(x.memory.peek(0x501), 0x2);
assert_eq!(x.memory.peek(0x502), 0x3);
// Store registers V0 through Vx in memory starting at location I.
//
// The interpreter copies the values of registers V0 through Vx into memory,
// starting at the address in I.
let mut x = Chip8Computer::new();
// Load Registers.
let to_load = [0xab, 0xba, 0xca, 0xca, 0xbe, 0xef];
for (idx, val) in to_load.iter().enumerate() {
x.registers.poke(idx as u8, *val);
}
x.registers.poke_i(0x500);
Chip8CpuInstructions::LDIX(to_load.len() as u8).execute(&mut x);
// Verify the values are in memory from 0x500 to 0x507
for (idx, value) in to_load.iter().enumerate() {
assert_eq!(x.memory.peek(0x500 + idx as u16), *value);
}
// Read registers V0 through Vx from memory starting at location I.
//
// The interpreter reads values from memory starting at location I into registers V0 through Vx.
let mut x = Chip8Computer::new();
let base_offset = 0x500;
let to_load = [0xab, 0xba, 0xca, 0xca, 0xbe, 0xef];
// start by setting values in memory
for (idx, memory) in to_load.iter().enumerate() {
let target_address = base_offset + idx;
let target_value = *memory;
x.memory.poke(target_address as u16, target_value);
}
// where to load from
x.registers.poke_i(0x500);
// how much to load
x.registers.poke(0x6, to_load.len() as u8);
// then copying them values memory to registers
Chip8CpuInstructions::LDRI(0x6).execute(&mut x);
// now check that we have the right values in our registers
for (idx, value) in to_load.iter().enumerate() {
assert_eq!(x.registers.peek(idx as u8), *value);
}
// ExA1 - SKNP Vx
// Skip next instruction if key with the value of Vx is not pressed.
//
// Checks the keyboard,
// and if the key corresponding to the value of Vx is currently in the up position,
// PC is increased by 2.
let mut x = Chip8Computer::new();
x.keypad.push_key(0x5);
x.registers.poke(0x1, 0x5);
Chip8CpuInstructions::SKNP(0x1).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x202);
x.keypad.release_key(0x5);
Chip8CpuInstructions::SKNP(0x1).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x206);
// Ex9E - SKP Vx
// Skip next instruction if key with the value of Vx is pressed.
//
// Checks the keyboard, and if the key corresponding to the value of Vx is currently in the down position, PC is increased by 2.
let mut x = Chip8Computer::new();
x.keypad.push_key(0x5);
x.registers.poke(0x1, 0x5);
Chip8CpuInstructions::SKP(0x1).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x204);
x.keypad.release_key(0x5);
Chip8CpuInstructions::SKP(0x1).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x206);
}
fn draw_nibble_vx_vy_n_test_hd() {
let mut x = Chip8Computer::new();
let x_register = 0x01;
let x_offset = 0x03;
let y_register = 0x02;
let y_offset = 0x04;
let char_offset = 0x100;
x.registers.poke(x_register, x_offset);
x.registers.poke(y_register, y_offset);
x.video_memory.set_highres();
x.registers.poke_i(char_offset);
Chip8CpuInstructions::DRW(x_register, y_register, 0).execute(&mut x);
println!("[[{}]]", x.video_memory.format_as_string());
assert_eq!(read_test_result(""), x.video_memory.format_as_string());
}
#[test]
fn draw_nibble_vx_vy_n_test_sd() {
let mut x = Chip8Computer::new();
let x_register = 0x1;
let y_register = 0x2;
let x_offset = 1;
let y_offset = 2;
let char_offset = 0x0A;
// now lets set the X and Y to 1,2
x.registers.poke(x_register, x_offset);
x.registers.poke(y_register, y_offset);
x.registers.poke_i(char_offset);
// we are using 5 rows.
Chip8CpuInstructions::DRW(x_register, y_register, 5).execute(&mut x);
// now check that video memory has the values at
// 1,2->1,9
// 2,2->2,9
// 3,2->3,9
// 4,2->4,9
// 5,2->5,9
// let byte_to_check = CHIP8FONT_0[0];
for row_in_sprite in 0..5 {
let row_data = CHIP8FONT_2[row_in_sprite];
for bit_in_byte in 0..8 {
let data_offset =
(x_offset as u16 + row_in_sprite as u16) * 64 + (bit_in_byte + y_offset) as u16;
let real_bit_in_byte = 7 - bit_in_byte;
let shifted_one = 0x01 << real_bit_in_byte;
let one_shift_set = (shifted_one & row_data) > 0;
debug!("ROWDATA = \t\t[{row_data:08b}]\tBIT IN BYTE = \t[{bit_in_byte}]\tONE_SHIFT_SET = [{one_shift_set}]\tSHIFTED ONE = [{shifted_one:08b}]");
debug!("DATA_OFFSET FOR SOURCE DATA {}x{} is {} / offset by {}x{} and should be {} working with byte {:08b}",
bit_in_byte, row_in_sprite, data_offset, x_offset, y_offset, one_shift_set, row_data);
}
}
}
#[test]
fn sub_test() {
// 2nnn
// Call a subroutine at 2nnn
let mut x = Chip8Computer::new();
Chip8CpuInstructions::CALL(0x124).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x124);
assert_eq!(x.stack.depth(), 1);
Chip8CpuInstructions::CALL(0x564).execute(&mut x);
assert_eq!(x.registers.peek_pc(), 0x564);
assert_eq!(x.stack.depth(), 2);
// SETUP
// Return from a subroutine.
let mut x = Chip8Computer::new();
x.stack.push(&0x132);
x.stack.push(&0xabc);
// EXECUTE
Chip8CpuInstructions::RET.execute(&mut x);
// VERIFY
assert_eq!(x.registers.peek_pc(), 0xabc);
assert_eq!(x.stack.depth(), 1);
// EXECUTE
Chip8CpuInstructions::RET.execute(&mut x);
// VERIFY
assert_eq!(x.registers.peek_pc(), 0x132);
assert_eq!(x.stack.depth(), 0);
}
#[test]
fn ldvxk_test() {
// SETUP
let mut x = Chip8Computer::new();
x.registers.poke(0x01, 0x01);
Chip8CpuInstructions::LDRK(0x1).execute(&mut x);
assert!(matches!(
x.state,
gemma::chip8::cpu_states::Chip8CpuStates::WaitingForKey
));
}
#[test]
fn series8xy4_corex_tests() {
/// 8xy4
/// Set Vx = Vx + Vy
/// Set VF=1 if Carry
///
// 1 + 1
let mut x = Chip8Computer::new();
x.registers.poke(0x01, 0x01);
x.registers.poke(0x02, 0x01);
Chip8CpuInstructions::ADDR(0x01, 0x02).execute(&mut x);
assert_eq!(x.registers.peek(0x01), 0x02);
assert_eq!(x.registers.peek(0x0f), 0x00);
// 255+1
let mut x = Chip8Computer::new();
x.registers.poke(0x01, 0xff);
x.registers.poke(0x02, 0x01);
Chip8CpuInstructions::ADDR(0x01, 0x02).execute(&mut x);
assert_eq!(x.registers.peek(0x01), 0x00);
assert_eq!(x.registers.peek(0x0f), 0x01);
// 128+192
let mut x = Chip8Computer::new();
x.registers.poke(0x01, 128);
x.registers.poke(0x02, 192);
Chip8CpuInstructions::ADDR(0x01, 0x02).execute(&mut x);
assert_eq!(x.registers.peek(0x01), 64);
assert_eq!(x.registers.peek(0x0f), 1);
// 8xy6 - SHR Vx {, Vy}
// Set Vx = Vx SHR 1.
//
// If the least-significant bit of Vx is 1, then VF is set to 1,
// otherwise 0. Then Vx is divided by 2.
let mut x = Chip8Computer::new();
// 0b10101010 -> 0b01010101
x.registers.poke(0x01, 0b10101010);
x.registers.poke(0x0f, 0x0);
Chip8CpuInstructions::SHL(0x01, 0x00).execute(&mut x);
assert_eq!(x.registers.peek(0x01), 0b01010100);
assert_eq!(x.registers.peek(0x0f), 1);
Chip8CpuInstructions::SHL(0x01, 0x00).execute(&mut x);
assert_eq!(x.registers.peek(0x01), 0b10101000);
assert_eq!(x.registers.peek(0x0f), 0x00);
Chip8CpuInstructions::SHL(0x01, 0x00).execute(&mut x);
assert_eq!(x.registers.peek(0x01), 0b01010000);
assert_eq!(x.registers.peek(0x0f), 0x01);
Chip8CpuInstructions::SHL(0x01, 0x00).execute(&mut x);
assert_eq!(x.registers.peek(0x01), 0b10100000);
assert_eq!(x.registers.peek(0x0f), 0x00);
Chip8CpuInstructions::SHL(0x01, 0x00).execute(&mut x);
assert_eq!(x.registers.peek(0x01), 0b01000000);
assert_eq!(x.registers.peek(0x0f), 0x01);
}
#[test]
fn random_produces_different_numbers() {
let mut x = Chip8Computer::new();
x.registers.poke(0x01, 0x00);
let first_number = Chip8CpuInstructions::RND(0x01, 0xff)
.execute(&mut x)
.registers
.peek(0x01);
let second_number = Chip8CpuInstructions::RND(0x01, 0xff)
.execute(&mut x)
.registers
.peek(0x01);
assert_ne!(first_number, second_number);
}
#[test]
fn delay_timer_ticks_reduce_time() {
let mut st = DelayTimer::new();
st.set_timer(100);
st.tick();
st.tick();
st.tick();
assert_eq!(st.current(), 97);
}
#[test]
fn delay_timer_out_of_ticks_works() {
let mut st = DelayTimer::new();
st.set_timer(0);
st.tick();
st.tick();
st.tick();
assert_eq!(st.current(), 0);
}
#[test]
fn keypad_keys_check() {
let mut k = Keypad::new();
for i in 0..16 {
assert!(!k.key_state(i));
}
// press a key
k.push_key(1);
k.push_key(2);
assert!(k.pressed(1));
assert!(k.pressed(2));
k.release_key(1);
assert!(k.released(1));
}
#[test]
fn keypad_string_format_test() {
let k = Keypad::new();
assert_eq!(
k.format_as_string(),
read_test_result("gemma_keypad_string_result.asc")
);
}
#[test]
fn register_rw_test() {
let mut x = Chip8Registers::default();
x.poke(0x0, 0xff);
x.poke(0x1, 0xab);
assert_eq!(x.peek(0x0), 0xff);
assert_eq!(x.peek(0x1), 0xab);
}
#[test]
fn pc_test() {
let mut x = Chip8Registers::default();
x.set_pc(0x300);
assert_eq!(x.peek_pc(), 0x300);
}
#[test]
#[should_panic]
fn invalid_register() {
let mut x = Chip8Registers::default();
x.poke(0x10, 0xff);
}
#[test]
fn format_as_string_looks_right() {
let mut x = Chip8Registers::default();
for i in 0..0x10 {
x.registers[i] = i as u8;
}
x.pc = 0xabc;
x.i_register = 0xcab;
let result_string = x.format_as_string();
assert_eq!(result_string, String::from("Vx: 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07\n 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f\nI: 0x0cab\tPC: 0x0abc"));
}
#[test]
fn reset_clears_registers() {
let mut x = Chip8Registers::default();
for register in 0..0x10 {
x.poke(register, random::<u8>());
}
x.reset();
for register in 0..0x10 {
assert_eq!(x.peek(register), 0x00);
}
}
#[test]
fn sound_timer_ticks_reduce_time() {
let mut st = SoundTimer::new();
st.set_timer(100);
st.tick();
st.tick();
st.tick();
assert_eq!(st.current(), 97);
}
#[test]
fn sound_timer_out_of_ticks_works() {
let mut st = SoundTimer::new();
st.set_timer(0);
st.tick();
st.tick();
st.tick();
assert_eq!(st.current(), 0);
}
#[test]
fn stack_push_pop_test() {
let mut x = Chip8Stack::new();
// lets see if we can push and pop a bunch
x.push(&0xabcu16);
x.push(&0xcdeu16);
x.pop();
assert_eq!(x.depth(), 1);
}
#[test]
#[should_panic]
fn stack_overflow_test() {
let mut x = Chip8Stack::new();
for i in 0..17 {
x.push(&i);
}
}
#[test]
#[should_panic]
fn stack_underflow_test() {
let mut x = Chip8Stack::new();
x.pop();
}
#[test]
fn stack_lots_of_subs() {
let mut x = Chip8Stack::new();
let stack_contents = [
0x123, 0x321, 0xabc, 0xdef, 0xbad, 0xbef, 0xfed, 0xcab, 0xbed, 0xcad, 0xfeb, 0xcab, 0xfff,
0x000, 0x001,
];
for i in stack_contents {
x.push(&i);
}
assert_eq!(x.depth(), 15);
// up to 50 random loops
let num_loops: u8 = random::<u8>() % 50;
for i in 0..num_loops {
let start_count = x.depth();
let num_pop = random::<u8>() % x.depth() as u8;
for current_pop in 0..num_pop {
x.pop();
}
let post_pop_count = x.depth();
assert_eq!(post_pop_count as u8, start_count as u8 - num_pop);
for current_push in 0..num_pop {
x.push(&stack_contents[(current_push + post_pop_count as u8) as usize]);
}
assert_eq!(x.depth(), 15);
}
}
#[test]
fn video_split_bytes() {
// from 0xABCD we should have AB high, CD low
let (low, high) = InstructionUtil::split_bytes(0xabcd);
assert_eq!(low, 0xAB);
assert_eq!(high, 0xCD);
}
#[test]
fn video_join_bytes() {
// from 0xAB low and 0xCD high we get 0xABCD
let merged = InstructionUtil::join_bytes(0xcd, 0xab);
assert_eq!(merged, 0xcdab);
}
#[test]
fn instruction_read_from_instruction() {
// from 0xABCD
let source = 0xABCD;
assert_eq!(InstructionUtil::read_addr_from_instruction(source), 0xBCD);
assert_eq!(InstructionUtil::read_nibble_from_instruction(source), 0xD);
assert_eq!(InstructionUtil::read_x_from_instruction(source), 0xB);
assert_eq!(InstructionUtil::read_y_from_instruction(source), 0xC);
assert_eq!(InstructionUtil::read_byte_from_instruction(source), 0xCD);
}
#[test]
fn instruction_ubln() {
// from 0xABCD we should see B
assert_eq!(InstructionUtil::read_upper_byte_lower_nibble(0xABCD), 0xB);
assert_eq!(InstructionUtil::read_upper_byte_lower_nibble(0x0123), 0x1);
assert_eq!(InstructionUtil::read_upper_byte_lower_nibble(0x0000), 0x0);
}
#[test]
fn instruction_byte_to_bool_changes() {
assert_eq!(
InstructionUtil::byte_to_bools(0b00000000),
[false, false, false, false, false, false, false, false]
);
assert_eq!(
InstructionUtil::byte_to_bools(0b11111111),
[true, true, true, true, true, true, true, true]
);
assert_eq!(
InstructionUtil::byte_to_bools(0b11001100),
[false, false, true, true, false, false, true, true]
);
assert_eq!(
InstructionUtil::byte_to_bools(0b11110000),
[false, false, false, false, true, true, true, true]
);
assert_eq!(
InstructionUtil::bools_to_byte([false, false, false, false, false, false, false, false]),
0b00000000
);
assert_eq!(
InstructionUtil::bools_to_byte([true, true, true, true, true, true, true, true]),
0b11111111
);
assert_eq!(
InstructionUtil::bools_to_byte([false, false, true, true, false, false, true, true]),
0b11001100
);
assert_eq!(
InstructionUtil::bools_to_byte([false, false, false, false, true, true, true, true]),
0b11110000
);
}
fn real_build_checkboard(in_hd: bool) -> Chip8Video {
let mut r = Chip8Video::default();
let (width, height) = if in_hd {
r.set_highres();
(SCHIP_VIDEO_WIDTH, SCHIP_VIDEO_HEIGHT)
} else {
(CHIP8_VIDEO_WIDTH, CHIP8_VIDEO_HEIGHT)
};
println!("BUILDING BOARD WITH SIZE OF {width}x{height}");
for row in 0..height {
let data_offset = row * width;
for col in 0..width {
// XOR row and column indices to alternate in a checkerboard pattern
let to_poke = (row % 2) ^ (col % 2) == 1;
let local_offset: u16 = (data_offset + col) as u16;
r.poke(local_offset, to_poke);
}
}
r
}
fn build_checkboard_hd() -> Chip8Video {
real_build_checkboard(true)
}
fn build_checkerboard() -> Chip8Video {
real_build_checkboard(false)
}
#[test]
fn video_default_test() {
let mut x = Chip8Video::default();
for i in 0..CHIP8_VIDEO_MEMORY {
assert!(!x.clone().peek(i as u16));
// then flip the value and test again.
x.poke(i as u16, true);
assert!(x.clone().peek(i as u16));
}
}
#[test]
fn video_set_initial_memory_sd() {
let mut x = Chip8Video::default();
// let mut initial_memory = [false; CHIP8_VIDEO_MEMORY];
let mut ws = String::new();
// set our checkerboard
for cbr in 0..32 {
for cbc in 0..64 {
let dof = cbr * 64 + cbc;
if (dof as i32 % 2) == 0 {
x.poke(dof, true);
ws += "*";
} else {
ws += " ";
}
}
ws += "\n";
}
assert_eq!(x.format_as_string(), ws);
}
#[test]
fn video_poke_byte_test() {
let to_poke = 0b11001111;
let mut x = Chip8Video::default();
x.poke_byte(0x05, to_poke);
let mut expected = String::new();
expected = " ** **** \n".to_string();
for i in 0..31 {
expected += &*(" ".repeat(64) + "\n");
}
assert_eq!(x.format_as_string(), expected);
}
#[test]
fn video_poke_2byte_test() {
let to_poke: [u8; 2] = [0b11001111, 0b00111100];
let mut x = Chip8Video::default();
x.poke_2byte(0x00, to_poke);
let mut expected = String::new();
expected = "** **** **** ".to_string() + &*" ".repeat(64 - 16).to_string() + "\n";
for i in 0..31 {
expected += &*((*" ".repeat(64)).to_string() + "\n");
}
assert_eq!(expected, x.format_as_string());
}
#[test]
fn video_poke_multirow_2_byte_sprite() {
// take 2 rows of 16bits and write them to memory
}
#[test]
fn video_cls_stddef() {
let width = 64;
let height = 32;
let initial_memory = vec![];
let mut ws = String::new();
let mut set_x = Chip8Video::new(initial_memory.into());
for cbr in 0..32 {
ws += &*" ".repeat(width);
ws += "\n";
}
set_x.cls();
assert_eq!(set_x.format_as_string(), ws);
}
#[test]
fn video_poke_byte_test_2() {
let to_poke = 0b10101010;
let mut v = Chip8Video::default();
v.poke_byte(0x00, to_poke);
assert!(v.clone().peek(0x00));
assert!(!v.clone().peek(0x01));
assert!(v.clone().peek(0x02));
assert!(!v.clone().peek(0x03));
assert!(v.clone().peek(0x04));
assert!(!v.clone().peek(0x05));
assert!(v.clone().peek(0x06));
assert!(!v.clone().peek(0x07));
for i in 0x8..CHIP8_VIDEO_MEMORY {
assert!(!v.clone().peek(i as u16));
}
}
#[test]
fn video_poke_multi_line_test() {
let mut v = Chip8Video::default();
let to_poke = [0b00000000, 0b11111111, 0b10101010, 0b01010101];
for (byte_in_set, byte_to_poke) in to_poke.iter().enumerate() {
let base_offset = byte_in_set * 64;
v.poke_byte(base_offset as u16, *byte_to_poke);
}
// row 2 column 1
{
assert!(v.clone().peek(0x40));
assert!(v.clone().peek(0x41));
assert!(v.clone().peek(0x42));
assert!(v.clone().peek(0x43));
assert!(v.clone().peek(0x44));
assert!(v.clone().peek(0x45));
assert!(v.clone().peek(0x46));
assert!(v.clone().peek(0x47));
// row 3 column 1
assert!(!v.clone().peek(0xC0));
assert!(v.clone().peek(0xC1));
assert!(!v.clone().peek(0xC2));
assert!(v.clone().peek(0xC3));
assert!(!v.clone().peek(0xC4));
assert!(v.clone().peek(0xC5));
assert!(!v.clone().peek(0xC6));
assert!(v.clone().peek(0xC7));
}
}
#[test]
fn video_moved_poke_test() {
let mut v = Chip8Video::default();
let to_poke = [0b00000000, 0b11111111, 0b10101010, 0b01010101];
let x_offset = 20;
let y_offset = 5;
for (byte_in_set, byte_to_poke) in to_poke.iter().enumerate() {
let base_offset = (x_offset + byte_in_set) * 64 + y_offset;
v.poke_byte(base_offset as u16, *byte_to_poke);
}
let test_offset = (x_offset * 64 + y_offset) as u16;
assert!(!v.clone().peek(test_offset));
assert!(!v.clone().peek(test_offset + 1));
assert!(!v.clone().peek(test_offset + 2));
assert!(!v.clone().peek(test_offset + 3));
assert!(!v.clone().peek(test_offset + 4));
assert!(!v.clone().peek(test_offset + 5));
assert!(!v.clone().peek(test_offset + 6));
assert!(!v.clone().peek(test_offset + 7));
let test_offset = test_offset + 0x40;
assert!(v.clone().peek(test_offset));
assert!(v.clone().peek(test_offset + 1));
assert!(v.clone().peek(test_offset + 2));
assert!(v.clone().peek(test_offset + 3));
assert!(v.clone().peek(test_offset + 4));
assert!(v.clone().peek(test_offset + 5));
assert!(v.clone().peek(test_offset + 6));
assert!(v.clone().peek(test_offset + 7));
}
#[test]
fn video_verify_change_registered() {
let mut v = Chip8Video::default();
v.poke(0x01, true);
v.poke(0x01, true);
assert!(v.has_frame_changed);
v.start_frame();
assert!(!v.has_frame_changed);
}
#[test]
fn video_write_checkboard() {
let v = build_checkerboard();
assert_eq!(
v.clone().format_as_string(),
read_test_result("test_video_write_checkerboard.asc")
);
}
#[test]
fn video_zero_test() {
let mut x = Chip8Video::default();
for (byte_index, data_offset) in (0..=0x100).step_by(0x40).enumerate() {
x.poke_byte(data_offset as u16, CHIP8FONT_0[byte_index]);
}
assert_eq!(
read_test_result("test_video_zero.asc"),
x.format_as_string()
);
}
#[test]
fn video_multi_sprite_test() {
let mut x = Chip8Video::default();
// draw a row of digits 01234567
let to_draw = [
CHIP8FONT_0,
CHIP8FONT_1,
CHIP8FONT_2,
CHIP8FONT_3,
CHIP8FONT_4,
CHIP8FONT_5,
CHIP8FONT_6,
CHIP8FONT_7,
];
for (index, sprite) in to_draw.iter().enumerate() {
let data_base_offset = index * 0x8;
for (index, offset) in (0..=0x100).step_by(0x40).enumerate() {
x.poke_byte((data_base_offset + offset) as u16, sprite[index]);
}
}
assert_eq!(
read_test_result("test_multi_sprite.asc"),
x.format_as_string()
);
}
#[test]
fn video_reset_test() {
let mut x = build_checkerboard();
x.reset();
assert_eq!(
x.format_as_string(),
read_test_result("test_reset_clears_video.asc")
);
}
#[test]
fn video_collision_test() {
// Setup: Set 0xFF to 0x00 with a new frame ready
// Action: Run Poke to the same area
// Test: Verify the 'changed' flag is tripped
let mut x = Chip8Video::default();
x.poke_byte(0x00, 0xff);
x.tick();
// set the cell thats already set...
x.poke(0x00, true);
// it becomes unset and theres a frame changed
assert!(!x.peek(0x00));
assert!(x.clone().has_frame_changed);
}
#[test]
fn video_collision_test2() {
let mut x = Chip8Video::default();
x.poke_byte(0x00, 0b11110000);
assert!(x.has_frame_changed);
x.tick();
assert!(!x.has_frame_changed);
// clear the 'has changed' flag
// now set a no-collision value
x.poke_byte(0x00, 0b00001111);
assert!(x.has_frame_changed);
}
#[test]
fn video_collision_test3() {
// draw a couple sprites that do not overlap.
// goal being drawing without triggering the collision
// detection.
let mut x = Chip8Video::default();
x.poke_byte(0x00, 0b11110000);
}
#[test]
fn video_peek_out_of_bounds_doesnt_panic() {
let x = Chip8Video::default();
let y = x.clone().peek(2049);
let y = x.clone().peek(0);
// if we got here we didn't panic
assert!(true);
}
#[test]
fn video_scroll_down_1_row_test() {
let mut x = build_checkerboard();
x.scroll_down(1);
assert_eq!(
read_test_result("test_video_scroll_down_1.asc"),
x.format_as_string()
);
}
#[test]
fn video_scroll_down_10_row_test() {
let mut x = build_checkerboard();
x.scroll_down(10);
assert_eq!(
read_test_result("test_video_scroll_down_10.asc"),
x.format_as_string()
);
}
#[test]
fn video_high_res_has_right_resolution() {
let x = build_checkboard_hd();
println!("[{}]", x.format_as_string());
assert_eq!(
read_test_result("test_video_highdef.asc"),
x.format_as_string()
);
}
#[test]
fn video_scroll_down_1_row_test_schip() {
let mut x = build_checkboard_hd();
x.scroll_down(1);
println!("[{}]", x.format_as_string());
println!("[{}]", read_test_result("test_scroll_down_1_hd.asc"));
assert_eq!(
read_test_result("test_scroll_down_1_hd.asc"),
x.format_as_string()
);
}
#[test]
fn video_scroll_down_10_row_test_schip() {
let mut x = build_checkboard_hd();
x.scroll_down(10);
assert_eq!(
read_test_result("test_scroll_down_10_hd.asc"),
x.format_as_string()
);
}
#[test]
fn video_scroll_left_4_row_test_std_def() {
let mut x = build_checkerboard();
x.scroll_left();
assert_eq!(
read_test_result("test_scroll_left_4.asc"),
x.format_as_string()
);
}
#[test]
fn video_scroll_left_4_row_test_high_def() {
let mut x = build_checkboard_hd();
x.scroll_left();
assert_eq!(
read_test_result("test_scroll_left_4_hd.asc"),
x.format_as_string()
);
}
#[test]
fn video_scroll_right_4_row_test_std_def() {
let mut x = build_checkerboard();
x.scroll_right();
assert_eq!(
read_test_result("test_scroll_right_4.asc"),
x.format_as_string()
);
}
#[test]
fn video_scroll_right_4_row_test_high_def() {
let mut x = build_checkboard_hd();
x.scroll_right();
assert_eq!(
read_test_result("test_scroll_right_4_hd.asc"),
x.format_as_string()
);
}
#[test]
fn instructions_operands_tests() {
assert_eq!(Chip8CpuInstructions::SYS(0x000).operands(), "0x0000");
assert_eq!(Chip8CpuInstructions::JPI(0x123).operands(), "0x0123");
assert_eq!(Chip8CpuInstructions::JPA(0x234).operands(), "0x0234");
assert_eq!(Chip8CpuInstructions::LDIA(0x345).operands(), "0x0345");
assert_eq!(Chip8CpuInstructions::CALL(0x456).operands(), "0x0456");
}
#[test]
fn instruction_ena_dis_tests() {
let mut x = Chip8Computer::new();
for quirk in [SChipModern, XOChip] {
x.quirk_mode = quirk;
assert!(!x.video_memory.is_highres());
Chip8CpuInstructions::HIGH.execute(&mut x);
assert!(x.video_memory.is_highres());
Chip8CpuInstructions::HIGH.execute(&mut x);
assert!(x.video_memory.is_highres());
Chip8CpuInstructions::LOW.execute(&mut x);
assert!(!x.video_memory.is_highres());
}
}
#[test]
fn instruction_test_scrolling_lowres() {
for quirk in [SChipModern, XOChip] {
let mut x = Chip8Computer::new();
x.video_memory = build_checkerboard();
x.quirk_mode = quirk.clone();
Chip8CpuInstructions::SCR.execute(&mut x);
assert_eq!(
read_test_result("test_scroll_right_4.asc"),
x.dump_video_to_string()
);
x = Chip8Computer::new();
x.video_memory = build_checkerboard();
x.quirk_mode = quirk.clone();
Chip8CpuInstructions::SCL.execute(&mut x);
assert_eq!(
read_test_result("test_scroll_left_4.asc"),
x.dump_video_to_string()
);
x = Chip8Computer::new();
x.video_memory = build_checkerboard();
x.quirk_mode = quirk.clone();
Chip8CpuInstructions::SCD(0x01).execute(&mut x);
assert_eq!(
read_test_result("test_video_scroll_down_1.asc"),
x.dump_video_to_string()
);
x = Chip8Computer::new();
x.video_memory = build_checkerboard();
x.quirk_mode = quirk.clone();
Chip8CpuInstructions::SCD(0xA).execute(&mut x);
assert_eq!(
read_test_result("test_video_scroll_down_10.asc"),
x.dump_video_to_string()
);
}
}
#[test]
fn computer_dump_keypad_to_string() {
let mut x = Chip8Computer::new();
x.keypad.push_key(0x1);
x.keypad.push_key(0x2);
assert_eq!(
read_test_result("test_keypad_to_string.asc"),
x.dump_keypad_to_string()
);
}
#[test]
fn computer_dump_registers_to_string() {
let mut x = Chip8Computer::new();
let values_to_set = [
0x0b, 0xad, 0xbe, 0xef, 0xca, 0xb0, 0x7a, 0xc0, 0xca, 0x70, 0xba, 0xdb, 0xed, 0x00, 0x00,
0x00,
];
let expected_value = "Vx: 0x0b 0xad 0xbe 0xef 0xca 0xb0 0x7a 0xc0\n 0xca 0x70 0xba 0xdb 0xed 0x00 0x00 0x00\nI: 0x0000\tPC: 0x0200";
for i in 0..16 {
x.registers.poke(i, values_to_set[i as usize]);
}
// now verify.
assert_eq!(expected_value, x.dump_registers_to_string());
}
#[test]
fn video_scroll_up_tests_sd() {
let mut x = build_checkerboard();
let distance = 1u8;
x.scroll_up(&distance);
assert_eq!(
read_test_result("test_video_scroll_up_test_sd.asc"),
x.format_as_string()
);
}
#[test]
fn video_scroll_up_tests_hd() {
let mut x = build_checkboard_hd();
let distance = 1u8;
x.scroll_up(&distance);
assert_eq!(
read_test_result("test_video_scroll_up_test_hd.asc"),
x.format_as_string()
);
}
#[test]
fn video_resolution_changing() {
let mut x = Chip8Video::default();
x.set_highres();
assert_eq!(x.get_resolution(), (SCHIP_VIDEO_WIDTH, SCHIP_VIDEO_HEIGHT));
assert!(matches!(
x.get_screen_resolution(),
Chip8VideoModes::HighRes
));
x.set_lowres();
assert_eq!(x.get_resolution(), (CHIP8_VIDEO_WIDTH, CHIP8_VIDEO_HEIGHT));
assert!(matches!(x.get_screen_resolution(), Chip8VideoModes::LowRes));
}
#[test]
fn delay_timer_default() {
let t = DelayTimer::default();
assert_eq!(t.current(), 0xff);
}
#[test]
fn instruction_jpx() {
// JPX -> 0xBXnn
// Jump to Xnn+Vx
let mut x = Chip8Computer::new();
// set X1 to 4...
x.registers.poke(0x01, 0x04);
// ...use (x1)+0x134
let to_execute = JPX(0x01, 0x34);
// expect to set PC to 0x834
to_execute.execute(&mut x);
assert_eq!(x.registers.peek_i(), 0x834);
}
#[test]
fn instruction_ldrd() {
let mut x = Chip8Computer::new();
x.state = WaitingForInstruction;
x.delay_timer.set_timer(0x01);
Chip8CpuInstructions::LDRD(0x01).execute(&mut x);
assert_eq!(x.registers.peek(0x01), 0x01);
x.delay_timer.set_timer(0xff);
Chip8CpuInstructions::LDRD(0x0).execute(&mut x);
assert_eq!(x.registers.peek(0x00), 0xff);
x.step_system();
x.step_system();
x.step_system();
x.step_system();
Chip8CpuInstructions::LDRD(0x0).execute(&mut x);
assert_eq!(x.registers.peek(0x0), 0xfb);
}
#[test]
fn video_hires_loop_check() {
let max_address = SCHIP_VIDE_MEMORY;
let x = build_checkboard_hd();
x.peek((max_address + 1) as u16);
// if we got here we didn't explode!
assert!(true);
}
#[test]
fn sound_timer_default() {
let x = SoundTimer::default();
assert_eq!(x.current(), 0);
}
#[test]
fn system_memory_new() {
let x = Chip8SystemMemory::new();
assert!(true);
}
#[test]
fn video_lowres_schip_draw_chip8_sprite() {
let mut x = Chip8Computer::new();
x.quirk_mode = QuirkMode::SChipModern;
x.video_memory.set_lowres();
// point at the 1 from chip8
x.registers.poke_i(0x0005);
// point to 1,2 for the drawing
x.registers.poke(0x01, 0x01);
x.registers.poke(0x02, 0x02);
Chip8CpuInstructions::DRW(0x01, 0x01, 0x08).execute(&mut x);
let expected_state = read_test_result("state/video_lowres_schip_draw_chip8_sprite_result.json");
let actual_state = serde_json::to_string(&x).unwrap();
assert_eq!(expected_state, actual_state);
}
#[test]
fn video_lowres_schip_draw_schip_sprite() {}
#[test]
fn video_highres_schip_draw_chip8_sprite() {}
#[test]
fn video_highres_schip_draw_schip_sprite() {}
#[test]
fn partial_eq_chip8computer() {
let x = Chip8Computer::new();
let y = Chip8Computer::new();
assert_eq!(x, y)
}
#[test]
fn quirk_mode_labels() {
assert_eq!(format!("{}", Chip8), LABEL_QUIRK_CHIP8);
assert_eq!(format!("{}", XOChip), LABEL_QUIRK_XOCHIP);
assert_eq!(format!("{}", SChipModern), LABEL_QUIRK_SCHIP);
}
#[test]
fn system_memory_load_program() {
let mut m = Chip8SystemMemory::new();
let mut program_to_load = vec![];
let file_to_load = format!("{}/2-ibm-logo.ch8", TEST_ROM_ROOT);
println!("Attempt to load {} from {}", file_to_load, std::env::current_dir().unwrap().display());
let mut file_to_load_from = File::open(file_to_load).expect("Unable to load sample rom");
file_to_load_from.read_to_end(&mut program_to_load).expect("Unable to read sample rom");
m.load_program(program_to_load.clone().into());
let expected_at_200 = program_to_load[0];
assert_eq!(m.peek(0x200), expected_at_200);
}
#[test]
fn start_stop_computer() {
let mut computer = Chip8ComputerManager::new();
assert_eq!(computer.core_should_run, false);
computer.start();
assert_eq!(computer.core_should_run, true);
computer.step();
assert_eq!(computer.core_should_run, true);
computer.stop();
assert_eq!(computer.core_should_run, false);
computer.reset(Chip8);
assert_eq!(computer.core_should_run, false);
}
#[test]
fn state_default_matches() {
let computer = Chip8Computer::default();
let mut manager = Chip8ComputerManager::default();
assert_eq!(computer, *manager.state());
}
#[test]
fn keys_test_manager() {
let mut manager = Chip8ComputerManager::default();
for i in 0..16 {
assert_eq!(manager.is_key_pressed(i), false);
}
// press key 5
manager.press_key(5);
assert_eq!(manager.is_key_pressed(5), true);
manager.release_key(5);
assert_eq!(manager.is_key_pressed(5), false);
}
#[test]
fn status_of_manager() {
let mut manager = Chip8ComputerManager::default();
println!("MANAGER STATUS [{}]", manager.status_as_string());
let expected_state = read_test_result("test_manager_status.asc");
assert_eq!(expected_state, manager.status_as_string());
}