Skip to content

Commit

Permalink
Add json output format
Browse files Browse the repository at this point in the history
  • Loading branch information
PaulWagener committed Jun 12, 2023
1 parent 5afa72f commit 3bfc0f3
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 3 deletions.
6 changes: 5 additions & 1 deletion src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,8 @@ pub struct Arguments {
value_name = "pretty|terse|json",
help = "Configure formatting of output: \n\
- pretty = Print verbose output\n\
- terse = Display one character per test\n",
- terse = Display one character per test\n\
- json = Print json events\n",
)]
pub format: Option<FormatSetting>,

Expand Down Expand Up @@ -176,6 +177,9 @@ pub enum FormatSetting {

/// One character per test. Usefull for test suites with many tests.
Terse,

/// Json output
Json,
}

impl Default for FormatSetting {
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,7 @@ pub fn run(args: &Arguments, mut tests: Vec<Trial>) -> Conclusion {

let mut failed_tests = Vec::new();
let mut handle_outcome = |outcome: Outcome, test: TestInfo, printer: &mut Printer| {
printer.print_single_outcome(&outcome);
printer.print_single_outcome(&test, &outcome);

// Handle outcome
match outcome {
Expand Down
61 changes: 60 additions & 1 deletion src/printer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@ impl Printer {
writeln!(self.out).unwrap();
writeln!(self.out, "running {} test{}", num_tests, plural_s).unwrap();
}
FormatSetting::Json => writeln!(
self.out,
"{{ \"type\": \"suite\", \"event\": \"started\", \"test_count\": {} }}",
num_tests
)
.unwrap(),
}
}

Expand Down Expand Up @@ -121,12 +127,20 @@ impl Printer {
// In terse mode, nothing is printed before the job. Only
// `print_single_outcome` prints one character.
}
FormatSetting::Json => {
writeln!(
self.out,
"{{ \"type\": \"test\", \"event\": \"started\", \"name\": \"{}\" }}",
name
)
.unwrap();
}
}
}

/// Prints the outcome of a single tests. `ok` or `FAILED` in pretty mode
/// and `.` or `F` in terse mode.
pub(crate) fn print_single_outcome(&mut self, outcome: &Outcome) {
pub(crate) fn print_single_outcome(&mut self, info: &TestInfo, outcome: &Outcome) {
match self.format {
FormatSetting::Pretty => {
self.print_outcome_pretty(outcome);
Expand All @@ -150,6 +164,34 @@ impl Printer {
write!(self.out, "{}", c).unwrap();
self.out.reset().unwrap();
}
FormatSetting::Json => {
if let Outcome::Measured(Measurement { avg, variance }) = outcome {
writeln!(
self.out,
r#"{{ "type": "bench", "name": "{}", "median": {}, "deviation": {} }}"#,
info.name, avg, variance
)
.unwrap();
} else {
writeln!(
self.out,
r#"{{ "type": "test", "name": "{}", "event": "{}"{} }}"#,
info.name,
match outcome {
Outcome::Passed => "ok",
Outcome::Failed(_) => "failed",
Outcome::Ignored => "ignored",
Outcome::Measured(_) => unreachable!(),
},
match outcome {
Outcome::Failed(Failed { msg: Some(msg) }) =>
format!(r#", "stdout": "Error: \"{}\"\n""#, msg.escape_default()),
_ => "".into(),
}
)
.unwrap();
}
}
}
}

Expand Down Expand Up @@ -179,6 +221,20 @@ impl Printer {
).unwrap();
writeln!(self.out).unwrap();
}
FormatSetting::Json => {
writeln!(
self.out,
"{{ \"type\": \"suite\", \"event\": \"{}\", \"passed\": {}, \"failed\": {}, \"ignored\": {}, \"measured\": {}, \"filtered_out\": {}, \"exec_time\": {} }}",
if conclusion.num_failed > 0 { "failed" } else { "ok" },
conclusion.num_passed,
conclusion.num_failed,
conclusion.num_ignored,
conclusion.num_measured,
conclusion.num_filtered_out,
execution_time.as_secs_f64()
)
.unwrap();
}
}
}

Expand Down Expand Up @@ -221,6 +277,9 @@ impl Printer {
/// Prints a list of failed tests with their messages. This is only called
/// if there were any failures.
pub(crate) fn print_failures(&mut self, fails: &[(TestInfo, Option<String>)]) {
if self.format == FormatSetting::Json {
return;
}
writeln!(self.out).unwrap();
writeln!(self.out, "failures:").unwrap();
writeln!(self.out).unwrap();
Expand Down
5 changes: 5 additions & 0 deletions tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ macro_rules! assert_log {
}
}

if let Some(pos) = actual.rfind("\"exec_time\":") {
actual.truncate(pos);
actual.push_str("\"exec_time\": 0.000000000 }");
}

assert_eq!(actual, expected);
};
}
Expand Down
53 changes: 53 additions & 0 deletions tests/mixed_bag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -527,3 +527,56 @@ fn terse_output() {
finished in 0.00s
");
}

#[test]
fn json_output() {
let (c, out) = do_run(args(["--format", "json", "--test-threads", "1"]), tests());
assert_eq!(
c,
Conclusion {
num_filtered_out: 0,
num_passed: 4,
num_failed: 4,
num_ignored: 8,
num_measured: 0,
}
);

assert_log!(
out,
r#"{ "type": "suite", "event": "started", "test_count": 16 }
{ "type": "test", "event": "started", "name": "cat" }
{ "type": "test", "name": "cat", "event": "ok" }
{ "type": "test", "event": "started", "name": "dog" }
{ "type": "test", "name": "dog", "event": "failed", "stdout": "Error: \"was not a good boy\"\n" }
{ "type": "test", "event": "started", "name": "fox" }
{ "type": "test", "name": "fox", "event": "ok" }
{ "type": "test", "event": "started", "name": "bunny" }
{ "type": "test", "name": "bunny", "event": "failed", "stdout": "Error: \"jumped too high\"\n" }
{ "type": "test", "event": "started", "name": "frog" }
{ "type": "test", "name": "frog", "event": "ignored" }
{ "type": "test", "event": "started", "name": "owl" }
{ "type": "test", "name": "owl", "event": "ignored" }
{ "type": "test", "event": "started", "name": "fly" }
{ "type": "test", "name": "fly", "event": "ignored" }
{ "type": "test", "event": "started", "name": "bear" }
{ "type": "test", "name": "bear", "event": "ignored" }
{ "type": "test", "event": "started", "name": "red" }
{ "type": "test", "name": "red", "event": "ok" }
{ "type": "test", "event": "started", "name": "blue" }
{ "type": "test", "name": "blue", "event": "failed", "stdout": "Error: \"sky fell down\"\n" }
{ "type": "test", "event": "started", "name": "yellow" }
{ "type": "test", "name": "yellow", "event": "ok" }
{ "type": "test", "event": "started", "name": "green" }
{ "type": "test", "name": "green", "event": "failed", "stdout": "Error: \"was poisoned\"\n" }
{ "type": "test", "event": "started", "name": "purple" }
{ "type": "test", "name": "purple", "event": "ignored" }
{ "type": "test", "event": "started", "name": "cyan" }
{ "type": "test", "name": "cyan", "event": "ignored" }
{ "type": "test", "event": "started", "name": "orange" }
{ "type": "test", "name": "orange", "event": "ignored" }
{ "type": "test", "event": "started", "name": "pink" }
{ "type": "test", "name": "pink", "event": "ignored" }
{ "type": "suite", "event": "failed", "passed": 4, "failed": 4, "ignored": 8, "measured": 0, "filtered_out": 0, "exec_time": 0.000000000 }"#
);
}

0 comments on commit 3bfc0f3

Please sign in to comment.