1use crate::{report, Test};
2use core::{
3 ffi,
4 fmt::{self, Write},
5 mem, ptr, slice,
6 sync::atomic::{AtomicPtr, Ordering},
7};
8use mycelium_trace::writer::MakeWriter;
9
10extern "C" {
17 static __start_MyceliumTests: ffi::c_void;
18 static __stop_MyceliumTests: ffi::c_void;
19}
20
21static CURRENT_TEST: AtomicPtr<Test> = AtomicPtr::new(ptr::null_mut());
22
23#[derive(Debug)]
24pub struct TestsFailed {
25 failed: usize,
26 passed: usize,
27}
28
29pub fn run_tests(mk_writer: impl for<'writer> MakeWriter<'writer>) -> Result<(), TestsFailed> {
37 let _span = tracing::info_span!("run tests").entered();
38
39 let mut passed = 0;
40 let mut failed = 0;
41 let tests = all_tests();
42 writeln!(
43 mk_writer.make_writer(),
44 "{}{}",
45 report::TEST_COUNT,
46 tests.len()
47 )
48 .expect("write failed");
49 for test in tests {
50 CURRENT_TEST.store(test as *const _ as *mut _, Ordering::Release);
51
52 writeln!(
53 mk_writer.make_writer(),
54 "{}{} {}",
55 report::START_TEST,
56 test.descr.module,
57 test.descr.name
58 )
59 .expect("write failed");
60
61 let _span =
62 tracing::info_span!("test", name = %test.descr.name, module = %test.descr.module)
63 .entered();
64
65 let outcome = (test.run)();
66 tracing::info!(?outcome);
67 CURRENT_TEST.store(ptr::null_mut(), Ordering::Release);
68 test.write_outcome(outcome, mk_writer.make_writer())
69 .expect("write failed");
70 if outcome.is_ok() {
71 passed += 1;
72 } else {
73 failed += 1;
74 }
75 }
76
77 tracing::warn!("{} passed | {} failed", passed, failed);
78
79 if failed > 0 {
80 Err(TestsFailed { passed, failed })
81 } else {
82 Ok(())
83 }
84}
85
86pub fn current_test() -> Option<&'static Test> {
88 let ptr = CURRENT_TEST.load(Ordering::Acquire);
89 ptr::NonNull::new(ptr).map(|ptr| unsafe {
90 &*(ptr.as_ptr() as *const _)
92 })
93}
94
95pub fn all_tests() -> &'static [Test] {
97 unsafe {
98 let start: *const ffi::c_void = &__start_MyceliumTests;
100 let stop: *const ffi::c_void = &__stop_MyceliumTests;
101
102 let len_bytes = (stop as usize) - (start as usize);
103 let len = len_bytes / mem::size_of::<Test>();
104 assert!(
105 len_bytes % mem::size_of::<Test>() == 0,
106 "Section should contain a whole number of `Test`s"
107 );
108
109 if len > 0 {
110 slice::from_raw_parts(start as *const Test, len)
111 } else {
112 &[]
113 }
114 }
115}
116
117impl fmt::Display for TestsFailed {
118 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119 write!(
120 f,
121 "{} tests failed (out of {})",
122 self.failed,
123 self.failed + self.passed
124 )
125 }
126}