From 0a65ebc7cde052201c4c36f62bdbfc4ec885cb12 Mon Sep 17 00:00:00 2001 From: Knarkzel Date: Tue, 23 Jan 2024 13:01:01 +0100 Subject: [PATCH] Added unit testing support --- .github/workflows/build.yml | 2 +- Cargo.lock | 57 ++++++++++++++++++++++++++++++++++- Cargo.toml | 12 ++++++-- src/main.rs | 59 ++++++++++++++++++++++++++++++++++++- src/serial.rs | 32 ++++++++++++++++++++ 5 files changed, 156 insertions(+), 6 deletions(-) create mode 100644 src/serial.rs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6a73b05..7bc0250 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -29,7 +29,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: tag_name: ${{ steps.commit_hash.outputs.commit_hash }} - release_name: Release ${{ steps.commit_hash.outputs.commit_hash }} + release_name: ${{ steps.commit_hash.outputs.commit_hash }} draft: false prerelease: false diff --git a/Cargo.lock b/Cargo.lock index 7f38bde..4752557 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,24 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "bit_field" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" + [[package]] name = "bootloader" version = "0.9.23" @@ -21,7 +39,9 @@ dependencies = [ "bootloader", "lazy_static", "spin 0.9.8", - "volatile", + "uart_16550", + "volatile 0.2.7", + "x86_64", ] [[package]] @@ -43,6 +63,12 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + [[package]] name = "scopeguard" version = "1.2.0" @@ -64,8 +90,37 @@ dependencies = [ "lock_api", ] +[[package]] +name = "uart_16550" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614ff2a87880d4bd4374722268598a970bbad05ced8bf630439417347254ab2e" +dependencies = [ + "bitflags 1.3.2", + "rustversion", + "x86_64", +] + [[package]] name = "volatile" version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6b06ad3ed06fef1713569d547cdbdb439eafed76341820fb0e0344f29a41945" + +[[package]] +name = "volatile" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "442887c63f2c839b346c192d047a7c87e73d0689c9157b00b53dcc27dd5ea793" + +[[package]] +name = "x86_64" +version = "0.14.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b835097a84e4457323331ec5d6eb23d096066cbfb215d54096dcb4b2e85f500" +dependencies = [ + "bit_field", + "bitflags 2.4.2", + "rustversion", + "volatile 0.4.6", +] diff --git a/Cargo.toml b/Cargo.toml index 7bf2988..290aef2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,14 +3,20 @@ name = "knarkos" version = "0.1.0" edition = "2021" -[profile.dev] -panic = "abort" - [profile.release] panic = "abort" [dependencies] spin = "0.9.8" +x86_64 = "0.14.2" volatile = "0.2.6" +uart_16550 = "0.2.0" bootloader = "0.9.23" lazy_static = { version = "1.0", features = ["spin_no_std"] } + +[package.metadata.bootimage] +test-success-exit-code = 33 +test-args = [ + "-device", "isa-debug-exit,iobase=0xf4,iosize=0x04", "-serial", "stdio", + "-display", "none" +] diff --git a/src/main.rs b/src/main.rs index f94cacc..98eadc7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,17 +1,74 @@ #![no_std] #![no_main] +#![feature(custom_test_frameworks)] +#![test_runner(crate::test_runner)] +#![reexport_test_harness_main = "test_main"] mod vga; +mod serial; #[no_mangle] pub extern "C" fn _start() -> ! { - println!("Hello, world!"); + #[cfg(test)] + test_main(); + loop {} } /// This function is called on panic. +pub trait Testable { + fn run(&self) -> (); +} + +impl Testable for T +where + T: Fn(), +{ + fn run(&self) { + serial_print!("{}...\t", core::any::type_name::()); + self(); + serial_println!("[ok]"); + } +} + +#[cfg(not(test))] // new attribute #[panic_handler] fn panic(info: &core::panic::PanicInfo) -> ! { println!("{}", info); loop {} } + +// our panic handler in test mode +#[cfg(test)] +#[panic_handler] +fn panic(info: &core::panic::PanicInfo) -> ! { + serial_println!("[failed]\n"); + serial_println!("Error: {}\n", info); + exit_qemu(QemuExitCode::Failed); + loop {} +} + +#[cfg(test)] +pub fn test_runner(tests: &[&dyn Testable]) { + serial_println!("Running {} tests", tests.len()); + for test in tests { + test.run(); + } + exit_qemu(QemuExitCode::Success); +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u32)] +pub enum QemuExitCode { + Success = 0x10, + Failed = 0x11, +} + +pub fn exit_qemu(exit_code: QemuExitCode) { + use x86_64::instructions::port::Port; + + unsafe { + let mut port = Port::new(0xf4); + port.write(exit_code as u32); + } +} diff --git a/src/serial.rs b/src/serial.rs new file mode 100644 index 0000000..ed3a90b --- /dev/null +++ b/src/serial.rs @@ -0,0 +1,32 @@ +use spin::Mutex; +use uart_16550::SerialPort; +use lazy_static::lazy_static; + +lazy_static! { + pub static ref SERIAL1: Mutex = { + let mut serial_port = unsafe { SerialPort::new(0x3F8) }; + serial_port.init(); + Mutex::new(serial_port) + }; +} + +#[macro_export] +macro_rules! serial_print { + ($($arg:tt)*) => { + $crate::serial::_print(format_args!($($arg)*)); + }; +} + +#[macro_export] +macro_rules! serial_println { + () => ($crate::serial_print!("\n")); + ($fmt:expr) => ($crate::serial_print!(concat!($fmt, "\n"))); + ($fmt:expr, $($arg:tt)*) => ($crate::serial_print!( + concat!($fmt, "\n"), $($arg)*)); +} + +#[doc(hidden)] +pub fn _print(args: ::core::fmt::Arguments) { + use core::fmt::Write; + SERIAL1.lock().write_fmt(args).expect("Printing to serial failed"); +}