Skip to content

Commit 588a6a3

Browse files
author
Gilad Naaman
committed
Added JSON output to libtest.
libtest: Json format now outputs failed tests' stdouts. libtest: Json format now outputs failed tests' stdouts. libtest: Json formatter now spews individiual events, not as an array libtest: JSON fixes libtest: Better JSON escaping libtest: Test start event is printed on time
1 parent d24f9af commit 588a6a3

File tree

3 files changed

+313
-51
lines changed

3 files changed

+313
-51
lines changed

src/libtest/formatters.rs

Lines changed: 219 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ use super::*;
1212

1313
pub(crate) trait OutputFormatter {
1414
fn write_run_start(&mut self, len: usize) -> io::Result<()>;
15-
fn write_test_start(&mut self,
16-
test: &TestDesc,
17-
align: NamePadding,
18-
max_name_len: usize) -> io::Result<()>;
15+
fn write_test_start(&mut self, test: &TestDesc) -> io::Result<()>;
1916
fn write_timeout(&mut self, desc: &TestDesc) -> io::Result<()>;
20-
fn write_result(&mut self, result: &TestResult) -> io::Result<()>;
17+
fn write_result(&mut self,
18+
desc: &TestDesc,
19+
result: &TestResult,
20+
stdout: &[u8]) -> io::Result<()>;
2121
fn write_run_finish(&mut self, state: &ConsoleTestState) -> io::Result<bool>;
2222
}
2323

@@ -26,15 +26,17 @@ pub(crate) struct HumanFormatter<T> {
2626
terse: bool,
2727
use_color: bool,
2828
test_count: usize,
29+
max_name_len: usize, // number of columns to fill when aligning names
2930
}
3031

3132
impl<T: Write> HumanFormatter<T> {
32-
pub fn new(out: OutputLocation<T>, use_color: bool, terse: bool) -> Self {
33+
pub fn new(out: OutputLocation<T>, use_color: bool, terse: bool, max_name_len: usize) -> Self {
3334
HumanFormatter {
3435
out,
3536
terse,
3637
use_color,
3738
test_count: 0,
39+
max_name_len,
3840
}
3941
}
4042

@@ -73,7 +75,7 @@ impl<T: Write> HumanFormatter<T> {
7375
// `stamp` in the rust CI).
7476
self.write_plain("\n")?;
7577
}
76-
78+
7779
self.test_count += 1;
7880
Ok(())
7981
} else {
@@ -170,20 +172,18 @@ impl<T: Write> OutputFormatter for HumanFormatter<T> {
170172
self.write_plain(&format!("\nrunning {} {}\n", len, noun))
171173
}
172174

173-
fn write_test_start(&mut self,
174-
test: &TestDesc,
175-
align: NamePadding,
176-
max_name_len: usize) -> io::Result<()> {
177-
if self.terse && align != PadOnRight {
178-
Ok(())
179-
}
180-
else {
181-
let name = test.padded_name(max_name_len, align);
182-
self.write_plain(&format!("test {} ... ", name))
183-
}
175+
fn write_test_start(&mut self, _desc: &TestDesc) -> io::Result<()> {
176+
// Do not print header, as priting it at this point will result in
177+
// an unreadable output when running tests concurrently.
178+
Ok(())
184179
}
185180

186-
fn write_result(&mut self, result: &TestResult) -> io::Result<()> {
181+
fn write_result(&mut self, desc: &TestDesc, result: &TestResult, _: &[u8]) -> io::Result<()> {
182+
if !(self.terse && desc.name.padding() != PadOnRight) {
183+
let name = desc.padded_name(self.max_name_len, desc.name.padding());
184+
self.write_plain(&format!("test {} ... ", name))?;
185+
}
186+
187187
match *result {
188188
TrOk => self.write_ok(),
189189
TrFailed | TrFailedMsg(_) => self.write_failed(),
@@ -244,3 +244,203 @@ impl<T: Write> OutputFormatter for HumanFormatter<T> {
244244
Ok(success)
245245
}
246246
}
247+
248+
pub(crate) struct JsonFormatter<T> {
249+
out: OutputLocation<T>
250+
}
251+
252+
impl<T: Write> JsonFormatter<T> {
253+
pub fn new(out: OutputLocation<T>) -> Self {
254+
Self {
255+
out, }
256+
}
257+
258+
fn write_str<S: AsRef<str>>(&mut self, s: S) -> io::Result<()> {
259+
self.out.write_all(s.as_ref().as_ref())?;
260+
self.out.write_all("\n".as_ref())
261+
}
262+
263+
fn write_event(&mut self,
264+
ty: &str,
265+
name: &str,
266+
evt: &str,
267+
extra: Option<String>) -> io::Result<()> {
268+
if let Some(extras) = extra {
269+
self.write_str(&*format!(r#"{{ "type": "{}", "name": "{}", "event": "{}", {} }}"#,
270+
ty,
271+
name,
272+
evt,
273+
extras))
274+
}
275+
else {
276+
self.write_str(&*format!(r#"{{ "type": "{}", "name": "{}", "event": "{}" }}"#,
277+
ty,
278+
name,
279+
evt))
280+
}
281+
}
282+
}
283+
284+
impl<T: Write> OutputFormatter for JsonFormatter<T> {
285+
fn write_run_start(&mut self, len: usize) -> io::Result<()> {
286+
self.write_str(
287+
&*format!(r#"{{ "type": "suite", "event": "started", "test_count": "{}" }}"#, len))
288+
}
289+
290+
fn write_test_start(&mut self, desc: &TestDesc) -> io::Result<()> {
291+
self.write_str(&*format!(r#"{{ "type": "test", "event": "started", "name": "{}" }}"#,
292+
desc.name))
293+
}
294+
295+
fn write_result(&mut self,
296+
desc: &TestDesc,
297+
result: &TestResult,
298+
stdout: &[u8]) -> io::Result<()> {
299+
match *result {
300+
TrOk => {
301+
self.write_event("test", desc.name.as_slice(), "ok", None)
302+
},
303+
304+
TrFailed => {
305+
let extra_data = if stdout.len() > 0 {
306+
Some(format!(r#""stdout": "{}""#,
307+
EscapedString(String::from_utf8_lossy(stdout))))
308+
}
309+
else {
310+
None
311+
};
312+
313+
self.write_event("test", desc.name.as_slice(), "failed", extra_data)
314+
},
315+
316+
TrFailedMsg(ref m) => {
317+
self.write_event("test",
318+
desc.name.as_slice(),
319+
"failed",
320+
Some(format!(r#""message": "{}""#, EscapedString(m))))
321+
},
322+
323+
TrIgnored => {
324+
self.write_event("test", desc.name.as_slice(), "ignored", None)
325+
},
326+
327+
TrAllowedFail => {
328+
self.write_event("test", desc.name.as_slice(), "allowed_failure", None)
329+
},
330+
331+
TrBench(ref bs) => {
332+
let median = bs.ns_iter_summ.median as usize;
333+
let deviation = (bs.ns_iter_summ.max - bs.ns_iter_summ.min) as usize;
334+
335+
let mbps = if bs.mb_s == 0 {
336+
"".into()
337+
}
338+
else {
339+
format!(r#", "mib_per_second": {}"#, bs.mb_s)
340+
};
341+
342+
let line = format!("{{ \"type\": \"bench\", \
343+
\"name\": \"{}\", \
344+
\"median\": {}, \
345+
\"deviation\": {}{} }}",
346+
desc.name,
347+
median,
348+
deviation,
349+
mbps);
350+
351+
self.write_str(&*line)
352+
},
353+
}
354+
}
355+
356+
fn write_timeout(&mut self, desc: &TestDesc) -> io::Result<()> {
357+
self.write_str(&*format!(r#"{{ "type": "test", "event": "timeout", "name": "{}" }}"#,
358+
desc.name))
359+
}
360+
361+
fn write_run_finish(&mut self, state: &ConsoleTestState) -> io::Result<bool> {
362+
363+
self.write_str(&*format!("{{ \"type\": \"suite\", \
364+
\"event\": \"{}\", \
365+
\"passed\": {}, \
366+
\"failed\": {}, \
367+
\"allowed_fail\": {}, \
368+
\"ignored\": {}, \
369+
\"measured\": {}, \
370+
\"filtered_out\": \"{}\" }}",
371+
if state.failed == 0 { "ok" } else { "failed" },
372+
state.passed,
373+
state.failed + state.allowed_fail,
374+
state.allowed_fail,
375+
state.ignored,
376+
state.measured,
377+
state.filtered_out))?;
378+
379+
Ok(state.failed == 0)
380+
}
381+
}
382+
383+
/// A formatting utility used to print strings with characters in need of escaping.
384+
/// Base code taken form `libserialize::json::escape_str`
385+
struct EscapedString<S: AsRef<str>>(S);
386+
387+
impl<S: AsRef<str>> ::std::fmt::Display for EscapedString<S> {
388+
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
389+
let mut start = 0;
390+
391+
for (i, byte) in self.0.as_ref().bytes().enumerate() {
392+
let escaped = match byte {
393+
b'"' => "\\\"",
394+
b'\\' => "\\\\",
395+
b'\x00' => "\\u0000",
396+
b'\x01' => "\\u0001",
397+
b'\x02' => "\\u0002",
398+
b'\x03' => "\\u0003",
399+
b'\x04' => "\\u0004",
400+
b'\x05' => "\\u0005",
401+
b'\x06' => "\\u0006",
402+
b'\x07' => "\\u0007",
403+
b'\x08' => "\\b",
404+
b'\t' => "\\t",
405+
b'\n' => "\\n",
406+
b'\x0b' => "\\u000b",
407+
b'\x0c' => "\\f",
408+
b'\r' => "\\r",
409+
b'\x0e' => "\\u000e",
410+
b'\x0f' => "\\u000f",
411+
b'\x10' => "\\u0010",
412+
b'\x11' => "\\u0011",
413+
b'\x12' => "\\u0012",
414+
b'\x13' => "\\u0013",
415+
b'\x14' => "\\u0014",
416+
b'\x15' => "\\u0015",
417+
b'\x16' => "\\u0016",
418+
b'\x17' => "\\u0017",
419+
b'\x18' => "\\u0018",
420+
b'\x19' => "\\u0019",
421+
b'\x1a' => "\\u001a",
422+
b'\x1b' => "\\u001b",
423+
b'\x1c' => "\\u001c",
424+
b'\x1d' => "\\u001d",
425+
b'\x1e' => "\\u001e",
426+
b'\x1f' => "\\u001f",
427+
b'\x7f' => "\\u007f",
428+
_ => { continue; }
429+
};
430+
431+
if start < i {
432+
f.write_str(&self.0.as_ref()[start..i])?;
433+
}
434+
435+
f.write_str(escaped)?;
436+
437+
start = i + 1;
438+
}
439+
440+
if start != self.0.as_ref().len() {
441+
f.write_str(&self.0.as_ref()[start..])?;
442+
}
443+
444+
Ok(())
445+
}
446+
}

0 commit comments

Comments
 (0)