Skip to content

Commit a18b750

Browse files
committed
Ensure test library issues json string line-by-line
1 parent 2034b6d commit a18b750

File tree

1 file changed

+62
-70
lines changed

1 file changed

+62
-70
lines changed

Diff for: library/test/src/formatters/json.rs

+62-70
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,10 @@ impl<T: Write> JsonFormatter<T> {
1818
}
1919

2020
fn writeln_message(&mut self, s: &str) -> io::Result<()> {
21-
assert!(!s.contains('\n'));
22-
23-
self.out.write_all(s.as_ref())?;
24-
self.out.write_all(b"\n")
25-
}
26-
27-
fn write_message(&mut self, s: &str) -> io::Result<()> {
28-
assert!(!s.contains('\n'));
21+
// self.out will take a lock, but that lock is released when write_all returns. This
22+
// results in a race condition and json output may not end with a new line. We avoid this
23+
// by issuing `write_all` calls line-by-line.
24+
assert_eq!(s.chars().last(), Some('\n'));
2925

3026
self.out.write_all(s.as_ref())
3127
}
@@ -34,34 +30,35 @@ impl<T: Write> JsonFormatter<T> {
3430
&mut self,
3531
ty: &str,
3632
name: &str,
37-
evt: &str,
33+
event: &str,
3834
exec_time: Option<&time::TestExecTime>,
3935
stdout: Option<Cow<'_, str>>,
4036
extra: Option<&str>,
4137
) -> io::Result<()> {
4238
// A doc test's name includes a filename which must be escaped for correct json.
43-
self.write_message(&format!(
44-
r#"{{ "type": "{}", "name": "{}", "event": "{}""#,
45-
ty,
46-
EscapedString(name),
47-
evt
48-
))?;
49-
if let Some(exec_time) = exec_time {
50-
self.write_message(&format!(r#", "exec_time": {}"#, exec_time.0.as_secs_f64()))?;
51-
}
52-
if let Some(stdout) = stdout {
53-
self.write_message(&format!(r#", "stdout": "{}""#, EscapedString(stdout)))?;
54-
}
55-
if let Some(extra) = extra {
56-
self.write_message(&format!(r#", {extra}"#))?;
57-
}
58-
self.writeln_message(" }")
39+
let name = EscapedString(name);
40+
let exec_time_json = if let Some(exec_time) = exec_time {
41+
format!(r#", "exec_time": {}"#, exec_time.0.as_secs_f64())
42+
} else {
43+
String::from("")
44+
};
45+
let stdout_json = if let Some(stdout) = stdout {
46+
format!(r#", "stdout": "{}""#, EscapedString(stdout))
47+
} else {
48+
String::from("")
49+
};
50+
let extra_json =
51+
if let Some(extra) = extra { format!(r#", {extra}"#) } else { String::from("") };
52+
let newline = "\n";
53+
54+
self.writeln_message(&format!(
55+
r#"{{ "type": "{ty}", "name": "{name}", "event": "{event}"{exec_time_json}{stdout_json}{extra_json} }}{newline}"#))
5956
}
6057
}
6158

6259
impl<T: Write> OutputFormatter for JsonFormatter<T> {
6360
fn write_discovery_start(&mut self) -> io::Result<()> {
64-
self.writeln_message(&format!(r#"{{ "type": "suite", "event": "discovery" }}"#))
61+
self.writeln_message(concat!(r#"{ "type": "suite", "event": "discovery" }"#, "\n"))
6562
}
6663

6764
fn write_test_discovered(&mut self, desc: &TestDesc, test_type: &str) -> io::Result<()> {
@@ -77,21 +74,24 @@ impl<T: Write> OutputFormatter for JsonFormatter<T> {
7774
..
7875
} = desc;
7976

77+
let name = EscapedString(name.as_slice());
78+
let ignore_message = ignore_message.unwrap_or("");
79+
let source_path = EscapedString(source_file);
80+
let newline = "\n";
81+
8082
self.writeln_message(&format!(
81-
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} }}"#,
82-
EscapedString(name.as_slice()),
83-
ignore_message.unwrap_or(""),
84-
EscapedString(source_file),
83+
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}"#
8584
))
8685
}
8786

8887
fn write_discovery_finish(&mut self, state: &ConsoleTestDiscoveryState) -> io::Result<()> {
8988
let ConsoleTestDiscoveryState { tests, benchmarks, ignored, .. } = state;
9089

9190
let total = tests + benchmarks;
91+
let newline = "\n";
9292
self.writeln_message(&format!(
93-
r#"{{ "type": "suite", "event": "completed", "tests": {tests}, "benchmarks": {benchmarks}, "total": {total}, "ignored": {ignored} }}"#
94-
))
93+
r#"{{ "type": "suite", "event": "completed", "tests": {tests}, "benchmarks": {benchmarks}, "total": {total}, "ignored": {ignored} }}{newline}"#
94+
))
9595
}
9696

9797
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> {
100100
} else {
101101
String::new()
102102
};
103+
let newline = "\n";
103104
self.writeln_message(&format!(
104-
r#"{{ "type": "suite", "event": "started", "test_count": {test_count}{shuffle_seed_json} }}"#
105-
))
105+
r#"{{ "type": "suite", "event": "started", "test_count": {test_count}{shuffle_seed_json} }}{newline}"#
106+
))
106107
}
107108

108109
fn write_test_start(&mut self, desc: &TestDesc) -> io::Result<()> {
110+
let name = EscapedString(desc.name.as_slice());
111+
let newline = "\n";
109112
self.writeln_message(&format!(
110-
r#"{{ "type": "test", "event": "started", "name": "{}" }}"#,
111-
EscapedString(desc.name.as_slice())
113+
r#"{{ "type": "test", "event": "started", "name": "{name}" }}{newline}"#
112114
))
113115
}
114116

@@ -173,53 +175,43 @@ impl<T: Write> OutputFormatter for JsonFormatter<T> {
173175
} else {
174176
format!(r#", "mib_per_second": {}"#, bs.mb_s)
175177
};
178+
let name = EscapedString(desc.name.as_slice());
176179

177-
let line = format!(
180+
self.writeln_message(&format!(
178181
"{{ \"type\": \"bench\", \
179-
\"name\": \"{}\", \
180-
\"median\": {}, \
181-
\"deviation\": {}{} }}",
182-
EscapedString(desc.name.as_slice()),
183-
median,
184-
deviation,
185-
mbps
186-
);
187-
188-
self.writeln_message(&line)
182+
\"name\": \"{name}\", \
183+
\"median\": {median}, \
184+
\"deviation\": {deviation}{mbps} }}\n",
185+
))
189186
}
190187
}
191188
}
192189

193190
fn write_timeout(&mut self, desc: &TestDesc) -> io::Result<()> {
191+
let name = EscapedString(desc.name.as_slice());
192+
let newline = "\n";
194193
self.writeln_message(&format!(
195-
r#"{{ "type": "test", "event": "timeout", "name": "{}" }}"#,
196-
EscapedString(desc.name.as_slice())
194+
r#"{{ "type": "test", "event": "timeout", "name": "{name}" }}{newline}"#,
197195
))
198196
}
199197

200198
fn write_run_finish(&mut self, state: &ConsoleTestState) -> io::Result<bool> {
201-
self.write_message(&format!(
202-
"{{ \"type\": \"suite\", \
203-
\"event\": \"{}\", \
204-
\"passed\": {}, \
205-
\"failed\": {}, \
206-
\"ignored\": {}, \
207-
\"measured\": {}, \
208-
\"filtered_out\": {}",
209-
if state.failed == 0 { "ok" } else { "failed" },
210-
state.passed,
211-
state.failed,
212-
state.ignored,
213-
state.measured,
214-
state.filtered_out,
215-
))?;
216-
217-
if let Some(ref exec_time) = state.exec_time {
218-
let time_str = format!(", \"exec_time\": {}", exec_time.0.as_secs_f64());
219-
self.write_message(&time_str)?;
220-
}
199+
let event = if state.failed == 0 { "ok" } else { "failed" };
200+
let passed = state.passed;
201+
let failed = state.failed;
202+
let ignored = state.ignored;
203+
let measured = state.measured;
204+
let filtered_out = state.filtered_out;
205+
let exec_time_json = if let Some(ref exec_time) = state.exec_time {
206+
format!(r#", "exec_time": {}"#, exec_time.0.as_secs_f64())
207+
} else {
208+
String::from("")
209+
};
210+
let newline = "\n";
221211

222-
self.writeln_message(" }")?;
212+
self.writeln_message(&format!(
213+
r#"{{ "type": "suite", "event": "{event}", "passed": {passed}, "failed": {failed}, "ignored": {ignored}, "measured": {measured}, "filtered_out": {filtered_out}{exec_time_json} }}{newline}"#
214+
))?;
223215

224216
Ok(state.failed == 0)
225217
}

0 commit comments

Comments
 (0)