1#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
7#![no_std]
8
9#[cfg(feature = "alloc")]
10extern crate alloc;
11
12use core::{
13 cmp,
14 fmt::{self, Write},
15 marker::PhantomData,
16};
17pub mod assert;
18pub mod report;
19#[cfg(feature = "runner")]
20pub mod runner;
21
22pub type TestResult = Result<(), assert::Failed>;
23pub type Outcome = Result<(), Failure>;
24pub use report::Failure;
25
26#[derive(Clone, Eq, PartialEq)]
27pub struct TestName<'a, S = &'a str> {
28 name: S,
29 module: S,
30 _lt: PhantomData<&'a str>,
31}
32
33pub struct Test {
36 #[doc(hidden)]
37 pub descr: TestName<'static>,
38 #[doc(hidden)]
39 pub run: fn() -> Outcome,
40}
41
42pub trait TestReport {
44 fn report(self) -> Outcome;
47}
48
49impl TestReport for () {
50 fn report(self) -> Outcome {
51 Ok(())
52 }
53}
54
55impl<T: fmt::Debug> TestReport for Result<(), T> {
56 fn report(self) -> Outcome {
57 match self {
58 Ok(_) => Ok(()),
59 Err(err) => {
60 tracing::error!("FAIL {:?}", err);
61 Err(Failure::Fail)
62 }
63 }
64 }
65}
66
67#[macro_export]
70macro_rules! decl_test {
71 (fn $name:ident $($t:tt)*) => {
72 #[allow(clippy::unnecessary_wraps)]
76 fn $name $($t)*
77
78 const _: () = {
82 #[used]
83 #[link_section = "MyceliumTests"]
84 static TEST: $crate::Test = $crate::Test {
85 descr: $crate::TestName::new(module_path!(), stringify!($name)),
86 run: || $crate::TestReport::report($name()),
87 };
88 };
89 }
90}
91
92impl<S> TestName<'_, S> {
95 pub const fn new(module: S, name: S) -> Self {
96 Self {
97 name,
98 module,
99 _lt: PhantomData,
100 }
101 }
102}
103
104impl<S> TestName<'_, S>
105where
106 S: AsRef<str>,
107{
108 pub fn name(&self) -> &str {
109 self.name.as_ref()
110 }
111
112 pub fn module(&self) -> &str {
113 self.module.as_ref()
114 }
115}
116
117impl<S: Ord> cmp::PartialOrd for TestName<'_, S> {
118 fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
119 Some(self.cmp(other))
120 }
121}
122
123impl<S: Ord> cmp::Ord for TestName<'_, S> {
124 fn cmp(&self, other: &Self) -> cmp::Ordering {
125 self.module
126 .cmp(&other.module)
127 .then_with(|| self.name.cmp(&other.name))
128 }
129}
130
131impl<S: fmt::Debug> fmt::Debug for TestName<'_, S> {
133 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
134 let Self { name, module, _lt } = self;
135 f.debug_struct("Test")
136 .field("name", name)
137 .field("module", module)
138 .finish()
139 }
140}
141
142impl<S: fmt::Display> fmt::Display for TestName<'_, S> {
143 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144 let Self { name, module, _lt } = self;
145 write!(f, "{module}::{name}")
146 }
147}
148
149impl Test {
152 pub fn write_outcome(&self, outcome: Outcome, mut writer: impl Write) -> fmt::Result {
153 tracing::trace!(?self.descr, ?outcome, "write_outcome",);
154 match outcome {
155 Ok(()) => writeln!(
156 writer,
157 "{} {} {}",
158 report::PASS_TEST,
159 self.descr.module,
160 self.descr.name
161 ),
162 Err(fail) => writeln!(
163 writer,
164 "{} {} {} {}",
165 report::FAIL_TEST,
166 fail.as_str(),
167 self.descr.module,
168 self.descr.name
169 ),
170 }
171 }
172}
173
174decl_test! {
175 fn it_works() -> Result<(), ()> {
176 tracing::info!("I'm running in a test!");
177 Ok(())
178 }
179}