junit_report/
collections.rs

1/*
2 * Copyright (c) 2018 Pascal Bach
3 * Copyright (c) 2021 Siemens Mobility GmbH
4 *
5 * SPDX-License-Identifier:     MIT
6 */
7
8use derive_getters::Getters;
9use time::{Duration, OffsetDateTime};
10
11/// A `TestSuite` groups together several [`TestCase`s](struct.TestCase.html).
12#[derive(Debug, Clone, Getters)]
13pub struct TestSuite {
14    pub name: String,
15    pub package: String,
16    pub timestamp: OffsetDateTime,
17    pub hostname: String,
18    pub testcases: Vec<TestCase>,
19    pub system_out: Option<String>,
20    pub system_err: Option<String>,
21}
22
23impl TestSuite {
24    /// Create a new `TestSuite` with a given name
25    pub fn new(name: &str) -> Self {
26        TestSuite {
27            hostname: "localhost".into(),
28            package: format!("testsuite/{}", &name),
29            name: name.into(),
30            timestamp: OffsetDateTime::now_utc(),
31            testcases: Vec::new(),
32            system_out: None,
33            system_err: None,
34        }
35    }
36
37    /// Add a [`TestCase`](struct.TestCase.html) to the `TestSuite`.
38    pub fn add_testcase(&mut self, testcase: TestCase) {
39        self.testcases.push(testcase);
40    }
41
42    /// Add several [`TestCase`s](struct.TestCase.html) from a Vec.
43    pub fn add_testcases(&mut self, testcases: impl IntoIterator<Item = TestCase>) {
44        self.testcases.extend(testcases);
45    }
46
47    /// Set the timestamp of the given `TestSuite`.
48    ///
49    /// By default the timestamp is set to the time when the `TestSuite` was created.
50    pub fn set_timestamp(&mut self, timestamp: OffsetDateTime) {
51        self.timestamp = timestamp;
52    }
53
54    pub fn set_system_out(&mut self, system_out: &str) {
55        self.system_out = Some(system_out.to_owned());
56    }
57
58    pub fn set_system_err(&mut self, system_err: &str) {
59        self.system_err = Some(system_err.to_owned());
60    }
61
62    pub fn tests(&self) -> usize {
63        self.testcases.len()
64    }
65
66    pub fn errors(&self) -> usize {
67        self.testcases.iter().filter(|x| x.is_error()).count()
68    }
69
70    pub fn failures(&self) -> usize {
71        self.testcases.iter().filter(|x| x.is_failure()).count()
72    }
73
74    pub fn skipped(&self) -> usize {
75        self.testcases.iter().filter(|x| x.is_skipped()).count()
76    }
77
78    pub fn time(&self) -> Duration {
79        self.testcases
80            .iter()
81            .fold(Duration::ZERO, |sum, d| sum + d.time)
82    }
83}
84
85///  Builder for [`TestSuite`](struct.TestSuite.html) objects.
86#[derive(Debug, Clone, Getters)]
87pub struct TestSuiteBuilder {
88    pub testsuite: TestSuite,
89}
90
91impl TestSuiteBuilder {
92    /// Create a new `TestSuiteBuilder` with a given name
93    pub fn new(name: &str) -> Self {
94        TestSuiteBuilder {
95            testsuite: TestSuite::new(name),
96        }
97    }
98
99    /// Add a [`TestCase`](struct.TestCase.html) to the `TestSuiteBuilder`.
100    pub fn add_testcase(&mut self, testcase: TestCase) -> &mut Self {
101        self.testsuite.testcases.push(testcase);
102        self
103    }
104
105    /// Add several [`TestCase`s](struct.TestCase.html) from a Vec.
106    pub fn add_testcases(&mut self, testcases: impl IntoIterator<Item = TestCase>) -> &mut Self {
107        self.testsuite.testcases.extend(testcases);
108        self
109    }
110
111    /// Set the timestamp of the `TestSuiteBuilder`.
112    ///
113    /// By default the timestamp is set to the time when the `TestSuiteBuilder` was created.
114    pub fn set_timestamp(&mut self, timestamp: OffsetDateTime) -> &mut Self {
115        self.testsuite.timestamp = timestamp;
116        self
117    }
118
119    pub fn set_system_out(&mut self, system_out: &str) -> &mut Self {
120        self.testsuite.system_out = Some(system_out.to_owned());
121        self
122    }
123
124    pub fn set_system_err(&mut self, system_err: &str) -> &mut Self {
125        self.testsuite.system_err = Some(system_err.to_owned());
126        self
127    }
128
129    /// Build and return a [`TestSuite`](struct.TestSuite.html) object based on the data stored in this TestSuiteBuilder object.
130    pub fn build(&self) -> TestSuite {
131        self.testsuite.clone()
132    }
133}
134
135/// One single test case
136#[derive(Debug, Clone, Getters)]
137pub struct TestCase {
138    pub name: String,
139    pub time: Duration,
140    pub result: TestResult,
141    pub classname: Option<String>,
142    pub filepath: Option<String>,
143    pub system_out: Option<String>,
144    pub system_err: Option<String>,
145}
146
147/// Result of a test case
148#[derive(Debug, Clone)]
149pub enum TestResult {
150    Success,
151    Skipped,
152    Error { type_: String, message: String },
153    Failure { type_: String, message: String },
154}
155
156impl TestCase {
157    /// Creates a new successful `TestCase`
158    pub fn success(name: &str, time: Duration) -> Self {
159        TestCase {
160            name: name.into(),
161            time,
162            result: TestResult::Success,
163            classname: None,
164            filepath: None,
165            system_out: None,
166            system_err: None,
167        }
168    }
169
170    /// Set the `classname` for the `TestCase`
171    pub fn set_classname(&mut self, classname: &str) {
172        self.classname = Some(classname.to_owned());
173    }
174
175    /// Set the `file` for the `TestCase`
176    pub fn set_filepath(&mut self, filepath: &str) {
177        self.filepath = Some(filepath.to_owned());
178    }
179
180    /// Set the `system_out` for the `TestCase`
181    pub fn set_system_out(&mut self, system_out: &str) {
182        self.system_out = Some(system_out.to_owned());
183    }
184
185    /// Set the `system_err` for the `TestCase`
186    pub fn set_system_err(&mut self, system_err: &str) {
187        self.system_err = Some(system_err.to_owned());
188    }
189
190    /// Check if a `TestCase` is successful
191    pub fn is_success(&self) -> bool {
192        matches!(self.result, TestResult::Success)
193    }
194
195    /// Creates a new erroneous `TestCase`
196    ///
197    /// An erroneous `TestCase` is one that encountered an unexpected error condition.
198    pub fn error(name: &str, time: Duration, type_: &str, message: &str) -> Self {
199        TestCase {
200            name: name.into(),
201            time,
202            result: TestResult::Error {
203                type_: type_.into(),
204                message: message.into(),
205            },
206            classname: None,
207            filepath: None,
208            system_out: None,
209            system_err: None,
210        }
211    }
212
213    /// Check if a `TestCase` is erroneous
214    pub fn is_error(&self) -> bool {
215        matches!(self.result, TestResult::Error { .. })
216    }
217
218    /// Creates a new failed `TestCase`
219    ///
220    /// A failed `TestCase` is one where an explicit assertion failed
221    pub fn failure(name: &str, time: Duration, type_: &str, message: &str) -> Self {
222        TestCase {
223            name: name.into(),
224            time,
225            result: TestResult::Failure {
226                type_: type_.into(),
227                message: message.into(),
228            },
229            classname: None,
230            filepath: None,
231            system_out: None,
232            system_err: None,
233        }
234    }
235
236    /// Check if a `TestCase` failed
237    pub fn is_failure(&self) -> bool {
238        matches!(self.result, TestResult::Failure { .. })
239    }
240
241    /// Create a new ignored `TestCase`
242    ///
243    /// An ignored `TestCase` is one where an ignored or skipped
244    pub fn skipped(name: &str) -> Self {
245        TestCase {
246            name: name.into(),
247            time: Duration::ZERO,
248            result: TestResult::Skipped,
249            classname: None,
250            filepath: None,
251            system_out: None,
252            system_err: None,
253        }
254    }
255
256    /// Check if a `TestCase` ignored
257    pub fn is_skipped(&self) -> bool {
258        matches!(self.result, TestResult::Skipped)
259    }
260}
261
262///  Builder for [`TestCase`](struct.TestCase.html) objects.
263#[derive(Debug, Clone, Getters)]
264pub struct TestCaseBuilder {
265    pub testcase: TestCase,
266}
267
268impl TestCaseBuilder {
269    /// Creates a new TestCaseBuilder for a successful `TestCase`
270    pub fn success(name: &str, time: Duration) -> Self {
271        TestCaseBuilder {
272            testcase: TestCase::success(name, time),
273        }
274    }
275
276    /// Set the `classname` for the `TestCase`
277    pub fn set_classname(&mut self, classname: &str) -> &mut Self {
278        self.testcase.classname = Some(classname.to_owned());
279        self
280    }
281
282    /// Set the `file` for the `TestCase`
283    pub fn set_filepath(&mut self, filepath: &str) -> &mut Self {
284        self.testcase.filepath = Some(filepath.to_owned());
285        self
286    }
287
288    /// Set the `system_out` for the `TestCase`
289    pub fn set_system_out(&mut self, system_out: &str) -> &mut Self {
290        self.testcase.system_out = Some(system_out.to_owned());
291        self
292    }
293
294    /// Set the `system_err` for the `TestCase`
295    pub fn set_system_err(&mut self, system_err: &str) -> &mut Self {
296        self.testcase.system_err = Some(system_err.to_owned());
297        self
298    }
299
300    /// Creates a new TestCaseBuilder for an erroneous `TestCase`
301    ///
302    /// An erroneous `TestCase` is one that encountered an unexpected error condition.
303    pub fn error(name: &str, time: Duration, type_: &str, message: &str) -> Self {
304        TestCaseBuilder {
305            testcase: TestCase::error(name, time, type_, message),
306        }
307    }
308
309    /// Creates a new TestCaseBuilder for a failed `TestCase`
310    ///
311    /// A failed `TestCase` is one where an explicit assertion failed
312    pub fn failure(name: &str, time: Duration, type_: &str, message: &str) -> Self {
313        TestCaseBuilder {
314            testcase: TestCase::failure(name, time, type_, message),
315        }
316    }
317
318    /// Creates a new TestCaseBuilder for an ignored `TestCase`
319    ///
320    /// An ignored `TestCase` is one where an ignored or skipped
321    pub fn skipped(name: &str) -> Self {
322        TestCaseBuilder {
323            testcase: TestCase::skipped(name),
324        }
325    }
326
327    /// Build and return a [`TestCase`](struct.TestCase.html) object based on the data stored in this TestCaseBuilder object.
328    pub fn build(&self) -> TestCase {
329        self.testcase.clone()
330    }
331}
332
333// Make sure the readme is tested too
334#[cfg(doctest)]
335doc_comment::doctest!("../README.md");