mycotest/
report.rs

1use crate::{Outcome, TestName};
2use core::{fmt, str::FromStr};
3
4pub const TEST_COUNT: &str = "MYCELIUM_TEST_COUNT:";
5pub const START_TEST: &str = "MYCELIUM_TEST_START:";
6pub const FAIL_TEST: &str = "MYCELIUM_TEST_FAIL:";
7pub const PASS_TEST: &str = "MYCELIUM_TEST_PASS:";
8
9#[derive(Debug, Copy, Clone, Eq, PartialEq)]
10pub enum Failure {
11    Fail,
12    Panic,
13    Fault,
14}
15
16#[derive(Debug, Clone, Eq, PartialEq)]
17pub struct ParseError(&'static str);
18
19// === impl Failure ===
20
21impl FromStr for Failure {
22    type Err = ParseError;
23    fn from_str(s: &str) -> Result<Self, Self::Err> {
24        match s.trim() {
25            s if s.starts_with("panic") => Ok(Self::Panic),
26            s if s.starts_with("fail") => Ok(Self::Fail),
27            s if s.starts_with("fault") => Ok(Self::Fault),
28            _ => Err(ParseError(
29                "invalid failure kind: expected one of `panic`, `fail`, or `fault`",
30            )),
31        }
32    }
33}
34
35impl Failure {
36    pub fn as_str(&self) -> &'static str {
37        match self {
38            Failure::Fail => "fail",
39            Failure::Fault => "fault",
40            Failure::Panic => "panic",
41        }
42    }
43}
44
45impl fmt::Display for Failure {
46    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47        f.pad(self.as_str())
48    }
49}
50
51impl fmt::Display for ParseError {
52    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53        fmt::Display::fmt(self.0, f)
54    }
55}
56
57impl<'a> TestName<'a> {
58    pub fn parse_start(line: &'a str) -> Option<Self> {
59        Self::parse(line.strip_prefix(START_TEST)?)
60    }
61
62    #[tracing::instrument(level = "trace")]
63    pub fn parse_outcome(line: &'a str) -> Result<Option<(Self, Outcome)>, ParseError> {
64        let Some(line) = line.strip_prefix("MYCELIUM_TEST_") else {
65            tracing::trace!("not a test outcome");
66            return Ok(None);
67        };
68        tracing::trace!(?line);
69        let (line, result) = if let Some(line) = line.strip_prefix("PASS:") {
70            (line, Ok(()))
71        } else if let Some(line) = line.strip_prefix("FAIL:") {
72            let line = line.trim();
73            tracing::trace!(?line);
74            let failure = line.parse::<Failure>();
75            tracing::trace!(?failure);
76            let failure = failure?;
77            let line = line.strip_prefix(failure.as_str()).unwrap_or(line);
78            (line, Err(failure))
79        } else {
80            tracing::trace!("this is a test start, not an outcome");
81            return Ok(None);
82        };
83        let test = Self::parse(line.trim()).ok_or(ParseError("failed to parse test"));
84        tracing::trace!(?test);
85        Ok(Some((test?, result)))
86    }
87
88    #[cfg(feature = "alloc")]
89    pub fn to_static(self) -> TestName<'static, alloc::string::String> {
90        use alloc::borrow::ToOwned;
91        TestName::new(self.module.to_owned(), self.name.to_owned())
92    }
93
94    fn parse(line: &'a str) -> Option<Self> {
95        let mut line = line.split_whitespace();
96        let module = line.next()?;
97        let name = line.next()?;
98        Some(Self::new(module, name))
99    }
100}