1 | use std::{io, io::prelude::Write}; |
2 | |
3 | use super::OutputFormatter; |
4 | use crate::{ |
5 | bench::fmt_bench_samples, |
6 | console::{ConsoleTestState, OutputLocation}, |
7 | test_result::TestResult, |
8 | time, |
9 | types::TestDesc, |
10 | }; |
11 | |
12 | pub(crate) struct PrettyFormatter<T> { |
13 | out: OutputLocation<T>, |
14 | use_color: bool, |
15 | time_options: Option<time::TestTimeOptions>, |
16 | |
17 | /// Number of columns to fill when aligning names |
18 | max_name_len: usize, |
19 | |
20 | is_multithreaded: bool, |
21 | } |
22 | |
23 | impl<T: Write> PrettyFormatter<T> { |
24 | pub fn new( |
25 | out: OutputLocation<T>, |
26 | use_color: bool, |
27 | max_name_len: usize, |
28 | is_multithreaded: bool, |
29 | time_options: Option<time::TestTimeOptions>, |
30 | ) -> Self { |
31 | PrettyFormatter { out, use_color, max_name_len, is_multithreaded, time_options } |
32 | } |
33 | |
34 | #[cfg (test)] |
35 | pub fn output_location(&self) -> &OutputLocation<T> { |
36 | &self.out |
37 | } |
38 | |
39 | pub fn write_ok(&mut self) -> io::Result<()> { |
40 | self.write_short_result("ok" , term::color::GREEN) |
41 | } |
42 | |
43 | pub fn write_failed(&mut self) -> io::Result<()> { |
44 | self.write_short_result("FAILED" , term::color::RED) |
45 | } |
46 | |
47 | pub fn write_ignored(&mut self) -> io::Result<()> { |
48 | self.write_short_result("ignored" , term::color::YELLOW) |
49 | } |
50 | |
51 | pub fn write_allowed_fail(&mut self) -> io::Result<()> { |
52 | self.write_short_result("FAILED (allowed)" , term::color::YELLOW) |
53 | } |
54 | |
55 | pub fn write_time_failed(&mut self) -> io::Result<()> { |
56 | self.write_short_result("FAILED (time limit exceeded)" , term::color::RED) |
57 | } |
58 | |
59 | pub fn write_bench(&mut self) -> io::Result<()> { |
60 | self.write_pretty("bench" , term::color::CYAN) |
61 | } |
62 | |
63 | pub fn write_short_result( |
64 | &mut self, |
65 | result: &str, |
66 | color: term::color::Color, |
67 | ) -> io::Result<()> { |
68 | self.write_pretty(result, color) |
69 | } |
70 | |
71 | pub fn write_pretty(&mut self, word: &str, color: term::color::Color) -> io::Result<()> { |
72 | match self.out { |
73 | OutputLocation::Pretty(ref mut term) => { |
74 | if self.use_color { |
75 | term.fg(color)?; |
76 | } |
77 | term.write_all(word.as_bytes())?; |
78 | if self.use_color { |
79 | term.reset()?; |
80 | } |
81 | term.flush() |
82 | } |
83 | OutputLocation::Raw(ref mut stdout) => { |
84 | stdout.write_all(word.as_bytes())?; |
85 | stdout.flush() |
86 | } |
87 | } |
88 | } |
89 | |
90 | pub fn write_plain<S: AsRef<str>>(&mut self, s: S) -> io::Result<()> { |
91 | let s = s.as_ref(); |
92 | self.out.write_all(s.as_bytes())?; |
93 | self.out.flush() |
94 | } |
95 | |
96 | fn write_time( |
97 | &mut self, |
98 | desc: &TestDesc, |
99 | exec_time: Option<&time::TestExecTime>, |
100 | ) -> io::Result<()> { |
101 | if let (Some(opts), Some(time)) = (self.time_options, exec_time) { |
102 | let time_str = format!(" < {}>" , time); |
103 | |
104 | let color = if opts.colored { |
105 | if opts.is_critical(desc, time) { |
106 | Some(term::color::RED) |
107 | } else if opts.is_warn(desc, time) { |
108 | Some(term::color::YELLOW) |
109 | } else { |
110 | None |
111 | } |
112 | } else { |
113 | None |
114 | }; |
115 | |
116 | match color { |
117 | Some(color) => self.write_pretty(&time_str, color)?, |
118 | None => self.write_plain(&time_str)?, |
119 | } |
120 | } |
121 | |
122 | Ok(()) |
123 | } |
124 | |
125 | fn write_results( |
126 | &mut self, |
127 | inputs: &Vec<(TestDesc, Vec<u8>)>, |
128 | results_type: &str, |
129 | ) -> io::Result<()> { |
130 | let results_out_str = format!(" \n{}: \n" , results_type); |
131 | |
132 | self.write_plain(&results_out_str)?; |
133 | |
134 | let mut results = Vec::new(); |
135 | let mut stdouts = String::new(); |
136 | for &(ref f, ref stdout) in inputs { |
137 | results.push(f.name.to_string()); |
138 | if !stdout.is_empty() { |
139 | stdouts.push_str(&format!("---- {} stdout ---- \n" , f.name)); |
140 | let output = String::from_utf8_lossy(stdout); |
141 | stdouts.push_str(&output); |
142 | stdouts.push(' \n' ); |
143 | } |
144 | } |
145 | if !stdouts.is_empty() { |
146 | self.write_plain(" \n" )?; |
147 | self.write_plain(&stdouts)?; |
148 | } |
149 | |
150 | self.write_plain(&results_out_str)?; |
151 | results.sort(); |
152 | for name in &results { |
153 | self.write_plain(&format!(" {}\n" , name))?; |
154 | } |
155 | Ok(()) |
156 | } |
157 | |
158 | pub fn write_successes(&mut self, state: &ConsoleTestState) -> io::Result<()> { |
159 | self.write_results(&state.not_failures, "successes" ) |
160 | } |
161 | |
162 | pub fn write_failures(&mut self, state: &ConsoleTestState) -> io::Result<()> { |
163 | self.write_results(&state.failures, "failures" ) |
164 | } |
165 | |
166 | pub fn write_time_failures(&mut self, state: &ConsoleTestState) -> io::Result<()> { |
167 | self.write_results(&state.time_failures, "failures (time limit exceeded)" ) |
168 | } |
169 | |
170 | fn write_test_name(&mut self, desc: &TestDesc) -> io::Result<()> { |
171 | let name = desc.padded_name(self.max_name_len, desc.name.padding()); |
172 | self.write_plain(&format!("test {} ... " , name))?; |
173 | |
174 | Ok(()) |
175 | } |
176 | } |
177 | |
178 | impl<T: Write> OutputFormatter for PrettyFormatter<T> { |
179 | fn write_run_start(&mut self, test_count: usize) -> io::Result<()> { |
180 | let noun = if test_count != 1 { "tests" } else { "test" }; |
181 | self.write_plain(&format!(" \nrunning {} {}\n" , test_count, noun)) |
182 | } |
183 | |
184 | fn write_test_start(&mut self, desc: &TestDesc) -> io::Result<()> { |
185 | // When running tests concurrently, we should not print |
186 | // the test's name as the result will be mis-aligned. |
187 | // When running the tests serially, we print the name here so |
188 | // that the user can see which test hangs. |
189 | if !self.is_multithreaded { |
190 | self.write_test_name(desc)?; |
191 | } |
192 | |
193 | Ok(()) |
194 | } |
195 | |
196 | fn write_result( |
197 | &mut self, |
198 | desc: &TestDesc, |
199 | result: &TestResult, |
200 | exec_time: Option<&time::TestExecTime>, |
201 | _: &[u8], |
202 | _: &ConsoleTestState, |
203 | ) -> io::Result<()> { |
204 | if self.is_multithreaded { |
205 | self.write_test_name(desc)?; |
206 | } |
207 | |
208 | match *result { |
209 | TestResult::TrOk => self.write_ok()?, |
210 | TestResult::TrFailed | TestResult::TrFailedMsg(_) => self.write_failed()?, |
211 | TestResult::TrIgnored => self.write_ignored()?, |
212 | TestResult::TrAllowedFail => self.write_allowed_fail()?, |
213 | TestResult::TrBench(ref bs) => { |
214 | self.write_bench()?; |
215 | self.write_plain(&format!(": {}" , fmt_bench_samples(bs)))?; |
216 | } |
217 | TestResult::TrTimedFail => self.write_time_failed()?, |
218 | } |
219 | |
220 | self.write_time(desc, exec_time)?; |
221 | self.write_plain(" \n" ) |
222 | } |
223 | |
224 | fn write_timeout(&mut self, desc: &TestDesc) -> io::Result<()> { |
225 | if self.is_multithreaded { |
226 | self.write_test_name(desc)?; |
227 | } |
228 | |
229 | self.write_plain(&format!( |
230 | "test {} has been running for over {} seconds \n" , |
231 | desc.name, |
232 | time::TEST_WARN_TIMEOUT_S |
233 | )) |
234 | } |
235 | |
236 | fn write_run_finish(&mut self, state: &ConsoleTestState) -> io::Result<bool> { |
237 | if state.options.display_output { |
238 | self.write_successes(state)?; |
239 | } |
240 | let success = state.failed == 0; |
241 | if !success { |
242 | if !state.failures.is_empty() { |
243 | self.write_failures(state)?; |
244 | } |
245 | |
246 | if !state.time_failures.is_empty() { |
247 | self.write_time_failures(state)?; |
248 | } |
249 | } |
250 | |
251 | self.write_plain(" \ntest result: " )?; |
252 | |
253 | if success { |
254 | // There's no parallelism at this point so it's safe to use color |
255 | self.write_pretty("ok" , term::color::GREEN)?; |
256 | } else { |
257 | self.write_pretty("FAILED" , term::color::RED)?; |
258 | } |
259 | |
260 | let s = if state.allowed_fail > 0 { |
261 | format!( |
262 | ". {} passed; {} failed ( {} allowed); {} ignored; {} measured; {} filtered out" , |
263 | state.passed, |
264 | state.failed + state.allowed_fail, |
265 | state.allowed_fail, |
266 | state.ignored, |
267 | state.measured, |
268 | state.filtered_out |
269 | ) |
270 | } else { |
271 | format!( |
272 | ". {} passed; {} failed; {} ignored; {} measured; {} filtered out" , |
273 | state.passed, state.failed, state.ignored, state.measured, state.filtered_out |
274 | ) |
275 | }; |
276 | |
277 | self.write_plain(&s)?; |
278 | |
279 | if let Some(ref exec_time) = state.exec_time { |
280 | let time_str = format!("; finished in {}" , exec_time); |
281 | self.write_plain(&time_str)?; |
282 | } |
283 | |
284 | self.write_plain(" \n\n" )?; |
285 | |
286 | Ok(success) |
287 | } |
288 | } |
289 | |