Skip to content

Commit 5a93b05

Browse files
authored
barebones test harness based on mgba-test-runner from agb-rs (#212)
* barebones test harness based on mgba-test-runner from agb-rs * mgba-test-runner needs libelf * fix warning spam on non-gba * test as_u32_slice as well --------- Co-authored-by: lif <>
1 parent 20edcdd commit 5a93b05

File tree

3 files changed

+150
-1
lines changed

3 files changed

+150
-1
lines changed

.github/workflows/ci-builds.yml

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
- uses: actions/checkout@v2
1616

1717
- name: Install Apt Dependencies
18-
run: sudo apt-get update && sudo apt-get install binutils-arm-none-eabi
18+
run: sudo apt-get update && sudo apt-get install binutils-arm-none-eabi libelf-dev
1919

2020
- uses: actions-rs/toolchain@v1
2121
with:
@@ -39,3 +39,19 @@ jobs:
3939
toolchain: ${{ matrix.rust.toolchain }}
4040
command: check
4141
args: --no-default-features
42+
43+
- name: Install mgba-test-runner
44+
uses: actions-rs/cargo@v1
45+
with:
46+
toolchain: ${{ matrix.rust.toolchain }}
47+
command: install
48+
# newer revisions don't build on aarch64, at least, because of a c_char mishap
49+
args: --git https://github.com/agbrs/agb --rev a7f9fdf01118a7a77d4dcf72f2b74a1961458b36 mgba-test-runner
50+
51+
- name: Run unit tests
52+
uses: actions-rs/cargo@v1
53+
env:
54+
CARGO_TARGET_THUMBV4T_NONE_EABI_RUNNER: mgba-test-runner
55+
with:
56+
toolchain: ${{ matrix.rust.toolchain }}
57+
command: test

src/lib.rs

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
#![allow(clippy::let_and_return)]
66
#![allow(clippy::result_unit_err)]
77
#![warn(clippy::missing_inline_in_public_items)]
8+
#![cfg_attr(test, feature(custom_test_frameworks))]
9+
#![cfg_attr(test, test_runner(test_harness::test_runner))]
10+
#![cfg_attr(test, no_main)]
11+
#![cfg_attr(test, reexport_test_harness_main = "test_main")]
812

913
//! A crate for GBA development.
1014
//!
@@ -174,3 +178,130 @@ macro_rules! include_aligned_bytes {
174178
Align4(*include_bytes!($file))
175179
}};
176180
}
181+
182+
#[cfg(test)]
183+
mod test_harness {
184+
use crate::prelude::*;
185+
use crate::{bios, mem, mgba};
186+
use core::fmt::Write;
187+
188+
#[panic_handler]
189+
fn panic(info: &core::panic::PanicInfo) -> ! {
190+
DISPSTAT.write(DisplayStatus::new().with_irq_vblank(true));
191+
BG_PALETTE.index(0).write(Color::from_rgb(25, 10, 5));
192+
IE.write(IrqBits::VBLANK);
193+
IME.write(true);
194+
VBlankIntrWait();
195+
VBlankIntrWait();
196+
VBlankIntrWait();
197+
198+
// the Fatal one kills emulation after one line / 256 bytes
199+
// so emit all the information as Error first
200+
if let Ok(mut log) =
201+
mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Error)
202+
{
203+
writeln!(log, "[failed]").ok();
204+
write!(log, "{}", info).ok();
205+
}
206+
207+
if let Ok(mut log) =
208+
mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Fatal)
209+
{
210+
if let Some(loc) = info.location() {
211+
write!(log, "panic at {loc}! see mgba error log for details.").ok();
212+
} else {
213+
write!(log, "panic! see mgba error log for details.").ok();
214+
}
215+
}
216+
217+
IE.write(IrqBits::new());
218+
bios::IntrWait(true, IrqBits::new());
219+
loop {}
220+
}
221+
222+
pub(crate) trait UnitTest {
223+
fn run(&self);
224+
}
225+
226+
impl<T: Fn()> UnitTest for T {
227+
fn run(&self) {
228+
if let Ok(mut log) =
229+
mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Info)
230+
{
231+
write!(log, "{}...", core::any::type_name::<T>()).ok();
232+
}
233+
234+
self();
235+
236+
if let Ok(mut log) =
237+
mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Info)
238+
{
239+
writeln!(log, "[ok]").ok();
240+
}
241+
}
242+
}
243+
244+
pub(crate) fn test_runner(tests: &[&dyn UnitTest]) {
245+
if let Ok(mut log) =
246+
mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Info)
247+
{
248+
write!(log, "Running {} tests", tests.len()).ok();
249+
}
250+
251+
for test in tests {
252+
test.run();
253+
}
254+
if let Ok(mut log) =
255+
mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Info)
256+
{
257+
write!(log, "Tests finished successfully").ok();
258+
}
259+
}
260+
261+
#[no_mangle]
262+
extern "C" fn main() {
263+
DISPCNT.write(DisplayControl::new().with_video_mode(VideoMode::_0));
264+
BG_PALETTE.index(0).write(Color::new());
265+
266+
crate::test_main();
267+
268+
BG_PALETTE.index(0).write(Color::from_rgb(5, 15, 25));
269+
BG_PALETTE.index(1).write(Color::new());
270+
BG0CNT
271+
.write(BackgroundControl::new().with_charblock(0).with_screenblock(31));
272+
DISPCNT.write(
273+
DisplayControl::new().with_video_mode(VideoMode::_0).with_show_bg0(true),
274+
);
275+
276+
// some niceties for people without mgba-test-runner
277+
let tsb = TEXT_SCREENBLOCKS.get_frame(31).unwrap();
278+
unsafe {
279+
mem::set_u32x80_unchecked(
280+
tsb.into_block::<1024>().as_mut_ptr().cast(),
281+
0,
282+
12,
283+
);
284+
}
285+
Cga8x8Thick.bitunpack_4bpp(CHARBLOCK0_4BPP.as_region(), 0);
286+
287+
let row = tsb.get_row(9).unwrap().iter().skip(6);
288+
for (addr, ch) in row.zip(b"all tests passed!") {
289+
addr.write(TextEntry::new().with_tile(*ch as u16));
290+
}
291+
292+
DISPSTAT.write(DisplayStatus::new());
293+
bios::IntrWait(true, IrqBits::new());
294+
}
295+
}
296+
297+
#[cfg(test)]
298+
mod test {
299+
use super::Align4;
300+
301+
#[test_case]
302+
fn align4_as_u16_u32_slice() {
303+
let a = Align4([0u8, 1u8, 2u8, 3u8]);
304+
assert_eq!(a.as_u16_slice(), &[0x100_u16.to_le(), 0x302_u16.to_le()]);
305+
assert_eq!(a.as_u32_slice(), &[0x3020100_u32.to_le()]);
306+
}
307+
}

src/mem.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#![cfg_attr(not(feature = "on_gba"), allow(unused_variables))]
2+
13
use crate::macros::on_gba_or_unimplemented;
24

35
/// Copies `u8` at a time between exclusive regions.

0 commit comments

Comments
 (0)