1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251
// SPDX-License-Identifier: MPL-2.0
//! # The kernel mode testing framework of OSTD.
//!
//! `ostd-test` stands for kernel-mode testing framework for OSTD. Its goal is to provide a
//! `cargo test`-like experience for any `#![no_std]` bare metal crates.
//!
//! In OSTD, all the tests written in the source tree of the crates will be run
//! immediately after the initialization of `ostd`. Thus you can use any
//! feature provided by the frame including the heap allocator, etc.
//!
//! By all means, ostd-test is an individual crate that only requires:
//! - a custom linker script section `.ktest_array`,
//! - and an alloc implementation.
//!
//! And the OSTD happens to provide both of them. Thus, any crates depending
//! on the OSTD can use ostd-test without any extra dependency.
//!
//! ## Usage
//!
//! To write a unit test for any crates, it is recommended to create a new test
//! module, e.g.:
//!
//! ```rust
//! #[cfg(ktest)]
//! mod test {
//! use ostd::prelude::*;
//!
//! #[ktest]
//! fn trivial_assertion() {
//! assert_eq!(0, 0);
//! }
//! #[ktest]
//! #[should_panic]
//! fn failing_assertion() {
//! assert_eq!(0, 1);
//! }
//! #[ktest]
//! #[should_panic(expected = "expected panic message")]
//! fn expect_panic() {
//! panic!("expected panic message");
//! }
//! }
//! ```
//!
//! Any crates using the ostd-test framework should be linked with ostd.
//!
//! By the way, `#[ktest]` attribute along also works, but it hinders test control
//! using cfgs since plain attribute marked test will be executed in all test runs
//! no matter what cfgs are passed to the compiler. More importantly, using `#[ktest]`
//! without cfgs occupies binary real estate since the `.ktest_array` section is not
//! explicitly stripped in normal builds.
//!
//! Rust cfg is used to control the compilation of the test module. In cooperation
//! with the `ktest` framework, OSDK will set the `RUSTFLAGS` environment variable
//! to pass the cfgs to all rustc invocations. To run the tests, you simply need
//! to use the command `cargo osdk test` in the crate directory. For more information,
//! please refer to the OSDK documentation.
//!
//! We support the `#[should_panic]` attribute just in the same way as the standard
//! library do, but the implementation is quite slow currently. Use it with cautious.
//!
//! Doctest is not taken into consideration yet, and the interface is subject to
//! change.
//!
#![cfg_attr(not(test), no_std)]
#![feature(panic_info_message)]
extern crate alloc;
use alloc::{boxed::Box, string::String};
#[derive(Clone, Debug)]
pub struct PanicInfo {
pub message: String,
pub file: String,
pub line: usize,
pub col: usize,
pub resolve_panic: fn(),
}
impl core::fmt::Display for PanicInfo {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
writeln!(f, "Panicked at {}:{}:{}", self.file, self.line, self.col)?;
writeln!(f, "{}", self.message)
}
}
/// The error that may occur during the test.
#[derive(Clone)]
pub enum KtestError {
Panic(Box<PanicInfo>),
ShouldPanicButNoPanic,
ExpectedPanicNotMatch(&'static str, Box<PanicInfo>),
Unknown,
}
/// The information of the unit test.
#[derive(Clone, PartialEq, Debug)]
pub struct KtestItemInfo {
/// The path of the module, not including the function name.
///
/// It would be separated by `::`.
pub module_path: &'static str,
/// The name of the unit test function.
pub fn_name: &'static str,
/// The name of the crate.
pub package: &'static str,
/// The source file where the test function resides.
pub source: &'static str,
/// The line number of the test function in the file.
pub line: usize,
/// The column number of the test function in the file.
pub col: usize,
}
#[derive(Clone, PartialEq, Debug)]
pub struct KtestItem {
fn_: fn() -> (),
should_panic: (bool, Option<&'static str>),
info: KtestItemInfo,
}
type CatchUnwindImpl = fn(f: fn() -> ()) -> Result<(), Box<dyn core::any::Any + Send>>;
impl KtestItem {
/// Create a new [`KtestItem`].
///
/// Do not use this function directly. Instead, use the `#[ktest]`
/// attribute to mark the test function.
#[doc(hidden)]
pub const fn new(
fn_: fn() -> (),
should_panic: (bool, Option<&'static str>),
info: KtestItemInfo,
) -> Self {
Self {
fn_,
should_panic,
info,
}
}
/// Get the information of the test.
pub fn info(&self) -> &KtestItemInfo {
&self.info
}
/// Run the test with a given catch_unwind implementation.
pub fn run(&self, catch_unwind_impl: &CatchUnwindImpl) -> Result<(), KtestError> {
let test_result = catch_unwind_impl(self.fn_);
if !self.should_panic.0 {
// Should not panic.
match test_result {
Ok(()) => Ok(()),
Err(e) => match e.downcast::<PanicInfo>() {
Ok(s) => Err(KtestError::Panic(s)),
Err(_payload) => Err(KtestError::Unknown),
},
}
} else {
// Should panic.
match test_result {
Ok(()) => Err(KtestError::ShouldPanicButNoPanic),
Err(e) => match e.downcast::<PanicInfo>() {
Ok(s) => {
(s.resolve_panic)();
if let Some(expected) = self.should_panic.1 {
if s.message == expected {
Ok(())
} else {
Err(KtestError::ExpectedPanicNotMatch(expected, s))
}
} else {
Ok(())
}
}
Err(_payload) => Err(KtestError::Unknown),
},
}
}
}
}
macro_rules! ktest_array {
() => {{
extern "C" {
fn __ktest_array();
fn __ktest_array_end();
}
let item_size = core::mem::size_of::<KtestItem>();
let l = (__ktest_array_end as usize - __ktest_array as usize) / item_size;
// SAFETY: __ktest_array is a static section consisting of KtestItem.
unsafe { core::slice::from_raw_parts(__ktest_array as *const KtestItem, l) }
}};
}
/// The iterator of the ktest array.
pub struct KtestIter {
index: usize,
}
impl Default for KtestIter {
fn default() -> Self {
Self::new()
}
}
impl KtestIter {
/// Create a new [`KtestIter`].
///
/// It will iterate over all the tests (marked with `#[ktest]`).
pub fn new() -> Self {
Self { index: 0 }
}
}
impl core::iter::Iterator for KtestIter {
type Item = KtestItem;
fn next(&mut self) -> Option<Self::Item> {
let ktest_item = ktest_array!().get(self.index)?;
self.index += 1;
Some(ktest_item.clone())
}
}
// The whitelists that will be generated by the OSDK as static consts.
// They deliver the target tests that the user wants to run.
extern "Rust" {
static KTEST_TEST_WHITELIST: Option<&'static [&'static str]>;
static KTEST_CRATE_WHITELIST: Option<&'static [&'static str]>;
}
/// Get the whitelist of the tests.
///
/// The whitelist is generated by the OSDK runner, indicating name of the
/// target tests that the user wants to run.
pub fn get_ktest_test_whitelist() -> Option<&'static [&'static str]> {
// SAFETY: The two extern statics in the base crate are generated by OSDK.
unsafe { KTEST_TEST_WHITELIST }
}
/// Get the whitelist of the crates.
///
/// The whitelist is generated by the OSDK runner, indicating the target crate
/// that the user wants to test.
pub fn get_ktest_crate_whitelist() -> Option<&'static [&'static str]> {
// SAFETY: The two extern statics in the base crate are generated by OSDK.
unsafe { KTEST_CRATE_WHITELIST }
}