Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion .github/workflows/ci-builds.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
131 changes: 131 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
//!
Expand Down Expand Up @@ -174,3 +178,130 @@ 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<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();
}
}

#[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_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()]);
}
}
2 changes: 2 additions & 0 deletions src/mem.rs
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
Loading