mycotest/
lib.rs

1//! Shared defs between the kernel and the test runner.
2//!
3//! This is in its own crate mainly so the constants are the same and I can't
4//! have the kernel write the wrong strings (which I did lol).
5
6#![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
33/// Test descriptor created by `decl_test!`. Describes and allows running an
34/// individual test.
35pub struct Test {
36    #[doc(hidden)]
37    pub descr: TestName<'static>,
38    #[doc(hidden)]
39    pub run: fn() -> Outcome,
40}
41
42/// Type which may be used as a test return type.
43pub trait TestReport {
44    /// Report any errors to `tracing`, then returns either `true` for a
45    /// success, or `false` for a failure.
46    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/// Declare a new test, sort-of like the `#[test]` attribute.
68// FIXME: Declare a `#[test]` custom attribute instead?
69#[macro_export]
70macro_rules! decl_test {
71    (fn $name:ident $($t:tt)*) => {
72        // Clippy will complain about these functions returning `Result<(),
73        // ()>` unnecessarily, but this is actually required by the test
74        // harness.
75        #[allow(clippy::unnecessary_wraps)]
76        fn $name $($t)*
77
78        // Introduce an anonymous const to avoid name conflicts. The `#[used]`
79        // will prevent the symbol from being dropped, and `link_section` will
80        // make it visible.
81        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
92// === impl TestName ===
93
94impl<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
131// Custom impl to skip `PhantomData` field.
132impl<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
149// === impl Test ===
150
151impl 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}