mirror of
https://github.com/italicsjenga/agb.git
synced 2025-01-11 09:31:34 +11:00
Add tests for cartridge save access.
This commit is contained in:
parent
2be44c12e5
commit
4397bb0d66
14
agb-tests/.cargo/config.toml
Normal file
14
agb-tests/.cargo/config.toml
Normal file
|
@ -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"
|
19
agb-tests/Cargo.toml
Normal file
19
agb-tests/Cargo.toml
Normal file
|
@ -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 = []
|
3
agb-tests/rust-toolchain.toml
Normal file
3
agb-tests/rust-toolchain.toml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[toolchain]
|
||||||
|
channel = "nightly"
|
||||||
|
components = ["rust-src", "clippy"]
|
105
agb-tests/tests/save_test_common/mod.rs
Normal file
105
agb-tests/tests/save_test_common/mod.rs
Normal file
|
@ -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<MediaInfo> = 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");
|
||||||
|
}
|
||||||
|
}
|
16
agb-tests/tests/test_save_sram.rs
Normal file
16
agb-tests/tests/test_save_sram.rs
Normal file
|
@ -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 {}
|
||||||
|
}
|
|
@ -165,6 +165,18 @@ pub struct MediaInfo {
|
||||||
/// Whether the save media type requires media be prepared before writing.
|
/// Whether the save media type requires media be prepared before writing.
|
||||||
pub uses_prepare_write: bool,
|
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.
|
/// A trait allowing low-level saving and writing to save media.
|
||||||
trait RawSaveAccess: Sync {
|
trait RawSaveAccess: Sync {
|
||||||
|
@ -221,16 +233,16 @@ impl SaveData {
|
||||||
/// Returns the sector size of the save media. It is generally optimal to
|
/// Returns the sector size of the save media. It is generally optimal to
|
||||||
/// write data in blocks that are aligned to the sector size.
|
/// write data in blocks that are aligned to the sector size.
|
||||||
pub fn sector_size(&self) -> usize {
|
pub fn sector_size(&self) -> usize {
|
||||||
1 << self.info.sector_shift
|
self.info.sector_size()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the total length of this save media.
|
/// Returns the total length of this save media.
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.info.sector_count << self.info.sector_shift
|
self.info.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_bounds(&self, range: Range<usize>) -> Result<(), Error> {
|
fn check_bounds(&self, range: Range<usize>) -> Result<(), Error> {
|
||||||
if range.start >= self.len() || range.end >= self.len() {
|
if range.start >= self.len() || range.end > self.len() {
|
||||||
Err(Error::OutOfBounds)
|
Err(Error::OutOfBounds)
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -284,7 +284,7 @@ mod test {
|
||||||
// the actual main test loop
|
// the actual main test loop
|
||||||
let mut interrupt_seen = false;
|
let mut interrupt_seen = false;
|
||||||
let mut no_interrupt_seen = false;
|
let mut no_interrupt_seen = false;
|
||||||
for i in 0..100000 {
|
for i in 0..250000 {
|
||||||
// write to the static
|
// write to the static
|
||||||
let new_value = [i; COUNT];
|
let new_value = [i; COUNT];
|
||||||
value.write(new_value);
|
value.write(new_value);
|
||||||
|
|
3
justfile
3
justfile
|
@ -13,6 +13,7 @@ clippy:
|
||||||
|
|
||||||
test:
|
test:
|
||||||
just _test-debug agb
|
just _test-debug agb
|
||||||
|
just _test-debug agb-tests
|
||||||
just _test-debug agb-fixnum
|
just _test-debug agb-fixnum
|
||||||
just _test-debug-arm agb
|
just _test-debug-arm agb
|
||||||
just _test-debug tools
|
just _test-debug tools
|
||||||
|
@ -20,6 +21,8 @@ test:
|
||||||
test-release:
|
test-release:
|
||||||
just _test-release agb
|
just _test-release agb
|
||||||
just _test-release-arm agb
|
just _test-release-arm agb
|
||||||
|
just _test-release agb-tests
|
||||||
|
just _test-release-arm agb-tests
|
||||||
|
|
||||||
doctest-agb:
|
doctest-agb:
|
||||||
(cd agb && cargo test --doc -Z doctest-xcompile)
|
(cd agb && cargo test --doc -Z doctest-xcompile)
|
||||||
|
|
Loading…
Reference in a new issue