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