mycotest/
report.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
use crate::{Outcome, TestName};
use core::{fmt, str::FromStr};

pub const TEST_COUNT: &str = "MYCELIUM_TEST_COUNT:";
pub const START_TEST: &str = "MYCELIUM_TEST_START:";
pub const FAIL_TEST: &str = "MYCELIUM_TEST_FAIL:";
pub const PASS_TEST: &str = "MYCELIUM_TEST_PASS:";

#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum Failure {
    Fail,
    Panic,
    Fault,
}

#[derive(Debug, Clone, Eq, PartialEq)]
pub struct ParseError(&'static str);

// === impl Failure ===

impl FromStr for Failure {
    type Err = ParseError;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.trim() {
            s if s.starts_with("panic") => Ok(Self::Panic),
            s if s.starts_with("fail") => Ok(Self::Fail),
            s if s.starts_with("fault") => Ok(Self::Fault),
            _ => Err(ParseError(
                "invalid failure kind: expected one of `panic`, `fail`, or `fault`",
            )),
        }
    }
}

impl Failure {
    pub fn as_str(&self) -> &'static str {
        match self {
            Failure::Fail => "fail",
            Failure::Fault => "fault",
            Failure::Panic => "panic",
        }
    }
}

impl fmt::Display for Failure {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.pad(self.as_str())
    }
}

impl fmt::Display for ParseError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::Display::fmt(self.0, f)
    }
}

impl<'a> TestName<'a> {
    pub fn parse_start(line: &'a str) -> Option<Self> {
        Self::parse(line.strip_prefix(START_TEST)?)
    }

    #[tracing::instrument(level = "trace")]
    pub fn parse_outcome(line: &'a str) -> Result<Option<(Self, Outcome)>, ParseError> {
        let Some(line) = line.strip_prefix("MYCELIUM_TEST_") else {
            tracing::trace!("not a test outcome");
            return Ok(None);
        };
        tracing::trace!(?line);
        let (line, result) = if let Some(line) = line.strip_prefix("PASS:") {
            (line, Ok(()))
        } else if let Some(line) = line.strip_prefix("FAIL:") {
            let line = line.trim();
            tracing::trace!(?line);
            let failure = line.parse::<Failure>();
            tracing::trace!(?failure);
            let failure = failure?;
            let line = line.strip_prefix(failure.as_str()).unwrap_or(line);
            (line, Err(failure))
        } else {
            tracing::trace!("this is a test start, not an outcome");
            return Ok(None);
        };
        let test = Self::parse(line.trim()).ok_or(ParseError("failed to parse test"));
        tracing::trace!(?test);
        Ok(Some((test?, result)))
    }

    #[cfg(feature = "alloc")]
    pub fn to_static(self) -> TestName<'static, alloc::string::String> {
        use alloc::borrow::ToOwned;
        TestName::new(self.module.to_owned(), self.name.to_owned())
    }

    fn parse(line: &'a str) -> Option<Self> {
        let mut line = line.split_whitespace();
        let module = line.next()?;
        let name = line.next()?;
        Some(Self::new(module, name))
    }
}