Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(testing): add basic section-based test declaration support #65

Merged
merged 8 commits into from
Feb 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Comment on lines +40 to +41
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it would be nice if we could figure out a way to use the github actions cache thingy so we don't have to install qemu over and over...not a blocket though!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't even know that was a thing, so I'm not sure I can help with that :-P
If you have some documentation for how the cache thing works to point me to, let me know.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can figure this out later!

- name: run tests
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be good to have a step that runs any "regular" (i.e. not in qemu) rust tests in crates as well. i think we define some now as of #64, so it would be good for them to run on CI as well.

if this was a separate job, it could run in parallel with the qemu tests.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, we should probably do that. We'll probably want to make sure to exclude the root mycelium_kernel crate, though.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we might have to just cargo test -p $FOO for every other crate...which is fine, it's just a little flaky since it needs to be manually updated as we add more crates...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ended up just using more #[cfg(...)] attributes to stub out a dummy fn main() for the mycelium_kernel crate when we're running on the host platform.

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) -> ! {
hawkw marked this conversation as resolved.
Show resolved Hide resolved
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