From 7a897a2aad6655867efe03a40717bbc3ae9f8f01 Mon Sep 17 00:00:00 2001 From: lif <> Date: Wed, 5 Feb 2025 18:46:03 -0800 Subject: [PATCH 1/4] barebones test harness based on mgba-test-runner from agb-rs --- .github/workflows/ci-builds.yml | 16 ++++ src/lib.rs | 130 ++++++++++++++++++++++++++++++++ 2 files changed, 146 insertions(+) diff --git a/.github/workflows/ci-builds.yml b/.github/workflows/ci-builds.yml index 271ef03..cb86376 100644 --- a/.github/workflows/ci-builds.yml +++ b/.github/workflows/ci-builds.yml @@ -39,3 +39,19 @@ jobs: toolchain: ${{ matrix.rust.toolchain }} command: check args: --no-default-features + + - name: Install mgba-test-runner + uses: actions-rs/cargo@v1 + with: + toolchain: ${{ matrix.rust.toolchain }} + command: install + # newer revisions don't build on aarch64, at least, because of a c_char mishap + args: --git https://github.com/agbrs/agb --rev a7f9fdf01118a7a77d4dcf72f2b74a1961458b36 mgba-test-runner + + - name: Run unit tests + uses: actions-rs/cargo@v1 + env: + CARGO_TARGET_THUMBV4T_NONE_EABI_RUNNER: mgba-test-runner + with: + toolchain: ${{ matrix.rust.toolchain }} + command: test diff --git a/src/lib.rs b/src/lib.rs index e57641f..4fd2bfb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,10 @@ #![allow(clippy::let_and_return)] #![allow(clippy::result_unit_err)] #![warn(clippy::missing_inline_in_public_items)] +#![cfg_attr(test, feature(custom_test_frameworks))] +#![cfg_attr(test, test_runner(test_harness::test_runner))] +#![cfg_attr(test, no_main)] +#![cfg_attr(test, reexport_test_harness_main = "test_main")] //! A crate for GBA development. //! @@ -174,3 +178,129 @@ macro_rules! include_aligned_bytes { Align4(*include_bytes!($file)) }}; } + +#[cfg(test)] +mod test_harness { + use crate::prelude::*; + use crate::{bios, mem, mgba}; + use core::fmt::Write; + + #[panic_handler] + fn panic(info: &core::panic::PanicInfo) -> ! { + DISPSTAT.write(DisplayStatus::new().with_irq_vblank(true)); + BG_PALETTE.index(0).write(Color::from_rgb(25, 10, 5)); + IE.write(IrqBits::VBLANK); + IME.write(true); + VBlankIntrWait(); + VBlankIntrWait(); + VBlankIntrWait(); + + // the Fatal one kills emulation after one line / 256 bytes + // so emit all the information as Error first + if let Ok(mut log) = + mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Error) + { + writeln!(log, "[failed]").ok(); + write!(log, "{}", info).ok(); + } + + if let Ok(mut log) = + mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Fatal) + { + if let Some(loc) = info.location() { + write!(log, "panic at {loc}! see mgba error log for details.").ok(); + } else { + write!(log, "panic! see mgba error log for details.").ok(); + } + } + + IE.write(IrqBits::new()); + bios::IntrWait(true, IrqBits::new()); + loop {} + } + + pub(crate) trait UnitTest { + fn run(&self); + } + + impl UnitTest for T { + fn run(&self) { + if let Ok(mut log) = + mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Info) + { + write!(log, "{}...", core::any::type_name::()).ok(); + } + + self(); + + if let Ok(mut log) = + mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Info) + { + writeln!(log, "[ok]").ok(); + } + } + } + + pub(crate) fn test_runner(tests: &[&dyn UnitTest]) { + if let Ok(mut log) = + mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Info) + { + write!(log, "Running {} tests", tests.len()).ok(); + } + + for test in tests { + test.run(); + } + if let Ok(mut log) = + mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Info) + { + write!(log, "Tests finished successfully").ok(); + } + } + + #[no_mangle] + extern "C" fn main() { + DISPCNT.write(DisplayControl::new().with_video_mode(VideoMode::_0)); + BG_PALETTE.index(0).write(Color::new()); + + crate::test_main(); + + BG_PALETTE.index(0).write(Color::from_rgb(5, 15, 25)); + BG_PALETTE.index(1).write(Color::new()); + BG0CNT + .write(BackgroundControl::new().with_charblock(0).with_screenblock(31)); + DISPCNT.write( + DisplayControl::new().with_video_mode(VideoMode::_0).with_show_bg0(true), + ); + + // some niceties for people without mgba-test-runner + let tsb = TEXT_SCREENBLOCKS.get_frame(31).unwrap(); + unsafe { + mem::set_u32x80_unchecked( + tsb.into_block::<1024>().as_mut_ptr().cast(), + 0, + 12, + ); + } + Cga8x8Thick.bitunpack_4bpp(CHARBLOCK0_4BPP.as_region(), 0); + + let row = tsb.get_row(9).unwrap().iter().skip(6); + for (addr, ch) in row.zip(b"all tests passed!") { + addr.write(TextEntry::new().with_tile(*ch as u16)); + } + + DISPSTAT.write(DisplayStatus::new()); + bios::IntrWait(true, IrqBits::new()); + } +} + +#[cfg(test)] +mod test { + use super::Align4; + + #[test_case] + fn align4_as_u16_slice() { + let a = Align4([0u8, 1u8, 2u8, 3u8]); + assert_eq!(a.as_u16_slice(), &[0x100_u16.to_le(), 0x302_u16.to_le()]); + } +} From f9884792d57f13838a63eb3811de715dad1544f7 Mon Sep 17 00:00:00 2001 From: lif <> Date: Wed, 5 Feb 2025 18:49:50 -0800 Subject: [PATCH 2/4] mgba-test-runner needs libelf --- .github/workflows/ci-builds.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-builds.yml b/.github/workflows/ci-builds.yml index cb86376..7cd33d2 100644 --- a/.github/workflows/ci-builds.yml +++ b/.github/workflows/ci-builds.yml @@ -15,7 +15,7 @@ jobs: - uses: actions/checkout@v2 - name: Install Apt Dependencies - run: sudo apt-get update && sudo apt-get install binutils-arm-none-eabi + run: sudo apt-get update && sudo apt-get install binutils-arm-none-eabi libelf-dev - uses: actions-rs/toolchain@v1 with: From a22fe4c44417597774ca757c1372121cc53e5c87 Mon Sep 17 00:00:00 2001 From: lif <> Date: Wed, 5 Feb 2025 20:07:01 -0800 Subject: [PATCH 3/4] fix warning spam on non-gba --- src/mem.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/mem.rs b/src/mem.rs index 0587234..7bfca54 100644 --- a/src/mem.rs +++ b/src/mem.rs @@ -1,3 +1,5 @@ +#![cfg_attr(not(feature = "on_gba"), allow(unused_variables))] + use crate::macros::on_gba_or_unimplemented; /// Copies `u8` at a time between exclusive regions. From 5e5af12e39e9900eb2fb4b86b9bb699e3342afc3 Mon Sep 17 00:00:00 2001 From: lif <> Date: Wed, 5 Feb 2025 20:07:17 -0800 Subject: [PATCH 4/4] test as_u32_slice as well --- src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 4fd2bfb..4263d00 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -299,8 +299,9 @@ mod test { use super::Align4; #[test_case] - fn align4_as_u16_slice() { + fn align4_as_u16_u32_slice() { let a = Align4([0u8, 1u8, 2u8, 3u8]); assert_eq!(a.as_u16_slice(), &[0x100_u16.to_le(), 0x302_u16.to_le()]); + assert_eq!(a.as_u32_slice(), &[0x3020100_u32.to_le()]); } }