Skip to main content

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    SkippedWithCause {
153        type_: String,
154        message: String,
155        cause: Option<String>,
156    },
157    Error {
158        type_: String,
159        message: String,
160        cause: Option<String>,
161    },
162    Failure {
163        type_: String,
164        message: String,
165        cause: Option<String>,
166    },
167}
168
169impl TestCase {
170    /// Creates a new successful `TestCase`
171    pub fn success(name: &str, time: Duration) -> Self {
172        TestCase {
173            name: name.into(),
174            time,
175            result: TestResult::Success,
176            classname: None,
177            filepath: None,
178            system_out: None,
179            system_err: None,
180        }
181    }
182
183    /// Set the `classname` for the `TestCase`
184    pub fn set_classname(&mut self, classname: &str) {
185        self.classname = Some(classname.to_owned());
186    }
187
188    /// Set the `file` for the `TestCase`
189    pub fn set_filepath(&mut self, filepath: &str) {
190        self.filepath = Some(filepath.to_owned());
191    }
192
193    /// Set the `system_out` for the `TestCase`
194    pub fn set_system_out(&mut self, system_out: &str) {
195        self.system_out = Some(system_out.to_owned());
196    }
197
198    /// Set the `system_err` for the `TestCase`
199    pub fn set_system_err(&mut self, system_err: &str) {
200        self.system_err = Some(system_err.to_owned());
201    }
202
203    /// Check if a `TestCase` is successful
204    pub fn is_success(&self) -> bool {
205        matches!(self.result, TestResult::Success)
206    }
207
208    /// Creates a new erroneous `TestCase`
209    ///
210    /// An erroneous `TestCase` is one that encountered an unexpected error condition.
211    pub fn error(name: &str, time: Duration, type_: &str, message: &str) -> Self {
212        TestCase {
213            name: name.into(),
214            time,
215            result: TestResult::Error {
216                type_: type_.into(),
217                message: message.into(),
218                cause: None,
219            },
220            classname: None,
221            filepath: None,
222            system_out: None,
223            system_err: None,
224        }
225    }
226
227    /// Check if a `TestCase` is erroneous
228    pub fn is_error(&self) -> bool {
229        matches!(self.result, TestResult::Error { .. })
230    }
231
232    /// Creates a new failed `TestCase`
233    ///
234    /// A failed `TestCase` is one where an explicit assertion failed
235    pub fn failure(name: &str, time: Duration, type_: &str, message: &str) -> Self {
236        TestCase {
237            name: name.into(),
238            time,
239            result: TestResult::Failure {
240                type_: type_.into(),
241                message: message.into(),
242                cause: None,
243            },
244            classname: None,
245            filepath: None,
246            system_out: None,
247            system_err: None,
248        }
249    }
250
251    /// Check if a `TestCase` failed
252    pub fn is_failure(&self) -> bool {
253        matches!(self.result, TestResult::Failure { .. })
254    }
255
256    /// Create a new ignored `TestCase`
257    ///
258    /// An ignored `TestCase` is one where an ignored or skipped
259    pub fn skipped(name: &str) -> Self {
260        TestCase {
261            name: name.into(),
262            time: Duration::ZERO,
263            result: TestResult::Skipped,
264            classname: None,
265            filepath: None,
266            system_out: None,
267            system_err: None,
268        }
269    }
270
271    /// Create a new ignored `TestCase` with an ignore cause
272    ///
273    /// An ignored `TestCase` is one where an ignored or skipped
274    pub fn skipped_with_cause(name: &str, type_: &str, message: &str) -> Self {
275        TestCase {
276            name: name.into(),
277            time: Duration::ZERO,
278            result: TestResult::SkippedWithCause {
279                type_: type_.into(),
280                message: message.into(),
281                cause: None,
282            },
283            classname: None,
284            filepath: None,
285            system_out: None,
286            system_err: None,
287        }
288    }
289
290    /// Check if a `TestCase` ignored
291    pub fn is_skipped(&self) -> bool {
292        matches!(
293            self.result,
294            TestResult::Skipped | TestResult::SkippedWithCause { .. }
295        )
296    }
297}
298
299///  Builder for [`TestCase`](struct.TestCase.html) objects.
300#[derive(Debug, Clone, Getters)]
301pub struct TestCaseBuilder {
302    pub testcase: TestCase,
303}
304
305impl TestCaseBuilder {
306    /// Creates a new TestCaseBuilder for a successful `TestCase`
307    pub fn success(name: &str, time: Duration) -> Self {
308        TestCaseBuilder {
309            testcase: TestCase::success(name, time),
310        }
311    }
312
313    /// Set the `classname` for the `TestCase`
314    pub fn set_classname(&mut self, classname: &str) -> &mut Self {
315        self.testcase.classname = Some(classname.to_owned());
316        self
317    }
318
319    /// Set the `file` for the `TestCase`
320    pub fn set_filepath(&mut self, filepath: &str) -> &mut Self {
321        self.testcase.filepath = Some(filepath.to_owned());
322        self
323    }
324
325    /// Set the `system_out` for the `TestCase`
326    pub fn set_system_out(&mut self, system_out: &str) -> &mut Self {
327        self.testcase.system_out = Some(system_out.to_owned());
328        self
329    }
330
331    /// Set the `system_err` for the `TestCase`
332    pub fn set_system_err(&mut self, system_err: &str) -> &mut Self {
333        self.testcase.system_err = Some(system_err.to_owned());
334        self
335    }
336
337    /// Set the `result.trace` for the `TestCase`
338    ///
339    /// It has no effect on successful `TestCase`s.
340    pub fn set_trace(&mut self, trace: &str) -> &mut Self {
341        match self.testcase.result {
342            TestResult::Error { ref mut cause, .. } => *cause = Some(trace.to_owned()),
343            TestResult::Failure { ref mut cause, .. } => *cause = Some(trace.to_owned()),
344            _ => {}
345        }
346        self
347    }
348
349    /// Creates a new TestCaseBuilder for an erroneous `TestCase`
350    ///
351    /// An erroneous `TestCase` is one that encountered an unexpected error condition.
352    pub fn error(name: &str, time: Duration, type_: &str, message: &str) -> Self {
353        TestCaseBuilder {
354            testcase: TestCase::error(name, time, type_, message),
355        }
356    }
357
358    /// Creates a new TestCaseBuilder for a failed `TestCase`
359    ///
360    /// A failed `TestCase` is one where an explicit assertion failed
361    pub fn failure(name: &str, time: Duration, type_: &str, message: &str) -> Self {
362        TestCaseBuilder {
363            testcase: TestCase::failure(name, time, type_, message),
364        }
365    }
366
367    /// Creates a new TestCaseBuilder for an ignored `TestCase`
368    ///
369    /// An ignored `TestCase` is one where an ignored or skipped
370    pub fn skipped(name: &str) -> Self {
371        TestCaseBuilder {
372            testcase: TestCase::skipped(name),
373        }
374    }
375
376    /// Creates a new TestCaseBuilder for an ignored `TestCase`, with a cause
377    ///
378    /// An ignored `TestCase` is one where an ignored or skipped
379    pub fn skipped_with_cause(name: &str, type_: &str, message: &str) -> Self {
380        TestCaseBuilder {
381            testcase: TestCase::skipped_with_cause(name, type_, message),
382        }
383    }
384
385    /// Build and return a [`TestCase`](struct.TestCase.html) object based on the data stored in this TestCaseBuilder object.
386    pub fn build(&self) -> TestCase {
387        self.testcase.clone()
388    }
389}
390
391// Make sure the readme is tested too
392#[cfg(doctest)]
393doc_comment::doctest!("../README.md");