From 88b3103db71a66190f791b2f9c8dcc50302eff5f Mon Sep 17 00:00:00 2001 From: Nick Spinale Date: Tue, 2 Jan 2024 11:17:45 +0000 Subject: [PATCH] Add test harness and test ring Signed-off-by: Nick Spinale --- Cargo.lock | 38 ++++ Cargo.toml | 4 + .../root-task/default-test-harness/Cargo.nix | 19 ++ .../root-task/default-test-harness/Cargo.toml | 24 +++ .../root-task/default-test-harness/src/lib.rs | 36 ++++ .../root-task/ring-test-harness/Cargo.nix | 34 ++++ .../root-task/ring-test-harness/Cargo.toml | 28 +++ .../root-task/ring-test-harness/src/lib.rs | 58 ++++++ .../default-test-harness/Cargo.nix | 18 ++ .../default-test-harness/Cargo.toml | 22 ++ .../default-test-harness/src/lib.rs | 22 ++ crates/sel4-test-harness/Cargo.nix | 22 ++ crates/sel4-test-harness/Cargo.toml | 22 ++ crates/sel4-test-harness/src/config/mod.rs | 26 +++ crates/sel4-test-harness/src/config/types.rs | 26 +++ crates/sel4-test-harness/src/entry.rs | 31 +++ .../src/for_generated_code/mod.rs | 55 +++++ .../src/for_generated_code/types.rs | 190 ++++++++++++++++++ crates/sel4-test-harness/src/lib.rs | 23 +++ crates/sel4-test-harness/src/run_tests.rs | 124 ++++++++++++ .../sel4-test-harness/src/short_backtrace.rs | 16 ++ hacking/nix/scope/crates.nix | 23 +-- hacking/nix/scope/world/instances/default.nix | 73 +++++++ hacking/nix/scope/worlds.nix | 3 +- 24 files changed, 923 insertions(+), 14 deletions(-) create mode 100644 crates/private/tests/root-task/default-test-harness/Cargo.nix create mode 100644 crates/private/tests/root-task/default-test-harness/Cargo.toml create mode 100644 crates/private/tests/root-task/default-test-harness/src/lib.rs create mode 100644 crates/private/tests/root-task/ring-test-harness/Cargo.nix create mode 100644 crates/private/tests/root-task/ring-test-harness/Cargo.toml create mode 100644 crates/private/tests/root-task/ring-test-harness/src/lib.rs create mode 100644 crates/sel4-root-task/default-test-harness/Cargo.nix create mode 100644 crates/sel4-root-task/default-test-harness/Cargo.toml create mode 100644 crates/sel4-root-task/default-test-harness/src/lib.rs create mode 100644 crates/sel4-test-harness/Cargo.nix create mode 100644 crates/sel4-test-harness/Cargo.toml create mode 100644 crates/sel4-test-harness/src/config/mod.rs create mode 100644 crates/sel4-test-harness/src/config/types.rs create mode 100644 crates/sel4-test-harness/src/entry.rs create mode 100644 crates/sel4-test-harness/src/for_generated_code/mod.rs create mode 100644 crates/sel4-test-harness/src/for_generated_code/types.rs create mode 100644 crates/sel4-test-harness/src/lib.rs create mode 100644 crates/sel4-test-harness/src/run_tests.rs create mode 100644 crates/sel4-test-harness/src/short_backtrace.rs diff --git a/Cargo.lock b/Cargo.lock index 345fc001a..0a8571ae7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2504,6 +2504,15 @@ dependencies = [ "sel4-runtime-common", ] +[[package]] +name = "sel4-root-task-default-test-harness" +version = "0.1.0" +dependencies = [ + "sel4", + "sel4-root-task", + "sel4-test-harness", +] + [[package]] name = "sel4-root-task-macros" version = "0.1.0" @@ -2692,6 +2701,15 @@ dependencies = [ "sel4-sys", ] +[[package]] +name = "sel4-test-harness" +version = "0.1.0" +dependencies = [ + "sel4-immediate-sync-once-cell", + "sel4-panicking", + "sel4-panicking-env", +] + [[package]] name = "semver" version = "1.0.19" @@ -2965,6 +2983,14 @@ dependencies = [ "sel4-sys", ] +[[package]] +name = "tests-root-task-default-test-harness" +version = "0.1.0" +dependencies = [ + "log", + "sel4-root-task-default-test-harness", +] + [[package]] name = "tests-root-task-loader" version = "0.1.0" @@ -2998,6 +3024,18 @@ dependencies = [ "sel4-root-task", ] +[[package]] +name = "tests-root-task-ring-test-harness" +version = "0.1.0" +dependencies = [ + "getrandom", + "rand", + "sel4", + "sel4-newlib", + "sel4-root-task", + "sel4-test-harness", +] + [[package]] name = "tests-root-task-tls" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index a691a051f..6858a3cca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,9 +51,11 @@ members = [ "crates/private/tests/root-task/c", "crates/private/tests/root-task/config", "crates/private/tests/root-task/core-libs", + "crates/private/tests/root-task/default-test-harness", "crates/private/tests/root-task/loader", "crates/private/tests/root-task/mbedtls", "crates/private/tests/root-task/panicking", + "crates/private/tests/root-task/ring-test-harness", "crates/private/tests/root-task/tls", "crates/sel4", "crates/sel4-async/block-io", @@ -109,6 +111,7 @@ members = [ "crates/sel4-platform-info/types", "crates/sel4-render-elf-with-data", "crates/sel4-root-task", + "crates/sel4-root-task/default-test-harness", "crates/sel4-root-task/macros", "crates/sel4-runtime-common", "crates/sel4-rustfmt-helper", @@ -118,6 +121,7 @@ members = [ "crates/sel4-shared-ring-buffer/bookkeeping", "crates/sel4-shared-ring-buffer/smoltcp", "crates/sel4-sync", + "crates/sel4-test-harness", "crates/sel4/bitfield-ops", "crates/sel4/bitfield-parser", "crates/sel4/bitfield-parser/test", diff --git a/crates/private/tests/root-task/default-test-harness/Cargo.nix b/crates/private/tests/root-task/default-test-harness/Cargo.nix new file mode 100644 index 000000000..d2ce80968 --- /dev/null +++ b/crates/private/tests/root-task/default-test-harness/Cargo.nix @@ -0,0 +1,19 @@ +# +# Copyright 2023, Colias Group, LLC +# +# SPDX-License-Identifier: BSD-2-Clause +# + +{ mk, localCrates, versions }: + +mk { + package.name = "tests-root-task-default-test-harness"; + dependencies = { + inherit (versions) log; + }; + dev-dependencies = { + test = let package = "sel4-root-task-default-test-harness"; in localCrates.${package} // { + inherit package; + }; + }; +} diff --git a/crates/private/tests/root-task/default-test-harness/Cargo.toml b/crates/private/tests/root-task/default-test-harness/Cargo.toml new file mode 100644 index 000000000..a32f7cd25 --- /dev/null +++ b/crates/private/tests/root-task/default-test-harness/Cargo.toml @@ -0,0 +1,24 @@ +# +# Copyright 2023, Colias Group, LLC +# +# SPDX-License-Identifier: BSD-2-Clause +# +# +# This file is generated from './Cargo.nix'. You can edit this file directly +# if you are not using this project's Cargo manifest management tools. +# See 'hacking/cargo-manifest-management/README.md' for more information. +# + +[package] +name = "tests-root-task-default-test-harness" +version = "0.1.0" +authors = ["Nick Spinale "] +edition = "2021" +license = "BSD-2-Clause" + +[dependencies] +log = "0.4.17" + +[dev-dependencies.test] +path = "../../../../sel4-root-task/default-test-harness" +package = "sel4-root-task-default-test-harness" diff --git a/crates/private/tests/root-task/default-test-harness/src/lib.rs b/crates/private/tests/root-task/default-test-harness/src/lib.rs new file mode 100644 index 000000000..caefc3c06 --- /dev/null +++ b/crates/private/tests/root-task/default-test-harness/src/lib.rs @@ -0,0 +1,36 @@ +// +// Copyright 2023, Colias Group, LLC +// +// SPDX-License-Identifier: BSD-2-Clause +// + +#![no_std] + +#[cfg(test)] +mod test { + #[test] + fn foo() {} + + #[test] + #[should_panic] + fn bar() { + assert!(false); + } +} + +mod m { + #[cfg(test)] + mod test { + #[test] + fn foo() {} + + #[ignore = "a reason"] + #[test] + fn bar() { + assert!(false); + } + } +} + +// cargo rustc $h --target aarch64-sel4 -p tests-root-task-default-test-harness --profile=check -- --test -Zunpretty=expanded +// cargo rustc $h --target riscv64imac-sel4 -p tests-root-task-default-test-harness --profile=check -- --test -Zunpretty=expanded diff --git a/crates/private/tests/root-task/ring-test-harness/Cargo.nix b/crates/private/tests/root-task/ring-test-harness/Cargo.nix new file mode 100644 index 000000000..229ff6a2b --- /dev/null +++ b/crates/private/tests/root-task/ring-test-harness/Cargo.nix @@ -0,0 +1,34 @@ +# +# Copyright 2023, Colias Group, LLC +# +# SPDX-License-Identifier: BSD-2-Clause +# + +{ mk, localCrates, versions }: + +mk rec { + package.name = "tests-root-task-ring-test-harness"; + dependencies = { + inherit (localCrates) + sel4 + sel4-root-task + sel4-test-harness + ; + sel4-newlib = localCrates.sel4-newlib // { + features = [ + "nosys" + "all-symbols" + "sel4-panicking-env" + ]; + }; + getrandom = { + version = "0.2.10"; + features = [ "custom" ]; + }; + rand = { + version = "0.8.5"; + default-features = false; + features = [ "small_rng" ]; + }; + }; +} diff --git a/crates/private/tests/root-task/ring-test-harness/Cargo.toml b/crates/private/tests/root-task/ring-test-harness/Cargo.toml new file mode 100644 index 000000000..7f812db44 --- /dev/null +++ b/crates/private/tests/root-task/ring-test-harness/Cargo.toml @@ -0,0 +1,28 @@ +# +# Copyright 2023, Colias Group, LLC +# +# SPDX-License-Identifier: BSD-2-Clause +# +# +# This file is generated from './Cargo.nix'. You can edit this file directly +# if you are not using this project's Cargo manifest management tools. +# See 'hacking/cargo-manifest-management/README.md' for more information. +# + +[package] +name = "tests-root-task-ring-test-harness" +version = "0.1.0" +authors = ["Nick Spinale "] +edition = "2021" +license = "BSD-2-Clause" + +[dependencies] +getrandom = { version = "0.2.10", features = ["custom"] } +rand = { version = "0.8.5", default-features = false, features = ["small_rng"] } +sel4 = { path = "../../../../sel4" } +sel4-root-task = { path = "../../../../sel4-root-task" } +sel4-test-harness = { path = "../../../../sel4-test-harness" } + +[dependencies.sel4-newlib] +path = "../../../../sel4-newlib" +features = ["nosys", "all-symbols", "sel4-panicking-env"] diff --git a/crates/private/tests/root-task/ring-test-harness/src/lib.rs b/crates/private/tests/root-task/ring-test-harness/src/lib.rs new file mode 100644 index 000000000..a6415ff4a --- /dev/null +++ b/crates/private/tests/root-task/ring-test-harness/src/lib.rs @@ -0,0 +1,58 @@ +// +// Copyright 2023, Colias Group, LLC +// +// SPDX-License-Identifier: BSD-2-Clause +// + +#![no_std] +#![no_main] +#![feature(cfg_target_thread_local)] +#![feature(thread_local)] + +use core::cell::RefCell; + +use rand::rngs::SmallRng; +use rand::{RngCore, SeedableRng}; + +use sel4_newlib as _; +use sel4_root_task::root_task; +use sel4_test_harness::run_test_main; + +pub use sel4_test_harness::for_generated_code::*; + +const HEAP_SIZE: usize = 256 * 1024 * 1024; + +#[root_task(heap_size = HEAP_SIZE)] +fn main(_bootinfo: &sel4::BootInfo) -> ! { + seed_insecure_dummy_rng(0); + run_test_main(); + sel4::BootInfo::init_thread_tcb().tcb_suspend().unwrap(); + unreachable!() +} + +#[cfg(not(target_thread_local))] +compile_error!(""); + +#[thread_local] +static RNG: RefCell> = RefCell::new(None); + +pub fn seed_insecure_dummy_rng(seed: u64) { + assert!(RNG.replace(Some(SmallRng::seed_from_u64(seed))).is_none()); +} + +pub fn insecure_dummy_rng(buf: &mut [u8]) -> Result<(), getrandom::Error> { + if 1_u32.swap_bytes() == 0 { + panic!() + } + RNG.borrow_mut().as_mut().unwrap().fill_bytes(buf); + Ok(()) +} + +getrandom::register_custom_getrandom!(insecure_dummy_rng); + +// https://github.com/rust-lang/compiler-builtins/pull/563 +#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] +#[no_mangle] +pub extern "C" fn __bswapsi2(u: u32) -> u32 { + u.swap_bytes() +} diff --git a/crates/sel4-root-task/default-test-harness/Cargo.nix b/crates/sel4-root-task/default-test-harness/Cargo.nix new file mode 100644 index 000000000..527f5ee63 --- /dev/null +++ b/crates/sel4-root-task/default-test-harness/Cargo.nix @@ -0,0 +1,18 @@ +# +# Copyright 2023, Colias Group, LLC +# +# SPDX-License-Identifier: BSD-2-Clause +# + +{ mk, localCrates, versions }: + +mk rec { + package.name = "sel4-root-task-default-test-harness"; + dependencies = { + inherit (localCrates) + sel4 + sel4-root-task + sel4-test-harness + ; + }; +} diff --git a/crates/sel4-root-task/default-test-harness/Cargo.toml b/crates/sel4-root-task/default-test-harness/Cargo.toml new file mode 100644 index 000000000..67b3bdf76 --- /dev/null +++ b/crates/sel4-root-task/default-test-harness/Cargo.toml @@ -0,0 +1,22 @@ +# +# Copyright 2023, Colias Group, LLC +# +# SPDX-License-Identifier: BSD-2-Clause +# +# +# This file is generated from './Cargo.nix'. You can edit this file directly +# if you are not using this project's Cargo manifest management tools. +# See 'hacking/cargo-manifest-management/README.md' for more information. +# + +[package] +name = "sel4-root-task-default-test-harness" +version = "0.1.0" +authors = ["Nick Spinale "] +edition = "2021" +license = "BSD-2-Clause" + +[dependencies] +sel4 = { path = "../../sel4" } +sel4-root-task = { path = ".." } +sel4-test-harness = { path = "../../sel4-test-harness" } diff --git a/crates/sel4-root-task/default-test-harness/src/lib.rs b/crates/sel4-root-task/default-test-harness/src/lib.rs new file mode 100644 index 000000000..b6f41a39e --- /dev/null +++ b/crates/sel4-root-task/default-test-harness/src/lib.rs @@ -0,0 +1,22 @@ +// +// Copyright 2023, Colias Group, LLC +// +// SPDX-License-Identifier: BSD-2-Clause +// + +#![no_std] +#![no_main] + +use sel4_root_task::root_task; +use sel4_test_harness::run_test_main; + +pub use sel4_test_harness::for_generated_code::*; + +const HEAP_SIZE: usize = 64 * 1024 * 1024; + +#[root_task(heap_size = HEAP_SIZE)] +fn main(_bootinfo: &sel4::BootInfo) -> ! { + run_test_main(); + sel4::BootInfo::init_thread_tcb().tcb_suspend().unwrap(); + unreachable!() +} diff --git a/crates/sel4-test-harness/Cargo.nix b/crates/sel4-test-harness/Cargo.nix new file mode 100644 index 000000000..b5b8dd920 --- /dev/null +++ b/crates/sel4-test-harness/Cargo.nix @@ -0,0 +1,22 @@ +# +# Copyright 2023, Colias Group, LLC +# +# SPDX-License-Identifier: BSD-2-Clause +# + +{ mk, localCrates, mkDefaultFrontmatterWithReuseArgs, defaultReuseFrontmatterArgs }: + +mk rec { + nix.frontmatter = mkDefaultFrontmatterWithReuseArgs (defaultReuseFrontmatterArgs // { + licenseID = package.license; + }); + package.name = "sel4-test-harness"; + package.license = "MIT OR Apache-2.0"; + dependencies = { + inherit (localCrates) + sel4-panicking-env + sel4-panicking + sel4-immediate-sync-once-cell + ; + }; +} diff --git a/crates/sel4-test-harness/Cargo.toml b/crates/sel4-test-harness/Cargo.toml new file mode 100644 index 000000000..e83bf4593 --- /dev/null +++ b/crates/sel4-test-harness/Cargo.toml @@ -0,0 +1,22 @@ +# +# Copyright 2023, Colias Group, LLC +# +# SPDX-License-Identifier: MIT OR Apache-2.0 +# +# +# This file is generated from './Cargo.nix'. You can edit this file directly +# if you are not using this project's Cargo manifest management tools. +# See 'hacking/cargo-manifest-management/README.md' for more information. +# + +[package] +name = "sel4-test-harness" +version = "0.1.0" +authors = ["Nick Spinale "] +edition = "2021" +license = "MIT OR Apache-2.0" + +[dependencies] +sel4-immediate-sync-once-cell = { path = "../sel4-immediate-sync-once-cell" } +sel4-panicking = { path = "../sel4-panicking" } +sel4-panicking-env = { path = "../sel4-panicking/env" } diff --git a/crates/sel4-test-harness/src/config/mod.rs b/crates/sel4-test-harness/src/config/mod.rs new file mode 100644 index 000000000..46e18992d --- /dev/null +++ b/crates/sel4-test-harness/src/config/mod.rs @@ -0,0 +1,26 @@ +// +// Copyright 2023, Colias Group, LLC +// +// SPDX-License-Identifier: MIT OR Apache-2.0 +// + +use alloc::borrow::Cow; + +use sel4_immediate_sync_once_cell::ImmediateSyncOnceCell; + +pub(crate) mod types; + +use types::Config; + +static CONFIG: ImmediateSyncOnceCell = ImmediateSyncOnceCell::new(); + +pub fn set_config(config: Config) { + CONFIG.set(config).unwrap_or_else(|_| panic!()) +} + +pub(crate) fn get_config() -> Cow<'static, Config> { + CONFIG + .get() + .map(Cow::Borrowed) + .unwrap_or_else(|| Default::default()) +} diff --git a/crates/sel4-test-harness/src/config/types.rs b/crates/sel4-test-harness/src/config/types.rs new file mode 100644 index 000000000..5cb151777 --- /dev/null +++ b/crates/sel4-test-harness/src/config/types.rs @@ -0,0 +1,26 @@ +// +// Copyright 2023, Colias Group, LLC +// Copyright 2023, Rust project contributors +// +// SPDX-License-Identifier: MIT OR Apache-2.0 +// + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)] +pub struct Config { + pub run_ignored: RunIgnored, +} + +/// Whether ignored test should be run or not +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum RunIgnored { + Yes, + No, + /// Run only ignored tests + Only, +} + +impl Default for RunIgnored { + fn default() -> Self { + Self::No + } +} diff --git a/crates/sel4-test-harness/src/entry.rs b/crates/sel4-test-harness/src/entry.rs new file mode 100644 index 000000000..7a3faeed9 --- /dev/null +++ b/crates/sel4-test-harness/src/entry.rs @@ -0,0 +1,31 @@ +// +// Copyright 2023, Colias Group, LLC +// +// SPDX-License-Identifier: MIT OR Apache-2.0 +// + +pub fn run_test_main() { + unsafe { + main(0, core::ptr::null()); + } +} + +extern "C" { + fn main(argc: isize, argv: *const *const u8); +} + +// HACK +trait IsUnit {} + +impl IsUnit for () {} + +#[lang = "start"] +fn lang_start( + main: fn() -> T, + _argc: isize, + _argv: *const *const u8, + _sigpipe: u8, +) -> isize { + main(); + 0 +} diff --git a/crates/sel4-test-harness/src/for_generated_code/mod.rs b/crates/sel4-test-harness/src/for_generated_code/mod.rs new file mode 100644 index 000000000..f5f3f25ae --- /dev/null +++ b/crates/sel4-test-harness/src/for_generated_code/mod.rs @@ -0,0 +1,55 @@ +// +// Copyright 2023, Colias Group, LLC +// +// SPDX-License-Identifier: MIT OR Apache-2.0 +// + +use alloc::format; +use alloc::string::String; +use core::fmt; + +use crate::{config::get_config, run_tests::run_tests_with_config}; + +pub(crate) mod types; + +pub use types::*; + +pub trait Termination { + type Error: fmt::Debug; + + fn report(self) -> Result<(), Self::Error>; +} + +impl Termination for () { + type Error = !; + + fn report(self) -> Result<(), Self::Error> { + Ok(()) + } +} + +impl Termination for ! { + type Error = !; + + fn report(self) -> Result<(), Self::Error> { + self + } +} + +impl Termination for Result { + type Error = E; + + fn report(self) -> Result<(), Self::Error> { + self.map(|_| ()) + } +} + +pub fn assert_test_result(result: T) -> Result<(), String> { + result.report().map_err(|err| { + format!("the test returned a termination value of Err({err:?}) which indicates a failure") + }) +} + +pub fn test_main_static(tests: &[&TestDescAndFn]) { + run_tests_with_config(&get_config(), tests) +} diff --git a/crates/sel4-test-harness/src/for_generated_code/types.rs b/crates/sel4-test-harness/src/for_generated_code/types.rs new file mode 100644 index 000000000..5b26db7a6 --- /dev/null +++ b/crates/sel4-test-harness/src/for_generated_code/types.rs @@ -0,0 +1,190 @@ +// +// Copyright 2023, Colias Group, LLC +// Copyright 2023, Rust project contributors +// +// SPDX-License-Identifier: MIT OR Apache-2.0 +// + +use alloc::borrow::Cow; +use alloc::fmt; +use alloc::string::String; + +use crate::short_backtrace::__rust_begin_short_backtrace; + +pub use NamePadding::*; +pub use TestFn::*; +pub use TestName::*; + +/// Whether test is expected to panic or not +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum ShouldPanic { + No, + Yes, + YesWithMessage(&'static str), +} + +impl ShouldPanic { + pub(crate) fn should_panic(&self) -> bool { + !matches!(self, Self::No) + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum TestType { + UnitTest, + IntegrationTest, + DocTest, + Unknown, +} + +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +pub enum NamePadding { + PadNone, + PadOnRight, +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug)] +pub enum TestName { + StaticTestName(&'static str), + DynTestName(String), + AlignedTestName(Cow<'static, str>, NamePadding), +} + +impl TestName { + pub fn as_slice(&self) -> &str { + match *self { + StaticTestName(s) => s, + DynTestName(ref s) => s, + AlignedTestName(ref s, _) => s, + } + } + + pub fn padding(&self) -> NamePadding { + match self { + &AlignedTestName(_, p) => p, + _ => PadNone, + } + } + + pub fn with_padding(&self, padding: NamePadding) -> TestName { + let name = match *self { + TestName::StaticTestName(name) => Cow::Borrowed(name), + TestName::DynTestName(ref name) => Cow::Owned(name.clone()), + TestName::AlignedTestName(ref name, _) => name.clone(), + }; + + TestName::AlignedTestName(name, padding) + } +} + +impl fmt::Display for TestName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self.as_slice(), f) + } +} + +pub enum TestFn { + StaticTestFn(fn() -> Result<(), String>), +} + +impl TestFn { + pub fn padding(&self) -> NamePadding { + match *self { + StaticTestFn(..) => PadNone, + } + } + + pub(crate) fn into_runnable(self) -> Runnable { + match self { + StaticTestFn(f) => Runnable::Test(RunnableTest::Static(f)), + } + } +} + +impl fmt::Debug for TestFn { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match *self { + StaticTestFn(..) => "StaticTestFn(..)", + }) + } +} + +pub(crate) enum Runnable { + Test(RunnableTest), +} + +pub(crate) enum RunnableTest { + Static(fn() -> Result<(), String>), +} + +impl RunnableTest { + pub(crate) fn run(self) -> Result<(), String> { + match self { + RunnableTest::Static(f) => __rust_begin_short_backtrace(f), + } + } +} + +// A unique integer associated with each test. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct TestId(pub usize); + +// The definition of a single test. A test runner will run a list of +// these. +#[derive(Clone, Debug)] +pub struct TestDesc { + pub name: TestName, + pub ignore: bool, + pub ignore_message: Option<&'static str>, + pub source_file: &'static str, + pub start_line: usize, + pub start_col: usize, + pub end_line: usize, + pub end_col: usize, + pub should_panic: ShouldPanic, + pub compile_fail: bool, + pub no_run: bool, + pub test_type: TestType, +} + +impl TestDesc { + pub fn padded_name(&self, column_count: usize, align: NamePadding) -> String { + let mut name = String::from(self.name.as_slice()); + let fill = column_count.saturating_sub(name.len()); + let pad = " ".repeat(fill); + match align { + PadNone => name, + PadOnRight => { + name.push_str(&pad); + name + } + } + } + + /// Returns None for ignored test or tests that are just run, otherwise returns a description of the type of test. + /// Descriptions include "should panic", "compile fail" and "compile". + pub fn test_mode(&self) -> Option<&'static str> { + if self.ignore { + return None; + } + match self.should_panic { + ShouldPanic::Yes | ShouldPanic::YesWithMessage(_) => { + return Some("should panic"); + } + ShouldPanic::No => {} + } + if self.compile_fail { + return Some("compile fail"); + } + if self.no_run { + return Some("compile"); + } + None + } +} + +#[derive(Debug)] +pub struct TestDescAndFn { + pub desc: TestDesc, + pub testfn: TestFn, +} diff --git a/crates/sel4-test-harness/src/lib.rs b/crates/sel4-test-harness/src/lib.rs new file mode 100644 index 000000000..1d5081079 --- /dev/null +++ b/crates/sel4-test-harness/src/lib.rs @@ -0,0 +1,23 @@ +// +// Copyright 2023, Colias Group, LLC +// +// SPDX-License-Identifier: MIT OR Apache-2.0 +// + +#![no_std] +#![feature(lang_items)] +#![feature(never_type)] + +extern crate alloc; + +mod config; +mod entry; +mod run_tests; +mod short_backtrace; + +pub mod for_generated_code; + +pub use { + config::{set_config, types::*}, + entry::run_test_main, +}; diff --git a/crates/sel4-test-harness/src/run_tests.rs b/crates/sel4-test-harness/src/run_tests.rs new file mode 100644 index 000000000..57c291c94 --- /dev/null +++ b/crates/sel4-test-harness/src/run_tests.rs @@ -0,0 +1,124 @@ +// +// Copyright 2023, Colias Group, LLC +// +// SPDX-License-Identifier: MIT OR Apache-2.0 +// + +use alloc::string::String; +use core::fmt; + +use sel4_panicking::catch_unwind; +use sel4_panicking_env::{debug_print, debug_println}; + +use crate::{ + config::types::*, + for_generated_code::{Runnable, ShouldPanic, TestDescAndFn, TestFn}, +}; + +pub fn run_tests_with_config(config: &Config, tests: &[&TestDescAndFn]) { + debug_println!(); + debug_println!("running {} tests", tests.len()); + + let mut num_passed = 0; + let mut num_failed = 0; + let mut num_ignored = 0; + + for test in tests.into_iter().map(make_owned_test) { + debug_print!("test {} ... ", test.desc.name); + let ignore = if test.desc.ignore { + config.run_ignored == RunIgnored::No + } else { + config.run_ignored == RunIgnored::Only + }; + if ignore { + num_ignored += 1; + debug_print!("... ignored"); + if let Some(message) = test.desc.ignore_message { + debug_print!(", {message}"); + } + debug_println!(""); + } else { + let result = match test.testfn.into_runnable() { + Runnable::Test(runnable) => wrap_run(test.desc.should_panic, || runnable.run()), + }; + match result { + TestResult::Ok => num_passed += 1, + TestResult::Failed => num_failed += 1, + } + debug_println!("... {result}"); + } + } + + assert_eq!(tests.len(), num_passed + num_failed + num_ignored); + + let result = TestResult::from(num_failed == 0); + + debug_println!(); + debug_println!( + "test result: {result}. {num_passed} passed; {num_failed} failed; {num_ignored} ignored", + ); + debug_println!(); + + match result { + TestResult::Ok => debug_println!("TEST_PASS"), + TestResult::Failed => debug_println!("TEST_FAIL"), + } +} + +fn make_owned_test(test: &&TestDescAndFn) -> TestDescAndFn { + match test.testfn { + TestFn::StaticTestFn(f) => TestDescAndFn { + testfn: TestFn::StaticTestFn(f), + desc: test.desc.clone(), + }, + } +} + +#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] +enum TestResult { + Ok, + Failed, +} + +impl TestResult { + #[allow(dead_code)] + fn ok(&self) -> bool { + matches!(self, Self::Ok) + } + + #[allow(dead_code)] + fn failed(&self) -> bool { + matches!(self, Self::Failed) + } +} + +impl From for TestResult { + fn from(passed: bool) -> Self { + match passed { + true => Self::Ok, + false => Self::Failed, + } + } +} + +impl fmt::Display for TestResult { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Ok => write!(f, "ok"), + Self::Failed => write!(f, "FAILED"), + } + } +} + +fn wrap_run(should_panic: ShouldPanic, f: impl FnOnce() -> Result<(), String>) -> TestResult { + match catch_unwind(f) { + Err(_) => TestResult::from(should_panic.should_panic()), + Ok(Ok(())) => TestResult::from(!should_panic.should_panic()), + Ok(Err(msg)) => { + debug_println!(); + debug_println!("{}", msg); + debug_println!(); + TestResult::Failed + } + } +} diff --git a/crates/sel4-test-harness/src/short_backtrace.rs b/crates/sel4-test-harness/src/short_backtrace.rs new file mode 100644 index 000000000..37dd1c37f --- /dev/null +++ b/crates/sel4-test-harness/src/short_backtrace.rs @@ -0,0 +1,16 @@ +// +// Copyright 2023, Colias Group, LLC +// Copyright 2023, Rust project contributors +// +// SPDX-License-Identifier: MIT OR Apache-2.0 +// + +use core::hint::black_box; + +#[inline(never)] +pub(crate) fn __rust_begin_short_backtrace T>(f: F) -> T { + let result = f(); + + // prevent this frame from being tail-call optimised away + black_box(result) +} diff --git a/hacking/nix/scope/crates.nix b/hacking/nix/scope/crates.nix index 39a1ceb73..f7d1bab28 100644 --- a/hacking/nix/scope/crates.nix +++ b/hacking/nix/scope/crates.nix @@ -44,19 +44,16 @@ let "assets" ]; }; - # mbedtls-sys-auto = { - # extraPaths = [ - # "build" - # "vendor" - # ]; - # }; - # mbedtls = { - # extraPaths = [ - # "benches" - # "examples" - # "tests" - # ]; - # }; + ring = { + resolveLinks = true; + extraPaths = [ + "tests" + "include" + "crypto" + "third_party" + ".git/HEAD" + ]; + }; }; unAugmentedCrates = lib.listToAttrs (lib.forEach workspaceMemberPaths (cratePath: rec { diff --git a/hacking/nix/scope/world/instances/default.nix b/hacking/nix/scope/world/instances/default.nix index 276dac712..3b9f5b2dc 100644 --- a/hacking/nix/scope/world/instances/default.nix +++ b/hacking/nix/scope/world/instances/default.nix @@ -10,6 +10,7 @@ , cpio , cmake, perl, python3Packages +, breakpointHook, bashInteractive , sources @@ -55,6 +56,7 @@ in rec { tests.root-task.panicking.abort.withoutAlloc tests.root-task.panicking.unwind.withAlloc tests.root-task.panicking.unwind.withoutAlloc + tests.root-task.default-test-harness tests.root-task.c tests.capdl.threads tests.capdl.utcover @@ -190,6 +192,77 @@ in rec { c = maybe (haveFullRuntime && hostPlatform.isAarch64) (callPackage ./c.nix { inherit canSimulate; }); + + default-test-harness = maybe haveFullRuntime (mkInstance { + rootTask = mkTask { + rootCrate = crates.tests-root-task-default-test-harness; + test = true; + }; + extraPlatformArgs = lib.optionalAttrs canSimulate { + canAutomateSimply = true; + }; + }); + + ring = maybe haveFullRuntime ( + let + rootTask = lib.makeOverridable mkTask { + rootCrate = crates.ring; + test = true; + features = [ + "less-safe-getrandom-custom-or-rdrand" + # "slow_tests" + ]; + release = true; + lastLayerModifications.modifyDerivation = drv: drv.overrideAttrs (attrs: { + nativeBuildInputs = (attrs.nativeBuildInputs or []) ++ [ + perl + ]; + }); + }; + + fnamesFile = runCommand "elfs.txt" {} '' + cd ${rootTask}/bin + echo -n *.elf > $out + ''; + + fnames = lib.splitString " " (builtins.readFile fnamesFile); + + byElf = lib.listToAttrs (lib.forEach fnames (fname: + let + name = lib.head (lib.splitString "." fname); + in + lib.nameValuePair name (mkInstance { + rootTask = rootTask.override { + getELF = drv: runCommand "test.elf" {} '' + ln -s ${drv}/bin/${fname} $out + ''; + }; + extraPlatformArgs = lib.optionalAttrs canSimulate { + canAutomateSimply = true; + simpleAutomationParams.timeout = 10 * 60; + }; + } + ) + )); + in { + inherit byElf; + + all = buildPackages.writeScript "run-tests" '' + #!${buildPackages.runtimeShell} + set -eu + + ${lib.concatStrings (lib.flip lib.mapAttrsToList byElf (k: v: '' + echo "<<< running test: ${k} >>>" + ${v.automate} + ''))} + + echo + echo '# All tests passed.' + echo + ''; + } + ); + }; capdl = { diff --git a/hacking/nix/scope/worlds.nix b/hacking/nix/scope/worlds.nix index 3bc2414c3..e30b30baa 100644 --- a/hacking/nix/scope/worlds.nix +++ b/hacking/nix/scope/worlds.nix @@ -16,7 +16,8 @@ with cmakeConfigHelpers; let kernelConfigCommon = { KernelVerificationBuild = off; - KernelRootCNodeSizeBits = mkString "14"; # For backtrace test with embedded debug info + # KernelRootCNodeSizeBits = mkString "14"; # For backtrace test with embedded debug info + KernelRootCNodeSizeBits = mkString "20"; # For ring test }; kernelLoaderConfig = {};