cargo_suity/
junit.rs

1/// Support for export in JUnit format.
2
3use crate::results::{Event, EventKind};
4use crate::errors::SuityError;
5use std::io::{Write,self};
6use xml_writer::XmlWriter;
7
8/// Indicates that the test failed. A failure is a test which the code has explicitly failed by
9/// using the mechanisms for that purpose. e.g., via an assertEq.
10/// Contains as a text node relevant data for the failure, e.g., a stack trace.
11#[derive(Debug, Eq, PartialEq)]
12pub struct Failure {
13    /// Relevant data for the failure
14    pub message: String,
15}
16
17/// Contains result of a test case
18#[derive(Debug, Eq, PartialEq)]
19pub struct TestCase {
20    /// The full name of test
21    pub name: String,
22    /// Indicates that test failed
23    pub failure: Option<Failure>,
24}
25
26#[derive(Debug, Eq, PartialEq)]
27pub struct TestSuite {
28    /// Name of the test suite
29    pub name: String,
30    /// How many tests erred out. In rust we can't can't detect it :(
31    pub errors: u64,
32    /// How many tests failed.
33    pub failures: u64,
34    /// Total amount of tests
35    pub tests: u64,
36    pub test_cases: Vec<TestCase>,
37}
38
39
40
41impl TestSuite {
42    /// Create TestSuite from event stream.
43    /// NOTE: Only works if event stream is related to a single testsuite. You have to run unit, doc
44    /// and integration tests separately!
45    pub fn new(events: Vec<Event>, name: String) -> Result<TestSuite,SuityError> {
46
47        let mut suite = TestSuite {
48            name,
49            errors: 0,
50            failures:0,
51            tests: 0,
52            test_cases: Vec::new()
53        };
54
55        let mut counter = 0;
56
57
58        for event in events {
59            match event {
60                Event::Suite(s) => {
61                    match s.event {
62                        EventKind::Ignored => { /* no-op */ },
63                        EventKind::Started => {
64                            suite.tests = s.test_count.unwrap();
65                            counter += 1;
66                            if counter > 1 {
67                                return Err(SuityError::MultipleTestRuns);
68                            }
69                        },
70                        EventKind::Failed | EventKind::Ok => {
71                            suite.failures = s.failed.unwrap();
72                        }
73                    }
74                },
75                Event::Test(t) => {
76                    match t.event {
77                        EventKind::Started => { /* no-op */ },
78                        EventKind::Ignored => { /* no-op */ },
79                        EventKind::Ok => {
80                            suite.test_cases.push(
81                                TestCase {
82                                    name: t.name,
83                                    failure: None
84                                }
85                            )
86                        }
87                        EventKind::Failed => {
88                            suite.test_cases.push(
89                                TestCase {
90                                    name: t.name,
91                                    failure: Some(Failure{
92                                        message: t.stdout.unwrap()
93                                    })
94                                }
95                            )
96                        }
97                    }
98
99                }
100            }
101        }
102        Ok(suite)
103    }
104}
105
106
107pub fn write_as_xml<W: Write>(suites: &Vec<TestSuite>, writer: W) -> Result<(),io::Error> {
108    let mut xml = XmlWriter::new(writer);
109    xml.dtd("utf-8")?;
110    xml.begin_elem("testsuites")?;
111    for suite in suites {
112        xml.begin_elem("testsuite")?;
113        xml.attr_esc("name", &suite.name)?;
114        xml.attr("errors", suite.errors.to_string().as_str())?;
115        xml.attr("failures", suite.failures.to_string().as_str())?;
116        xml.attr("tests", suite.tests.to_string().as_str())?;
117        for testcase in &suite.test_cases {
118            xml.begin_elem("testcase")?;
119            xml.attr("name", &testcase.name)?;
120            if let Some(ref failure) = &testcase.failure {
121                xml.begin_elem("failure")?;
122                xml.attr_esc("message", &failure.message)?;
123                xml.end_elem()?;
124            }
125            xml.end_elem()?;
126        }
127        xml.end_elem()?;
128    }
129    xml.end_elem()?;
130    xml.close()?;
131    xml.flush()
132}
133
134#[cfg(test)]
135mod tests {
136
137    use crate::results::parse_test_results;
138    use super::{TestSuite, TestCase, Failure, write_as_xml};
139
140    #[test]
141    fn test_simple_output() {
142        let stdout = r#"{ "type": "suite", "event": "started", "test_count": 1 }
143{ "type": "test", "event": "started", "name": "parsers::test::test_zpools_on_single_zpool" }
144{ "type": "test", "name": "parsers::test::test_zpools_on_single_zpool", "event": "ok" }
145{ "type": "suite", "event": "ok", "passed": 1, "failed": 0, "allowed_fail": 0, "ignored": 0, "measured": 0, "filtered_out": 40 }"#;
146
147        let events = parse_test_results(stdout);
148
149        let name = String::from("Doc Tests");
150        let test_name = String::from("parsers::test::test_zpools_on_single_zpool");
151        let expected_test_case = TestCase {
152            name: test_name.clone(),
153            failure: None,
154        };
155        let expected = TestSuite {
156            name: name.clone(),
157            errors: 0,
158            failures: 0,
159            tests: 1,
160            test_cases: vec![expected_test_case]
161        };
162        let suite = TestSuite::new(events, name).unwrap();
163
164        assert_eq!(expected, suite);
165    }
166
167    #[test]
168    fn test_failed_output() {
169        let stdout = r#"{ "type": "suite", "event": "started", "test_count": 2 }
170{ "type": "test", "event": "started", "name": "parsers::test::test_zpools_on_single_zpool" }
171{ "type": "test", "name": "parsers::test::test_zpools_on_single_zpool", "event": "ok" }
172{ "type": "test", "event": "started", "name": "failed" }
173{ "type": "test", "name": "failed", "event": "failed", "stdout": "idk dawg" }
174{ "type": "suite", "event": "ok", "passed": 1, "failed": 1, "allowed_fail": 0, "ignored": 0, "measured": 0, "filtered_out": 40 }"#;
175
176        let events = parse_test_results(stdout);
177
178        let name = String::from("Doc Tests");
179        let expected_test_case = TestCase {
180            name: String::from("parsers::test::test_zpools_on_single_zpool"),
181            failure: None,
182        };
183        let expected_test_case2 = TestCase {
184            name: String::from("failed"),
185            failure: Some(Failure {
186                message: String::from("idk dawg")
187            }),
188        };
189        let expected = TestSuite {
190            name: name.clone(),
191            errors: 0,
192            failures: 1,
193            tests: 2,
194            test_cases: vec![expected_test_case, expected_test_case2]
195        };
196        let suite = TestSuite::new(events, name).unwrap();
197
198        assert_eq!(expected, suite);
199    }
200
201
202    #[test]
203    fn test_generate_xml_no_error_single_testsuite() {
204        let stdout = r#"{ "type": "suite", "event": "started", "test_count": 2 }
205{ "type": "test", "event": "started", "name": "parsers::test::test_zpools_on_single_zpool" }
206{ "type": "test", "name": "parsers::test::test_zpools_on_single_zpool", "event": "ok" }
207{ "type": "test", "event": "started", "name": "failed" }
208{ "type": "test", "name": "failed", "event": "failed", "stdout": "idk dawg" }
209{ "type": "suite", "event": "ok", "passed": 1, "failed": 1, "allowed_fail": 0, "ignored": 0, "measured": 0, "filtered_out": 40 }"#;
210
211        let name = String::from("Doc Tests");
212        let events = parse_test_results(stdout);
213        let suite = TestSuite::new(events, name).unwrap();
214
215        let suites = vec![suite];
216
217        let mut output = Vec::with_capacity(128);
218
219        write_as_xml(&suites, &mut output).unwrap();
220    }
221    #[test]
222    fn test_multiple_outputs() {
223        let stdout = r#"{ "type": "suite", "event": "started", "test_count": 1 }
224{ "type": "test", "event": "started", "name": "parsers::test::test_zpools_on_single_zpool" }
225{ "type": "test", "name": "parsers::test::test_zpools_on_single_zpool", "event": "ok" }
226{ "type": "suite", "event": "ok", "passed": 1, "failed": 0, "allowed_fail": 0, "ignored": 0, "measured": 0, "filtered_out": 40 }
227{ "type": "suite", "event": "started", "test_count": 1 }
228{ "type": "test", "event": "started", "name": "parsers::test::test_zpools_on_single_zpool" }
229{ "type": "test", "name": "parsers::test::test_zpools_on_single_zpool", "event": "ok" }
230{ "type": "suite", "event": "ok", "passed": 1, "failed": 0, "allowed_fail": 0, "ignored": 0, "measured": 0, "filtered_out": 40 }"#;
231        let events = parse_test_results(stdout);
232        let suite = TestSuite::new(events, String::from("should fail"));
233        assert!(suite.is_err());
234    }
235}