tests: Coverage and cleanup/speedup
- Improved test coverage to >80% of lines, functions - When doctests are included. - Wrote new unit tests: - Explicit tests for invalid instructions in the ranges {`5xyn`, `8xyn`, `9xyn`, `Fxbb`} - `rand` Tests for 1052671 cycles, to ensure randomly generated number is < ANDed byte - `Ex9E` (sek), `ExA1`(snek) will press only the expected key, then every key except the expected key, for every address - `Fx0A` (waitk) asserts based on the waveform of a keypress. After all, an A press is an A press. - Improved test performance by printing slightly less - Removed nightly requirement - (now optional, with feature = "unstable") - Amended justfile to test with `cargo nextest` (nice) - Changed release builds to optlevel 3
This commit is contained in:
parent
66bed02a5e
commit
fbc0a0b2ea
@ -6,20 +6,21 @@ authors = ["John Breaux"]
|
|||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = ["unstable"]
|
||||||
|
unstable = []
|
||||||
rhexdump = ["dep:rhexdump"]
|
rhexdump = ["dep:rhexdump"]
|
||||||
serde = ["dep:serde"]
|
serde = ["dep:serde"]
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
[profile.release]
|
[profile.release]
|
||||||
opt-level = 'z'
|
opt-level = 3
|
||||||
debug = false
|
debug = false
|
||||||
rpath = false
|
rpath = false
|
||||||
lto = true
|
lto = true
|
||||||
debug-assertions = false
|
debug-assertions = false
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
panic = 'unwind'
|
panic = 'unwind'
|
||||||
incremental = true
|
incremental = false
|
||||||
overflow-checks = false
|
overflow-checks = false
|
||||||
|
|
||||||
|
|
||||||
|
25
src/cpu.rs
25
src/cpu.rs
@ -74,10 +74,35 @@ pub struct ControlFlags {
|
|||||||
|
|
||||||
impl ControlFlags {
|
impl ControlFlags {
|
||||||
/// Toggles debug mode
|
/// Toggles debug mode
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```rust
|
||||||
|
///# use chirp::prelude::*;
|
||||||
|
///# fn main() -> Result<()> {
|
||||||
|
/// let mut cpu = CPU::default();
|
||||||
|
/// assert_eq!(true, cpu.flags.debug);
|
||||||
|
/// cpu.flags.debug();
|
||||||
|
/// assert_eq!(false, cpu.flags.debug);
|
||||||
|
///# Ok(())
|
||||||
|
///# }
|
||||||
|
/// ```
|
||||||
pub fn debug(&mut self) {
|
pub fn debug(&mut self) {
|
||||||
self.debug = !self.debug
|
self.debug = !self.debug
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Toggles pause
|
/// Toggles pause
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```rust
|
||||||
|
///# use chirp::prelude::*;
|
||||||
|
///# fn main() -> Result<()> {
|
||||||
|
/// let mut cpu = CPU::default();
|
||||||
|
/// assert_eq!(false, cpu.flags.pause);
|
||||||
|
/// cpu.flags.pause();
|
||||||
|
/// assert_eq!(true, cpu.flags.pause);
|
||||||
|
///# Ok(())
|
||||||
|
///# }
|
||||||
|
/// ```
|
||||||
pub fn pause(&mut self) {
|
pub fn pause(&mut self) {
|
||||||
self.pause = !self.pause
|
self.pause = !self.pause
|
||||||
}
|
}
|
||||||
|
@ -309,8 +309,9 @@ impl Disassemble {
|
|||||||
}
|
}
|
||||||
/// `Dxyn`: Draws n-byte sprite to the screen at coordinates (vX, vY)
|
/// `Dxyn`: Draws n-byte sprite to the screen at coordinates (vX, vY)
|
||||||
pub fn draw(&self, x: Reg, y: Reg, n: Nib) -> String {
|
pub fn draw(&self, x: Reg, y: Reg, n: Nib) -> String {
|
||||||
#[rustfmt::skip]
|
format!("draw #{n:x}, v{x:X}, v{y:X}")
|
||||||
format!("draw #{n:x}, v{x:X}, v{y:X}").style(self.normal).to_string()
|
.style(self.normal)
|
||||||
|
.to_string()
|
||||||
}
|
}
|
||||||
/// `Ex9E`: Skip next instruction if key == #X
|
/// `Ex9E`: Skip next instruction if key == #X
|
||||||
pub fn skip_if_key_equals_x(&self, x: Reg) -> String {
|
pub fn skip_if_key_equals_x(&self, x: Reg) -> String {
|
||||||
|
250
src/cpu/tests.rs
250
src/cpu/tests.rs
@ -4,6 +4,13 @@
|
|||||||
//! Tests for cpu.rs
|
//! Tests for cpu.rs
|
||||||
//!
|
//!
|
||||||
//! These run instructions, and ensure their output is consistent with previous builds
|
//! These run instructions, and ensure their output is consistent with previous builds
|
||||||
|
//!
|
||||||
|
//! General test format:
|
||||||
|
//! 1. Prepare to do the thing
|
||||||
|
//! 2. Do the thing
|
||||||
|
//! 3. Compare the result to the expected result
|
||||||
|
//!
|
||||||
|
//! Some of these tests run >16M times, which is very silly
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
pub(self) use crate::{
|
pub(self) use crate::{
|
||||||
@ -25,8 +32,8 @@ fn setup_environment() -> (CPU, Bus) {
|
|||||||
bus! {
|
bus! {
|
||||||
// Load the charset into ROM
|
// Load the charset into ROM
|
||||||
Charset [0x0050..0x00A0] = include_bytes!("../mem/charset.bin"),
|
Charset [0x0050..0x00A0] = include_bytes!("../mem/charset.bin"),
|
||||||
// Load the ROM file into RAM
|
// Load the ROM file into RAM (dummy binary which contains nothing but `jmp pc+2`)
|
||||||
Program [0x0200..0x1000] = include_bytes!("../../chip-8/BC_test.ch8"),
|
Program [0x0200..0x1000] = include_bytes!("tests/roms/jumptest.ch8"),
|
||||||
// Create a screen
|
// Create a screen
|
||||||
Screen [0x0F00..0x1000] = include_bytes!("../../chip-8/IBM Logo.ch8"),
|
Screen [0x0F00..0x1000] = include_bytes!("../../chip-8/IBM Logo.ch8"),
|
||||||
},
|
},
|
||||||
@ -38,15 +45,37 @@ fn print_screen(bytes: &[u8]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Unused instructions
|
/// Unused instructions
|
||||||
///
|
mod unimplemented {
|
||||||
/// TODO: Exhaustively test unused instructions
|
use super::*;
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic]
|
#[should_panic]
|
||||||
fn unimplemented() {
|
fn ins_5xyn() {
|
||||||
|
let (mut cpu, mut bus) = setup_environment();
|
||||||
|
bus.write(0x200u16, 0x500fu16); // 0x500f is not an instruction
|
||||||
|
cpu.tick(&mut bus);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn ins_8xyn() {
|
||||||
|
let (mut cpu, mut bus) = setup_environment();
|
||||||
|
bus.write(0x200u16, 0x800fu16); // 0x800f is not an instruction
|
||||||
|
cpu.tick(&mut bus);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn ins_9xyn() {
|
||||||
|
let (mut cpu, mut bus) = setup_environment();
|
||||||
|
bus.write(0x200u16, 0x900fu16); // 0x800f is not an instruction
|
||||||
|
cpu.tick(&mut bus);
|
||||||
|
}
|
||||||
|
// Fxbb
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
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, 0xffffu16); // 0xffff is not an instruction
|
||||||
cpu.tick(&mut bus);
|
cpu.tick(&mut bus);
|
||||||
cpu.unimplemented(0xffff);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod sys {
|
mod sys {
|
||||||
@ -133,12 +162,12 @@ mod cf {
|
|||||||
for x in 0..=0xf {
|
for x in 0..=0xf {
|
||||||
// set the PC to a random address
|
// set the PC to a random address
|
||||||
cpu.pc = addr;
|
cpu.pc = addr;
|
||||||
// set the register under test to a
|
|
||||||
cpu.v[x] = a;
|
cpu.v[x] = a;
|
||||||
// do the thing
|
|
||||||
cpu.skip_equals_immediate(x, b);
|
cpu.skip_equals_immediate(x, b);
|
||||||
// validate the result
|
|
||||||
assert_eq!(cpu.pc, addr.wrapping_add(if dbg!(a == b) { 2 } else { 0 }));
|
assert_eq!(cpu.pc, addr.wrapping_add(if a == b { 2 } else { 0 }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -152,11 +181,11 @@ mod cf {
|
|||||||
for x in 0..=0xf {
|
for x in 0..=0xf {
|
||||||
// set the PC to a random address
|
// set the PC to a random address
|
||||||
cpu.pc = addr;
|
cpu.pc = addr;
|
||||||
// set the register under test to a
|
|
||||||
cpu.v[x] = a;
|
cpu.v[x] = a;
|
||||||
// do the thing
|
|
||||||
cpu.skip_not_equals_immediate(x, b);
|
cpu.skip_not_equals_immediate(x, b);
|
||||||
// validate the result
|
|
||||||
assert_eq!(cpu.pc, addr.wrapping_add(if a != b { 2 } else { 0 }));
|
assert_eq!(cpu.pc, addr.wrapping_add(if a != b { 2 } else { 0 }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -175,11 +204,11 @@ mod cf {
|
|||||||
}
|
}
|
||||||
// set the PC to a random address
|
// set the PC to a random address
|
||||||
cpu.pc = addr;
|
cpu.pc = addr;
|
||||||
// set the registers under test to a, b
|
|
||||||
(cpu.v[x], cpu.v[y]) = (a, b);
|
(cpu.v[x], cpu.v[y]) = (a, b);
|
||||||
// do the thing
|
|
||||||
cpu.skip_equals(x, y);
|
cpu.skip_equals(x, y);
|
||||||
// validate the result
|
|
||||||
assert_eq!(cpu.pc, addr.wrapping_add(if a == b { 2 } else { 0 }));
|
assert_eq!(cpu.pc, addr.wrapping_add(if a == b { 2 } else { 0 }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -198,11 +227,11 @@ mod cf {
|
|||||||
}
|
}
|
||||||
// set the PC to a random address
|
// set the PC to a random address
|
||||||
cpu.pc = addr;
|
cpu.pc = addr;
|
||||||
// set the registers under test to a, b
|
|
||||||
(cpu.v[x], cpu.v[y]) = (a, b);
|
(cpu.v[x], cpu.v[y]) = (a, b);
|
||||||
// do the thing
|
|
||||||
cpu.skip_not_equals(x, y);
|
cpu.skip_not_equals(x, y);
|
||||||
// validate the result
|
|
||||||
assert_eq!(cpu.pc, addr.wrapping_add(if a != b { 2 } else { 0 }));
|
assert_eq!(cpu.pc, addr.wrapping_add(if a != b { 2 } else { 0 }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -218,9 +247,9 @@ mod cf {
|
|||||||
for v0 in 0..=0xff {
|
for v0 in 0..=0xff {
|
||||||
// set v[0] = v0
|
// set v[0] = v0
|
||||||
cpu.v[0] = v0;
|
cpu.v[0] = v0;
|
||||||
// jump indexed
|
|
||||||
cpu.jump_indexed(addr);
|
cpu.jump_indexed(addr);
|
||||||
// Validate register set
|
|
||||||
assert_eq!(cpu.pc, addr.wrapping_add(v0.into()));
|
assert_eq!(cpu.pc, addr.wrapping_add(v0.into()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -248,11 +277,11 @@ mod math {
|
|||||||
for test_register in 0x0..=0xf {
|
for test_register in 0x0..=0xf {
|
||||||
let mut sum = 0u8;
|
let mut sum = 0u8;
|
||||||
for test_byte in 0x0..=0xff {
|
for test_byte in 0x0..=0xff {
|
||||||
// Wrapping-add to the running total (Chip-8 allows unsigned overflow)
|
// Note: Chip-8 allows unsigned overflow
|
||||||
sum = sum.wrapping_add(test_byte);
|
sum = sum.wrapping_add(test_byte);
|
||||||
// Perform add #byte, vReg
|
|
||||||
cpu.add_immediate(test_register, test_byte);
|
cpu.add_immediate(test_register, test_byte);
|
||||||
//Verify the running total in the register matches
|
|
||||||
assert_eq!(cpu.v[test_register], sum);
|
assert_eq!(cpu.v[test_register], sum);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -273,7 +302,9 @@ mod math {
|
|||||||
cpu.v[y] = test_value;
|
cpu.v[y] = test_value;
|
||||||
// zero X
|
// zero X
|
||||||
cpu.v[x] = 0;
|
cpu.v[x] = 0;
|
||||||
|
|
||||||
cpu.load(x, y);
|
cpu.load(x, y);
|
||||||
|
|
||||||
// verify results
|
// verify results
|
||||||
assert_eq!(cpu.v[x], test_value);
|
assert_eq!(cpu.v[x], test_value);
|
||||||
assert_eq!(cpu.v[y], test_value);
|
assert_eq!(cpu.v[y], test_value);
|
||||||
@ -292,13 +323,11 @@ mod math {
|
|||||||
let expected_result = a | b;
|
let expected_result = a | b;
|
||||||
for reg in 0..=0xff {
|
for reg in 0..=0xff {
|
||||||
let (x, y) = (reg & 0xf, reg >> 4);
|
let (x, y) = (reg & 0xf, reg >> 4);
|
||||||
// set the registers under test to a, b
|
|
||||||
(cpu.v[x], cpu.v[y]) = (a, b);
|
(cpu.v[x], cpu.v[y]) = (a, b);
|
||||||
|
|
||||||
// do the thing
|
|
||||||
cpu.or(x, y);
|
cpu.or(x, y);
|
||||||
|
|
||||||
// validate the result
|
|
||||||
assert_eq!(cpu.v[x], if x == y { b } else { expected_result });
|
assert_eq!(cpu.v[x], if x == y { b } else { expected_result });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -315,13 +344,11 @@ mod math {
|
|||||||
let expected_result = a & b;
|
let expected_result = a & b;
|
||||||
for reg in 0..=0xff {
|
for reg in 0..=0xff {
|
||||||
let (x, y) = (reg & 0xf, reg >> 4);
|
let (x, y) = (reg & 0xf, reg >> 4);
|
||||||
// set the registers under test to a, b
|
|
||||||
(cpu.v[x], cpu.v[y]) = (a, b);
|
(cpu.v[x], cpu.v[y]) = (a, b);
|
||||||
|
|
||||||
// do the thing
|
|
||||||
cpu.and(x, y);
|
cpu.and(x, y);
|
||||||
|
|
||||||
// validate the result
|
|
||||||
assert_eq!(cpu.v[x], if x == y { b } else { expected_result });
|
assert_eq!(cpu.v[x], if x == y { b } else { expected_result });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -338,13 +365,11 @@ mod math {
|
|||||||
let expected_result = a ^ b;
|
let expected_result = a ^ b;
|
||||||
for reg in 0..=0xff {
|
for reg in 0..=0xff {
|
||||||
let (x, y) = (reg & 0xf, reg >> 4);
|
let (x, y) = (reg & 0xf, reg >> 4);
|
||||||
// set the registers under test to a, b
|
|
||||||
(cpu.v[x], cpu.v[y]) = (a, b);
|
(cpu.v[x], cpu.v[y]) = (a, b);
|
||||||
|
|
||||||
// do the thing
|
|
||||||
cpu.xor(x, y);
|
cpu.xor(x, y);
|
||||||
|
|
||||||
// validate the result
|
|
||||||
assert_eq!(cpu.v[x], if x == y { 0 } else { expected_result });
|
assert_eq!(cpu.v[x], if x == y { 0 } else { expected_result });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -362,13 +387,11 @@ mod math {
|
|||||||
// calculate the expected result
|
// calculate the expected result
|
||||||
// If x == y, a is discarded
|
// If x == y, a is discarded
|
||||||
let (expected, carry) = if x == y { b } else { a }.overflowing_add(b);
|
let (expected, carry) = if x == y { b } else { a }.overflowing_add(b);
|
||||||
// set the registers under test to a, b
|
|
||||||
(cpu.v[x], cpu.v[y]) = (a, b);
|
(cpu.v[x], cpu.v[y]) = (a, b);
|
||||||
|
|
||||||
// do the thing
|
|
||||||
cpu.add(x, y);
|
cpu.add(x, y);
|
||||||
|
|
||||||
// validate the result
|
|
||||||
// if the destination is vF, the result was discarded, and only the carry was kept
|
// if the destination is vF, the result was discarded, and only the carry was kept
|
||||||
if x != 0xf {
|
if x != 0xf {
|
||||||
assert_eq!(cpu.v[x], expected);
|
assert_eq!(cpu.v[x], expected);
|
||||||
@ -388,13 +411,11 @@ mod math {
|
|||||||
let (x, y) = (reg & 0xf, reg >> 4);
|
let (x, y) = (reg & 0xf, reg >> 4);
|
||||||
// calculate the expected result
|
// calculate the expected result
|
||||||
let (expected, carry) = if x == y { b } else { a }.overflowing_sub(b);
|
let (expected, carry) = if x == y { b } else { a }.overflowing_sub(b);
|
||||||
// set the registers under test to a, b
|
|
||||||
(cpu.v[x], cpu.v[y]) = (a, b);
|
(cpu.v[x], cpu.v[y]) = (a, b);
|
||||||
|
|
||||||
// do the thing
|
|
||||||
cpu.sub(x, y);
|
cpu.sub(x, y);
|
||||||
|
|
||||||
// validate the result
|
|
||||||
// if the destination is vF, the result was discarded, and only the carry was kept
|
// if the destination is vF, the result was discarded, and only the carry was kept
|
||||||
if x != 0xf {
|
if x != 0xf {
|
||||||
assert_eq!(cpu.v[x], expected);
|
assert_eq!(cpu.v[x], expected);
|
||||||
@ -414,10 +435,9 @@ mod math {
|
|||||||
for x in 0..=0xf {
|
for x in 0..=0xf {
|
||||||
// set the register under test to `word`
|
// set the register under test to `word`
|
||||||
cpu.v[x] = word;
|
cpu.v[x] = word;
|
||||||
// do the thing
|
|
||||||
cpu.shift_right(x, x);
|
cpu.shift_right(x, x);
|
||||||
|
|
||||||
// validate the result
|
|
||||||
// if the destination is vF, the result was discarded, and only the carry was kept
|
// if the destination is vF, the result was discarded, and only the carry was kept
|
||||||
if x != 0xf {
|
if x != 0xf {
|
||||||
assert_eq!(cpu.v[x], word >> 1);
|
assert_eq!(cpu.v[x], word >> 1);
|
||||||
@ -438,13 +458,10 @@ mod math {
|
|||||||
let (x, y) = (reg & 0xf, reg >> 4);
|
let (x, y) = (reg & 0xf, reg >> 4);
|
||||||
// calculate the expected result
|
// calculate the expected result
|
||||||
let (expected, carry) = if x == y { a } else { b }.overflowing_sub(a);
|
let (expected, carry) = if x == y { a } else { b }.overflowing_sub(a);
|
||||||
// set the registers under test to a, b
|
|
||||||
(cpu.v[x], cpu.v[y]) = (a, b);
|
(cpu.v[x], cpu.v[y]) = (a, b);
|
||||||
|
|
||||||
// do the thing
|
|
||||||
cpu.backwards_sub(x, y);
|
cpu.backwards_sub(x, y);
|
||||||
|
|
||||||
// validate the result
|
|
||||||
// if the destination is vF, the result was discarded, and only the carry was kept
|
// if the destination is vF, the result was discarded, and only the carry was kept
|
||||||
if x != 0xf {
|
if x != 0xf {
|
||||||
assert_eq!(cpu.v[x], expected);
|
assert_eq!(cpu.v[x], expected);
|
||||||
@ -464,10 +481,9 @@ mod math {
|
|||||||
for x in 0..=0xf {
|
for x in 0..=0xf {
|
||||||
// set the register under test to `word`
|
// set the register under test to `word`
|
||||||
cpu.v[x] = word;
|
cpu.v[x] = word;
|
||||||
// do the thing
|
|
||||||
cpu.shift_left(x, x);
|
cpu.shift_left(x, x);
|
||||||
|
|
||||||
// validate the result
|
|
||||||
// if the destination is vF, the result was discarded, and only the carry was kept
|
// if the destination is vF, the result was discarded, and only the carry was kept
|
||||||
if x != 0xf {
|
if x != 0xf {
|
||||||
assert_eq!(cpu.v[x], word << 1);
|
assert_eq!(cpu.v[x], word << 1);
|
||||||
@ -523,15 +539,21 @@ mod io {
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
/// Cxbb: Stores a random number & the provided byte into vX
|
/// Cxbb: Stores a random number & the provided byte into vX
|
||||||
//#[test]
|
#[test]
|
||||||
#[allow(dead_code)]
|
|
||||||
fn rand() {
|
fn rand() {
|
||||||
todo!()
|
let (mut cpu, _) = setup_environment();
|
||||||
|
for xb in 0..0x100fff {
|
||||||
|
let (x, b) = ((xb >> 8) % 16, xb as u8);
|
||||||
|
cpu.v[x] = 0;
|
||||||
|
cpu.rand(x, b);
|
||||||
|
// We don't know what the number will be,
|
||||||
|
// but we do know it'll be <= b
|
||||||
|
assert!(cpu.v[x] <= b);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod display {
|
mod display {
|
||||||
use super::*;
|
use super::*;
|
||||||
#[derive(Debug)]
|
|
||||||
struct ScreenTest {
|
struct ScreenTest {
|
||||||
program: &'static [u8],
|
program: &'static [u8],
|
||||||
screen: &'static [u8],
|
screen: &'static [u8],
|
||||||
@ -542,7 +564,7 @@ mod io {
|
|||||||
const SCREEN_TESTS: [ScreenTest; 4] = [
|
const SCREEN_TESTS: [ScreenTest; 4] = [
|
||||||
// Passing BC_test
|
// Passing BC_test
|
||||||
// # Quirks:
|
// # Quirks:
|
||||||
// - Requires
|
// - Requires shift and dma_inc to act like Super-CHIP
|
||||||
ScreenTest {
|
ScreenTest {
|
||||||
program: include_bytes!("../../chip-8/BC_test.ch8"),
|
program: include_bytes!("../../chip-8/BC_test.ch8"),
|
||||||
screen: include_bytes!("tests/screens/BC_test.ch8/197.bin"),
|
screen: include_bytes!("tests/screens/BC_test.ch8/197.bin"),
|
||||||
@ -551,21 +573,21 @@ mod io {
|
|||||||
bin_ops: true,
|
bin_ops: true,
|
||||||
shift: false,
|
shift: false,
|
||||||
draw_wait: true,
|
draw_wait: true,
|
||||||
|
|
||||||
dma_inc: false,
|
dma_inc: false,
|
||||||
stupid_jumps: false,
|
stupid_jumps: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// The IBM Logo
|
// The IBM Logo
|
||||||
|
// # Quirks
|
||||||
|
// Originally timed without quirks
|
||||||
ScreenTest {
|
ScreenTest {
|
||||||
program: include_bytes!("../../chip-8/IBM Logo.ch8"),
|
program: include_bytes!("../../chip-8/IBM Logo.ch8"),
|
||||||
screen: include_bytes!("tests/screens/IBM Logo.ch8/20.bin"),
|
screen: include_bytes!("tests/screens/IBM Logo.ch8/20.bin"),
|
||||||
steps: 20,
|
steps: 56,
|
||||||
quirks: Quirks {
|
quirks: Quirks {
|
||||||
bin_ops: true,
|
bin_ops: true,
|
||||||
shift: true,
|
shift: true,
|
||||||
draw_wait: false,
|
draw_wait: true,
|
||||||
|
|
||||||
dma_inc: true,
|
dma_inc: true,
|
||||||
stupid_jumps: false,
|
stupid_jumps: false,
|
||||||
},
|
},
|
||||||
@ -623,26 +645,94 @@ mod io {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mod cf {
|
mod cf {
|
||||||
//use super::*;
|
use super::*;
|
||||||
/// Ex9E: Skip next instruction if key == #X
|
/// Ex9E: Skip next instruction if key == #X
|
||||||
//#[test]
|
#[test]
|
||||||
#[allow(dead_code)]
|
|
||||||
fn skip_key_equals() {
|
fn skip_key_equals() {
|
||||||
todo!()
|
let (mut cpu, _) = setup_environment();
|
||||||
|
for ka in 0..=0x7fef {
|
||||||
|
let (key, addr) = ((ka & 0xf) as u8, ka >> 8);
|
||||||
|
// positive test (no keys except key pressed)
|
||||||
|
cpu.keys = [false; 16]; // unset all keys
|
||||||
|
cpu.keys[key as usize] = true; // except the one we care about
|
||||||
|
for x in 0..=0xf {
|
||||||
|
cpu.pc = addr;
|
||||||
|
cpu.v[x] = key;
|
||||||
|
cpu.skip_key_equals(x);
|
||||||
|
assert_eq!(cpu.pc, addr.wrapping_add(2));
|
||||||
|
cpu.v[x] = 0xff;
|
||||||
|
}
|
||||||
|
// negative test (all keys except key pressed)
|
||||||
|
cpu.keys = [true; 16]; // set all keys
|
||||||
|
cpu.keys[key as usize] = false; // except the one we care about
|
||||||
|
for x in 0..=0xf {
|
||||||
|
cpu.pc = addr;
|
||||||
|
cpu.v[x] = key;
|
||||||
|
cpu.skip_key_equals(x);
|
||||||
|
assert_eq!(cpu.pc, addr);
|
||||||
|
cpu.v[x] = 0xff;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ExaE: Skip next instruction if key != #X
|
/// ExaE: Skip next instruction if key != #X
|
||||||
//#[test]
|
#[test]
|
||||||
#[allow(dead_code)]
|
|
||||||
fn skip_key_not_equals() {
|
fn skip_key_not_equals() {
|
||||||
todo!()
|
let (mut cpu, _) = setup_environment();
|
||||||
|
for ka in 0..=0x7fcf {
|
||||||
|
let (key, addr) = ((ka & 0xf) as u8, ka >> 8);
|
||||||
|
// positive test (no keys except key pressed)
|
||||||
|
cpu.keys = [false; 16]; // unset all keys
|
||||||
|
cpu.keys[key as usize] = true; // except the one we care about
|
||||||
|
for x in 0..=0xf {
|
||||||
|
cpu.pc = addr;
|
||||||
|
cpu.v[x] = key;
|
||||||
|
cpu.skip_key_not_equals(x);
|
||||||
|
assert_eq!(cpu.pc, addr);
|
||||||
|
cpu.v[x] = 0xff;
|
||||||
|
}
|
||||||
|
// negative test (all keys except key pressed)
|
||||||
|
cpu.keys = [true; 16]; // set all keys
|
||||||
|
cpu.keys[key as usize] = false; // except the one we care about
|
||||||
|
for x in 0..=0xf {
|
||||||
|
cpu.pc = addr;
|
||||||
|
cpu.v[x] = key;
|
||||||
|
cpu.skip_key_not_equals(x);
|
||||||
|
assert_eq!(cpu.pc, addr.wrapping_add(2));
|
||||||
|
cpu.v[x] = 0xff;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fx0A: Wait for key, then vX = K
|
/// Fx0A: Wait for key, then vX = K
|
||||||
//#[test]
|
///
|
||||||
#[allow(dead_code)]
|
/// The write happens on key *release*
|
||||||
|
#[test]
|
||||||
fn wait_for_key() {
|
fn wait_for_key() {
|
||||||
todo!()
|
let (mut cpu, _) = setup_environment();
|
||||||
|
for key in 0..0xf {
|
||||||
|
for x in 0..0xf {
|
||||||
|
cpu.v[x] = 0xff;
|
||||||
|
cpu.wait_for_key(x);
|
||||||
|
assert_eq!(true, cpu.flags.keypause);
|
||||||
|
assert_eq!(0xff, cpu.v[x]);
|
||||||
|
// There are three parts to a button press
|
||||||
|
// When the button is pressed
|
||||||
|
cpu.press(key);
|
||||||
|
assert_eq!(true, cpu.flags.keypause);
|
||||||
|
assert_eq!(0xff, cpu.v[x]);
|
||||||
|
// When the button is held
|
||||||
|
cpu.wait_for_key(x);
|
||||||
|
assert_eq!(true, cpu.flags.keypause);
|
||||||
|
assert_eq!(0xff, cpu.v[x]);
|
||||||
|
// And when the button is released!
|
||||||
|
cpu.release(key);
|
||||||
|
assert_eq!(false, cpu.flags.keypause);
|
||||||
|
assert_eq!(Some(key), cpu.flags.lastkey);
|
||||||
|
cpu.wait_for_key(x);
|
||||||
|
assert_eq!(key as u8, cpu.v[x]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -654,9 +744,9 @@ mod io {
|
|||||||
for x in 0..=0xf {
|
for x in 0..=0xf {
|
||||||
// set the register under test to `word`
|
// set the register under test to `word`
|
||||||
cpu.delay = word as f64;
|
cpu.delay = word as f64;
|
||||||
// do the thing
|
|
||||||
cpu.load_delay_timer(x);
|
cpu.load_delay_timer(x);
|
||||||
// validate the result
|
|
||||||
assert_eq!(cpu.v[x], word);
|
assert_eq!(cpu.v[x], word);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -670,9 +760,9 @@ mod io {
|
|||||||
for x in 0..=0xf {
|
for x in 0..=0xf {
|
||||||
// set the register under test to `word`
|
// set the register under test to `word`
|
||||||
cpu.v[x] = word;
|
cpu.v[x] = word;
|
||||||
// do the thing
|
|
||||||
cpu.store_delay_timer(x);
|
cpu.store_delay_timer(x);
|
||||||
// validate the result
|
|
||||||
assert_eq!(cpu.delay, word as f64);
|
assert_eq!(cpu.delay, word as f64);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -680,15 +770,15 @@ mod io {
|
|||||||
|
|
||||||
/// Fx18: Load vX into ST
|
/// Fx18: Load vX into ST
|
||||||
#[test]
|
#[test]
|
||||||
fn load_sound_timer() {
|
fn store_sound_timer() {
|
||||||
let (mut cpu, _) = setup_environment();
|
let (mut cpu, _) = setup_environment();
|
||||||
for word in 0..=0xff {
|
for word in 0..=0xff {
|
||||||
for x in 0..=0xf {
|
for x in 0..=0xf {
|
||||||
// set the register under test to `word`
|
// set the register under test to `word`
|
||||||
cpu.v[x] = word;
|
cpu.v[x] = word;
|
||||||
// do the thing
|
|
||||||
cpu.store_sound_timer(x);
|
cpu.store_sound_timer(x);
|
||||||
// validate the result
|
|
||||||
assert_eq!(cpu.sound, word as f64);
|
assert_eq!(cpu.sound, word as f64);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -724,7 +814,6 @@ mod io {
|
|||||||
|
|
||||||
/// Fx29: Load sprite for character vX into I
|
/// Fx29: Load sprite for character vX into I
|
||||||
#[test]
|
#[test]
|
||||||
#[allow(dead_code)]
|
|
||||||
fn load_sprite() {
|
fn load_sprite() {
|
||||||
let (mut cpu, bus) = setup_environment();
|
let (mut cpu, bus) = setup_environment();
|
||||||
for test in TESTS {
|
for test in TESTS {
|
||||||
@ -735,15 +824,7 @@ mod io {
|
|||||||
cpu.load_sprite(reg);
|
cpu.load_sprite(reg);
|
||||||
|
|
||||||
let addr = cpu.i as usize;
|
let addr = cpu.i as usize;
|
||||||
assert_eq!(
|
assert_eq!(bus.get(addr..addr.wrapping_add(5)).unwrap(), test.output,);
|
||||||
bus.get(addr..addr.wrapping_add(5)).unwrap(),
|
|
||||||
test.output,
|
|
||||||
"\nTesting load_sprite({:x}) -> {:04x} {{\n\tout:{:02x?}\n\texp:{:02x?}\n}}",
|
|
||||||
test.input,
|
|
||||||
cpu.i,
|
|
||||||
bus.get(addr..addr.wrapping_add(5)).unwrap(),
|
|
||||||
test.output,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -775,7 +856,6 @@ mod io {
|
|||||||
|
|
||||||
/// Fx33: BCD convert X into I`[0..3]`
|
/// Fx33: BCD convert X into I`[0..3]`
|
||||||
#[test]
|
#[test]
|
||||||
#[allow(dead_code)]
|
|
||||||
fn bcd_convert() {
|
fn bcd_convert() {
|
||||||
for test in BCD_TESTS {
|
for test in BCD_TESTS {
|
||||||
let (mut cpu, mut bus) = setup_environment();
|
let (mut cpu, mut bus) = setup_environment();
|
||||||
@ -785,7 +865,7 @@ mod io {
|
|||||||
cpu.v[5] = test.input;
|
cpu.v[5] = test.input;
|
||||||
|
|
||||||
cpu.bcd_convert(5, &mut bus);
|
cpu.bcd_convert(5, &mut bus);
|
||||||
// validate the results
|
|
||||||
assert_eq!(bus.get(addr..addr.saturating_add(3)), Some(test.output))
|
assert_eq!(bus.get(addr..addr.saturating_add(3)), Some(test.output))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -794,7 +874,6 @@ mod io {
|
|||||||
/// Fx55: DMA Stor from I to registers 0..X
|
/// Fx55: DMA Stor from I to registers 0..X
|
||||||
// TODO: Test with dma_inc quirk set
|
// TODO: Test with dma_inc quirk set
|
||||||
#[test]
|
#[test]
|
||||||
#[allow(dead_code)]
|
|
||||||
fn dma_store() {
|
fn dma_store() {
|
||||||
let (mut cpu, mut bus) = setup_environment();
|
let (mut cpu, mut bus) = setup_environment();
|
||||||
const DATA: &[u8] = b"ABCDEFGHIJKLMNOP";
|
const DATA: &[u8] = b"ABCDEFGHIJKLMNOP";
|
||||||
@ -817,7 +896,6 @@ mod io {
|
|||||||
/// Fx65: DMA Load from I to registers 0..X
|
/// Fx65: DMA Load from I to registers 0..X
|
||||||
// TODO: Test with dma_inc quirk set
|
// TODO: Test with dma_inc quirk set
|
||||||
#[test]
|
#[test]
|
||||||
#[allow(dead_code)]
|
|
||||||
fn dma_load() {
|
fn dma_load() {
|
||||||
let (mut cpu, mut bus) = setup_environment();
|
let (mut cpu, mut bus) = setup_environment();
|
||||||
const DATA: &[u8] = b"ABCDEFGHIJKLMNOP";
|
const DATA: &[u8] = b"ABCDEFGHIJKLMNOP";
|
||||||
|
2
src/cpu/tests/roms/jumptest.ch8
Normal file
2
src/cpu/tests/roms/jumptest.ch8
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
"$&(*,.02468:<>@BDFHJLNPRTVXZ\^`
|
BIN
src/cpu/tests/roms/looptest.ch8
Normal file
BIN
src/cpu/tests/roms/looptest.ch8
Normal file
Binary file not shown.
@ -266,6 +266,7 @@ pub fn identify_key(key: Key) -> usize {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "unstable", no_coverage)]
|
||||||
pub fn debug_dump_screen(ch8: &Chip8, rom: &Path) -> Result<()> {
|
pub fn debug_dump_screen(ch8: &Chip8, rom: &Path) -> Result<()> {
|
||||||
let path = PathBuf::new()
|
let path = PathBuf::new()
|
||||||
.join("src/cpu/tests/screens/")
|
.join("src/cpu/tests/screens/")
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
// (c) 2023 John A. Breaux
|
// (c) 2023 John A. Breaux
|
||||||
// This code is licensed under MIT license (see LICENSE.txt for details)
|
// This code is licensed under MIT license (see LICENSE.txt for details)
|
||||||
|
|
||||||
#![feature(stmt_expr_attributes)]
|
#![cfg_attr(feature = "unstable", feature(no_coverage))]
|
||||||
//! This crate implements a Chip-8 interpreter as if it were a real CPU architecture,
|
//! This crate implements a Chip-8 interpreter as if it were a real CPU architecture,
|
||||||
//! to the best of my current knowledge. As it's the first emulator project I've
|
//! to the best of my current knowledge. As it's the first emulator project I've
|
||||||
//! embarked on, and I'm fairly new to Rust, it's going to be rough around the edges.
|
//! embarked on, and I'm fairly new to Rust, it's going to be rough around the edges.
|
||||||
|
230
tests/struct.rs
Normal file
230
tests/struct.rs
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
//! Testing methods on Chirp's structs
|
||||||
|
use chirp::prelude::*;
|
||||||
|
use std::{collections::hash_map::DefaultHasher, hash::Hash};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn chip8() {
|
||||||
|
let ch8 = Chip8::default(); // Default
|
||||||
|
let ch82 = ch8.clone(); // Clone
|
||||||
|
assert_eq!(ch8, ch82); // PartialEq
|
||||||
|
println!("{ch8:?}"); // Debug
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn error() {
|
||||||
|
let error = chirp::error::Error::FunkyMath {
|
||||||
|
word: 0x1234,
|
||||||
|
explanation: "This shit was funky".to_owned(),
|
||||||
|
};
|
||||||
|
println!("{error} {error:?}");
|
||||||
|
}
|
||||||
|
|
||||||
|
mod ui_builder {
|
||||||
|
use super::*;
|
||||||
|
#[test]
|
||||||
|
fn ui_builder() -> Result<()> {
|
||||||
|
let builder = UIBuilder::new(32, 64).rom("dummy.ch8").build()?;
|
||||||
|
println!("{builder:?}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn default() {
|
||||||
|
let ui_builder = UIBuilder::default();
|
||||||
|
println!("{ui_builder:?}");
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn clone_debug() {
|
||||||
|
let ui_builder_clone = UIBuilder::default().clone();
|
||||||
|
println!("{ui_builder_clone:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mod ui {
|
||||||
|
use super::*;
|
||||||
|
fn new_chip8() -> Chip8 {
|
||||||
|
Chip8 {
|
||||||
|
cpu: CPU::default(),
|
||||||
|
bus: bus! {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn frame() -> Result<()> {
|
||||||
|
let mut ui = UIBuilder::new(32, 64).build()?;
|
||||||
|
let mut ch8 = new_chip8();
|
||||||
|
ui.frame(&mut ch8).unwrap();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn keys() -> Result<()> {
|
||||||
|
let mut ui = UIBuilder::new(32, 64).build()?;
|
||||||
|
let mut ch8 = new_chip8();
|
||||||
|
let ch8 = &mut ch8;
|
||||||
|
ui.frame(ch8).unwrap();
|
||||||
|
ui.keys(ch8);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn debug() -> Result<()> {
|
||||||
|
println!("{:?}", UIBuilder::new(32, 64).build()?);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod framebuffer_format {
|
||||||
|
use super::*;
|
||||||
|
#[test]
|
||||||
|
fn default() {
|
||||||
|
let _fbf = FrameBufferFormat::default();
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn clone() {
|
||||||
|
let fbf = FrameBufferFormat {
|
||||||
|
fg: 0x12345678,
|
||||||
|
bg: 0x90abcdef,
|
||||||
|
};
|
||||||
|
let fbf2 = fbf.clone();
|
||||||
|
assert_eq!(fbf, fbf2);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn debug() {
|
||||||
|
println!("{:?}", FrameBufferFormat::default());
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn eq() {
|
||||||
|
assert_eq!(FrameBufferFormat::default(), FrameBufferFormat::default());
|
||||||
|
assert_ne!(
|
||||||
|
FrameBufferFormat {
|
||||||
|
fg: 0xff00ff,
|
||||||
|
bg: 0x00ff00
|
||||||
|
},
|
||||||
|
FrameBufferFormat {
|
||||||
|
fg: 0x00ff00,
|
||||||
|
bg: 0xff00ff
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn ord() {
|
||||||
|
assert!(
|
||||||
|
FrameBufferFormat::default()
|
||||||
|
== FrameBufferFormat {
|
||||||
|
fg: 0xffffff,
|
||||||
|
bg: 0xffffff,
|
||||||
|
}
|
||||||
|
.min(FrameBufferFormat::default())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn hash() {
|
||||||
|
println!(
|
||||||
|
"{:?}",
|
||||||
|
FrameBufferFormat::default().hash(&mut DefaultHasher::new())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod framebuffer {
|
||||||
|
use super::*;
|
||||||
|
// [derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
#[test]
|
||||||
|
fn new() {
|
||||||
|
assert_eq!(FrameBuffer::new(64, 32), FrameBuffer::default());
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn clone() {
|
||||||
|
let fb1 = FrameBuffer::default();
|
||||||
|
let fb2 = fb1.clone();
|
||||||
|
assert_eq!(fb1, fb2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn debug() {
|
||||||
|
println!("{:?}", FrameBuffer::default());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eq() {
|
||||||
|
assert_eq!(FrameBuffer::new(64, 32), FrameBuffer::default());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ord() {
|
||||||
|
assert!(FrameBuffer::new(21, 12) == FrameBuffer::new(21, 12).min(FrameBuffer::new(34, 46)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hash() {
|
||||||
|
println!(
|
||||||
|
"{:?}",
|
||||||
|
FrameBuffer::default().hash(&mut DefaultHasher::new())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod quirks {
|
||||||
|
use super::*;
|
||||||
|
use chirp::cpu::Quirks;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn from_true() {
|
||||||
|
let quirks_true = Quirks::from(true);
|
||||||
|
assert_eq!(
|
||||||
|
quirks_true,
|
||||||
|
Quirks {
|
||||||
|
bin_ops: true,
|
||||||
|
shift: true,
|
||||||
|
draw_wait: true,
|
||||||
|
dma_inc: true,
|
||||||
|
stupid_jumps: false,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn from_false() {
|
||||||
|
let quirks_true = Quirks::from(false);
|
||||||
|
assert_eq!(
|
||||||
|
quirks_true,
|
||||||
|
Quirks {
|
||||||
|
bin_ops: false,
|
||||||
|
shift: false,
|
||||||
|
draw_wait: false,
|
||||||
|
dma_inc: false,
|
||||||
|
stupid_jumps: false,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn clone() {
|
||||||
|
let q1 = Quirks {
|
||||||
|
bin_ops: false,
|
||||||
|
shift: true,
|
||||||
|
draw_wait: false,
|
||||||
|
dma_inc: true,
|
||||||
|
stupid_jumps: false,
|
||||||
|
};
|
||||||
|
let q2 = q1.clone();
|
||||||
|
assert_eq!(q1, q2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn debug() {
|
||||||
|
println!("{:?}", Quirks::from(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eq() {
|
||||||
|
assert_ne!(Quirks::from(false), Quirks::from(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ord() {
|
||||||
|
assert!(Quirks::from(false) < Quirks::from(true));
|
||||||
|
assert!(Quirks::from(true) == Quirks::from(false).max(Quirks::from(true)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hash() {
|
||||||
|
println!("{:?}", Quirks::from(true).hash(&mut DefaultHasher::new()));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user