1 //! Module providing interface for running tests in the console.
4 use std::io::prelude::Write;
10 bench::fmt_bench_samples,
12 event::{TestEvent, CompletedTest},
13 formatters::{JsonFormatter, OutputFormatter, PrettyFormatter, TerseFormatter},
15 concurrency::get_concurrency,
18 types::{TestDesc, TestDescAndFn, NamePadding},
19 options::{Options, OutputFormat},
20 test_result::TestResult,
26 /// Generic wrapper over stdout.
27 pub enum OutputLocation<T> {
28 Pretty(Box<term::StdoutTerminal>),
32 impl<T: Write> Write for OutputLocation<T> {
33 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
35 OutputLocation::Pretty(ref mut term) => term.write(buf),
36 OutputLocation::Raw(ref mut stdout) => stdout.write(buf),
40 fn flush(&mut self) -> io::Result<()> {
42 OutputLocation::Pretty(ref mut term) => term.flush(),
43 OutputLocation::Raw(ref mut stdout) => stdout.flush(),
48 pub struct ConsoleTestState {
49 pub log_out: Option<File>,
54 pub allowed_fail: usize,
55 pub filtered_out: usize,
57 pub metrics: MetricMap,
58 pub failures: Vec<(TestDesc, Vec<u8>)>,
59 pub not_failures: Vec<(TestDesc, Vec<u8>)>,
60 pub time_failures: Vec<(TestDesc, Vec<u8>)>,
64 impl ConsoleTestState {
65 pub fn new(opts: &TestOpts) -> io::Result<ConsoleTestState> {
66 let log_out = match opts.logfile {
67 Some(ref path) => Some(File::create(path)?),
80 metrics: MetricMap::new(),
82 not_failures: Vec::new(),
83 time_failures: Vec::new(),
84 options: opts.options,
88 pub fn write_log<F, S>(
100 let msg = msg.as_ref();
101 o.write_all(msg.as_bytes())
106 pub fn write_log_result(&mut self,test: &TestDesc,
108 exec_time: Option<&TestExecTime>,
109 ) -> io::Result<()> {
110 self.write_log(|| format!(
113 TestResult::TrOk => "ok".to_owned(),
114 TestResult::TrFailed => "failed".to_owned(),
115 TestResult::TrFailedMsg(ref msg) => format!("failed: {}", msg),
116 TestResult::TrIgnored => "ignored".to_owned(),
117 TestResult::TrAllowedFail => "failed (allowed)".to_owned(),
118 TestResult::TrBench(ref bs) => fmt_bench_samples(bs),
119 TestResult::TrTimedFail => "failed (time limit exceeded)".to_owned(),
123 if let Some(exec_time) = exec_time {
124 self.write_log(|| format!(" <{}>", exec_time))?;
126 self.write_log(|| "\n")
129 fn current_test_count(&self) -> usize {
130 self.passed + self.failed + self.ignored + self.measured + self.allowed_fail
134 // List the tests to console, and optionally to logfile. Filters are honored.
135 pub fn list_tests_console(opts: &TestOpts, tests: Vec<TestDescAndFn>) -> io::Result<()> {
136 let mut output = match term::stdout() {
137 None => OutputLocation::Raw(io::stdout()),
138 Some(t) => OutputLocation::Pretty(t),
141 let quiet = opts.format == OutputFormat::Terse;
142 let mut st = ConsoleTestState::new(opts)?;
147 for test in filter_tests(&opts, tests) {
148 use crate::TestFn::*;
151 desc: TestDesc { name, .. },
155 let fntype = match testfn {
156 StaticTestFn(..) | DynTestFn(..) => {
160 StaticBenchFn(..) | DynBenchFn(..) => {
166 writeln!(output, "{}: {}", name, fntype)?;
167 st.write_log(|| format!("{} {}\n", fntype, name))?;
170 fn plural(count: u32, s: &str) -> String {
172 1 => format!("{} {}", 1, s),
173 n => format!("{} {}s", n, s),
178 if ntest != 0 || nbench != 0 {
179 writeln!(output, "")?;
185 plural(ntest, "test"),
186 plural(nbench, "benchmark")
193 // Updates `ConsoleTestState` depending on result of the test execution.
194 fn handle_test_result(st: &mut ConsoleTestState, completed_test: CompletedTest) {
195 let test = completed_test.desc;
196 let stdout = completed_test.stdout;
197 match completed_test.result {
198 TestResult::TrOk => {
200 st.not_failures.push((test, stdout));
202 TestResult::TrIgnored => st.ignored += 1,
203 TestResult::TrAllowedFail => st.allowed_fail += 1,
204 TestResult::TrBench(bs) => {
205 st.metrics.insert_metric(
206 test.name.as_slice(),
207 bs.ns_iter_summ.median,
208 bs.ns_iter_summ.max - bs.ns_iter_summ.min,
212 TestResult::TrFailed => {
214 st.failures.push((test, stdout));
216 TestResult::TrFailedMsg(msg) => {
218 let mut stdout = stdout;
219 stdout.extend_from_slice(format!("note: {}", msg).as_bytes());
220 st.failures.push((test, stdout));
222 TestResult::TrTimedFail => {
224 st.time_failures.push((test, stdout));
229 // Handler for events that occur during test execution.
230 // It is provided as a callback to the `run_tests` function.
233 st: &mut ConsoleTestState,
234 out: &mut dyn OutputFormatter,
235 ) -> io::Result<()> {
236 match (*event).clone() {
237 TestEvent::TeFiltered(ref filtered_tests) => {
238 st.total = filtered_tests.len();
239 out.write_run_start(filtered_tests.len())?;
241 TestEvent::TeFilteredOut(filtered_out) => {
242 st.filtered_out = filtered_out;
244 TestEvent::TeWait(ref test) => out.write_test_start(test)?,
245 TestEvent::TeTimeout(ref test) => out.write_timeout(test)?,
246 TestEvent::TeResult(completed_test) => {
247 let test = &completed_test.desc;
248 let result = &completed_test.result;
249 let exec_time = &completed_test.exec_time;
250 let stdout = &completed_test.stdout;
252 st.write_log_result(test, result, exec_time.as_ref())?;
253 out.write_result(test, result, exec_time.as_ref(), &*stdout, st)?;
254 handle_test_result(st, completed_test);
261 /// A simple console test runner.
262 /// Runs provided tests reporting process and results to the stdout.
263 pub fn run_tests_console(opts: &TestOpts, tests: Vec<TestDescAndFn>) -> io::Result<bool> {
264 let output = match term::stdout() {
265 None => OutputLocation::Raw(io::stdout()),
266 Some(t) => OutputLocation::Pretty(t),
269 let max_name_len = tests
271 .max_by_key(|t| len_if_padded(*t))
272 .map(|t| t.desc.name.as_slice().len())
275 let is_multithreaded = opts.test_threads.unwrap_or_else(get_concurrency) > 1;
277 let mut out: Box<dyn OutputFormatter> = match opts.format {
278 OutputFormat::Pretty => Box::new(PrettyFormatter::new(
285 OutputFormat::Terse => Box::new(TerseFormatter::new(
291 OutputFormat::Json => Box::new(JsonFormatter::new(output)),
293 let mut st = ConsoleTestState::new(opts)?;
295 run_tests(opts, tests, |x| on_test_event(&x, &mut st, &mut *out))?;
297 assert!(st.current_test_count() == st.total);
299 out.write_run_finish(&st)
302 // Calculates padding for given test description.
303 fn len_if_padded(t: &TestDescAndFn) -> usize {
304 match t.testfn.padding() {
305 NamePadding::PadNone => 0,
306 NamePadding::PadOnRight => t.desc.name.as_slice().len(),