Skip to content

Commit ba06aa9

Browse files
author
Gilad Naaman
committed
Added JSON output to libtest.
1 parent c239356 commit ba06aa9

File tree

3 files changed

+125
-13
lines changed

3 files changed

+125
-13
lines changed

src/libtest/formatters.rs

+85-2
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

@@ -187,7 +187,7 @@ impl<T: Write> OutputFormatter for HumanFormatter<T> {
187187
}
188188
}
189189

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

src/libtest/lib.rs

+39-10
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,13 @@ pub enum ColorConfig {
337337
NeverColor,
338338
}
339339

340+
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
341+
pub enum OutputFormat {
342+
Pretty,
343+
Terse,
344+
Json
345+
}
346+
340347
#[derive(Debug)]
341348
pub struct TestOpts {
342349
pub list: bool,
@@ -348,7 +355,7 @@ pub struct TestOpts {
348355
pub logfile: Option<PathBuf>,
349356
pub nocapture: bool,
350357
pub color: ColorConfig,
351-
pub quiet: bool,
358+
pub format: OutputFormat,
352359
pub test_threads: Option<usize>,
353360
pub skip: Vec<String>,
354361
pub options: Options,
@@ -367,7 +374,7 @@ impl TestOpts {
367374
logfile: None,
368375
nocapture: false,
369376
color: AutoColor,
370-
quiet: false,
377+
format: OutputFormat,
371378
test_threads: None,
372379
skip: vec![],
373380
options: Options::new(),
@@ -399,7 +406,11 @@ fn optgroups() -> getopts::Options {
399406
.optopt("", "color", "Configure coloring of output:
400407
auto = colorize if stdout is a tty and tests are run on serially (default);
401408
always = always colorize output;
402-
never = never colorize output;", "auto|always|never");
409+
never = never colorize output;", "auto|always|never")
410+
.optopt("", "format", "Configure formatting of output:
411+
pretty = Print verbose output;
412+
terse = Display one character per test;
413+
json = Output a json document", "pretty|terse|json");
403414
return opts
404415
}
405416

@@ -499,6 +510,19 @@ pub fn parse_opts(args: &[String]) -> Option<OptRes> {
499510
}
500511
};
501512

513+
let format = match matches.opt_str("format").as_ref().map(|s| &**s) {
514+
None if quiet => OutputFormat::Terse,
515+
Some("pretty") | None => OutputFormat::Pretty,
516+
Some("terse") => OutputFormat::Terse,
517+
Some("json") => OutputFormat::Json,
518+
519+
Some(v) => {
520+
return Some(Err(format!("argument for --format must be pretty, terse, or json (was \
521+
{})",
522+
v)))
523+
}
524+
};
525+
502526
let test_opts = TestOpts {
503527
list,
504528
filter,
@@ -509,7 +533,7 @@ pub fn parse_opts(args: &[String]) -> Option<OptRes> {
509533
logfile,
510534
nocapture,
511535
color,
512-
quiet,
536+
format,
513537
test_threads,
514538
skip: matches.opt_strs("skip"),
515539
options: Options::new(),
@@ -673,7 +697,9 @@ pub fn list_tests_console(opts: &TestOpts, tests: Vec<TestDescAndFn>) -> io::Res
673697
None => Raw(io::stdout()),
674698
Some(t) => Pretty(t),
675699
};
676-
let mut out = HumanFormatter::new(output, use_color(opts), opts.quiet);
700+
701+
let quiet = opts.format == OutputFormat::Terse;
702+
let mut out = HumanFormatter::new(output, use_color(opts), quiet);
677703
let mut st = ConsoleTestState::new(opts)?;
678704

679705
let mut ntest = 0;
@@ -702,7 +728,7 @@ pub fn list_tests_console(opts: &TestOpts, tests: Vec<TestDescAndFn>) -> io::Res
702728
}
703729
}
704730

705-
if !opts.quiet {
731+
if !quiet {
706732
if ntest != 0 || nbench != 0 || nmetric != 0 {
707733
out.write_plain("\n")?;
708734
}
@@ -732,7 +758,7 @@ pub fn run_tests_console(opts: &TestOpts, tests: Vec<TestDescAndFn>) -> io::Resu
732758
TeTimeout(ref test) => out.write_timeout(test),
733759
TeResult(test, result, stdout) => {
734760
st.write_log_result(&test, &result)?;
735-
out.write_result(&result)?;
761+
out.write_result(&test, &result)?;
736762
match result {
737763
TrOk => {
738764
st.passed += 1;
@@ -778,8 +804,11 @@ pub fn run_tests_console(opts: &TestOpts, tests: Vec<TestDescAndFn>) -> io::Resu
778804
Some(t) => Pretty(t),
779805
};
780806

781-
let mut out = HumanFormatter::new(output, use_color(opts), opts.quiet);
782-
807+
let mut out: Box<OutputFormatter> = match opts.format {
808+
OutputFormat::Pretty => Box::new(HumanFormatter::new(output, use_color(opts), false)),
809+
OutputFormat::Terse => Box::new(HumanFormatter::new(output, use_color(opts), true)),
810+
OutputFormat::Json => Box::new(JsonFormatter::new(output)),
811+
};
783812
let mut st = ConsoleTestState::new(opts)?;
784813
fn len_if_padded(t: &TestDescAndFn) -> usize {
785814
match t.testfn.padding() {
@@ -791,7 +820,7 @@ pub fn run_tests_console(opts: &TestOpts, tests: Vec<TestDescAndFn>) -> io::Resu
791820
let n = t.desc.name.as_slice();
792821
st.max_name_len = n.len();
793822
}
794-
run_tests(opts, tests, |x| callback(&x, &mut st, &mut out))?;
823+
run_tests(opts, tests, |x| callback(&x, &mut st, &mut *out))?;
795824

796825
assert!(st.current_test_count() == st.total);
797826

src/tools/compiletest/src/main.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ pub fn test_opts(config: &Config) -> test::TestOpts {
340340
filter: config.filter.clone(),
341341
filter_exact: config.filter_exact,
342342
run_ignored: config.run_ignored,
343-
quiet: config.quiet,
343+
format: if config.quiet { test::OutputFormat::Terse } else { test::OutputFormat::Pretty },
344344
logfile: config.logfile.clone(),
345345
run_tests: true,
346346
bench_benchmarks: true,

0 commit comments

Comments
 (0)