Skip to content

Commit 93b0d4e

Browse files
author
Gilad Naaman
committed
Added JSON output to libtest.
1 parent 22fc649 commit 93b0d4e

File tree

3 files changed

+126
-14
lines changed

3 files changed

+126
-14
lines changed

src/libtest/formatters.rs

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ pub(crate) trait OutputFormatter {
1717
align: NamePadding,
1818
max_name_len: usize) -> io::Result<()>;
1919
fn write_timeout(&mut self, desc: &TestDesc) -> io::Result<()>;
20-
fn write_result(&mut self, result: &TestResult) -> io::Result<()>;
20+
fn write_result(&mut self, desc: &TestDesc, result: &TestResult) -> io::Result<()>;
2121
fn write_run_finish(&mut self, state: &ConsoleTestState) -> io::Result<bool>;
2222
}
2323

@@ -183,7 +183,7 @@ impl<T: Write> OutputFormatter for HumanFormatter<T> {
183183
}
184184
}
185185

186-
fn write_result(&mut self, result: &TestResult) -> io::Result<()> {
186+
fn write_result(&mut self, _desc: &TestDesc, result: &TestResult) -> io::Result<()> {
187187
match *result {
188188
TrOk => self.write_ok(),
189189
TrFailed | TrFailedMsg(_) => self.write_failed(),
@@ -244,3 +244,86 @@ impl<T: Write> OutputFormatter for HumanFormatter<T> {
244244
Ok(success)
245245
}
246246
}
247+
248+
pub(crate) struct JsonFormatter<T> {
249+
out: OutputLocation<T>,
250+
had_events: bool
251+
}
252+
253+
impl<T: Write> JsonFormatter<T> {
254+
pub fn new(out: OutputLocation<T>) -> Self {
255+
Self {
256+
out,
257+
had_events: false
258+
}
259+
}
260+
261+
fn write_str<S: AsRef<str>>(&mut self, s: S) -> io::Result<()> {
262+
self.out.write_all(s.as_ref().as_ref())
263+
}
264+
265+
fn write_event(&mut self, event: &str) -> io::Result<()> {
266+
if self.had_events {
267+
self.out.write_all(b",\n")?;
268+
}
269+
else {
270+
self.had_events = true;
271+
}
272+
273+
self.out.write_all(event.as_ref())
274+
}
275+
}
276+
277+
impl<T: Write> OutputFormatter for JsonFormatter<T> {
278+
fn write_run_start(&mut self, _len: usize) -> io::Result<()> {
279+
self.write_str("{\n\tevents: [\n")
280+
}
281+
282+
fn write_test_start(&mut self,
283+
desc: &TestDesc,
284+
_align: NamePadding,
285+
_max_name_len: usize) -> io::Result<()> {
286+
self.write_event(&*format!("\t\t{{ \"test\": \"{}\", \"event\": \"started\" }}", desc.name))
287+
}
288+
289+
fn write_result(&mut self, desc: &TestDesc, result: &TestResult) -> io::Result<()> {
290+
let output = match *result {
291+
TrOk => format!("\t\t{{ \"test\": \"{}\", \"event\": \"ok\" }}", desc.name),
292+
TrFailed => format!("\t\t{{ \"test\": \"{}\", \"event\": \"failed\" }}", desc.name),
293+
TrFailedMsg(ref m) => format!("\t\t{{ \"test\": \"{}\", \"event\": \"failed\", \"extra\": \"{}\" }}", desc.name, m),
294+
TrIgnored => format!("\t\t{{ \"test\": \"{}\", \"event\": \"ignored\" }}", desc.name),
295+
TrAllowedFail => format!("\t\t{{ \"test\": \"{}\", \"event\": \"allowed_failure\" }}", desc.name),
296+
TrMetrics(ref mm) => format!("\t\t{{ \"test\": \"{}\", \"event\": \"metrics\", \"extra\": \"{}\" }}", desc.name, mm.fmt_metrics()),
297+
TrBench(ref bs) => format!("\t\t{{ \"test\": \"{}\", \"event\": \"bench\", \"extra\": \"{}\" }}", desc.name, fmt_bench_samples(bs)),
298+
};
299+
300+
self.write_event(&*output)
301+
}
302+
303+
fn write_timeout(&mut self, desc: &TestDesc) -> io::Result<()> {
304+
self.write_event(&*format!("\t{{ \"test\": \"{}\", \"event\": \"timeout\" }}", desc.name))
305+
}
306+
307+
fn write_run_finish(&mut self, state: &ConsoleTestState) -> io::Result<bool> {
308+
self.write_str("\n\t],\n\t\"summary\": {\n")?;
309+
310+
self.write_str(&*format!("\t\t\"passed\": {},\n", state.passed))?;
311+
self.write_str(&*format!("\t\t\"failed\": {},\n", state.failed + state.allowed_fail))?;
312+
self.write_str(&*format!("\t\t\"allowed_fail\": {},\n", state.allowed_fail))?;
313+
self.write_str(&*format!("\t\t\"ignored\": {},\n", state.ignored))?;
314+
self.write_str(&*format!("\t\t\"measured\": {},\n", state.measured))?;
315+
316+
if state.failed == 0 {
317+
self.write_str(&*format!("\t\t\"filtered_out\": {}\n", state.filtered_out))?;
318+
} else {
319+
self.write_str(&*format!("\t\t\"filtered_out\": {},\n", state.filtered_out))?;
320+
self.write_str("\t\t\"failures\": [")?;
321+
322+
self.write_str("\t\t]\n")?;
323+
}
324+
325+
self.write_str("\t}\n}\n")?;
326+
327+
Ok(state.failed == 0)
328+
}
329+
}

src/libtest/lib.rs

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,13 @@ pub enum ColorConfig {
321321
NeverColor,
322322
}
323323

324+
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
325+
pub enum OutputFormat {
326+
Pretty,
327+
Terse,
328+
Json
329+
}
330+
324331
#[derive(Debug)]
325332
pub struct TestOpts {
326333
pub list: bool,
@@ -332,7 +339,7 @@ pub struct TestOpts {
332339
pub logfile: Option<PathBuf>,
333340
pub nocapture: bool,
334341
pub color: ColorConfig,
335-
pub quiet: bool,
342+
pub format: OutputFormat,
336343
pub test_threads: Option<usize>,
337344
pub skip: Vec<String>,
338345
pub options: Options,
@@ -351,7 +358,7 @@ impl TestOpts {
351358
logfile: None,
352359
nocapture: false,
353360
color: AutoColor,
354-
quiet: false,
361+
format: OutputFormat,
355362
test_threads: None,
356363
skip: vec![],
357364
options: Options::new(),
@@ -383,7 +390,11 @@ fn optgroups() -> getopts::Options {
383390
.optopt("", "color", "Configure coloring of output:
384391
auto = colorize if stdout is a tty and tests are run on serially (default);
385392
always = always colorize output;
386-
never = never colorize output;", "auto|always|never");
393+
never = never colorize output;", "auto|always|never")
394+
.optopt("", "format", "Configure formatting of output:
395+
pretty = Print verbose output;
396+
terse = Display one character per test;
397+
json = Output a json document", "pretty|terse|json");
387398
return opts
388399
}
389400

@@ -484,6 +495,19 @@ pub fn parse_opts(args: &[String]) -> Option<OptRes> {
484495
}
485496
};
486497

498+
let format = match matches.opt_str("format").as_ref().map(|s| &**s) {
499+
None if quiet => OutputFormat::Terse,
500+
Some("pretty") | None => OutputFormat::Pretty,
501+
Some("terse") => OutputFormat::Terse,
502+
Some("json") => OutputFormat::Json,
503+
504+
Some(v) => {
505+
return Some(Err(format!("argument for --format must be pretty, terse, or json (was \
506+
{})",
507+
v)))
508+
}
509+
};
510+
487511
let test_opts = TestOpts {
488512
list,
489513
filter,
@@ -494,7 +518,7 @@ pub fn parse_opts(args: &[String]) -> Option<OptRes> {
494518
logfile,
495519
nocapture,
496520
color,
497-
quiet,
521+
format,
498522
test_threads,
499523
skip: matches.opt_strs("skip"),
500524
options: Options::new(),
@@ -656,7 +680,9 @@ pub fn list_tests_console(opts: &TestOpts, tests: Vec<TestDescAndFn>) -> io::Res
656680
None => Raw(io::stdout()),
657681
Some(t) => Pretty(t),
658682
};
659-
let mut out = HumanFormatter::new(output, use_color(opts), opts.quiet);
683+
684+
let quiet = opts.format == OutputFormat::Terse;
685+
let mut out = HumanFormatter::new(output, use_color(opts), quiet);
660686
let mut st = ConsoleTestState::new(opts)?;
661687

662688
let mut ntest = 0;
@@ -683,9 +709,9 @@ pub fn list_tests_console(opts: &TestOpts, tests: Vec<TestDescAndFn>) -> io::Res
683709
}
684710
}
685711

686-
if !opts.quiet {
712+
if !quiet {
687713
if ntest != 0 || nbench != 0 {
688-
st.write_plain("\n")?;
714+
out.write_plain("\n")?;
689715
}
690716
st.write_plain(format!("{}, {}\n",
691717
plural(ntest, "test"),
@@ -712,7 +738,7 @@ pub fn run_tests_console(opts: &TestOpts, tests: Vec<TestDescAndFn>) -> io::Resu
712738
TeTimeout(ref test) => out.write_timeout(test),
713739
TeResult(test, result, stdout) => {
714740
st.write_log_result(&test, &result)?;
715-
out.write_result(&result)?;
741+
out.write_result(&test, &result)?;
716742
match result {
717743
TrOk => {
718744
st.passed += 1;
@@ -749,8 +775,11 @@ pub fn run_tests_console(opts: &TestOpts, tests: Vec<TestDescAndFn>) -> io::Resu
749775
Some(t) => Pretty(t),
750776
};
751777

752-
let mut out = HumanFormatter::new(output, use_color(opts), opts.quiet);
753-
778+
let mut out: Box<OutputFormatter> = match opts.format {
779+
OutputFormat::Pretty => Box::new(HumanFormatter::new(output, use_color(opts), false)),
780+
OutputFormat::Terse => Box::new(HumanFormatter::new(output, use_color(opts), true)),
781+
OutputFormat::Json => Box::new(JsonFormatter::new(output)),
782+
};
754783
let mut st = ConsoleTestState::new(opts)?;
755784
fn len_if_padded(t: &TestDescAndFn) -> usize {
756785
match t.testfn.padding() {
@@ -762,7 +791,7 @@ pub fn run_tests_console(opts: &TestOpts, tests: Vec<TestDescAndFn>) -> io::Resu
762791
let n = t.desc.name.as_slice();
763792
st.max_name_len = n.len();
764793
}
765-
run_tests(opts, tests, |x| callback(&x, &mut st, &mut out))?;
794+
run_tests(opts, tests, |x| callback(&x, &mut st, &mut *out))?;
766795

767796
assert!(st.current_test_count() == st.total);
768797

src/tools/compiletest/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -484,7 +484,7 @@ pub fn test_opts(config: &Config) -> test::TestOpts {
484484
filter: config.filter.clone(),
485485
filter_exact: config.filter_exact,
486486
run_ignored: config.run_ignored,
487-
quiet: config.quiet,
487+
format: if config.quiet { test::OutputFormat::Terse } else { test::OutputFormat::Pretty },
488488
logfile: config.logfile.clone(),
489489
run_tests: true,
490490
bench_benchmarks: true,

0 commit comments

Comments
 (0)