Skip to content

Commit

Permalink
feat(testing): add basic section-based test declaration support (#65)
Browse files Browse the repository at this point in the history
Implements a section-based approach for declaring tests within the kernel from across the dependency graph with a single test runner.

In addition, fixes a number of host tests to allow them to run under CI.

Example Output:
```
 [INFO] run tests
 [INFO] | test, test.name="basic_alloc", test.module="mycelium_kernel"
 [INFO] | |- mycelium_kernel: vec=[], vec.addr=0x8
 [INFO] | |- mycelium_kernel: vec=[5], vec.addr=0x3fc058
 [INFO] | |- mycelium_kernel: vec=[5, 10], vec.addr=0x3fc048
 [INFO] | test, test.name="wasm_hello_world", test.module="mycelium_kernel"
[TRACE] | | invoke_index, index=0, args=RuntimeArgs([I32(1), I32(0), I32(1), I32(20)])
 [INFO] | | | fd_write, fd=1, iovs=0, iovs_len=1, nwritten=20
 [INFO] | | | |- mycelium_kernel::wasm::wasi: fd_write, buf_slice=[104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 10], buf_str=Ok("hello world\n")
 [INFO] | test, test.name="it_works", test.module="mycelium_util::testing"
 [INFO] | |- mycelium_util::testing: I'm running in a test!
 [WARN] |- mycelium_kernel: 3 passed | 0 failed
```
  • Loading branch information
mystor committed Feb 8, 2020
1 parent 46f2268 commit 7cf668d
Show file tree
Hide file tree
Showing 12 changed files with 287 additions and 85 deletions.
1 change: 1 addition & 0 deletions .cargo/config
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ runner = "bootimage runner"
dev-env = "install cargo-xbuild bootimage"
run-x64 = "xrun --target=x86_64-mycelium.json"
debug-x64 = "xrun --target=x86_64-mycelium.json -- -gdb tcp::1234 -S"
test-x64 = "xtest --target=x86_64-mycelium.json"
40 changes: 39 additions & 1 deletion .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,55 @@ jobs:
with:
command: bootimage
args: --target=x86_64-mycelium.json
- name: Test

test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: install rust toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
components: rust-src, llvm-tools-preview
- name: install dev env
uses: actions-rs/cargo@v1.0.1
with:
command: dev-env
- name: install qemu
run: sudo apt-get update && sudo apt-get install qemu
- name: run tests
uses: actions-rs/cargo@v1.0.1
with:
command: test-x64

host-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: install rust toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
- name: run host tests
uses: actions-rs/cargo@v1.0.1
with:
command: test
args: --all --all-features

clippy:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v1
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
components: clippy
- name: rust-clippy-check
uses: actions-rs/clippy-check@v1.0.5
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --all-features
12 changes: 12 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,17 @@ edition = "2018"

[lib]
name = "mycelium_kernel"
harness = false

[[bin]]
name = "mycelium_kernel"
path = "src/main.rs"
test = false

[dependencies]
hal-core = { path = "hal-core" }
mycelium-alloc = { path = "alloc" }
mycelium-util = { path = "util" }
tracing = { version = "0.1", default_features = false }

[target.'cfg(target_arch = "x86_64")'.dependencies]
Expand All @@ -35,6 +42,11 @@ wat = "1.0"

[package.metadata.bootimage]
default-target = "x86_64-mycelium.json"
test-success-exit-code = 33 # (0x10 << 1) | 1
test-args = [
"-device", "isa-debug-exit,iobase=0xf4,iosize=0x04",
"-serial", "stdio", "-display", "none"
]

[package.metadata.target.'cfg(target_arch = "x86_64")'.cargo-xbuild]
memcpy = true
Expand Down
10 changes: 5 additions & 5 deletions alloc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//! Allocates into a "large" static array.
#![no_std]

use core::alloc::{Layout, GlobalAlloc};
use core::alloc::{GlobalAlloc, Layout};
use core::cell::UnsafeCell;
use core::mem::MaybeUninit;
use core::ptr;
Expand All @@ -14,7 +14,7 @@ macro_rules! try_null {
Some(x) => x,
None => return ptr::null_mut(),
}
}
};
}

// 640k is enough for anyone
Expand Down Expand Up @@ -85,7 +85,7 @@ mod test {
assert_eq!(p0.align_offset(mem::align_of::<u32>()), 0);
assert_eq!(p1.align_offset(mem::align_of::<u32>()), 0);

assert_eq!((p1 as usize) - (p0 as usize), 4);
assert_eq!((p0 as usize) - (p1 as usize), 4);
}

#[test]
Expand All @@ -99,8 +99,8 @@ mod test {
let p2 = unsafe { alloc(Layout::new::<u32>()) };
assert!(!p2.is_null());

assert_eq!((p1 as usize) - (p0 as usize), 1);
assert!((p2 as usize) - (p1 as usize) > 0);
assert_eq!((p0 as usize) - (p1 as usize), 1);
assert!((p1 as usize) - (p2 as usize) > 0);

assert_eq!(p2.align_offset(mem::align_of::<u32>()), 0);
}
Expand Down
4 changes: 2 additions & 2 deletions hal-core/src/addr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ pub trait Address:
/// This is equivalent to
/// ```rust
/// # use hal_core::Address;
/// # fn doc<A: Address>(addr: A) -> A {
/// # fn doc<T: Address>(addr: T) -> T {
/// addr.align_up(core::mem::align_of::<T>())
/// # }
/// ````
Expand Down Expand Up @@ -68,7 +68,7 @@ pub trait Address:
/// This is equivalent to
/// ```rust
/// # use hal_core::Address;
/// # fn doc<A: Address>(addr: A) -> A {
/// # fn doc<T: Address>(addr: T) -> T {
/// addr.align_down(core::mem::align_of::<T>())
/// # }
/// ````
Expand Down
29 changes: 29 additions & 0 deletions src/arch/x86_64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,38 @@ pub fn oops(cause: &dyn core::fmt::Display) -> ! {
let _ = writeln!(vga, "\n uwu we did a widdle fucky-wucky!\n{}", cause);
let _ = vga.write_str("\n it will never be safe to turn off your computer.");

#[cfg(test)]
qemu_exit(QemuExitCode::Failed);

#[cfg(not(test))]
loop {
unsafe {
asm!("hlt" :::: "volatile");
}
}
}

#[cfg(test)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub(crate) enum QemuExitCode {
Success = 0x10,
Failed = 0x11,
}

/// Exit using `isa-debug-exit`, for use in tests.
///
/// NOTE: This is a temporary mechanism until we get proper shutdown implemented.
#[cfg(test)]
pub(crate) fn qemu_exit(exit_code: QemuExitCode) -> ! {
let code = exit_code as u32;
unsafe {
asm!("out 0xf4, eax" :: "{eax}"(code) :: "intel","volatile");

// If the previous line didn't immediately trigger shutdown, hang.
asm!("cli" :::: "volatile");
loop {
asm!("hlt" :::: "volatile");
}
}
}
105 changes: 85 additions & 20 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
#![cfg_attr(all(target_os = "none", test), no_main)]
#![cfg_attr(target_os = "none", no_std)]
#![cfg_attr(target_os = "none", feature(alloc_error_handler))]
#![cfg_attr(target_os = "none", feature(panic_info_message, track_caller))]
#![feature(asm)]

extern crate alloc;

pub mod arch;

use core::fmt::Write;
use hal_core::{boot::BootInfo, mem};

use alloc::vec::Vec;

mod wasm;

const HELLOWORLD_WASM: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/helloworld.wasm"));

pub fn kernel_main(bootinfo: &impl BootInfo) -> ! {
let mut writer = bootinfo.writer();
writeln!(
Expand Down Expand Up @@ -62,10 +61,44 @@ pub fn kernel_main(bootinfo: &impl BootInfo) -> ! {

arch::interrupt::init::<arch::InterruptHandlers>();

#[cfg(test)]
{
let span = tracing::info_span!("alloc test");
let span = tracing::info_span!("run tests");
let _enter = span.enter();

let mut passed = 0;
let mut failed = 0;
for test in mycelium_util::testing::all_tests() {
let span = tracing::info_span!("test", test.name, test.module);
let _enter = span.enter();

if (test.run)() {
passed += 1;
} else {
failed += 1;
}
}

tracing::warn!("{} passed | {} failed", passed, failed);
if failed == 0 {
arch::qemu_exit(arch::QemuExitCode::Success);
} else {
arch::qemu_exit(arch::QemuExitCode::Failed);
}
}

// if this function returns we would boot loop. Hang, instead, so the debug
// output can be read.
//
// eventually we'll call into a kernel main loop here...
#[allow(clippy::empty_loop)]
loop {}
}

mycelium_util::decl_test! {
fn basic_alloc() {
// Let's allocate something, for funsies
use alloc::vec::Vec;
let mut v = Vec::new();
tracing::info!(vec = ?v, vec.addr = ?v.as_ptr());
v.push(5u64);
Expand All @@ -75,23 +108,13 @@ pub fn kernel_main(bootinfo: &impl BootInfo) -> ! {
assert_eq!(v.pop(), Some(10));
assert_eq!(v.pop(), Some(5));
}
}

{
let span = tracing::info_span!("wasm test");
let _enter = span.enter();

match wasm::run_wasm(HELLOWORLD_WASM) {
Ok(()) => tracing::info!("wasm test Ok!"),
Err(err) => tracing::error!(?err, "wasm test Err"),
}
mycelium_util::decl_test! {
fn wasm_hello_world() -> Result<(), wasmi::Error> {
const HELLOWORLD_WASM: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/helloworld.wasm"));
wasm::run_wasm(HELLOWORLD_WASM)
}

// if this function returns we would boot loop. Hang, instead, so the debug
// output can be read.
//
// eventually we'll call into a kernel main loop here...
#[allow(clippy::empty_loop)]
loop {}
}

#[global_allocator]
Expand All @@ -103,3 +126,45 @@ pub static GLOBAL: mycelium_alloc::Alloc = mycelium_alloc::Alloc;
fn alloc_error(layout: core::alloc::Layout) -> ! {
panic!("alloc error: {:?}", layout);
}

#[cfg(target_os = "none")]
#[panic_handler]
#[cold]
fn panic(panic: &core::panic::PanicInfo) -> ! {
use core::fmt;

struct PrettyPanic<'a>(&'a core::panic::PanicInfo<'a>);
impl<'a> fmt::Display for PrettyPanic<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let message = self.0.message();
let location = self.0.location();
let caller = core::panic::Location::caller();
if let Some(message) = message {
writeln!(f, " mycelium panicked: {}", message)?;
if let Some(loc) = location {
writeln!(f, " at: {}:{}:{}", loc.file(), loc.line(), loc.column(),)?;
}
} else {
writeln!(f, " mycelium panicked: {}", self.0)?;
}
writeln!(
f,
" in: {}:{}:{}",
caller.file(),
caller.line(),
caller.column()
)?;
Ok(())
}
}

let caller = core::panic::Location::caller();
tracing::error!(%panic, ?caller);
let pp = PrettyPanic(panic);
arch::oops(&pp)
}

#[cfg(all(test, not(target_os = "none")))]
pub fn main() {
/* no host-platform tests in this crate */
}
45 changes: 2 additions & 43 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,47 +3,6 @@
#![cfg_attr(target_os = "none", feature(alloc_error_handler))]
#![cfg_attr(target_os = "none", feature(asm))]
#![cfg_attr(target_os = "none", feature(panic_info_message, track_caller))]
use mycelium_kernel::arch;

#[cfg(target_os = "none")]
#[panic_handler]
#[cold]
fn panic(panic: &core::panic::PanicInfo) -> ! {
use core::fmt;

struct PrettyPanic<'a>(&'a core::panic::PanicInfo<'a>);
impl<'a> fmt::Display for PrettyPanic<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let message = self.0.message();
let location = self.0.location();
let caller = core::panic::Location::caller();
if let Some(message) = message {
writeln!(f, " mycelium panicked: {}", message)?;
if let Some(loc) = location {
writeln!(f, " at: {}:{}:{}", loc.file(), loc.line(), loc.column(),)?;
}
} else {
writeln!(f, " mycelium panicked: {}", self.0)?;
}
writeln!(
f,
" in: {}:{}:{}",
caller.file(),
caller.line(),
caller.column()
)?;
Ok(())
}
}

let caller = core::panic::Location::caller();
tracing::error!(%panic, ?caller);
let pp = PrettyPanic(panic);
arch::oops(&pp)
}

fn main() {
unsafe {
core::hint::unreachable_unchecked();
}
}
// Force linking to the `mycelium_kernel` lib.
use mycelium_kernel;
1 change: 1 addition & 0 deletions util/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ edition = "2018"
alloc = []

[dependencies]
tracing = { version = "0.1", default_features = false }
loom = { version = "0.2.14", optional = true }

[dev-dependencies]
Expand Down
Loading

0 comments on commit 7cf668d

Please sign in to comment.