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

Commit 457084a

Browse files
committed
Added very basic JUnit output
1 parent dbf328d commit 457084a

File tree

4 files changed

+150
-7
lines changed

4 files changed

+150
-7
lines changed

libtest/Cargo.toml

Lines changed: 2 additions & 1 deletion
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

Lines changed: 123 additions & 0 deletions
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

Lines changed: 2 additions & 0 deletions
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

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
//! Rust's built-in unit-test and micro-benchmarking framework.
2-
#![cfg_attr(any(unix, target_os = "cloudabi", target_os = "fuchsia"), feature(libc, rustc_private))]
2+
#![cfg_attr(
3+
any(unix, target_os = "cloudabi", target_os = "fuchsia"),
4+
feature(libc, rustc_private)
5+
)]
36
#![feature(fnbox)]
47
#![feature(set_stdio)]
58
#![feature(panic_unwind)]
@@ -56,7 +59,8 @@ mod formatters;
5659
pub mod stats;
5760

5861
use crate::formatters::{
59-
JsonFormatter, OutputFormatter, PrettyFormatter, TerseFormatter,
62+
JUnitFormatter, JsonFormatter, OutputFormatter, PrettyFormatter,
63+
TerseFormatter,
6064
};
6165

6266
/// Whether to execute tests concurrently or not
@@ -327,6 +331,7 @@ pub enum OutputFormat {
327331
Pretty,
328332
Terse,
329333
Json,
334+
JUnit,
330335
}
331336

332337
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
@@ -441,8 +446,9 @@ fn optgroups() -> getopts::Options {
441446
"Configure formatting of output:
442447
pretty = Print verbose output;
443448
terse = Display one character per test;
444-
json = Output a json document",
445-
"pretty|terse|json",
449+
json = Output a json document;
450+
junit = Output a JUnit document",
451+
"pretty|terse|json|junit",
446452
)
447453
.optopt(
448454
"Z",
@@ -622,10 +628,18 @@ pub fn parse_opts(args: &[String]) -> Option<OptRes> {
622628
}
623629
OutputFormat::Json
624630
}
631+
Some("junit") => {
632+
if !allow_unstable {
633+
return Some(Err(
634+
"The \"junit\" format is only accepted on the nightly compiler".into(),
635+
));
636+
}
637+
OutputFormat::JUnit
638+
}
625639

626640
Some(v) => {
627641
return Some(Err(format!(
628-
"argument for --format must be pretty, terse, or json (was \
642+
"argument for --format must be pretty, terse, json, or junit (was \
629643
{})",
630644
v
631645
)));
@@ -704,6 +718,7 @@ struct ConsoleTestState {
704718
failures: Vec<(TestDesc, Vec<u8>)>,
705719
not_failures: Vec<(TestDesc, Vec<u8>)>,
706720
options: Options,
721+
start_time: Instant,
707722
}
708723

709724
impl ConsoleTestState {
@@ -726,6 +741,7 @@ impl ConsoleTestState {
726741
failures: Vec::new(),
727742
not_failures: Vec::new(),
728743
options: opts.options,
744+
start_time: Instant::now(),
729745
})
730746
}
731747

@@ -962,9 +978,9 @@ pub fn run_tests_console(
962978
is_multithreaded,
963979
)),
964980
OutputFormat::Json => Box::new(JsonFormatter::new(output)),
981+
OutputFormat::JUnit => Box::new(JUnitFormatter::new(output)),
965982
};
966983
let mut st = ConsoleTestState::new(opts)?;
967-
968984
run_tests(opts, tests, |x| callback(&x, &mut st, &mut *out))?;
969985

970986
assert!(st.current_test_count() == st.total);
@@ -1008,6 +1024,7 @@ fn should_sort_failures_before_printing_them() {
10081024
failures: vec![(test_b, Vec::new()), (test_a, Vec::new())],
10091025
options: Options::new(),
10101026
not_failures: Vec::new(),
1027+
start_time: Instant::now(),
10111028
};
10121029

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

0 commit comments

Comments
 (0)