lit/event_handler/
default.rs

1use crate::{util, Config, model::*};
2
3use itertools::Itertools;
4use std::io;
5use std::io::prelude::*;
6use term;
7
8/// The default event handler, logging to stdout/stderr.
9pub struct EventHandler {
10    test_results: Vec<TestResult>,
11}
12
13impl EventHandler {
14    /// Creates a new default event handler.
15    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(); // our white might not match initial console white. we should be consistent.
29
30        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        // Sort the test results so that they will be consecutive.
39        // This is required for itertools group_by used before to work properly.
40        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); // indent the errors.
59                self::result(failed_test_result, false, config);
60            }
61        }
62
63        print::test_suite_status_message(passed, true, &self.test_results);
64
65        // 'cargo test' will use the color we last emitted if we don't do this.
66        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            // FIXME: improve formatting
112
113            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