diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 96ab018..57aa426 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -40,9 +40,6 @@ jobs: rustup target add x86_64-unknown-none rustup component add rust-src rustup component add llvm-tools-preview - - name: Install bootimage - run: | - cargo install bootimage - run: cargo test --verbose fmt: diff --git a/.gitignore b/.gitignore index 6936990..12cd9d5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target **/*.rs.bk Cargo.lock +/testing/target diff --git a/testing/.cargo/config.toml b/testing/.cargo/config.toml index 5c19ef0..fcf2bc7 100644 --- a/testing/.cargo/config.toml +++ b/testing/.cargo/config.toml @@ -1,6 +1,4 @@ -[build] -target = "x86_64-unknown-none" -rustflags = ["-Crelocation-model=static"] - -[target.'cfg(target_os = "none")'] -runner = "bootimage runner" +[unstable] +# enable the unstable artifact-dependencies feature, see +# https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#artifact-dependencies +bindeps = true diff --git a/testing/Cargo.toml b/testing/Cargo.toml index 96f3e1d..e133c7f 100644 --- a/testing/Cargo.toml +++ b/testing/Cargo.toml @@ -1,26 +1,14 @@ [package] name = "testing" version = "0.1.0" -authors = ["Philipp Oppermann "] -edition = "2018" +edition = "2021" -[[test]] -name = "basic" -harness = false +[workspace] +members = ["kernel"] [dependencies] -bootloader = "0.9.10" -x86_64 = "0.15.1" -com_logger = { path = ".." } -log = "0.4.22" - -[dependencies.lazy_static] -version = "1.3.0" -features = ["spin_no_std"] - -[package.metadata.bootimage] -test-args = [ - "-device", "isa-debug-exit,iobase=0xf4,iosize=0x04", "-serial", "stdio", - "-display", "none" -] -test-success-exit-code = 33 +ovmf-prebuilt = "0.1.0-alpha.1" +bootloader = "0.11.7" +kernel = { path = "kernel", artifact = "bin", target = "x86_64-unknown-none" } +wait-timeout = "0.2.0" +serial_test = "3.1.1" diff --git a/testing/kernel/Cargo.toml b/testing/kernel/Cargo.toml new file mode 100644 index 0000000..9365cea --- /dev/null +++ b/testing/kernel/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "kernel" +version = "0.1.0" +authors = ["Yushi OMOTE "] +edition = "2021" + +[dependencies] +bootloader_api = "0.11.7" +com_logger = { path = "../../" } +log = "0.4.22" +x86_64 = "0.15.1" + +[[bin]] +name = "basic" +path = "src/basic.rs" + +[[bin]] +name = "format" +path = "src/format.rs" + +[[bin]] +name = "multi" +path = "src/multi.rs" + diff --git a/testing/kernel/src/basic.rs b/testing/kernel/src/basic.rs new file mode 100644 index 0000000..bc95773 --- /dev/null +++ b/testing/kernel/src/basic.rs @@ -0,0 +1,20 @@ +#![no_std] +#![no_main] + +use bootloader_api::{entry_point, BootInfo}; +use log::info; + +entry_point!(kernel_main); + +fn kernel_main(_info: &'static mut BootInfo) -> ! { + com_logger::init(); + + info!("Hello world!"); + + kernel::success() +} + +#[panic_handler] +fn panic(info: &core::panic::PanicInfo) -> ! { + kernel::panic(info) +} diff --git a/testing/kernel/src/format.rs b/testing/kernel/src/format.rs new file mode 100644 index 0000000..79ac2f5 --- /dev/null +++ b/testing/kernel/src/format.rs @@ -0,0 +1,22 @@ +#![no_std] +#![no_main] + +use bootloader_api::{entry_point, BootInfo}; +use log::info; + +entry_point!(kernel_main); + +fn kernel_main(_info: &'static mut BootInfo) -> ! { + com_logger::builder() + .formatter(|buf, record| writeln!(buf, "**** {} ****", record.args())) + .setup(); + + info!("Hello world!"); + + kernel::success() +} + +#[panic_handler] +fn panic(info: &core::panic::PanicInfo) -> ! { + kernel::panic(info) +} diff --git a/testing/kernel/src/lib.rs b/testing/kernel/src/lib.rs new file mode 100644 index 0000000..ac997e6 --- /dev/null +++ b/testing/kernel/src/lib.rs @@ -0,0 +1,32 @@ +#![no_std] +#![no_main] + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u32)] +enum QemuExitCode { + Success = 0x10, + Failed = 0x11, +} + +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); + } +} + +pub fn success() -> ! { + exit_qemu(QemuExitCode::Success); + loop {} +} + +pub fn failed() { + exit_qemu(QemuExitCode::Failed); +} + +pub fn panic(_info: &core::panic::PanicInfo) -> ! { + exit_qemu(QemuExitCode::Failed); + loop {} +} diff --git a/testing/kernel/src/multi.rs b/testing/kernel/src/multi.rs new file mode 100644 index 0000000..ee130b4 --- /dev/null +++ b/testing/kernel/src/multi.rs @@ -0,0 +1,24 @@ +#![no_std] +#![no_main] + +use bootloader_api::{entry_point, BootInfo}; +use log::info; + +entry_point!(kernel_main); + +fn kernel_main(_info: &'static mut BootInfo) -> ! { + com_logger::builder() + .formatter(|buf, record| writeln!(buf, "{}", record.args())) + .setup(); + + for i in 0..100 { + info!("Hello world! {}", i); + } + + kernel::success() +} + +#[panic_handler] +fn panic(info: &core::panic::PanicInfo) -> ! { + kernel::panic(info) +} diff --git a/testing/src/lib.rs b/testing/src/lib.rs index 0b86fef..4ef3607 100644 --- a/testing/src/lib.rs +++ b/testing/src/lib.rs @@ -1,54 +1,79 @@ -#![feature(custom_test_frameworks)] -#![test_runner(crate::test_runner)] -#![reexport_test_harness_main = "test_main"] -#![no_std] -#![no_main] - -use core::panic::PanicInfo; -use log::error; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(u32)] -pub enum QemuExitCode { - Success = 0x10, - Failed = 0x11, +use std::{io::Read, path::PathBuf, process::Stdio, time::Duration}; +use wait_timeout::ChildExt; + +#[macro_export] +macro_rules! test_kernel { + ($test_name:expr, $mode:expr) => { + $crate::test_kernel_internal( + env!(concat!("CARGO_BIN_FILE_KERNEL_", $test_name)), + env!("CARGO_TARGET_TMPDIR"), + $mode, + ) + }; } -pub fn exit() { - exit_qemu(QemuExitCode::Success) +pub enum Mode { + Uefi, + Bios, } -pub fn exit_qemu(exit_code: QemuExitCode) { - use x86_64::instructions::port::PortWriteOnly; +pub fn test_kernel_internal(kernel_path: &str, tmp_dir: &str, mode: Mode) -> Vec { + let out_dir = PathBuf::from(tmp_dir); + + println!("Found test kernels: {}", kernel_path); + + let kernel = PathBuf::from(kernel_path); - unsafe { - let mut port = PortWriteOnly::new(0xf4); - port.write(exit_code as u32); + let mut cmd = std::process::Command::new("qemu-system-x86_64"); + match mode { + Mode::Uefi => { + // create an UEFI disk image (optional) + let uefi_path = out_dir.join("uefi.img"); + bootloader::UefiBoot::new(&kernel) + .create_disk_image(&uefi_path) + .unwrap(); + + cmd.arg("-bios").arg(ovmf_prebuilt::ovmf_pure_efi()); + cmd.arg("-drive") + .arg(format!("format=raw,file={}", uefi_path.display())); + } + Mode::Bios => { + // create a BIOS disk image + let bios_path = out_dir.join("bios.img"); + bootloader::BiosBoot::new(&kernel) + .create_disk_image(&bios_path) + .unwrap(); + + cmd.arg("-drive") + .arg(format!("format=raw,file={}", bios_path.display())); + } } -} + cmd.arg("-device") + .arg("isa-debug-exit,iobase=0xf4,iosize=0x04"); + cmd.arg("-serial").arg("stdio"); + cmd.arg("-display").arg("none"); + cmd.stdout(Stdio::piped()); + let mut child = cmd.spawn().unwrap(); -pub fn test_runner(tests: &[&dyn Fn()]) { - for test in tests { - test(); + match child.wait_timeout(Duration::from_secs(30)).unwrap() { + Some(status) => assert_eq!(status.code(), Some(33)), + None => panic!("Test timed out"), } - exit_qemu(QemuExitCode::Success); -} -pub fn test_panic_handler(info: &PanicInfo) -> ! { - error!("{:#?}", info); - exit_qemu(QemuExitCode::Failed); - loop {} -} + println!("Finish"); -#[cfg(test)] -#[no_mangle] -pub extern "C" fn _start() -> ! { - test_main(); - loop {} -} + let stdout = child.stdout.as_mut().unwrap(); + let mut output = String::default(); + + stdout.read_to_string(&mut output).unwrap(); + + println!("Finish: {}", output); -#[cfg(test)] -#[panic_handler] -fn panic(info: &PanicInfo) -> ! { - test_panic_handler(info) + let mut vec: Vec<_> = output + .trim_end_matches('\n') + .split('\n') + .map(|s| s.into()) + .collect(); + vec.reverse(); + vec } diff --git a/testing/tests/basic.rs b/testing/tests/basic.rs deleted file mode 100644 index 066ff00..0000000 --- a/testing/tests/basic.rs +++ /dev/null @@ -1,16 +0,0 @@ -#![no_std] -#![no_main] - -use core::panic::PanicInfo; - -#[no_mangle] // don't mangle the name of this function -pub extern "C" fn _start() { - com_logger::init(); - log::info!("Hello world"); - testing::exit(); -} - -#[panic_handler] -fn panic(info: &PanicInfo) -> ! { - testing::test_panic_handler(info) -} diff --git a/testing/tests/test.rs b/testing/tests/test.rs new file mode 100644 index 0000000..1994fbe --- /dev/null +++ b/testing/tests/test.rs @@ -0,0 +1,68 @@ +use serial_test::serial; +use testing::{test_kernel, Mode}; + +const BASIC_EXPECTED_PREFIX: &'static str = " INFO: Hello world! (basic"; + +#[test] +#[serial] +fn basic_bios() { + let output = test_kernel!("basic", Mode::Bios); + + assert!(output[0].starts_with(BASIC_EXPECTED_PREFIX), "{:?}", output); +} + +#[test] +#[serial] +fn basic_uefi() { + let output = test_kernel!("basic", Mode::Uefi); + + assert!(output[0].starts_with(BASIC_EXPECTED_PREFIX), "{:?}", output); +} + +const FORMAT_EXPECTED: &'static str = "**** Hello world! ****"; + +#[test] +#[serial] +fn custom_format_uefi() { + let output = test_kernel!("format", Mode::Uefi); + + assert_eq!(output[0], FORMAT_EXPECTED, "{:?}", output); +} + +#[test] +#[serial] +fn custom_format_bios() { + let output = test_kernel!("format", Mode::Bios); + + assert_eq!(output[0], FORMAT_EXPECTED, "{:?}", output); +} + +#[test] +#[serial] +fn multi_line_uefi() { + let output = test_kernel!("multi", Mode::Uefi); + + for i in 0..100 { + assert_eq!( + output[i], + format!("Hello world! {}", 99 - i), + "{:?}", + output + ); + } +} + +#[test] +#[serial] +fn multi_line_bios() { + let output = test_kernel!("multi", Mode::Bios); + + for i in 0..100 { + assert_eq!( + output[i], + format!("Hello world! {}", 99 - i), + "{:?}", + output + ); + } +}