Ensure test library issues json string line-by-line
This commit is contained in:
parent
2034b6d23c
commit
a18b750de2
1 changed files with 62 additions and 70 deletions
|
@ -18,14 +18,10 @@ impl<T: Write> JsonFormatter<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn writeln_message(&mut self, s: &str) -> io::Result<()> {
|
fn writeln_message(&mut self, s: &str) -> io::Result<()> {
|
||||||
assert!(!s.contains('\n'));
|
// self.out will take a lock, but that lock is released when write_all returns. This
|
||||||
|
// results in a race condition and json output may not end with a new line. We avoid this
|
||||||
self.out.write_all(s.as_ref())?;
|
// by issuing `write_all` calls line-by-line.
|
||||||
self.out.write_all(b"\n")
|
assert_eq!(s.chars().last(), Some('\n'));
|
||||||
}
|
|
||||||
|
|
||||||
fn write_message(&mut self, s: &str) -> io::Result<()> {
|
|
||||||
assert!(!s.contains('\n'));
|
|
||||||
|
|
||||||
self.out.write_all(s.as_ref())
|
self.out.write_all(s.as_ref())
|
||||||
}
|
}
|
||||||
|
@ -34,34 +30,35 @@ impl<T: Write> JsonFormatter<T> {
|
||||||
&mut self,
|
&mut self,
|
||||||
ty: &str,
|
ty: &str,
|
||||||
name: &str,
|
name: &str,
|
||||||
evt: &str,
|
event: &str,
|
||||||
exec_time: Option<&time::TestExecTime>,
|
exec_time: Option<&time::TestExecTime>,
|
||||||
stdout: Option<Cow<'_, str>>,
|
stdout: Option<Cow<'_, str>>,
|
||||||
extra: Option<&str>,
|
extra: Option<&str>,
|
||||||
) -> io::Result<()> {
|
) -> io::Result<()> {
|
||||||
// A doc test's name includes a filename which must be escaped for correct json.
|
// A doc test's name includes a filename which must be escaped for correct json.
|
||||||
self.write_message(&format!(
|
let name = EscapedString(name);
|
||||||
r#"{{ "type": "{}", "name": "{}", "event": "{}""#,
|
let exec_time_json = if let Some(exec_time) = exec_time {
|
||||||
ty,
|
format!(r#", "exec_time": {}"#, exec_time.0.as_secs_f64())
|
||||||
EscapedString(name),
|
} else {
|
||||||
evt
|
String::from("")
|
||||||
))?;
|
};
|
||||||
if let Some(exec_time) = exec_time {
|
let stdout_json = if let Some(stdout) = stdout {
|
||||||
self.write_message(&format!(r#", "exec_time": {}"#, exec_time.0.as_secs_f64()))?;
|
format!(r#", "stdout": "{}""#, EscapedString(stdout))
|
||||||
}
|
} else {
|
||||||
if let Some(stdout) = stdout {
|
String::from("")
|
||||||
self.write_message(&format!(r#", "stdout": "{}""#, EscapedString(stdout)))?;
|
};
|
||||||
}
|
let extra_json =
|
||||||
if let Some(extra) = extra {
|
if let Some(extra) = extra { format!(r#", {extra}"#) } else { String::from("") };
|
||||||
self.write_message(&format!(r#", {extra}"#))?;
|
let newline = "\n";
|
||||||
}
|
|
||||||
self.writeln_message(" }")
|
self.writeln_message(&format!(
|
||||||
|
r#"{{ "type": "{ty}", "name": "{name}", "event": "{event}"{exec_time_json}{stdout_json}{extra_json} }}{newline}"#))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Write> OutputFormatter for JsonFormatter<T> {
|
impl<T: Write> OutputFormatter for JsonFormatter<T> {
|
||||||
fn write_discovery_start(&mut self) -> io::Result<()> {
|
fn write_discovery_start(&mut self) -> io::Result<()> {
|
||||||
self.writeln_message(&format!(r#"{{ "type": "suite", "event": "discovery" }}"#))
|
self.writeln_message(concat!(r#"{ "type": "suite", "event": "discovery" }"#, "\n"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_test_discovered(&mut self, desc: &TestDesc, test_type: &str) -> io::Result<()> {
|
fn write_test_discovered(&mut self, desc: &TestDesc, test_type: &str) -> io::Result<()> {
|
||||||
|
@ -77,11 +74,13 @@ impl<T: Write> OutputFormatter for JsonFormatter<T> {
|
||||||
..
|
..
|
||||||
} = desc;
|
} = desc;
|
||||||
|
|
||||||
|
let name = EscapedString(name.as_slice());
|
||||||
|
let ignore_message = ignore_message.unwrap_or("");
|
||||||
|
let source_path = EscapedString(source_file);
|
||||||
|
let newline = "\n";
|
||||||
|
|
||||||
self.writeln_message(&format!(
|
self.writeln_message(&format!(
|
||||||
r#"{{ "type": "{test_type}", "event": "discovered", "name": "{}", "ignore": {ignore}, "ignore_message": "{}", "source_path": "{}", "start_line": {start_line}, "start_col": {start_col}, "end_line": {end_line}, "end_col": {end_col} }}"#,
|
r#"{{ "type": "{test_type}", "event": "discovered", "name": "{name}", "ignore": {ignore}, "ignore_message": "{ignore_message}", "source_path": "{source_path}", "start_line": {start_line}, "start_col": {start_col}, "end_line": {end_line}, "end_col": {end_col} }}{newline}"#
|
||||||
EscapedString(name.as_slice()),
|
|
||||||
ignore_message.unwrap_or(""),
|
|
||||||
EscapedString(source_file),
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,9 +88,10 @@ impl<T: Write> OutputFormatter for JsonFormatter<T> {
|
||||||
let ConsoleTestDiscoveryState { tests, benchmarks, ignored, .. } = state;
|
let ConsoleTestDiscoveryState { tests, benchmarks, ignored, .. } = state;
|
||||||
|
|
||||||
let total = tests + benchmarks;
|
let total = tests + benchmarks;
|
||||||
|
let newline = "\n";
|
||||||
self.writeln_message(&format!(
|
self.writeln_message(&format!(
|
||||||
r#"{{ "type": "suite", "event": "completed", "tests": {tests}, "benchmarks": {benchmarks}, "total": {total}, "ignored": {ignored} }}"#
|
r#"{{ "type": "suite", "event": "completed", "tests": {tests}, "benchmarks": {benchmarks}, "total": {total}, "ignored": {ignored} }}{newline}"#
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_run_start(&mut self, test_count: usize, shuffle_seed: Option<u64>) -> io::Result<()> {
|
fn write_run_start(&mut self, test_count: usize, shuffle_seed: Option<u64>) -> io::Result<()> {
|
||||||
|
@ -100,15 +100,17 @@ impl<T: Write> OutputFormatter for JsonFormatter<T> {
|
||||||
} else {
|
} else {
|
||||||
String::new()
|
String::new()
|
||||||
};
|
};
|
||||||
|
let newline = "\n";
|
||||||
self.writeln_message(&format!(
|
self.writeln_message(&format!(
|
||||||
r#"{{ "type": "suite", "event": "started", "test_count": {test_count}{shuffle_seed_json} }}"#
|
r#"{{ "type": "suite", "event": "started", "test_count": {test_count}{shuffle_seed_json} }}{newline}"#
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_test_start(&mut self, desc: &TestDesc) -> io::Result<()> {
|
fn write_test_start(&mut self, desc: &TestDesc) -> io::Result<()> {
|
||||||
|
let name = EscapedString(desc.name.as_slice());
|
||||||
|
let newline = "\n";
|
||||||
self.writeln_message(&format!(
|
self.writeln_message(&format!(
|
||||||
r#"{{ "type": "test", "event": "started", "name": "{}" }}"#,
|
r#"{{ "type": "test", "event": "started", "name": "{name}" }}{newline}"#
|
||||||
EscapedString(desc.name.as_slice())
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,54 +175,44 @@ impl<T: Write> OutputFormatter for JsonFormatter<T> {
|
||||||
} else {
|
} else {
|
||||||
format!(r#", "mib_per_second": {}"#, bs.mb_s)
|
format!(r#", "mib_per_second": {}"#, bs.mb_s)
|
||||||
};
|
};
|
||||||
|
let name = EscapedString(desc.name.as_slice());
|
||||||
|
|
||||||
let line = format!(
|
self.writeln_message(&format!(
|
||||||
"{{ \"type\": \"bench\", \
|
"{{ \"type\": \"bench\", \
|
||||||
\"name\": \"{}\", \
|
\"name\": \"{name}\", \
|
||||||
\"median\": {}, \
|
\"median\": {median}, \
|
||||||
\"deviation\": {}{} }}",
|
\"deviation\": {deviation}{mbps} }}\n",
|
||||||
EscapedString(desc.name.as_slice()),
|
))
|
||||||
median,
|
|
||||||
deviation,
|
|
||||||
mbps
|
|
||||||
);
|
|
||||||
|
|
||||||
self.writeln_message(&line)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_timeout(&mut self, desc: &TestDesc) -> io::Result<()> {
|
fn write_timeout(&mut self, desc: &TestDesc) -> io::Result<()> {
|
||||||
|
let name = EscapedString(desc.name.as_slice());
|
||||||
|
let newline = "\n";
|
||||||
self.writeln_message(&format!(
|
self.writeln_message(&format!(
|
||||||
r#"{{ "type": "test", "event": "timeout", "name": "{}" }}"#,
|
r#"{{ "type": "test", "event": "timeout", "name": "{name}" }}{newline}"#,
|
||||||
EscapedString(desc.name.as_slice())
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_run_finish(&mut self, state: &ConsoleTestState) -> io::Result<bool> {
|
fn write_run_finish(&mut self, state: &ConsoleTestState) -> io::Result<bool> {
|
||||||
self.write_message(&format!(
|
let event = if state.failed == 0 { "ok" } else { "failed" };
|
||||||
"{{ \"type\": \"suite\", \
|
let passed = state.passed;
|
||||||
\"event\": \"{}\", \
|
let failed = state.failed;
|
||||||
\"passed\": {}, \
|
let ignored = state.ignored;
|
||||||
\"failed\": {}, \
|
let measured = state.measured;
|
||||||
\"ignored\": {}, \
|
let filtered_out = state.filtered_out;
|
||||||
\"measured\": {}, \
|
let exec_time_json = if let Some(ref exec_time) = state.exec_time {
|
||||||
\"filtered_out\": {}",
|
format!(r#", "exec_time": {}"#, exec_time.0.as_secs_f64())
|
||||||
if state.failed == 0 { "ok" } else { "failed" },
|
} else {
|
||||||
state.passed,
|
String::from("")
|
||||||
state.failed,
|
};
|
||||||
state.ignored,
|
let newline = "\n";
|
||||||
state.measured,
|
|
||||||
state.filtered_out,
|
self.writeln_message(&format!(
|
||||||
|
r#"{{ "type": "suite", "event": "{event}", "passed": {passed}, "failed": {failed}, "ignored": {ignored}, "measured": {measured}, "filtered_out": {filtered_out}{exec_time_json} }}{newline}"#
|
||||||
))?;
|
))?;
|
||||||
|
|
||||||
if let Some(ref exec_time) = state.exec_time {
|
|
||||||
let time_str = format!(", \"exec_time\": {}", exec_time.0.as_secs_f64());
|
|
||||||
self.write_message(&time_str)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.writeln_message(" }")?;
|
|
||||||
|
|
||||||
Ok(state.failed == 0)
|
Ok(state.failed == 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue