Skip to content
This repository was archived by the owner on Aug 12, 2021. It is now read-only.

Commit 0d46507

Browse files
committed
Added very basic JUnit output
1 parent dbf328d commit 0d46507

File tree

4 files changed

+145
-8
lines changed

4 files changed

+145
-8
lines changed

libtest/Cargo.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@ crate-type = ["dylib", "rlib"]
1717

1818
[dependencies]
1919
getopts = "0.2"
20-
term = "0.5"
20+
term = "0.5"
21+
chrono = "0.4"

libtest/formatters/junit.rs

+123
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
use super::*;
2+
use ::chrono::prelude::*;
3+
use chrono::SecondsFormat;
4+
5+
pub(crate) struct JUnitFormatter<T> {
6+
out: OutputLocation<T>,
7+
results: Vec<(TestDesc, TestResult)>,
8+
}
9+
10+
impl<T: Write> JUnitFormatter<T> {
11+
pub fn new(out: OutputLocation<T>) -> Self {
12+
Self {
13+
out,
14+
results: Vec::new(),
15+
}
16+
}
17+
18+
fn write_message(&mut self, s: &str) -> io::Result<()> {
19+
assert!(!s.contains('\n'));
20+
21+
self.out.write_all(s.as_ref())?;
22+
self.out.write_all(b"\n")
23+
}
24+
}
25+
26+
impl<T: Write> OutputFormatter for JUnitFormatter<T> {
27+
fn write_run_start(&mut self, _test_count: usize) -> io::Result<()> {
28+
self.write_message(&"<?xml version=\"1.0\" encoding=\"UTF-8\"?>")
29+
}
30+
31+
fn write_test_start(&mut self, _desc: &TestDesc) -> io::Result<()> {
32+
// We do not output anything on test start.
33+
Ok(())
34+
}
35+
36+
fn write_timeout(&mut self, _desc: &TestDesc) -> io::Result<()> {
37+
Ok(())
38+
}
39+
40+
fn write_result(
41+
&mut self,
42+
desc: &TestDesc,
43+
result: &TestResult,
44+
_stdout: &[u8],
45+
) -> io::Result<()> {
46+
self.results.push((desc.clone(), result.clone()));
47+
Ok(())
48+
}
49+
50+
fn write_run_finish(
51+
&mut self,
52+
state: &ConsoleTestState,
53+
) -> io::Result<bool> {
54+
self.write_message("<?xml version=\"1.0\" encoding=\"UTF-8\"?>")?;
55+
self.write_message("<testsuites>")?;
56+
57+
// JUnit expects time in the ISO8601, which was proposed in RFC 3339.
58+
let timestamp =
59+
Local::now().to_rfc3339_opts(SecondsFormat::Secs, false);
60+
let elapsed_time =
61+
state.start_time.elapsed().as_millis() as f32 / 1000.0;
62+
self.write_message(&*format!(
63+
"<testsuite name=\"test\" package=\"test\" id=\"0\" \
64+
hostname=\"localhost\" \
65+
errors=\"0\" \
66+
failures=\"{}\" \
67+
tests=\"{}\" \
68+
time=\"{}\" \
69+
timestamp=\"{}\">",
70+
state.failed, state.total, elapsed_time, timestamp
71+
))?;
72+
for (desc, result) in std::mem::replace(&mut self.results, Vec::new())
73+
{
74+
match result {
75+
TestResult::TrFailed => {
76+
self.write_message(&*format!(
77+
"<testcase classname=\"test.global\" \
78+
name=\"{}\" time=\"0\">",
79+
desc.name.as_slice()
80+
))?;
81+
self.write_message("<failure type=\"assert\"/>")?;
82+
self.write_message("</testcase>")?;
83+
}
84+
85+
TestResult::TrFailedMsg(ref m) => {
86+
self.write_message(&*format!(
87+
"<testcase classname=\"test.global\" \
88+
name=\"{}\" time=\"0\">",
89+
desc.name.as_slice()
90+
))?;
91+
self.write_message(&*format!(
92+
"<failure message=\"{}\" type=\"assert\"/>",
93+
m
94+
))?;
95+
self.write_message("</testcase>")?;
96+
}
97+
98+
TestResult::TrBench(ref b) => {
99+
self.write_message(&*format!(
100+
"<testcase classname=\"test.global\" \
101+
name=\"{}\" time=\"{}\" />",
102+
desc.name.as_slice(),
103+
b.ns_iter_summ.sum
104+
))?;
105+
}
106+
107+
_ => {
108+
self.write_message(&*format!(
109+
"<testcase classname=\"test.global\" \
110+
name=\"{}\" time=\"0\"/>",
111+
desc.name.as_slice()
112+
))?;
113+
}
114+
}
115+
}
116+
self.write_message("<system-out/>")?;
117+
self.write_message("<system-err/>")?;
118+
self.write_message("</testsuite>")?;
119+
self.write_message("</testsuites>")?;
120+
121+
Ok(state.failed == 0)
122+
}
123+
}

libtest/formatters/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
use super::*;
22

33
mod json;
4+
mod junit;
45
mod pretty;
56
mod terse;
67

78
pub(crate) use self::json::JsonFormatter;
9+
pub(crate) use self::junit::JUnitFormatter;
810
pub(crate) use self::pretty::PrettyFormatter;
911
pub(crate) use self::terse::TerseFormatter;
1012

libtest/lib.rs

+18-7
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,7 @@ const QUIET_MODE_MAX_COLUMN: usize = 100; // insert a '\n' after 100 tests in qu
5555
mod formatters;
5656
pub mod stats;
5757

58-
use crate::formatters::{
59-
JsonFormatter, OutputFormatter, PrettyFormatter, TerseFormatter,
60-
};
58+
use crate::formatters::{JsonFormatter, OutputFormatter, PrettyFormatter, TerseFormatter, JUnitFormatter};
6159

6260
/// Whether to execute tests concurrently or not
6361
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
@@ -327,6 +325,7 @@ pub enum OutputFormat {
327325
Pretty,
328326
Terse,
329327
Json,
328+
JUnit,
330329
}
331330

332331
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
@@ -441,8 +440,9 @@ fn optgroups() -> getopts::Options {
441440
"Configure formatting of output:
442441
pretty = Print verbose output;
443442
terse = Display one character per test;
444-
json = Output a json document",
445-
"pretty|terse|json",
443+
json = Output a json document;
444+
junit = Output a JUnit document",
445+
"pretty|terse|json|junit",
446446
)
447447
.optopt(
448448
"Z",
@@ -622,10 +622,18 @@ pub fn parse_opts(args: &[String]) -> Option<OptRes> {
622622
}
623623
OutputFormat::Json
624624
}
625+
Some("junit") => {
626+
if !allow_unstable {
627+
return Some(Err(
628+
"The \"junit\" format is only accepted on the nightly compiler".into(),
629+
));
630+
}
631+
OutputFormat::JUnit
632+
}
625633

626634
Some(v) => {
627635
return Some(Err(format!(
628-
"argument for --format must be pretty, terse, or json (was \
636+
"argument for --format must be pretty, terse, json, or junit (was \
629637
{})",
630638
v
631639
)));
@@ -704,6 +712,7 @@ struct ConsoleTestState {
704712
failures: Vec<(TestDesc, Vec<u8>)>,
705713
not_failures: Vec<(TestDesc, Vec<u8>)>,
706714
options: Options,
715+
start_time: Instant,
707716
}
708717

709718
impl ConsoleTestState {
@@ -726,6 +735,7 @@ impl ConsoleTestState {
726735
failures: Vec::new(),
727736
not_failures: Vec::new(),
728737
options: opts.options,
738+
start_time: Instant::now()
729739
})
730740
}
731741

@@ -962,9 +972,9 @@ pub fn run_tests_console(
962972
is_multithreaded,
963973
)),
964974
OutputFormat::Json => Box::new(JsonFormatter::new(output)),
975+
OutputFormat::JUnit => Box::new(JUnitFormatter::new(output)),
965976
};
966977
let mut st = ConsoleTestState::new(opts)?;
967-
968978
run_tests(opts, tests, |x| callback(&x, &mut st, &mut *out))?;
969979

970980
assert!(st.current_test_count() == st.total);
@@ -1008,6 +1018,7 @@ fn should_sort_failures_before_printing_them() {
10081018
failures: vec![(test_b, Vec::new()), (test_a, Vec::new())],
10091019
options: Options::new(),
10101020
not_failures: Vec::new(),
1021+
start_time: Instant::now()
10111022
};
10121023

10131024
out.write_failures(&st).unwrap();

0 commit comments

Comments
 (0)