unit test support

This commit is contained in:
lif 2025-04-29 02:12:56 -07:00
parent 221e9bd8f6
commit cc2eabd500
2 changed files with 116 additions and 3 deletions

View file

@ -1,12 +1,17 @@
// from https://github.com/rust-console/gba/blob/main/examples/hello.rs
#![no_std] #![no_std]
#![no_main] #![no_main]
#![cfg_attr(test, feature(custom_test_frameworks))]
#![cfg_attr(test, test_runner(test_harness::test_runner))]
#![cfg_attr(test, reexport_test_harness_main = "test_main")]
#[cfg(test)]
mod test_harness;
use core::panic::PanicInfo; use core::panic::PanicInfo;
use gba::prelude::*; use gba::prelude::*;
#[allow(unused_must_use)] #[allow(unused_must_use)]
#[unsafe(no_mangle)] #[cfg_attr(not(test), unsafe(no_mangle))]
#[instruction_set(arm::t32)] #[instruction_set(arm::t32)]
extern "C" fn main() -> ! { extern "C" fn main() -> ! {
BG_PALETTE.index(0).write(Color(0x7F2B)); // blue BG_PALETTE.index(0).write(Color(0x7F2B)); // blue
@ -33,7 +38,8 @@ extern "C" fn main() -> ! {
unreachable!(); unreachable!();
} }
#[panic_handler] #[cfg_attr(not(test), panic_handler)]
#[cfg_attr(test, allow(unused))]
fn panic_handler(panic_info: &PanicInfo) -> ! { fn panic_handler(panic_info: &PanicInfo) -> ! {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
if let Ok(mut log) = MgbaBufferedLogger::try_new(MgbaMessageLevel::Fatal) { if let Ok(mut log) = MgbaBufferedLogger::try_new(MgbaMessageLevel::Fatal) {
@ -48,3 +54,11 @@ fn panic_handler(panic_info: &PanicInfo) -> ! {
VBlankIntrWait(); VBlankIntrWait();
} }
} }
#[cfg(test)]
mod tests {
#[test_case]
fn it_works() {
assert_eq!(1 + 1, 2);
}
}

99
src/test_harness.rs Normal file
View file

@ -0,0 +1,99 @@
use core::fmt::Write;
use gba::{bios, mem, mgba, prelude::*};
#[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<T: Fn()> UnitTest for T {
fn run(&self) {
if let Ok(mut log) = mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Info) {
write!(log, "{}...", core::any::type_name::<T>()).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();
}
}
#[unsafe(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());
}