]>
Commit | Line | Data |
---|---|---|
e74abb32 XL |
1 | //! Module providing interface for running tests in the console. |
2 | ||
3 | use std::fs::File; | |
e74abb32 | 4 | use std::io; |
dfeec247 | 5 | use std::io::prelude::Write; |
e74abb32 | 6 | |
e74abb32 XL |
7 | use super::{ |
8 | bench::fmt_bench_samples, | |
9 | cli::TestOpts, | |
dfeec247 XL |
10 | event::{CompletedTest, TestEvent}, |
11 | filter_tests, | |
e74abb32 | 12 | formatters::{JsonFormatter, OutputFormatter, PrettyFormatter, TerseFormatter}, |
dfeec247 | 13 | helpers::{concurrency::get_concurrency, metrics::MetricMap}, |
e74abb32 | 14 | options::{Options, OutputFormat}, |
dfeec247 | 15 | run_tests, |
e74abb32 XL |
16 | test_result::TestResult, |
17 | time::TestExecTime, | |
dfeec247 | 18 | types::{NamePadding, TestDesc, TestDescAndFn}, |
e74abb32 XL |
19 | }; |
20 | ||
21 | /// Generic wrapper over stdout. | |
22 | pub enum OutputLocation<T> { | |
23 | Pretty(Box<term::StdoutTerminal>), | |
24 | Raw(T), | |
25 | } | |
26 | ||
27 | impl<T: Write> Write for OutputLocation<T> { | |
28 | fn write(&mut self, buf: &[u8]) -> io::Result<usize> { | |
29 | match *self { | |
30 | OutputLocation::Pretty(ref mut term) => term.write(buf), | |
31 | OutputLocation::Raw(ref mut stdout) => stdout.write(buf), | |
32 | } | |
33 | } | |
34 | ||
35 | fn flush(&mut self) -> io::Result<()> { | |
36 | match *self { | |
37 | OutputLocation::Pretty(ref mut term) => term.flush(), | |
38 | OutputLocation::Raw(ref mut stdout) => stdout.flush(), | |
39 | } | |
40 | } | |
41 | } | |
42 | ||
43 | pub struct ConsoleTestState { | |
44 | pub log_out: Option<File>, | |
45 | pub total: usize, | |
46 | pub passed: usize, | |
47 | pub failed: usize, | |
48 | pub ignored: usize, | |
49 | pub allowed_fail: usize, | |
50 | pub filtered_out: usize, | |
51 | pub measured: usize, | |
52 | pub metrics: MetricMap, | |
53 | pub failures: Vec<(TestDesc, Vec<u8>)>, | |
54 | pub not_failures: Vec<(TestDesc, Vec<u8>)>, | |
55 | pub time_failures: Vec<(TestDesc, Vec<u8>)>, | |
56 | pub options: Options, | |
57 | } | |
58 | ||
59 | impl ConsoleTestState { | |
60 | pub fn new(opts: &TestOpts) -> io::Result<ConsoleTestState> { | |
61 | let log_out = match opts.logfile { | |
62 | Some(ref path) => Some(File::create(path)?), | |
63 | None => None, | |
64 | }; | |
65 | ||
66 | Ok(ConsoleTestState { | |
67 | log_out, | |
68 | total: 0, | |
69 | passed: 0, | |
70 | failed: 0, | |
71 | ignored: 0, | |
72 | allowed_fail: 0, | |
73 | filtered_out: 0, | |
74 | measured: 0, | |
75 | metrics: MetricMap::new(), | |
76 | failures: Vec::new(), | |
77 | not_failures: Vec::new(), | |
78 | time_failures: Vec::new(), | |
79 | options: opts.options, | |
80 | }) | |
81 | } | |
82 | ||
dfeec247 | 83 | pub fn write_log<F, S>(&mut self, msg: F) -> io::Result<()> |
e74abb32 XL |
84 | where |
85 | S: AsRef<str>, | |
86 | F: FnOnce() -> S, | |
87 | { | |
88 | match self.log_out { | |
89 | None => Ok(()), | |
90 | Some(ref mut o) => { | |
91 | let msg = msg(); | |
92 | let msg = msg.as_ref(); | |
93 | o.write_all(msg.as_bytes()) | |
dfeec247 | 94 | } |
e74abb32 XL |
95 | } |
96 | } | |
97 | ||
dfeec247 XL |
98 | pub fn write_log_result( |
99 | &mut self, | |
100 | test: &TestDesc, | |
e74abb32 XL |
101 | result: &TestResult, |
102 | exec_time: Option<&TestExecTime>, | |
103 | ) -> io::Result<()> { | |
dfeec247 XL |
104 | self.write_log(|| { |
105 | format!( | |
106 | "{} {}", | |
107 | match *result { | |
108 | TestResult::TrOk => "ok".to_owned(), | |
109 | TestResult::TrFailed => "failed".to_owned(), | |
110 | TestResult::TrFailedMsg(ref msg) => format!("failed: {}", msg), | |
111 | TestResult::TrIgnored => "ignored".to_owned(), | |
112 | TestResult::TrAllowedFail => "failed (allowed)".to_owned(), | |
113 | TestResult::TrBench(ref bs) => fmt_bench_samples(bs), | |
114 | TestResult::TrTimedFail => "failed (time limit exceeded)".to_owned(), | |
115 | }, | |
116 | test.name, | |
117 | ) | |
118 | })?; | |
e74abb32 XL |
119 | if let Some(exec_time) = exec_time { |
120 | self.write_log(|| format!(" <{}>", exec_time))?; | |
121 | } | |
122 | self.write_log(|| "\n") | |
123 | } | |
124 | ||
125 | fn current_test_count(&self) -> usize { | |
126 | self.passed + self.failed + self.ignored + self.measured + self.allowed_fail | |
127 | } | |
128 | } | |
129 | ||
130 | // List the tests to console, and optionally to logfile. Filters are honored. | |
131 | pub fn list_tests_console(opts: &TestOpts, tests: Vec<TestDescAndFn>) -> io::Result<()> { | |
132 | let mut output = match term::stdout() { | |
133 | None => OutputLocation::Raw(io::stdout()), | |
134 | Some(t) => OutputLocation::Pretty(t), | |
135 | }; | |
136 | ||
137 | let quiet = opts.format == OutputFormat::Terse; | |
138 | let mut st = ConsoleTestState::new(opts)?; | |
139 | ||
140 | let mut ntest = 0; | |
141 | let mut nbench = 0; | |
142 | ||
143 | for test in filter_tests(&opts, tests) { | |
144 | use crate::TestFn::*; | |
145 | ||
dfeec247 | 146 | let TestDescAndFn { desc: TestDesc { name, .. }, testfn } = test; |
e74abb32 XL |
147 | |
148 | let fntype = match testfn { | |
149 | StaticTestFn(..) | DynTestFn(..) => { | |
150 | ntest += 1; | |
151 | "test" | |
152 | } | |
153 | StaticBenchFn(..) | DynBenchFn(..) => { | |
154 | nbench += 1; | |
155 | "benchmark" | |
156 | } | |
157 | }; | |
158 | ||
159 | writeln!(output, "{}: {}", name, fntype)?; | |
160 | st.write_log(|| format!("{} {}\n", fntype, name))?; | |
161 | } | |
162 | ||
163 | fn plural(count: u32, s: &str) -> String { | |
164 | match count { | |
165 | 1 => format!("{} {}", 1, s), | |
166 | n => format!("{} {}s", n, s), | |
167 | } | |
168 | } | |
169 | ||
170 | if !quiet { | |
171 | if ntest != 0 || nbench != 0 { | |
ba9703b0 | 172 | writeln!(output)?; |
e74abb32 XL |
173 | } |
174 | ||
dfeec247 | 175 | writeln!(output, "{}, {}", plural(ntest, "test"), plural(nbench, "benchmark"))?; |
e74abb32 XL |
176 | } |
177 | ||
178 | Ok(()) | |
179 | } | |
180 | ||
181 | // Updates `ConsoleTestState` depending on result of the test execution. | |
182 | fn handle_test_result(st: &mut ConsoleTestState, completed_test: CompletedTest) { | |
183 | let test = completed_test.desc; | |
184 | let stdout = completed_test.stdout; | |
185 | match completed_test.result { | |
186 | TestResult::TrOk => { | |
187 | st.passed += 1; | |
188 | st.not_failures.push((test, stdout)); | |
189 | } | |
190 | TestResult::TrIgnored => st.ignored += 1, | |
191 | TestResult::TrAllowedFail => st.allowed_fail += 1, | |
192 | TestResult::TrBench(bs) => { | |
193 | st.metrics.insert_metric( | |
194 | test.name.as_slice(), | |
195 | bs.ns_iter_summ.median, | |
196 | bs.ns_iter_summ.max - bs.ns_iter_summ.min, | |
197 | ); | |
198 | st.measured += 1 | |
199 | } | |
200 | TestResult::TrFailed => { | |
201 | st.failed += 1; | |
202 | st.failures.push((test, stdout)); | |
203 | } | |
204 | TestResult::TrFailedMsg(msg) => { | |
205 | st.failed += 1; | |
206 | let mut stdout = stdout; | |
207 | stdout.extend_from_slice(format!("note: {}", msg).as_bytes()); | |
208 | st.failures.push((test, stdout)); | |
209 | } | |
210 | TestResult::TrTimedFail => { | |
211 | st.failed += 1; | |
212 | st.time_failures.push((test, stdout)); | |
213 | } | |
214 | } | |
215 | } | |
216 | ||
217 | // Handler for events that occur during test execution. | |
218 | // It is provided as a callback to the `run_tests` function. | |
219 | fn on_test_event( | |
220 | event: &TestEvent, | |
221 | st: &mut ConsoleTestState, | |
222 | out: &mut dyn OutputFormatter, | |
223 | ) -> io::Result<()> { | |
224 | match (*event).clone() { | |
225 | TestEvent::TeFiltered(ref filtered_tests) => { | |
226 | st.total = filtered_tests.len(); | |
227 | out.write_run_start(filtered_tests.len())?; | |
228 | } | |
229 | TestEvent::TeFilteredOut(filtered_out) => { | |
230 | st.filtered_out = filtered_out; | |
231 | } | |
232 | TestEvent::TeWait(ref test) => out.write_test_start(test)?, | |
233 | TestEvent::TeTimeout(ref test) => out.write_timeout(test)?, | |
234 | TestEvent::TeResult(completed_test) => { | |
235 | let test = &completed_test.desc; | |
236 | let result = &completed_test.result; | |
237 | let exec_time = &completed_test.exec_time; | |
238 | let stdout = &completed_test.stdout; | |
239 | ||
240 | st.write_log_result(test, result, exec_time.as_ref())?; | |
241 | out.write_result(test, result, exec_time.as_ref(), &*stdout, st)?; | |
242 | handle_test_result(st, completed_test); | |
243 | } | |
244 | } | |
245 | ||
246 | Ok(()) | |
247 | } | |
248 | ||
249 | /// A simple console test runner. | |
250 | /// Runs provided tests reporting process and results to the stdout. | |
251 | pub fn run_tests_console(opts: &TestOpts, tests: Vec<TestDescAndFn>) -> io::Result<bool> { | |
252 | let output = match term::stdout() { | |
253 | None => OutputLocation::Raw(io::stdout()), | |
254 | Some(t) => OutputLocation::Pretty(t), | |
255 | }; | |
256 | ||
257 | let max_name_len = tests | |
258 | .iter() | |
259 | .max_by_key(|t| len_if_padded(*t)) | |
260 | .map(|t| t.desc.name.as_slice().len()) | |
261 | .unwrap_or(0); | |
262 | ||
263 | let is_multithreaded = opts.test_threads.unwrap_or_else(get_concurrency) > 1; | |
264 | ||
265 | let mut out: Box<dyn OutputFormatter> = match opts.format { | |
266 | OutputFormat::Pretty => Box::new(PrettyFormatter::new( | |
267 | output, | |
268 | opts.use_color(), | |
269 | max_name_len, | |
270 | is_multithreaded, | |
271 | opts.time_options, | |
272 | )), | |
dfeec247 XL |
273 | OutputFormat::Terse => { |
274 | Box::new(TerseFormatter::new(output, opts.use_color(), max_name_len, is_multithreaded)) | |
275 | } | |
e74abb32 XL |
276 | OutputFormat::Json => Box::new(JsonFormatter::new(output)), |
277 | }; | |
278 | let mut st = ConsoleTestState::new(opts)?; | |
279 | ||
280 | run_tests(opts, tests, |x| on_test_event(&x, &mut st, &mut *out))?; | |
281 | ||
282 | assert!(st.current_test_count() == st.total); | |
283 | ||
284 | out.write_run_finish(&st) | |
285 | } | |
286 | ||
287 | // Calculates padding for given test description. | |
288 | fn len_if_padded(t: &TestDescAndFn) -> usize { | |
289 | match t.testfn.padding() { | |
290 | NamePadding::PadNone => 0, | |
291 | NamePadding::PadOnRight => t.desc.name.as_slice().len(), | |
292 | } | |
293 | } |