@@ -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
6259impl < 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