From 4397bb0d6649c662ab2435cf4241dfd872b218b7 Mon Sep 17 00:00:00 2001 From: Alissa Rao Date: Thu, 15 Sep 2022 23:21:10 -0700 Subject: [PATCH] Add tests for cartridge save access. --- agb-tests/.cargo/config.toml | 14 ++++ agb-tests/Cargo.toml | 19 +++++ agb-tests/rust-toolchain.toml | 3 + agb-tests/tests/save_test_common/mod.rs | 105 ++++++++++++++++++++++++ agb-tests/tests/test_save_sram.rs | 16 ++++ agb/src/save/mod.rs | 18 +++- agb/src/sync/statics.rs | 2 +- justfile | 3 + 8 files changed, 176 insertions(+), 4 deletions(-) create mode 100644 agb-tests/.cargo/config.toml create mode 100644 agb-tests/Cargo.toml create mode 100644 agb-tests/rust-toolchain.toml create mode 100644 agb-tests/tests/save_test_common/mod.rs create mode 100644 agb-tests/tests/test_save_sram.rs diff --git a/agb-tests/.cargo/config.toml b/agb-tests/.cargo/config.toml new file mode 100644 index 00000000..d5f7f86c --- /dev/null +++ b/agb-tests/.cargo/config.toml @@ -0,0 +1,14 @@ +[unstable] +build-std = ["core", "alloc"] +build-std-features = ["compiler-builtins-mem"] + +[build] +target = "thumbv4t-none-eabi" + +[target.thumbv4t-none-eabi] +rustflags = ["-Clink-arg=-T../agb/gba.ld", "-Ctarget-cpu=arm7tdmi"] +runner = "mgba-test-runner" + +[target.armv4t-none-eabi] +rustflags = ["-Clink-arg=-T../agb/gba.ld", "-Ctarget-cpu=arm7tdmi"] +runner = "mgba-test-runner" diff --git a/agb-tests/Cargo.toml b/agb-tests/Cargo.toml new file mode 100644 index 00000000..9917f81b --- /dev/null +++ b/agb-tests/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "agb-tests" +version = "0.1.0" +edition = "2018" + +[profile.dev] +opt-level = 3 +debug = true + +[profile.release] +lto = true +debug = true + +[dependencies] +agb = { version = "*", path = "../agb", features = ["testing"] } + +[package.metadata.docs.rs] +default-target = "thumbv6m-none-eabi" +targets = [] diff --git a/agb-tests/rust-toolchain.toml b/agb-tests/rust-toolchain.toml new file mode 100644 index 00000000..06842486 --- /dev/null +++ b/agb-tests/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "nightly" +components = ["rust-src", "clippy"] \ No newline at end of file diff --git a/agb-tests/tests/save_test_common/mod.rs b/agb-tests/tests/save_test_common/mod.rs new file mode 100644 index 00000000..9e494424 --- /dev/null +++ b/agb-tests/tests/save_test_common/mod.rs @@ -0,0 +1,105 @@ +use core::cmp; +use agb::save::{Error, MediaInfo}; +use agb::sync::InitOnce; + +fn init_sram(gba: &mut agb::Gba) -> &'static MediaInfo { + static ONCE: InitOnce = InitOnce::new(); + ONCE.get(|| { + crate::save_setup(gba); + gba.save.access().unwrap().media_info().clone() + }) +} + +#[derive(Clone)] +struct Rng(u32); +impl Rng { + fn iter(&mut self) { + self.0 = self.0.wrapping_mul(2891336453).wrapping_add(100001); + } + fn next_u8(&mut self) -> u8 { + self.iter(); + (self.0 >> 22) as u8 ^ self.0 as u8 + } + fn next_under(&mut self, under: u32) -> u32 { + self.iter(); + let scale = 31 - under.leading_zeros(); + ((self.0 >> scale) ^ self.0) % under + } +} + +const MAX_BLOCK_SIZE: usize = 4 * 1024; + +#[allow(clippy::needless_range_loop)] +fn do_test( + gba: &mut agb::Gba, seed: Rng, offset: usize, len: usize, block_size: usize, +) -> Result<(), Error> { + let mut buffer = [0; MAX_BLOCK_SIZE]; + + let timers = gba.timers.timers(); + let mut access = gba.save.access_with_timer(timers.timer2)?; + + // writes data to the save media + let prepared = access.prepare_write(offset..offset + len)?; + let mut rng = seed.clone(); + let mut current = offset; + let end = offset + len; + while current != end { + let cur_len = cmp::min(end - current, block_size); + for i in 0..cur_len { + buffer[i] = rng.next_u8(); + } + prepared.write(current, &buffer[..cur_len])?; + current += cur_len; + } + + // validates the save media + rng = seed; + current = offset; + while current != end { + let cur_len = cmp::min(end - current, block_size); + access.read(current, &mut buffer[..cur_len])?; + for i in 0..cur_len { + let cur_byte = rng.next_u8(); + assert_eq!( + buffer[i], cur_byte, + "Read does not match earlier write: {} != {} @ 0x{:05x}", + buffer[i], cur_byte, current + i, + ); + } + current += cur_len; + } + + Ok(()) +} + +#[test_case] +fn test_4k_blocks(gba: &mut agb::Gba) { + let info = init_sram(gba); + + if info.len() >= (1 << 12) { + do_test(gba, Rng(2000), 0, info.len(), 4 * 1024).expect("Test encountered error"); + } +} + +#[test_case] +fn test_512b_blocks(gba: &mut agb::Gba) { + let info = init_sram(gba); + do_test(gba, Rng(1000), 0, info.len(), 512).expect("Test encountered error"); +} + +#[test_case] +fn test_partial_writes(gba: &mut agb::Gba) { + let info = init_sram(gba); + + // test with random segments now. + let mut rng = Rng(12345); + for i in 0..8 { + let rand_length = rng.next_under((info.len() >> 1) as u32) as usize + 50; + let rand_offset = rng.next_under(info.len() as u32 - rand_length as u32) as usize; + let block_size = cmp::min(rand_length >> 2, MAX_BLOCK_SIZE - 100); + let block_size = rng.next_under(block_size as u32) as usize + 50; + + do_test(gba, Rng(i * 10000), rand_offset, rand_length, block_size) + .expect("Test encountered error"); + } +} \ No newline at end of file diff --git a/agb-tests/tests/test_save_sram.rs b/agb-tests/tests/test_save_sram.rs new file mode 100644 index 00000000..f3348eda --- /dev/null +++ b/agb-tests/tests/test_save_sram.rs @@ -0,0 +1,16 @@ +#![no_std] +#![no_main] +#![feature(custom_test_frameworks)] +#![reexport_test_harness_main = "test_main"] +#![test_runner(agb::test_runner::test_runner)] + +mod save_test_common; + +fn save_setup(gba: &mut agb::Gba) { + gba.save.init_sram(); +} + +#[agb::entry] +fn entry(_gba: agb::Gba) -> ! { + loop {} +} diff --git a/agb/src/save/mod.rs b/agb/src/save/mod.rs index af9d7927..7de71242 100644 --- a/agb/src/save/mod.rs +++ b/agb/src/save/mod.rs @@ -165,6 +165,18 @@ pub struct MediaInfo { /// Whether the save media type requires media be prepared before writing. pub uses_prepare_write: bool, } +impl MediaInfo { + /// Returns the sector size of the save media. It is generally optimal to + /// write data in blocks that are aligned to the sector size. + pub fn sector_size(&self) -> usize { + 1 << self.sector_shift + } + + /// Returns the total length of this save media. + pub fn len(&self) -> usize { + self.sector_count << self.sector_shift + } +} /// A trait allowing low-level saving and writing to save media. trait RawSaveAccess: Sync { @@ -221,16 +233,16 @@ impl SaveData { /// Returns the sector size of the save media. It is generally optimal to /// write data in blocks that are aligned to the sector size. pub fn sector_size(&self) -> usize { - 1 << self.info.sector_shift + self.info.sector_size() } /// Returns the total length of this save media. pub fn len(&self) -> usize { - self.info.sector_count << self.info.sector_shift + self.info.len() } fn check_bounds(&self, range: Range) -> Result<(), Error> { - if range.start >= self.len() || range.end >= self.len() { + if range.start >= self.len() || range.end > self.len() { Err(Error::OutOfBounds) } else { Ok(()) diff --git a/agb/src/sync/statics.rs b/agb/src/sync/statics.rs index 3970589f..62ec9d1d 100644 --- a/agb/src/sync/statics.rs +++ b/agb/src/sync/statics.rs @@ -284,7 +284,7 @@ mod test { // the actual main test loop let mut interrupt_seen = false; let mut no_interrupt_seen = false; - for i in 0..100000 { + for i in 0..250000 { // write to the static let new_value = [i; COUNT]; value.write(new_value); diff --git a/justfile b/justfile index 23837d70..ba8f243c 100644 --- a/justfile +++ b/justfile @@ -13,6 +13,7 @@ clippy: test: just _test-debug agb + just _test-debug agb-tests just _test-debug agb-fixnum just _test-debug-arm agb just _test-debug tools @@ -20,6 +21,8 @@ test: test-release: just _test-release agb just _test-release-arm agb + just _test-release agb-tests + just _test-release-arm agb-tests doctest-agb: (cd agb && cargo test --doc -Z doctest-xcompile)