Add testing framework

Based on os.phil-opp.com ideas it includes running test framework in
qemu semihosting mode so that tests can indicate pass or fail to calling
process.

GitHub Actions are configured to run these tests and validate acceptance.

Add test-runner target to perform tests in qemu.

Add -nographic for qemu running tests.

Drop serial for qemu tests as well.
This commit is contained in:
Berkus Decker 2020-09-14 07:54:37 +03:00
parent 33722e895f
commit af1cc83530
10 changed files with 117 additions and 3 deletions

View File

@ -8,3 +8,4 @@ rustflags = [
"-C", "target-cpu=cortex-a53",
"-C", "embed-bitcode=yes",
]
runner = "cargo make test-runner"

View File

@ -46,7 +46,18 @@ jobs:
run: rustup component add rust-src llvm-tools-preview
- name: "Install build tools"
run: cargo install cargo-make
run: cargo install cargo-make cargo-binutils
- name: "Validate rust-lld"
run: |
which rust-lld || echo "Not found"
otool -L ~/.cargo/bin/rust-lld
if: runner.os == 'macOS'
- name: "Print Tools Version"
run: |
cargo make --version
cargo objcopy --version
- name: "Deny Warnings"
run: cargo make build
@ -55,6 +66,8 @@ jobs:
- name: Install QEMU (Linux)
run: |
sudo apt install software-properties-common
sudo add-apt-repository ppa:jacob/virtualisation
sudo apt update
sudo apt install qemu-system-aarch64
if: runner.os == 'Linux'
@ -85,7 +98,8 @@ jobs:
- name: 'Build kernel'
run: cargo make build
# TODO: add tests runner
- name: 'Run tests'
run: cargo make test
check_formatting:
name: "Check Formatting"

View File

@ -14,3 +14,6 @@ clean:
clippy:
cargo make clippy
test:
cargo make test

View File

@ -29,7 +29,8 @@ QEMU_CONTAINER_CMD = "qemu-system-aarch64"
#
# -d in_asm,unimp,int
QEMU_OPTS = "-M raspi3 -d int -semihosting"
QEMU_SERIAL = "-serial null -serial stdio"
QEMU_SERIAL_OPTS = "-serial null -serial stdio"
QEMU_TESTS_OPTS = "-nographic"
QEMU = "qemu-system-aarch64"
# For gdb connection:

View File

@ -47,6 +47,12 @@ To build kernel for Raspberry Pi and copy it to SDCard mounted at `/Volumes/BOOT
just device
```
To run tests (tests require QEMU):
```
just test
```
On the device boot SD card you'll need a configuration file instructing RasPi to launch in 64-bit mode.
```

View File

@ -9,10 +9,26 @@ script = [
"${OBJCOPY} ${OBJCOPY_PARAMS} ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/target/${DEFAULT_TARGET}/release/vesper ${KERNEL_BIN}"
]
[tasks.custom-binary]
script = [
"cp ${@} ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/target/`basename ${@}`.elf",
"${OBJCOPY} ${OBJCOPY_PARAMS} ${@} ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/target/`basename ${@}`.bin"
]
[tasks.build]
env = { "TARGET_FEATURES" = "" }
args = ["build", "${BUILD_STD}", "--target=${TARGET_JSON}", "--release", "--features=${TARGET_FEATURES}"]
[tasks.test]
env = { "TARGET_FEATURES" = "${QEMU_FEATURES}" }
args = ["test", "${BUILD_STD}", "--target=${TARGET_JSON}", "--features=${TARGET_FEATURES}"]
[tasks.test-runner]
dependencies = ["custom-binary"]
script = [
"${QEMU} ${QEMU_OPTS} ${QEMU_TESTS_OPTS} -dtb ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/targets/bcm2710-rpi-3-b-plus.dtb -kernel ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/target/`basename ${@}`.bin"
]
[tasks.build-qemu]
env = { "TARGET_FEATURES" = "${QEMU_FEATURES}" }
command = "cargo"

32
nucleus/src/macros.rs Normal file
View File

@ -0,0 +1,32 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
*/
// https://doc.rust-lang.org/src/std/macros.rs.html
#[macro_export]
macro_rules! print {
($($arg:tt)*) => ($crate::macros::_print(format_args!($($arg)*)));
}
// https://doc.rust-lang.org/src/std/macros.rs.html
#[macro_export]
macro_rules! println {
() => (print!("\n"));
($($arg:tt)*) => ({
$crate::macros::_print(format_args_nl!($($arg)*));
})
}
#[doc(hidden)]
#[cfg(not(any(test, qemu)))]
pub fn _print(_args: core::fmt::Arguments) {
// @todo real system implementation
}
#[doc(hidden)]
#[cfg(any(test, qemu))] // qemu feature not enabled here?? we pass --features=qemu to cargo test
pub fn _print(args: core::fmt::Arguments) {
use crate::{qemu, write_to};
let mut buf = [0u8; 512];
qemu::semihosting_sys_write0_call(write_to::c_show(&mut buf, args).unwrap());
}

View File

@ -3,13 +3,26 @@
*/
#![no_std]
#![no_main]
#![feature(asm)]
#![feature(format_args_nl)]
#![feature(custom_test_frameworks)]
#![test_runner(crate::tests::test_runner)]
#![reexport_test_harness_main = "test_main"]
#[cfg(not(target_arch = "aarch64"))]
use architecture_not_supported_sorry;
extern crate rlibc; // To enable linking memory intrinsics.
#[macro_use]
pub mod arch;
pub use arch::*;
mod macros;
#[cfg(feature = "qemu")]
mod qemu;
#[cfg(test)]
mod tests;
mod write_to;
entry!(kmain);
@ -17,6 +30,9 @@ entry!(kmain);
// arch crate is responsible for calling this
#[inline]
pub fn kmain() -> ! {
#[cfg(test)]
test_main();
endless_sleep()
}

11
nucleus/src/qemu.rs Normal file
View File

@ -0,0 +1,11 @@
#[cfg(test)]
pub fn semihosting_sys_write0_call(text: &str) {
// SAFETY: text must be \0-terminated!
unsafe {
asm!(
"mov w0, #0x04
hlt #0xF000"
, in("x1") text.as_ptr() as u64
);
}
}

14
nucleus/src/tests.rs Normal file
View File

@ -0,0 +1,14 @@
//============================================================================
// Testing environment
//============================================================================
use crate::println;
#[cfg(test)]
pub fn test_runner(tests: &[&dyn Fn()]) {
println!("Running {} tests", tests.len());
for test in tests {
test();
}
println!("[success]");
qemu_exit::aarch64::exit_success();
}