1use crate::{util, Config, model::*};
2
3use itertools::Itertools;
4use std::io;
5use std::io::prelude::*;
6use term;
7
8pub struct EventHandler {
10 test_results: Vec<TestResult>,
11}
12
13impl EventHandler {
14 pub fn new() -> Self {
16 EventHandler { test_results: Vec::new() }
17 }
18}
19
20impl std::default::Default for EventHandler {
21 fn default() -> Self {
22 EventHandler::new()
23 }
24}
25
26impl super::EventHandler for EventHandler {
27 fn on_test_suite_started(&mut self, suite_details: &super::TestSuiteDetails, _: &Config) {
28 print::reset_colors(); print::line();
31 print::horizontal_rule();
32 print::textln(format!("Running tests ({} files)", suite_details.number_of_test_files));
33 print::horizontal_rule();
34 print::line();
35 }
36
37 fn on_test_suite_finished(&mut self, passed: bool, config: &Config) {
38 self.test_results.sort_by_key(|r| r.overall_result.human_label_pluralized());
41
42 print::line();
43 print::textln("finished running tests");
44 print::test_suite_status_message(passed, false, &self.test_results);
45 print::line();
46 print::horizontal_rule();
47 print::horizontal_rule();
48 print::line();
49
50 if !passed {
51 let failed_results = self.test_results.iter().filter(|r| r.overall_result.is_erroneous()).collect::<Vec<_>>();
52
53 print::line();
54 print::textln_colored(format!("Failing tests ({}/{}):", failed_results.len(), self.test_results.len()), print::YELLOW);
55 print::line();
56
57 for failed_test_result in failed_results.iter() {
58 print::with(" ", print::StdStream::Err, print::RED); self::result(failed_test_result, false, config);
60 }
61 }
62
63 print::test_suite_status_message(passed, true, &self.test_results);
64
65 print::reset_colors();
67 }
68
69 fn on_test_finished(&mut self, result: TestResult, config: &Config) {
70 self::result(&result, true, config);
71
72 self.test_results.push(result);
73 }
74
75 fn note_warning(&mut self, message: &str) {
76 print::warning(message);
77 }
78}
79
80pub fn result(result: &TestResult, verbose: bool, config: &Config) {
81 match result.overall_result {
82 TestResultKind::Pass => {
83 print::success(format!("PASS :: {}", result.path.relative.display()));
84 },
85 TestResultKind::UnexpectedPass => {
86 print::failure(format!("UNEXPECTED PASS :: {}", result.path.relative.display()));
87 },
88 TestResultKind::Skip => {
89 print::line();
90 print::warning(format!(
91 "SKIP :: {} (test does not contain any test commands, perhaps you meant to add a 'CHECK'?)",
92 result.path.relative.display()));
93 print::line();
94 },
95 TestResultKind::Error { ref message } => {
96 if verbose { print::line(); }
97
98 print::error(format!("ERROR :: {}", result.path.relative.display()));
99
100 if verbose {
101 print::textln(message);
102
103 print::line();
104 }
105 }
106 TestResultKind::Fail { ref reason, ref hint } => {
107 if verbose { print::line(); }
108
109 print::failure(format!("FAIL :: {}", result.path.relative.display()));
110
111 if verbose {
114 print::line();
115 print::text("test failed: ");
116 print::textln_colored(reason.human_summary(), print::RED);
117 print::line();
118 print::textln(reason.human_detail_message(config));
119
120 if let Some(hint_text) = hint {
121 print::textln(format!("hint: {}", hint_text));
122 }
123
124 print::line();
125 }
126 },
127 TestResultKind::ExpectedFailure { .. } => {
128 print::warning(format!("XFAIL :: {}", result.path.relative.display()));
129 },
130 TestResultKind::EmptyTest { .. } => {
131 print::error(format!("EMPTY TEST :: {}", result.path.relative.display()));
132 },
133 }
134
135 if verbose && (result.overall_result.is_erroneous() || config.always_show_stderr) {
136 for individual_run_result in result.individual_run_results.iter() {
137 let (_, _, command_line, output) = individual_run_result;
138
139 let formatted_stderr = crate::model::format_test_output("stderr", &output.stderr, 1, util::TruncateDirection::Bottom, config);
140 if !output.stderr.is_empty() {
141 print::textln(format!("NOTE: the program '{}' emitted text on standard error:", command_line));
142 print::line();
143 print::textln(formatted_stderr);
144 print::line();
145 }
146 }
147 }
148}
149
150mod print {
151 pub use term::color::*;
152 use super::*;
153
154 #[derive(Copy, Clone)]
155 pub enum StdStream { Out, Err }
156
157 pub fn line() {
158 with("\n",
159 StdStream::Out,
160 term::color::WHITE);
161 }
162
163 pub fn horizontal_rule() {
164 with("=================================================================\n",
165 StdStream::Out,
166 term::color::WHITE);
167 }
168
169 pub fn textln<S>(msg: S)
170 where S: Into<String> {
171 text(format!("{}\n", msg.into()))
172 }
173
174 pub fn text<S>(msg: S)
175 where S: Into<String> {
176 with(format!("{}", msg.into()),
177 StdStream::Out,
178 term::color::WHITE);
179 }
180
181
182 pub fn textln_colored<S>(msg: S, color: u32)
183 where S: Into<String> {
184 with(format!("{}\n", msg.into()),
185 StdStream::Out,
186 color);
187 }
188
189
190 pub fn success<S>(msg: S)
191 where S: Into<String> {
192 with(format!("{}\n", msg.into()),
193 StdStream::Out,
194 term::color::GREEN);
195 }
196
197 pub fn warning<S>(msg: S)
198 where S: Into<String> {
199 with(format!("{}\n", msg.into()),
200 StdStream::Err,
201 term::color::YELLOW);
202 }
203
204 pub fn error<S>(msg: S)
205 where S: Into<String> {
206 with(format!("{}\n", msg.into()),
207 StdStream::Err,
208 term::color::RED);
209 }
210
211 pub fn failure<S>(msg: S)
212 where S: Into<String> {
213 with(format!("{}\n", msg.into()),
214 StdStream::Err,
215 term::color::MAGENTA);
216 }
217
218 pub fn test_suite_status_message(passed: bool, verbose: bool, test_results: &[TestResult]) {
219 if verbose {
220 self::line();
221 self::horizontal_rule();
222 }
223
224 if verbose {
225 self::textln("Suite Status:");
226 self::line();
227
228 for (result_label, corresponding_results) in &test_results.iter().group_by(|r| r.overall_result.human_label_pluralized()) {
229 self::textln(format!(" {}: {}", result_label, corresponding_results.count()));
230 }
231
232 self::line();
233 self::horizontal_rule();
234 self::line();
235 }
236
237 match passed {
238 true => self::success("all tests succeeded"),
239 false => self::error("error: tests failed"),
240 }
241 }
242
243 pub fn with<S>(msg: S,
244 stream: StdStream,
245 color: term::color::Color)
246 where S: Into<String> {
247 set_color(Some(msg), stream, color);
248 reset_colors();
249 }
250
251 pub fn set_color<S>(msg: Option<S>,
252 stream: StdStream,
253 color: term::color::Color)
254 where S: Into<String> {
255
256
257 match stream {
258 StdStream::Out => {
259 let stdout_term_color = term::stdout().and_then(|mut t| if let Ok(()) = t.fg(color) { Some(t) } else { None });
260
261 if let Some(mut color_term) = stdout_term_color {
262 if let Some(msg) = msg {
263 write!(color_term, "{}", msg.into()).unwrap();
264 }
265 } else {
266 if let Some(msg) = msg {
267 write!(io::stdout(), "{}", msg.into()).unwrap();
268 }
269 }
270 },
271 StdStream::Err => {
272 let stderr_term_color = term::stderr().and_then(|mut t| if let Ok(()) = t.fg(color) { Some(t) } else { None });
273
274 if let Some(mut color_term) = stderr_term_color {
275 if let Some(msg) = msg {
276 write!(color_term, "{}", msg.into()).unwrap();
277 }
278 } else {
279 if let Some(msg) = msg {
280 write!(io::stderr(), "{}", msg.into()).unwrap();
281 }
282 }
283 },
284 }
285 }
286
287 pub fn reset_colors() {
288 for stream in [StdStream::Out, StdStream::Err].iter().cloned() {
289 set_color::<String>(None, stream, term::color::WHITE);
290 }
291 }
292}
293