]>
Commit | Line | Data |
---|---|---|
dfeec247 | 1 | use std::{borrow::Cow, io, io::prelude::Write}; |
e74abb32 | 2 | |
dfeec247 | 3 | use super::OutputFormatter; |
e74abb32 | 4 | use crate::{ |
e74abb32 | 5 | console::{ConsoleTestState, OutputLocation}, |
dfeec247 XL |
6 | test_result::TestResult, |
7 | time, | |
8 | types::TestDesc, | |
e74abb32 | 9 | }; |
2c00a5a8 XL |
10 | |
11 | pub(crate) struct JsonFormatter<T> { | |
12 | out: OutputLocation<T>, | |
13 | } | |
14 | ||
15 | impl<T: Write> JsonFormatter<T> { | |
16 | pub fn new(out: OutputLocation<T>) -> Self { | |
17 | Self { out } | |
18 | } | |
19 | ||
e1599b0c | 20 | fn writeln_message(&mut self, s: &str) -> io::Result<()> { |
2c00a5a8 XL |
21 | assert!(!s.contains('\n')); |
22 | ||
23 | self.out.write_all(s.as_ref())?; | |
24 | self.out.write_all(b"\n") | |
25 | } | |
26 | ||
e1599b0c XL |
27 | fn write_message(&mut self, s: &str) -> io::Result<()> { |
28 | assert!(!s.contains('\n')); | |
29 | ||
30 | self.out.write_all(s.as_ref()) | |
31 | } | |
32 | ||
2c00a5a8 XL |
33 | fn write_event( |
34 | &mut self, | |
35 | ty: &str, | |
36 | name: &str, | |
37 | evt: &str, | |
e74abb32 | 38 | exec_time: Option<&time::TestExecTime>, |
e1599b0c XL |
39 | stdout: Option<Cow<'_, str>>, |
40 | extra: Option<&str>, | |
2c00a5a8 | 41 | ) -> io::Result<()> { |
29967ef6 | 42 | // A doc test's name includes a filename which must be escaped for correct json. |
e1599b0c XL |
43 | self.write_message(&*format!( |
44 | r#"{{ "type": "{}", "name": "{}", "event": "{}""#, | |
29967ef6 XL |
45 | ty, |
46 | EscapedString(name), | |
47 | evt | |
e1599b0c | 48 | ))?; |
e74abb32 | 49 | if let Some(exec_time) = exec_time { |
fc512014 | 50 | self.write_message(&*format!(r#", "exec_time": {}"#, exec_time.0.as_secs_f64()))?; |
e74abb32 | 51 | } |
e1599b0c | 52 | if let Some(stdout) = stdout { |
dfeec247 | 53 | self.write_message(&*format!(r#", "stdout": "{}""#, EscapedString(stdout)))?; |
e1599b0c XL |
54 | } |
55 | if let Some(extra) = extra { | |
dfeec247 | 56 | self.write_message(&*format!(r#", {}"#, extra))?; |
2c00a5a8 | 57 | } |
e1599b0c | 58 | self.writeln_message(" }") |
2c00a5a8 XL |
59 | } |
60 | } | |
61 | ||
62 | impl<T: Write> OutputFormatter for JsonFormatter<T> { | |
c295e0f8 XL |
63 | fn write_run_start(&mut self, test_count: usize, shuffle_seed: Option<u64>) -> io::Result<()> { |
64 | let shuffle_seed_json = if let Some(shuffle_seed) = shuffle_seed { | |
65 | format!(r#", "shuffle_seed": {}"#, shuffle_seed) | |
66 | } else { | |
67 | String::new() | |
68 | }; | |
e1599b0c | 69 | self.writeln_message(&*format!( |
c295e0f8 XL |
70 | r#"{{ "type": "suite", "event": "started", "test_count": {}{} }}"#, |
71 | test_count, shuffle_seed_json | |
2c00a5a8 XL |
72 | )) |
73 | } | |
74 | ||
75 | fn write_test_start(&mut self, desc: &TestDesc) -> io::Result<()> { | |
e1599b0c | 76 | self.writeln_message(&*format!( |
2c00a5a8 | 77 | r#"{{ "type": "test", "event": "started", "name": "{}" }}"#, |
29967ef6 | 78 | EscapedString(desc.name.as_slice()) |
2c00a5a8 XL |
79 | )) |
80 | } | |
81 | ||
82 | fn write_result( | |
83 | &mut self, | |
84 | desc: &TestDesc, | |
85 | result: &TestResult, | |
e74abb32 | 86 | exec_time: Option<&time::TestExecTime>, |
2c00a5a8 | 87 | stdout: &[u8], |
e1599b0c | 88 | state: &ConsoleTestState, |
2c00a5a8 | 89 | ) -> io::Result<()> { |
e74abb32 | 90 | let display_stdout = state.options.display_output || *result != TestResult::TrOk; |
74b04a01 | 91 | let stdout = if display_stdout && !stdout.is_empty() { |
e1599b0c XL |
92 | Some(String::from_utf8_lossy(stdout)) |
93 | } else { | |
94 | None | |
95 | }; | |
2c00a5a8 | 96 | match *result { |
e74abb32 XL |
97 | TestResult::TrOk => { |
98 | self.write_event("test", desc.name.as_slice(), "ok", exec_time, stdout, None) | |
99 | } | |
2c00a5a8 | 100 | |
e74abb32 XL |
101 | TestResult::TrFailed => { |
102 | self.write_event("test", desc.name.as_slice(), "failed", exec_time, stdout, None) | |
103 | } | |
2c00a5a8 | 104 | |
e74abb32 | 105 | TestResult::TrTimedFail => self.write_event( |
0531ce1d XL |
106 | "test", |
107 | desc.name.as_slice(), | |
108 | "failed", | |
e74abb32 | 109 | exec_time, |
e1599b0c | 110 | stdout, |
e74abb32 | 111 | Some(r#""reason": "time limit exceeded""#), |
0531ce1d | 112 | ), |
2c00a5a8 | 113 | |
e74abb32 XL |
114 | TestResult::TrFailedMsg(ref m) => self.write_event( |
115 | "test", | |
116 | desc.name.as_slice(), | |
117 | "failed", | |
118 | exec_time, | |
119 | stdout, | |
120 | Some(&*format!(r#""message": "{}""#, EscapedString(m))), | |
121 | ), | |
2c00a5a8 | 122 | |
04454e1e FG |
123 | TestResult::TrIgnored => self.write_event( |
124 | "test", | |
125 | desc.name.as_slice(), | |
126 | "ignored", | |
127 | exec_time, | |
128 | stdout, | |
129 | desc.ignore_message | |
130 | .map(|msg| format!(r#""message": "{}""#, EscapedString(msg))) | |
131 | .as_deref(), | |
132 | ), | |
2c00a5a8 | 133 | |
e74abb32 | 134 | TestResult::TrBench(ref bs) => { |
2c00a5a8 XL |
135 | let median = bs.ns_iter_summ.median as usize; |
136 | let deviation = (bs.ns_iter_summ.max - bs.ns_iter_summ.min) as usize; | |
137 | ||
138 | let mbps = if bs.mb_s == 0 { | |
b7449926 | 139 | String::new() |
2c00a5a8 XL |
140 | } else { |
141 | format!(r#", "mib_per_second": {}"#, bs.mb_s) | |
142 | }; | |
143 | ||
144 | let line = format!( | |
145 | "{{ \"type\": \"bench\", \ | |
0531ce1d XL |
146 | \"name\": \"{}\", \ |
147 | \"median\": {}, \ | |
148 | \"deviation\": {}{} }}", | |
29967ef6 XL |
149 | EscapedString(desc.name.as_slice()), |
150 | median, | |
151 | deviation, | |
152 | mbps | |
2c00a5a8 XL |
153 | ); |
154 | ||
e1599b0c | 155 | self.writeln_message(&*line) |
2c00a5a8 XL |
156 | } |
157 | } | |
158 | } | |
159 | ||
160 | fn write_timeout(&mut self, desc: &TestDesc) -> io::Result<()> { | |
e1599b0c | 161 | self.writeln_message(&*format!( |
2c00a5a8 | 162 | r#"{{ "type": "test", "event": "timeout", "name": "{}" }}"#, |
29967ef6 | 163 | EscapedString(desc.name.as_slice()) |
2c00a5a8 XL |
164 | )) |
165 | } | |
166 | ||
167 | fn write_run_finish(&mut self, state: &ConsoleTestState) -> io::Result<bool> { | |
fc512014 | 168 | self.write_message(&*format!( |
2c00a5a8 | 169 | "{{ \"type\": \"suite\", \ |
0531ce1d XL |
170 | \"event\": \"{}\", \ |
171 | \"passed\": {}, \ | |
172 | \"failed\": {}, \ | |
0531ce1d XL |
173 | \"ignored\": {}, \ |
174 | \"measured\": {}, \ | |
fc512014 | 175 | \"filtered_out\": {}", |
2c00a5a8 XL |
176 | if state.failed == 0 { "ok" } else { "failed" }, |
177 | state.passed, | |
5099ac24 | 178 | state.failed, |
2c00a5a8 XL |
179 | state.ignored, |
180 | state.measured, | |
fc512014 | 181 | state.filtered_out, |
2c00a5a8 XL |
182 | ))?; |
183 | ||
fc512014 XL |
184 | if let Some(ref exec_time) = state.exec_time { |
185 | let time_str = format!(", \"exec_time\": {}", exec_time.0.as_secs_f64()); | |
186 | self.write_message(&time_str)?; | |
187 | } | |
188 | ||
189 | self.writeln_message(" }")?; | |
190 | ||
2c00a5a8 XL |
191 | Ok(state.failed == 0) |
192 | } | |
193 | } | |
194 | ||
195 | /// A formatting utility used to print strings with characters in need of escaping. | |
196 | /// Base code taken form `libserialize::json::escape_str` | |
197 | struct EscapedString<S: AsRef<str>>(S); | |
198 | ||
29967ef6 XL |
199 | impl<S: AsRef<str>> std::fmt::Display for EscapedString<S> { |
200 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> ::std::fmt::Result { | |
2c00a5a8 XL |
201 | let mut start = 0; |
202 | ||
203 | for (i, byte) in self.0.as_ref().bytes().enumerate() { | |
204 | let escaped = match byte { | |
205 | b'"' => "\\\"", | |
206 | b'\\' => "\\\\", | |
207 | b'\x00' => "\\u0000", | |
208 | b'\x01' => "\\u0001", | |
209 | b'\x02' => "\\u0002", | |
210 | b'\x03' => "\\u0003", | |
211 | b'\x04' => "\\u0004", | |
212 | b'\x05' => "\\u0005", | |
213 | b'\x06' => "\\u0006", | |
214 | b'\x07' => "\\u0007", | |
215 | b'\x08' => "\\b", | |
216 | b'\t' => "\\t", | |
217 | b'\n' => "\\n", | |
218 | b'\x0b' => "\\u000b", | |
219 | b'\x0c' => "\\f", | |
220 | b'\r' => "\\r", | |
221 | b'\x0e' => "\\u000e", | |
222 | b'\x0f' => "\\u000f", | |
223 | b'\x10' => "\\u0010", | |
224 | b'\x11' => "\\u0011", | |
225 | b'\x12' => "\\u0012", | |
226 | b'\x13' => "\\u0013", | |
227 | b'\x14' => "\\u0014", | |
228 | b'\x15' => "\\u0015", | |
229 | b'\x16' => "\\u0016", | |
230 | b'\x17' => "\\u0017", | |
231 | b'\x18' => "\\u0018", | |
232 | b'\x19' => "\\u0019", | |
233 | b'\x1a' => "\\u001a", | |
234 | b'\x1b' => "\\u001b", | |
235 | b'\x1c' => "\\u001c", | |
236 | b'\x1d' => "\\u001d", | |
237 | b'\x1e' => "\\u001e", | |
238 | b'\x1f' => "\\u001f", | |
239 | b'\x7f' => "\\u007f", | |
240 | _ => { | |
241 | continue; | |
242 | } | |
243 | }; | |
244 | ||
245 | if start < i { | |
246 | f.write_str(&self.0.as_ref()[start..i])?; | |
247 | } | |
248 | ||
249 | f.write_str(escaped)?; | |
250 | ||
251 | start = i + 1; | |
252 | } | |
253 | ||
254 | if start != self.0.as_ref().len() { | |
255 | f.write_str(&self.0.as_ref()[start..])?; | |
256 | } | |
257 | ||
258 | Ok(()) | |
259 | } | |
260 | } |