use crate::{report, Test};
use core::{
ffi,
fmt::{self, Write},
mem, ptr, slice,
sync::atomic::{AtomicPtr, Ordering},
};
use mycelium_trace::writer::MakeWriter;
extern "C" {
static __start_MyceliumTests: ffi::c_void;
static __stop_MyceliumTests: ffi::c_void;
}
static CURRENT_TEST: AtomicPtr<Test> = AtomicPtr::new(ptr::null_mut());
#[derive(Debug)]
pub struct TestsFailed {
failed: usize,
passed: usize,
}
pub fn run_tests(mk_writer: impl for<'writer> MakeWriter<'writer>) -> Result<(), TestsFailed> {
let _span = tracing::info_span!("run tests").entered();
let mut passed = 0;
let mut failed = 0;
let tests = all_tests();
writeln!(
mk_writer.make_writer(),
"{}{}",
report::TEST_COUNT,
tests.len()
)
.expect("write failed");
for test in tests {
CURRENT_TEST.store(test as *const _ as *mut _, Ordering::Release);
writeln!(
mk_writer.make_writer(),
"{}{} {}",
report::START_TEST,
test.descr.module,
test.descr.name
)
.expect("write failed");
let _span =
tracing::info_span!("test", name = %test.descr.name, module = %test.descr.module)
.entered();
let outcome = (test.run)();
tracing::info!(?outcome);
CURRENT_TEST.store(ptr::null_mut(), Ordering::Release);
test.write_outcome(outcome, mk_writer.make_writer())
.expect("write failed");
if outcome.is_ok() {
passed += 1;
} else {
failed += 1;
}
}
tracing::warn!("{} passed | {} failed", passed, failed);
if failed > 0 {
Err(TestsFailed { passed, failed })
} else {
Ok(())
}
}
pub fn current_test() -> Option<&'static Test> {
let ptr = CURRENT_TEST.load(Ordering::Acquire);
ptr::NonNull::new(ptr).map(|ptr| unsafe {
&*(ptr.as_ptr() as *const _)
})
}
pub fn all_tests() -> &'static [Test] {
unsafe {
let start: *const ffi::c_void = &__start_MyceliumTests;
let stop: *const ffi::c_void = &__stop_MyceliumTests;
let len_bytes = (stop as usize) - (start as usize);
let len = len_bytes / mem::size_of::<Test>();
assert!(
len_bytes % mem::size_of::<Test>() == 0,
"Section should contain a whole number of `Test`s"
);
if len > 0 {
slice::from_raw_parts(start as *const Test, len)
} else {
&[]
}
}
}
impl fmt::Display for TestsFailed {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{} tests failed (out of {})",
self.failed,
self.failed + self.passed
)
}
}