Skip to content

Commit 12d3278

Browse files
committed
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.
1 parent 9fd9612 commit 12d3278

File tree

10 files changed

+117
-3
lines changed

10 files changed

+117
-3
lines changed

.cargo/config.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ rustflags = [
88
"-C", "target-cpu=cortex-a53",
99
"-C", "embed-bitcode=yes",
1010
]
11+
runner = "cargo make test-runner"

.github/workflows/build.yml

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,18 @@ jobs:
4646
run: rustup component add rust-src llvm-tools-preview
4747

4848
- name: "Install build tools"
49-
run: cargo install cargo-make
49+
run: cargo install cargo-make cargo-binutils
50+
51+
- name: "Validate rust-lld"
52+
run: |
53+
which rust-lld || echo "Not found"
54+
otool -L ~/.cargo/bin/rust-lld
55+
if: runner.os == 'macOS'
56+
57+
- name: "Print Tools Version"
58+
run: |
59+
cargo make --version
60+
cargo objcopy --version
5061
5162
- name: "Deny Warnings"
5263
run: cargo make build
@@ -55,6 +66,8 @@ jobs:
5566

5667
- name: Install QEMU (Linux)
5768
run: |
69+
sudo apt install software-properties-common
70+
sudo add-apt-repository ppa:jacob/virtualisation
5871
sudo apt update
5972
sudo apt install qemu-system-aarch64
6073
if: runner.os == 'Linux'
@@ -85,7 +98,8 @@ jobs:
8598
- name: 'Build kernel'
8699
run: cargo make build
87100

88-
# TODO: add tests runner
101+
- name: 'Run tests'
102+
run: cargo make test
89103

90104
check_formatting:
91105
name: "Check Formatting"

Justfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,6 @@ clean:
1414

1515
clippy:
1616
cargo make clippy
17+
18+
test:
19+
cargo make test

Makefile.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ QEMU_CONTAINER_CMD = "qemu-system-aarch64"
2929
#
3030
# -d in_asm,unimp,int
3131
QEMU_OPTS = "-M raspi3 -d int -semihosting"
32-
QEMU_SERIAL = "-serial null -serial stdio"
32+
QEMU_SERIAL_OPTS = "-serial null -serial stdio"
33+
QEMU_TESTS_OPTS = "-nographic"
3334
QEMU = "qemu-system-aarch64"
3435

3536
# For gdb connection:

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ To build kernel for Raspberry Pi and copy it to SDCard mounted at `/Volumes/BOOT
4747
just device
4848
```
4949

50+
To run tests (tests require QEMU):
51+
52+
```
53+
just test
54+
```
55+
5056
On the device boot SD card you'll need a configuration file instructing RasPi to launch in 64-bit mode.
5157

5258
```

nucleus/Makefile.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,26 @@ script = [
99
"${OBJCOPY} ${OBJCOPY_PARAMS} ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/target/${DEFAULT_TARGET}/release/vesper ${KERNEL_BIN}"
1010
]
1111

12+
[tasks.custom-binary]
13+
script = [
14+
"cp ${@} ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/target/`basename ${@}`.elf",
15+
"${OBJCOPY} ${OBJCOPY_PARAMS} ${@} ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/target/`basename ${@}`.bin"
16+
]
17+
1218
[tasks.build]
1319
env = { "TARGET_FEATURES" = "" }
1420
args = ["build", "${BUILD_STD}", "--target=${TARGET_JSON}", "--release", "--features=${TARGET_FEATURES}"]
1521

22+
[tasks.test]
23+
env = { "TARGET_FEATURES" = "${QEMU_FEATURES}" }
24+
args = ["test", "${BUILD_STD}", "--target=${TARGET_JSON}", "--features=${TARGET_FEATURES}"]
25+
26+
[tasks.test-runner]
27+
dependencies = ["custom-binary"]
28+
script = [
29+
"${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"
30+
]
31+
1632
[tasks.build-qemu]
1733
env = { "TARGET_FEATURES" = "${QEMU_FEATURES}" }
1834
command = "cargo"

nucleus/src/macros.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* SPDX-License-Identifier: BlueOak-1.0.0
3+
*/
4+
5+
// https://doc.rust-lang.org/src/std/macros.rs.html
6+
#[macro_export]
7+
macro_rules! print {
8+
($($arg:tt)*) => ($crate::macros::_print(format_args!($($arg)*)));
9+
}
10+
11+
// https://doc.rust-lang.org/src/std/macros.rs.html
12+
#[macro_export]
13+
macro_rules! println {
14+
() => (print!("\n"));
15+
($($arg:tt)*) => ({
16+
$crate::macros::_print(format_args_nl!($($arg)*));
17+
})
18+
}
19+
20+
#[doc(hidden)]
21+
#[cfg(not(any(test, qemu)))]
22+
pub fn _print(_args: core::fmt::Arguments) {
23+
// @todo real system implementation
24+
}
25+
26+
#[doc(hidden)]
27+
#[cfg(any(test, qemu))] // qemu feature not enabled here?? we pass --features=qemu to cargo test
28+
pub fn _print(args: core::fmt::Arguments) {
29+
use crate::{qemu, write_to};
30+
let mut buf = [0u8; 512];
31+
qemu::semihosting_sys_write0_call(write_to::c_show(&mut buf, args).unwrap());
32+
}

nucleus/src/main.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,36 @@
33
*/
44
#![no_std]
55
#![no_main]
6+
#![feature(asm)]
7+
#![feature(format_args_nl)]
8+
#![feature(custom_test_frameworks)]
9+
#![test_runner(crate::tests::test_runner)]
10+
#![reexport_test_harness_main = "test_main"]
611

712
#[cfg(not(target_arch = "aarch64"))]
813
use architecture_not_supported_sorry;
914

15+
extern crate rlibc; // To enable linking memory intrinsics.
16+
1017
#[macro_use]
1118
pub mod arch;
1219
pub use arch::*;
20+
mod macros;
21+
#[cfg(feature = "qemu")]
22+
mod qemu;
23+
#[cfg(test)]
24+
mod tests;
25+
mod write_to;
1326

1427
entry!(kmain);
1528

1629
// Kernel entry point
1730
// arch crate is responsible for calling this
1831
#[inline]
1932
pub fn kmain() -> ! {
33+
#[cfg(test)]
34+
test_main();
35+
2036
endless_sleep()
2137
}
2238

nucleus/src/qemu.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#[cfg(test)]
2+
pub fn semihosting_sys_write0_call(text: &str) {
3+
// SAFETY: text must be \0-terminated!
4+
unsafe {
5+
asm!(
6+
"mov w0, #0x04
7+
hlt #0xF000"
8+
, in("x1") text.as_ptr() as u64
9+
);
10+
}
11+
}

nucleus/src/tests.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//============================================================================
2+
// Testing environment
3+
//============================================================================
4+
use crate::println;
5+
6+
#[cfg(test)]
7+
pub fn test_runner(tests: &[&dyn Fn()]) {
8+
println!("Running {} tests", tests.len());
9+
for test in tests {
10+
test();
11+
}
12+
println!("[success]");
13+
qemu_exit::aarch64::exit_success();
14+
}

0 commit comments

Comments
 (0)