diff --git a/.appveyor.yml b/.appveyor.yml index 81bc6891..71c10ea7 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -55,8 +55,7 @@ install: - set PATH=%PATH%;%USERPROFILE%\.cargo\bin - rustc -vV - cargo -vV - - # Install qemu + # Install qemu - echo %cd% - mkdir "C:\Program Files\qemu" - cd "C:\Program Files\qemu" @@ -85,7 +84,6 @@ before_test: - cargo install-latest cargo-xbuild bootimage test_script: - - bootimage build - - if %target%==x86_64-pc-windows-gnu cargo test - - if %target%==x86_64-pc-windows-msvc cargo test - - bootimage test + - cargo xbuild + - cargo bootimage + - cargo xtest diff --git a/.cargo/config b/.cargo/config new file mode 100644 index 00000000..7f2ad55d --- /dev/null +++ b/.cargo/config @@ -0,0 +1,5 @@ +[build] +target = "x86_64-blog_os.json" + +[target.'cfg(target_os = "none")'] +runner = "bootimage runner" diff --git a/.travis.yml b/.travis.yml index 37dc8b75..487e0e31 100644 --- a/.travis.yml +++ b/.travis.yml @@ -42,11 +42,10 @@ install: before_script: - rustup component add rust-src llvm-tools-preview - - (test -x $HOME/.cargo/bin/cargo-install-latest || cargo install cargo-install-latest) - - cargo install-latest cargo-xbuild bootimage cargo-cache + - cargo install cargo-xbuild bootimage cargo-cache --debug -Z install-upgrade script: - - bootimage build - - cargo test - - bootimage test + - cargo xbuild + - cargo bootimage + - cargo xtest - cargo cache --autoclean diff --git a/Cargo.lock b/Cargo.lock index 3282f302..370d630a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,13 +1,5 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -[[package]] -name = "array-init" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "array-init" version = "0.0.4" @@ -30,18 +22,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" name = "blog_os" version = "0.1.0" dependencies = [ - "array-init 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "bootloader 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "bootloader 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "spin 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", - "uart_16550 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "uart_16550 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "volatile 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", - "x86_64 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", + "x86_64 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "bootloader" -version = "0.5.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "fixedvec 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -54,7 +45,7 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.34" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -150,7 +141,7 @@ version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "cc 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.35 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -221,11 +212,11 @@ dependencies = [ [[package]] name = "uart_16550" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "x86_64 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", + "x86_64 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -267,18 +258,6 @@ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "x86_64" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bit_field 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "os_bootinfo 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "usize_conversions 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "ux 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "x86_64" version = "0.3.6" @@ -294,7 +273,7 @@ dependencies = [ [[package]] name = "x86_64" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "array-init 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -319,12 +298,11 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] -"checksum array-init 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c3cc8456d0ae81a8c76f59e384683a601548c38949a4bfcb65dd31ded5c75ff3" "checksum array-init 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "23589ecb866b460d3a0f1278834750268c607e8e28a1b982c907219f3178cd72" "checksum bit_field 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ed8765909f9009617974ab6b7d332625b320b33c326b1e9321382ef1999b5d56" "checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" -"checksum bootloader 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2735a1e3ddf16e6832fff86db617778dd3cecd5804fc8c2f20f448750d4c989c" -"checksum cc 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)" = "30f813bf45048a18eda9190fd3c6b78644146056740c43172a5a3699118588fd" +"checksum bootloader 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8654b1ebbd38d2a8687a451ad53466d01b5edc9d75ec63d676525a6103d77151" +"checksum cc 1.0.35 (registry+https://github.com/rust-lang/crates.io-index)" = "5e5f3fee5eeb60324c2781f1e41286bdee933850fff9b3c672587fed5ec58c83" "checksum fixedvec 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7c6c16d316ccdac21a4dd648e314e76facbbaf316e83ca137d0857a9c07419d0" "checksum font8x8 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b81d84c3c978af7d05d31a2198af4b9ba956d819d15d8f6d58fc150e33f8dc1f" "checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" @@ -348,7 +326,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum spin 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ceac490aa12c567115b40b7b7fceca03a6c9d53d5defea066123debc83c5dc1f" "checksum spin 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44363f6f51401c34e7be73db0db371c04705d35efbe9f7d6082e03a921a32c55" "checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" -"checksum uart_16550 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "269f953d8de3226f7c065c589c7b4a3e83d10a419c7c3b5e2e0f197e6acc966e" +"checksum uart_16550 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b9392f60931fe3bf8f24e0a15ee4f51528770f1d64c48768ab66571334d95b0" "checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" "checksum usize_conversions 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f70329e2cbe45d6c97a5112daad40c34cd9a4e18edb5a2a18fefeb584d8d25e5" "checksum ux 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "88dfeb711b61ce620c0cb6fd9f8e3e678622f0c971da2a63c4b3e25e88ed012f" @@ -356,8 +334,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -"checksum x86_64 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "2bd647af1614659e1febec1d681231aea4ebda4818bf55a578aff02f3e4db4b4" "checksum x86_64 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f9258d7e2dd25008d69e8c9e9ee37865887a5e1e3d06a62f1cb3f6c209e6f177" -"checksum x86_64 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8f7e92e985f4052118fd69f2b366c67e91288c0f01f4ae52610dce236425dfa0" +"checksum x86_64 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1d0a8201f52d2c7b373c7243dcdfb27c0dd5012f221ef6a126f507ee82005204" "checksum xmas-elf 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "22678df5df766e8d1e5d609da69f0c3132d794edf6ab5e75e7abcd2270d4cf58" "checksum zero 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5f1bc8a6b2005884962297587045002d8cfb8dcec9db332f4ca216ddc5de82c5" diff --git a/Cargo.toml b/Cargo.toml index 74d3c4e5..d984b6ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,20 +4,21 @@ version = "0.1.0" authors = ["Philipp Oppermann "] edition = "2018" +[[test]] +name = "panic_handler" +harness = false + [dependencies] -bootloader = "0.5.1" +bootloader = "0.6.0" volatile = "0.2.3" spin = "0.4.9" -uart_16550 = "0.1.0" x86_64 = "0.5.2" +uart_16550 = "0.2.0" [dependencies.lazy_static] version = "1.0" features = ["spin_no_std"] -[dev-dependencies] -array-init = "0.0.3" - [profile.dev] panic = "abort" @@ -26,3 +27,8 @@ panic = "abort" [package.metadata.bootimage] default-target = "x86_64-blog_os.json" +test-args = [ + "-device", "isa-debug-exit,iobase=0xf4,iosize=0x04", "-serial", "mon:stdio", + "-display", "none" +] +test-success-exit-code = 33 # (0x10 << 1) | 1 diff --git a/README.md b/README.md index a9347dd5..c0d1c6c9 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,13 @@ cargo install cargo-xbuild bootimage Then you can build the project by running: ``` -bootimage build +cargo xbuild +``` + +To create a bootable disk image, run: + +``` +cargo bootimage ``` This creates a bootable disk image in the `target/x86_64-blog_os/debug` directory. @@ -33,7 +39,7 @@ You can run the disk image in [QEMU] through: [QEMU]: https://www.qemu.org/ ``` -bootimage run +cargo xrun ``` Of course [QEMU] needs to be installed for this. @@ -48,7 +54,7 @@ Where `sdX` is the device name of your USB stick. **Be careful** to choose the c ## Testing -To run the unit tests on the host system, execute `cargo test`. To run the integration tests in [QEMU], run `bootimage test`. +To run the unit and integration tests, execute `cargo xtest`. ## License The source code is dual-licensed under MIT or the Apache License (Version 2.0). diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 05f74c8a..577b7e35 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -52,16 +52,14 @@ steps: - script: rustup component add rust-src llvm-tools-preview displayName: 'Install Rustup Components' -- script: | - cargo install cargo-xbuild --debug - cargo install bootimage --debug +- script: cargo install cargo-xbuild bootimage --debug displayName: 'Install cargo-xbuild and bootimage' -- script: bootimage build +- script: cargo xbuild displayName: 'Build' -- script: cargo test - displayName: 'Unit Tests' +- script: cargo bootimage + displayName: 'Create Bootimage' - script: sudo apt update && sudo apt install qemu-system-x86 condition: eq( variables['Agent.OS'], 'Linux' ) @@ -83,8 +81,8 @@ steps: condition: eq( variables['Agent.OS'], 'Windows_NT' ) displayName: 'Install QEMU (Windows)' -- script: bootimage test - displayName: 'Integration Tests' +- script: cargo xtest + displayName: 'Test' - script: rustup component add rustfmt displayName: 'Install Rustfmt' diff --git a/src/bin/test-basic-boot.rs b/src/bin/test-basic-boot.rs deleted file mode 100644 index f77d4a02..00000000 --- a/src/bin/test-basic-boot.rs +++ /dev/null @@ -1,33 +0,0 @@ -#![cfg_attr(not(test), no_std)] -#![cfg_attr(not(test), no_main)] // disable all Rust-level entry points -#![cfg_attr(test, allow(unused_imports))] - -use blog_os::{exit_qemu, serial_println}; -use core::panic::PanicInfo; - -/// This function is the entry point, since the linker looks for a function -/// named `_start` by default. -#[cfg(not(test))] -#[no_mangle] // don't mangle the name of this function -pub extern "C" fn _start() -> ! { - serial_println!("ok"); - - unsafe { - exit_qemu(); - } - loop {} -} - -/// This function is called on panic. -#[cfg(not(test))] -#[panic_handler] -fn panic(info: &PanicInfo) -> ! { - serial_println!("failed"); - - serial_println!("{}", info); - - unsafe { - exit_qemu(); - } - loop {} -} diff --git a/src/bin/test-exception-breakpoint.rs b/src/bin/test-exception-breakpoint.rs deleted file mode 100644 index 0185aee6..00000000 --- a/src/bin/test-exception-breakpoint.rs +++ /dev/null @@ -1,34 +0,0 @@ -#![no_std] -#![cfg_attr(not(test), no_main)] -#![cfg_attr(test, allow(dead_code, unused_macros, unused_imports))] - -use blog_os::{exit_qemu, serial_println}; -use core::panic::PanicInfo; - -#[cfg(not(test))] -#[no_mangle] -pub extern "C" fn _start() -> ! { - blog_os::interrupts::init_idt(); - - x86_64::instructions::interrupts::int3(); - - serial_println!("ok"); - - unsafe { - exit_qemu(); - } - loop {} -} - -#[cfg(not(test))] -#[panic_handler] -fn panic(info: &PanicInfo) -> ! { - serial_println!("failed"); - - serial_println!("{}", info); - - unsafe { - exit_qemu(); - } - loop {} -} diff --git a/src/bin/test-panic.rs b/src/bin/test-panic.rs deleted file mode 100644 index c68ac51d..00000000 --- a/src/bin/test-panic.rs +++ /dev/null @@ -1,23 +0,0 @@ -#![cfg_attr(not(test), no_std)] -#![cfg_attr(not(test), no_main)] -#![cfg_attr(test, allow(unused_imports))] - -use blog_os::{exit_qemu, serial_println}; -use core::panic::PanicInfo; - -#[cfg(not(test))] -#[no_mangle] -pub extern "C" fn _start() -> ! { - panic!(); -} - -#[cfg(not(test))] -#[panic_handler] -fn panic(_info: &PanicInfo) -> ! { - serial_println!("ok"); - - unsafe { - exit_qemu(); - } - loop {} -} diff --git a/src/interrupts.rs b/src/interrupts.rs index b0223110..b68668d3 100644 --- a/src/interrupts.rs +++ b/src/interrupts.rs @@ -36,3 +36,14 @@ extern "x86-interrupt" fn double_fault_handler( println!("EXCEPTION: DOUBLE FAULT\n{:#?}", stack_frame); loop {} } + +#[cfg(test)] +use crate::{serial_print, serial_println}; + +#[test_case] +fn test_breakpoint_exception() { + serial_print!("test_breakpoint_exception..."); + // invoke a breakpoint exception + x86_64::instructions::interrupts::int3(); + serial_println!("[ok]"); +} diff --git a/src/lib.rs b/src/lib.rs index 0bd6fe54..6c136eb7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,14 +1,63 @@ -#![cfg_attr(not(test), no_std)] +#![no_std] +#![cfg_attr(test, no_main)] +#![feature(custom_test_frameworks)] #![feature(abi_x86_interrupt)] +#![test_runner(crate::test_runner)] +#![reexport_test_harness_main = "test_main"] + +use core::panic::PanicInfo; pub mod gdt; pub mod interrupts; pub mod serial; pub mod vga_buffer; -pub unsafe fn exit_qemu() { +pub fn init() { + interrupts::init_idt(); +} + +pub fn test_runner(tests: &[&dyn Fn()]) { + serial_println!("Running {} tests", tests.len()); + for test in tests { + test(); + } + exit_qemu(QemuExitCode::Success); +} + +pub fn test_panic_handler(info: &PanicInfo) -> ! { + serial_println!("[failed]\n"); + serial_println!("Error: {}\n", info); + exit_qemu(QemuExitCode::Failed); + loop {} +} + +#[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; - let mut port = Port::::new(0xf4); - port.write(0); + unsafe { + let mut port = Port::new(0xf4); + port.write(exit_code as u32); + } +} + +/// Entry point for `cargo xtest` +#[cfg(test)] +#[no_mangle] +pub extern "C" fn _start() -> ! { + init(); + test_main(); + loop {} +} + +#[cfg(test)] +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + test_panic_handler(info) } diff --git a/src/main.rs b/src/main.rs index aabf5128..48cd260e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,17 +1,18 @@ -#![cfg_attr(not(test), no_std)] -#![cfg_attr(not(test), no_main)] -#![cfg_attr(test, allow(unused_imports))] +#![no_std] +#![no_main] +#![feature(custom_test_frameworks)] +#![test_runner(blog_os::test_runner)] +#![reexport_test_harness_main = "test_main"] use blog_os::println; use core::panic::PanicInfo; -#[cfg(not(test))] #[no_mangle] pub extern "C" fn _start() -> ! { println!("Hello World{}", "!"); blog_os::gdt::init(); - blog_os::interrupts::init_idt(); + blog_os::init(); fn stack_overflow() { stack_overflow(); // for each recursion, the return address is pushed @@ -20,6 +21,9 @@ pub extern "C" fn _start() -> ! { // trigger a stack overflow stack_overflow(); + #[cfg(test)] + test_main(); + println!("It did not crash!"); loop {} } @@ -31,3 +35,9 @@ fn panic(info: &PanicInfo) -> ! { println!("{}", info); loop {} } + +#[cfg(test)] +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + blog_os::test_panic_handler(info) +} diff --git a/src/serial.rs b/src/serial.rs index c6990d4f..e8807df8 100644 --- a/src/serial.rs +++ b/src/serial.rs @@ -4,7 +4,7 @@ use uart_16550::SerialPort; lazy_static! { pub static ref SERIAL1: Mutex = { - let mut serial_port = SerialPort::new(0x3F8); + let mut serial_port = unsafe { SerialPort::new(0x3F8) }; serial_port.init(); Mutex::new(serial_port) }; @@ -32,5 +32,6 @@ macro_rules! serial_print { 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)*)); + ($fmt:expr, $($arg:tt)*) => ($crate::serial_print!( + concat!($fmt, "\n"), $($arg)*)); } diff --git a/src/vga_buffer.rs b/src/vga_buffer.rs index d89b4948..fcf95893 100644 --- a/src/vga_buffer.rs +++ b/src/vga_buffer.rs @@ -3,6 +3,9 @@ use lazy_static::lazy_static; use spin::Mutex; use volatile::Volatile; +#[cfg(test)] +use crate::{serial_print, serial_println}; + lazy_static! { /// A global `Writer` instance that can be used for printing to the VGA text buffer. /// @@ -170,85 +173,32 @@ pub fn _print(args: fmt::Arguments) { WRITER.lock().write_fmt(args).unwrap(); } -#[cfg(test)] -mod test { - use super::*; - - fn construct_writer() -> Writer { - use std::boxed::Box; - - let buffer = construct_buffer(); - Writer { - column_position: 0, - color_code: ColorCode::new(Color::Blue, Color::Magenta), - buffer: Box::leak(Box::new(buffer)), - } - } - - fn construct_buffer() -> Buffer { - use array_init::array_init; - - Buffer { - chars: array_init(|_| array_init(|_| Volatile::new(empty_char()))), - } - } - - fn empty_char() -> ScreenChar { - ScreenChar { - ascii_character: b' ', - color_code: ColorCode::new(Color::Green, Color::Brown), - } - } - - #[test] - fn write_byte() { - let mut writer = construct_writer(); - writer.write_byte(b'X'); - writer.write_byte(b'Y'); - - for (i, row) in writer.buffer.chars.iter().enumerate() { - for (j, screen_char) in row.iter().enumerate() { - let screen_char = screen_char.read(); - if i == BUFFER_HEIGHT - 1 && j == 0 { - assert_eq!(screen_char.ascii_character, b'X'); - assert_eq!(screen_char.color_code, writer.color_code); - } else if i == BUFFER_HEIGHT - 1 && j == 1 { - assert_eq!(screen_char.ascii_character, b'Y'); - assert_eq!(screen_char.color_code, writer.color_code); - } else { - assert_eq!(screen_char, empty_char()); - } - } - } - } - - #[test] - fn write_formatted() { - use core::fmt::Write; - - let mut writer = construct_writer(); - writeln!(&mut writer, "a").unwrap(); - writeln!(&mut writer, "b{}", "c").unwrap(); - - for (i, row) in writer.buffer.chars.iter().enumerate() { - for (j, screen_char) in row.iter().enumerate() { - let screen_char = screen_char.read(); - if i == BUFFER_HEIGHT - 3 && j == 0 { - assert_eq!(screen_char.ascii_character, b'a'); - assert_eq!(screen_char.color_code, writer.color_code); - } else if i == BUFFER_HEIGHT - 2 && j == 0 { - assert_eq!(screen_char.ascii_character, b'b'); - assert_eq!(screen_char.color_code, writer.color_code); - } else if i == BUFFER_HEIGHT - 2 && j == 1 { - assert_eq!(screen_char.ascii_character, b'c'); - assert_eq!(screen_char.color_code, writer.color_code); - } else if i >= BUFFER_HEIGHT - 2 { - assert_eq!(screen_char.ascii_character, b' '); - assert_eq!(screen_char.color_code, writer.color_code); - } else { - assert_eq!(screen_char, empty_char()); - } - } - } - } +#[test_case] +fn test_println_simple() { + serial_print!("test_println... "); + println!("test_println_simple output"); + serial_println!("[ok]"); +} + +#[test_case] +fn test_println_many() { + serial_print!("test_println_many... "); + for _ in 0..200 { + println!("test_println_many output"); + } + serial_println!("[ok]"); +} + +#[test_case] +fn test_println_output() { + serial_print!("test_println_output... "); + + let s = "Some test string that fits on a single line"; + println!("{}", s); + for (i, c) in s.chars().enumerate() { + let screen_char = WRITER.lock().buffer.chars[BUFFER_HEIGHT - 2][i].read(); + assert_eq!(char::from(screen_char.ascii_character), c); + } + + serial_println!("[ok]"); } diff --git a/tests/basic_boot.rs b/tests/basic_boot.rs new file mode 100644 index 00000000..f5cf85e5 --- /dev/null +++ b/tests/basic_boot.rs @@ -0,0 +1,27 @@ +#![no_std] +#![no_main] +#![feature(custom_test_frameworks)] +#![test_runner(blog_os::test_runner)] +#![reexport_test_harness_main = "test_main"] + +use blog_os::{println, serial_print, serial_println}; +use core::panic::PanicInfo; + +#[no_mangle] // don't mangle the name of this function +pub extern "C" fn _start() -> ! { + test_main(); + + loop {} +} + +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + blog_os::test_panic_handler(info) +} + +#[test_case] +fn test_println() { + serial_print!("test_println... "); + println!("test_println output"); + serial_println!("[ok]"); +} diff --git a/tests/panic_handler.rs b/tests/panic_handler.rs new file mode 100644 index 00000000..5a55537c --- /dev/null +++ b/tests/panic_handler.rs @@ -0,0 +1,72 @@ +#![no_std] +#![no_main] +#![feature(panic_info_message)] + +use blog_os::{exit_qemu, serial_print, serial_println, QemuExitCode}; +use core::{ + fmt::{self, Write}, + panic::PanicInfo, +}; + +const MESSAGE: &str = "Example panic message from panic_handler test"; +const PANIC_LINE: u32 = 17; // adjust this when moving the `panic!` call + +#[no_mangle] +pub extern "C" fn _start() -> ! { + serial_print!("panic_handler... "); + panic!(MESSAGE); // must be in line `PANIC_LINE` +} + +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + check_location(info); + check_message(info); + + serial_println!("[ok]"); + exit_qemu(QemuExitCode::Success); + loop {} +} + +fn fail(error: &str) -> ! { + serial_println!("[failed]"); + serial_println!("{}", error); + exit_qemu(QemuExitCode::Failed); + loop {} +} + +fn check_location(info: &PanicInfo) { + let location = info.location().unwrap_or_else(|| fail("no location")); + if location.file() != file!() { + fail("file name wrong"); + } + if location.line() != PANIC_LINE { + fail("file line wrong"); + } +} + +fn check_message(info: &PanicInfo) { + let message = info.message().unwrap_or_else(|| fail("no message")); + let mut compare_message = CompareMessage { equals: false }; + write!(&mut compare_message, "{}", message).unwrap_or_else(|_| fail("write failed")); + if !compare_message.equals { + fail("message not equal to expected message"); + } +} + +/// Compares a `fmt::Arguments` instance with the `MESSAGE` string +/// +/// To use this type, write the `fmt::Arguments` instance to it using the +/// `write` macro. If a message component matches `MESSAGE`, the equals +/// field is set to true. +struct CompareMessage { + equals: bool, +} + +impl fmt::Write for CompareMessage { + fn write_str(&mut self, s: &str) -> fmt::Result { + if s == MESSAGE { + self.equals = true; + } + Ok(()) + } +}