#[cfg(feature = "std")]
type PanicHook = Box<dyn Fn(&std::panic::PanicHookInfo) + Send + Sync>;
#[cfg(feature = "std")]
struct PanicHookGuard(Option<PanicHook>);
#[cfg(feature = "std")]
impl PanicHookGuard {
fn suppress_stack_trace() -> Self {
let prev = std::panic::take_hook();
std::panic::set_hook(Box::new(|_| {}));
Self(Some(prev))
}
}
#[cfg(feature = "std")]
impl Drop for PanicHookGuard {
fn drop(&mut self) {
if let Some(hook) = self.0.take() {
std::panic::set_hook(hook);
}
}
}
#[cfg(feature = "std")]
fn panic_message(data: &(dyn core::any::Any + Send)) -> Option<&str> {
data.downcast_ref::<&str>()
.copied()
.or_else(|| data.downcast_ref::<String>().map(String::as_str))
}
#[cfg(feature = "std")]
pub fn expect_panic(
test_fn: fn(),
expected_message: Option<&'static str>,
) -> Result<(), libtest_mimic::Failed> {
let result = {
let _guard = PanicHookGuard::suppress_stack_trace();
std::panic::catch_unwind(test_fn)
};
match result {
Ok(_) => Err(libtest_mimic::Failed::from(
"expected panic but test succeeded",
)),
Err(e) => {
if let Some(expected) = expected_message {
let msg = panic_message(e.as_ref()).unwrap_or("");
if !msg.contains(expected) {
return Err(libtest_mimic::Failed::from(format!(
"panic message did not contain expected string \"{expected}\""
)));
}
}
Ok(())
}
}
}
#[cfg(feature = "std")]
#[macro_export]
macro_rules! test_harness {
() => {
extern crate alloc;
use alloc::format;
use alloc::string::String;
use alloc::vec::Vec;
pub fn main() {
let mut args = $crate::libtest_mimic::Arguments::from_args();
args.test_threads
.get_or_insert($crate::DEFAULT_TEST_THREADS);
let tests = $crate::inventory::iter::<$crate::TestCase>()
.map(|test_case| {
let test_fn = test_case.test_fn;
let module = test_case
.module
.find("::")
.map(|i| &test_case.module[i + 2..])
.unwrap_or("");
let trial_name = if module.is_empty() {
alloc::string::String::from(test_case.name)
} else {
alloc::format!("{}::{}", module, test_case.name)
};
$crate::libtest_mimic::Trial::test(trial_name, move || {
match test_case.run_mode {
$crate::RunMode::ExpectPanic(msg) => {
$crate::test_harness::expect_panic(test_fn, msg)
}
_ => {
test_fn();
Ok(())
}
}
})
.with_ignored_flag(matches!(test_case.run_mode, $crate::RunMode::Ignore(_)))
})
.collect::<Vec<_>>();
$crate::libtest_mimic::run(&args, tests).exit();
}
};
}
#[cfg(not(feature = "std"))]
#[macro_export]
macro_rules! test_harness {
() => {
#[unsafe(no_mangle)]
pub extern "C" fn main(_argc: isize, _argv: *const *const u8) -> isize {
run_tests()
}
#[cfg(any(target_os = "linux", target_os = "nto"))]
#[panic_handler]
fn panic(info: &core::panic::PanicInfo) -> ! {
$crate::internal::coutln!("");
$crate::internal::coutln!("");
$crate::internal::cout!("Failed: {}", info);
$crate::internal::coutln!("");
$crate::internal::coutln!("");
unsafe {
$crate::internal::abort();
core::hint::unreachable_unchecked()
}
}
#[cfg(not(any(target_os = "linux", target_os = "nto")))]
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
loop {}
}
#[unsafe(no_mangle)]
extern "C" fn rust_eh_personality() {}
fn run_tests() -> isize {
let tests = $crate::inventory::iter::<$crate::TestCase>();
let mut passed = 0;
let mut failed = 0;
$crate::internal::coutln!("");
$crate::internal::cout!("running tests:");
$crate::internal::coutln!("");
$crate::internal::coutln!("");
for test in tests {
$crate::internal::cout!("test ");
let module = test
.module
.find("::")
.map(|i| &test.module[i + 2..])
.unwrap_or("");
if module.is_empty() {
$crate::internal::cout!("{}", test.name);
} else {
$crate::internal::cout!("{}::{}", module, test.name);
}
$crate::internal::cout!(" ... ");
if let $crate::RequiresStd::Yes(reason) = test.requires_std {
match reason {
Some(r) => $crate::internal::coutln!("std only: {}", r),
None => $crate::internal::coutln!("std only"),
}
continue;
}
match test.run_mode {
$crate::RunMode::Ignore(reason) => {
match reason {
Some(r) => $crate::internal::coutln!("ignored: {}", r),
None => $crate::internal::coutln!("ignored"),
}
continue;
}
$crate::RunMode::ExpectPanic(_) => {
$crate::internal::coutln!("ignored: panics");
continue;
}
$crate::RunMode::Normal => {}
}
(test.test_fn)();
$crate::internal::coutln!("ok");
passed += 1;
}
$crate::internal::coutln!("");
$crate::internal::cout!("test result: ");
if failed == 0 {
$crate::internal::cout!("ok. ");
} else {
$crate::internal::cout!("FAILED. ");
}
$crate::internal::cout!("{}", passed);
$crate::internal::cout!(" passed; ");
$crate::internal::cout!("{}", failed);
$crate::internal::cout!(" failed");
$crate::internal::coutln!("");
if failed > 0 { 1 } else { 0 }
}
};
}