Skip to main content

junit_report/
lib.rs

1/*
2 * Copyright (c) 2018 Pascal Bach
3 * Copyright (c) 2021 Siemens Mobility GmbH
4 *
5 * SPDX-License-Identifier:     MIT
6 */
7
8//! Create JUnit compatible XML reports.
9//!
10//! ## Example
11//!
12//! ```rust
13//!     use junit_report::{datetime, Duration, ReportBuilder, TestCase, TestCaseBuilder, TestSuiteBuilder};
14//!
15//!     let timestamp = datetime!(1970-01-01 01:01 UTC);
16//!
17//!     let test_success = TestCase::success("good test", Duration::seconds(15));
18//!     let test_error = TestCase::error(
19//!         "error test",
20//!         Duration::seconds(5),
21//!         "git error",
22//!         "unable to fetch",
23//!     );
24//!     let test_failure = TestCaseBuilder::failure(
25//!         "failure test",
26//!         Duration::seconds(10),
27//!         "assert_eq",
28//!         "not equal",
29//!     ).set_classname("classname").set_filepath("./foo.rs")
30//!     .build();
31//!
32//!     let ts1 = TestSuiteBuilder::new("ts1").set_timestamp(timestamp).build();
33//!
34//!     let ts2 = TestSuiteBuilder::new("ts2").set_timestamp(timestamp)
35//!       .add_testcase(test_success)
36//!       .add_testcase(test_error)
37//!       .add_testcase(test_failure)
38//!       .build();
39//!
40//!     let r = ReportBuilder::new()
41//!       .add_testsuite(ts1)
42//!       .add_testsuite(ts2)
43//!       .build();
44//!
45//!     let mut out: Vec<u8> = Vec::new();
46//!
47//!     r.write_xml(&mut out).unwrap();
48//! ```
49
50mod collections;
51mod error;
52mod reports;
53
54pub use error::Error;
55pub use time::{macros::datetime, Duration, OffsetDateTime};
56
57pub use crate::{
58    collections::{TestCase, TestCaseBuilder, TestResult, TestSuite, TestSuiteBuilder},
59    reports::{Report, ReportBuilder},
60};
61
62#[cfg(test)]
63mod tests {
64    use crate::{
65        datetime, Duration, Report, ReportBuilder, TestCase, TestCaseBuilder, TestSuite,
66        TestSuiteBuilder,
67    };
68    use pretty_assertions::assert_eq;
69
70    #[test]
71    fn empty_testsuites() {
72        let r = Report::new();
73
74        let mut out: Vec<u8> = Vec::new();
75
76        r.write_xml(&mut out).unwrap();
77
78        // language=xml
79        assert_eq!(
80            String::from_utf8(out).unwrap(),
81            "<?xml version=\"1.0\" encoding=\"utf-8\"?><testsuites/>",
82        );
83    }
84
85    #[test]
86    fn add_empty_testsuite_single() {
87        let timestamp = datetime!(1970-01-01 01:01 UTC);
88
89        let ts1 = TestSuiteBuilder::new("ts1")
90            .set_timestamp(timestamp)
91            .build();
92        let mut tsb = TestSuiteBuilder::new("ts2");
93        tsb.set_timestamp(timestamp);
94        let ts2 = tsb.build();
95
96        let r = ReportBuilder::new()
97            .add_testsuite(ts1)
98            .add_testsuite(ts2)
99            .build();
100
101        let mut out: Vec<u8> = Vec::new();
102
103        r.write_xml(&mut out).unwrap();
104
105        // language=xml
106        assert_eq!(
107            String::from_utf8(out).unwrap(),
108            "\
109<?xml version=\"1.0\" encoding=\"utf-8\"?>\
110<testsuites>\
111  <testsuite id=\"0\" name=\"ts1\" package=\"testsuite/ts1\" tests=\"0\" errors=\"0\" failures=\"0\" hostname=\"localhost\" timestamp=\"1970-01-01T01:01:00Z\" time=\"0\"/>\
112  <testsuite id=\"1\" name=\"ts2\" package=\"testsuite/ts2\" tests=\"0\" errors=\"0\" failures=\"0\" hostname=\"localhost\" timestamp=\"1970-01-01T01:01:00Z\" time=\"0\"/>\
113</testsuites>",
114        );
115    }
116
117    #[test]
118    fn add_empty_testsuite_single_with_sysout() {
119        let timestamp = datetime!(1970-01-01 01:01 UTC);
120
121        let ts1 = TestSuiteBuilder::new("ts1")
122            .set_system_out("Test sysout")
123            .set_timestamp(timestamp)
124            .build();
125
126        let r = ReportBuilder::new().add_testsuite(ts1).build();
127
128        let mut out: Vec<u8> = Vec::new();
129
130        r.write_xml(&mut out).unwrap();
131
132        // language=xml
133        assert_eq!(
134            String::from_utf8(out).unwrap(),
135            "\
136<?xml version=\"1.0\" encoding=\"utf-8\"?>\
137<testsuites>\
138  <testsuite id=\"0\" name=\"ts1\" package=\"testsuite/ts1\" tests=\"0\" errors=\"0\" failures=\"0\" hostname=\"localhost\" timestamp=\"1970-01-01T01:01:00Z\" time=\"0\">\
139    <system-out><![CDATA[Test sysout]]></system-out>\
140  </testsuite>\
141</testsuites>",
142        );
143    }
144
145    #[test]
146    fn add_empty_testsuite_single_with_syserror() {
147        let timestamp = datetime!(1970-01-01 01:01 UTC);
148
149        let ts1 = TestSuiteBuilder::new("ts1")
150            .set_system_err("Test syserror")
151            .set_timestamp(timestamp)
152            .build();
153
154        let r = ReportBuilder::new().add_testsuite(ts1).build();
155
156        let mut out: Vec<u8> = Vec::new();
157
158        r.write_xml(&mut out).unwrap();
159
160        // language=xml
161        assert_eq!(
162            String::from_utf8(out).unwrap(),
163            "\
164<?xml version=\"1.0\" encoding=\"utf-8\"?>\
165<testsuites>\
166  <testsuite id=\"0\" name=\"ts1\" package=\"testsuite/ts1\" tests=\"0\" errors=\"0\" failures=\"0\" hostname=\"localhost\" timestamp=\"1970-01-01T01:01:00Z\" time=\"0\">\
167    <system-err><![CDATA[Test syserror]]></system-err>\
168  </testsuite>\
169</testsuites>",
170        );
171    }
172
173    #[test]
174    fn add_empty_testsuite_batch() {
175        let timestamp = datetime!(1970-01-01 01:01 UTC);
176
177        let ts1 = TestSuiteBuilder::new("ts1")
178            .set_timestamp(timestamp)
179            .build();
180        let ts2 = TestSuiteBuilder::new("ts2")
181            .set_timestamp(timestamp)
182            .build();
183
184        let v = vec![ts1, ts2];
185
186        let r = ReportBuilder::new().add_testsuites(v).build();
187
188        let mut out: Vec<u8> = Vec::new();
189
190        r.write_xml(&mut out).unwrap();
191
192        // language=xml
193        assert_eq!(
194            String::from_utf8(out).unwrap(),
195            "\
196<?xml version=\"1.0\" encoding=\"utf-8\"?>\
197<testsuites>\
198  <testsuite id=\"0\" name=\"ts1\" package=\"testsuite/ts1\" tests=\"0\" errors=\"0\" failures=\"0\" hostname=\"localhost\" timestamp=\"1970-01-01T01:01:00Z\" time=\"0\"/>\
199  <testsuite id=\"1\" name=\"ts2\" package=\"testsuite/ts2\" tests=\"0\" errors=\"0\" failures=\"0\" hostname=\"localhost\" timestamp=\"1970-01-01T01:01:00Z\" time=\"0\"/>\
200</testsuites>",
201        );
202    }
203
204    #[test]
205    fn count_tests() {
206        let mut ts = TestSuite::new("ts");
207
208        let tc1 = TestCase::success("mysuccess", Duration::milliseconds(6001));
209        let tc2 = TestCase::error(
210            "myerror",
211            Duration::seconds(6),
212            "Some Error",
213            "An Error happened",
214        );
215        let tc3 = TestCase::failure(
216            "myerror",
217            Duration::seconds(6),
218            "Some failure",
219            "A Failure happened",
220        );
221
222        assert_eq!(0, ts.tests());
223        assert_eq!(0, ts.errors());
224        assert_eq!(0, ts.failures());
225
226        ts.add_testcase(tc1);
227
228        assert_eq!(1, ts.tests());
229        assert_eq!(0, ts.errors());
230        assert_eq!(0, ts.failures());
231
232        ts.add_testcase(tc2);
233
234        assert_eq!(2, ts.tests());
235        assert_eq!(1, ts.errors());
236        assert_eq!(0, ts.failures());
237
238        ts.add_testcase(tc3);
239
240        assert_eq!(3, ts.tests());
241        assert_eq!(1, ts.errors());
242        assert_eq!(1, ts.failures());
243    }
244
245    #[test]
246    fn testcases_no_stdout_stderr() {
247        let timestamp = datetime!(1970-01-01 01:01 UTC);
248
249        let test_success = TestCaseBuilder::success("good test", Duration::milliseconds(15001))
250            .set_classname("MyClass")
251            .set_filepath("./foo.rs")
252            .build();
253        let test_error = TestCaseBuilder::error(
254            "error test",
255            Duration::seconds(5),
256            "git error",
257            "unable to fetch",
258        )
259        .build();
260        let test_failure = TestCaseBuilder::failure(
261            "failure test",
262            Duration::seconds(10),
263            "assert_eq",
264            "not equal",
265        )
266        .build();
267
268        let ts1 = TestSuiteBuilder::new("ts1")
269            .set_timestamp(timestamp)
270            .build();
271        let ts2 = TestSuiteBuilder::new("ts2")
272            .set_timestamp(timestamp)
273            .add_testcase(test_success)
274            .add_testcase(test_error)
275            .add_testcase(test_failure)
276            .build();
277
278        let r = ReportBuilder::new()
279            .add_testsuite(ts1)
280            .add_testsuite(ts2)
281            .build();
282
283        let mut out: Vec<u8> = Vec::new();
284
285        r.write_xml(&mut out).unwrap();
286
287        // language=xml
288        assert_eq!(
289            String::from_utf8(out).unwrap(),
290            "\
291<?xml version=\"1.0\" encoding=\"utf-8\"?>\
292<testsuites>\
293  <testsuite id=\"0\" name=\"ts1\" package=\"testsuite/ts1\" tests=\"0\" errors=\"0\" failures=\"0\" hostname=\"localhost\" timestamp=\"1970-01-01T01:01:00Z\" time=\"0\"/>\
294  <testsuite id=\"1\" name=\"ts2\" package=\"testsuite/ts2\" tests=\"3\" errors=\"1\" failures=\"1\" hostname=\"localhost\" timestamp=\"1970-01-01T01:01:00Z\" time=\"30.001\">\
295    <testcase name=\"good test\" time=\"15.001\" classname=\"MyClass\" file=\"./foo.rs\"/>\
296    <testcase name=\"error test\" time=\"5\">\
297      <error type=\"git error\" message=\"unable to fetch\"/>\
298    </testcase>\
299    <testcase name=\"failure test\" time=\"10\">\
300      <failure type=\"assert_eq\" message=\"not equal\"/>\
301    </testcase>\
302  </testsuite>\
303</testsuites>",
304        );
305    }
306
307    #[test]
308    fn test_cases_with_sysout_and_syserr() {
309        let timestamp = datetime!(1970-01-01 01:01 UTC);
310
311        let test_success = TestCaseBuilder::success("good test", Duration::milliseconds(15001))
312            .set_classname("MyClass")
313            .set_filepath("./foo.rs")
314            .set_system_out("Some sysout message")
315            .build();
316        let test_error = TestCaseBuilder::error(
317            "error test",
318            Duration::seconds(5),
319            "git error",
320            "unable to fetch",
321        )
322        .set_system_err("Some syserror message")
323        .build();
324        let test_failure = TestCaseBuilder::failure(
325            "failure test",
326            Duration::seconds(10),
327            "assert_eq",
328            "not equal",
329        )
330        .set_system_out("System out or error message")
331        .set_system_err("Another system error message")
332        .build();
333
334        let ts1 = TestSuiteBuilder::new("ts1")
335            .set_timestamp(timestamp)
336            .build();
337        let ts2 = TestSuiteBuilder::new("ts2")
338            .set_timestamp(timestamp)
339            .add_testcase(test_success)
340            .add_testcase(test_error)
341            .add_testcase(test_failure)
342            .build();
343
344        let r = ReportBuilder::new()
345            .add_testsuite(ts1)
346            .add_testsuite(ts2)
347            .build();
348
349        let mut out: Vec<u8> = Vec::new();
350
351        r.write_xml(&mut out).unwrap();
352
353        // language=xml
354        assert_eq!(
355            String::from_utf8(out).unwrap(),
356            "\
357<?xml version=\"1.0\" encoding=\"utf-8\"?>\
358<testsuites>\
359  <testsuite id=\"0\" name=\"ts1\" package=\"testsuite/ts1\" tests=\"0\" errors=\"0\" failures=\"0\" hostname=\"localhost\" timestamp=\"1970-01-01T01:01:00Z\" time=\"0\"/>\
360  <testsuite id=\"1\" name=\"ts2\" package=\"testsuite/ts2\" tests=\"3\" errors=\"1\" failures=\"1\" hostname=\"localhost\" timestamp=\"1970-01-01T01:01:00Z\" time=\"30.001\">\
361    <testcase name=\"good test\" time=\"15.001\" classname=\"MyClass\" file=\"./foo.rs\">\
362      <system-out><![CDATA[Some sysout message]]></system-out>\
363    </testcase>\
364    <testcase name=\"error test\" time=\"5\">\
365      <error type=\"git error\" message=\"unable to fetch\"/>\
366      <system-err><![CDATA[Some syserror message]]></system-err>\
367    </testcase>\
368    <testcase name=\"failure test\" time=\"10\">\
369      <failure type=\"assert_eq\" message=\"not equal\"/>\
370      <system-out><![CDATA[System out or error message]]></system-out>\
371      <system-err><![CDATA[Another system error message]]></system-err>\
372    </testcase>\
373  </testsuite>\
374</testsuites>",
375        );
376    }
377
378    #[test]
379    fn test_cases_with_trace() {
380        let timestamp = datetime!(1970-01-01 01:01 UTC);
381
382        let test_success = TestCaseBuilder::success("good test", Duration::milliseconds(15001))
383            .set_classname("MyClass")
384            .set_filepath("./foo.rs")
385            .set_trace("Some trace message") // This should be ignored
386            .build();
387        let test_error = TestCaseBuilder::error(
388            "error test",
389            Duration::seconds(5),
390            "git error",
391            "unable to fetch",
392        )
393        .set_trace("Some error trace")
394        .build();
395        let test_failure = TestCaseBuilder::failure(
396            "failure test",
397            Duration::seconds(10),
398            "assert_eq",
399            "not equal",
400        )
401        .set_trace("Some failure trace")
402        .build();
403
404        let ts1 = TestSuiteBuilder::new("ts1")
405            .set_timestamp(timestamp)
406            .build();
407        let ts2 = TestSuiteBuilder::new("ts2")
408            .set_timestamp(timestamp)
409            .add_testcase(test_success)
410            .add_testcase(test_error)
411            .add_testcase(test_failure)
412            .build();
413
414        let r = ReportBuilder::new()
415            .add_testsuite(ts1)
416            .add_testsuite(ts2)
417            .build();
418
419        let mut out: Vec<u8> = Vec::new();
420
421        r.write_xml(&mut out).unwrap();
422
423        // language=xml
424        assert_eq!(
425            String::from_utf8(out).unwrap(),
426            "\
427<?xml version=\"1.0\" encoding=\"utf-8\"?>\
428<testsuites>\
429  <testsuite id=\"0\" name=\"ts1\" package=\"testsuite/ts1\" tests=\"0\" errors=\"0\" failures=\"0\" hostname=\"localhost\" timestamp=\"1970-01-01T01:01:00Z\" time=\"0\"/>\
430  <testsuite id=\"1\" name=\"ts2\" package=\"testsuite/ts2\" tests=\"3\" errors=\"1\" failures=\"1\" hostname=\"localhost\" timestamp=\"1970-01-01T01:01:00Z\" time=\"30.001\">\
431    <testcase name=\"good test\" time=\"15.001\" classname=\"MyClass\" file=\"./foo.rs\"/>\
432    <testcase name=\"error test\" time=\"5\">\
433      <error type=\"git error\" message=\"unable to fetch\"><![CDATA[Some error trace]]></error>\
434    </testcase>\
435    <testcase name=\"failure test\" time=\"10\">\
436      <failure type=\"assert_eq\" message=\"not equal\"><![CDATA[Some failure trace]]></failure>\
437    </testcase>\
438  </testsuite>\
439</testsuites>",
440        );
441    }
442}