1 //! Module providing interface for running tests in the console.
5 use std::io::prelude::Write;
6 use std::time::Instant;
9 bench::fmt_bench_samples,
11 event::{CompletedTest, TestEvent},
13 formatters::{JsonFormatter, JunitFormatter, OutputFormatter, PrettyFormatter, TerseFormatter},
14 helpers::{concurrency::get_concurrency, metrics::MetricMap},
15 options::{Options, OutputFormat},
17 test_result::TestResult,
18 time::{TestExecTime, TestSuiteExecTime},
19 types::{NamePadding, TestDesc, TestDescAndFn},
22 /// Generic wrapper over stdout.
23 pub enum OutputLocation<T> {
24 Pretty(Box<term::StdoutTerminal>),
28 impl<T: Write> Write for OutputLocation<T> {
29 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
31 OutputLocation::Pretty(ref mut term) => term.write(buf),
32 OutputLocation::Raw(ref mut stdout) => stdout.write(buf),
36 fn flush(&mut self) -> io::Result<()> {
38 OutputLocation::Pretty(ref mut term) => term.flush(),
39 OutputLocation::Raw(ref mut stdout) => stdout.flush(),
44 pub struct ConsoleTestState {
45 pub log_out: Option<File>,
50 pub filtered_out: usize,
52 pub exec_time: Option<TestSuiteExecTime>,
53 pub metrics: MetricMap,
54 pub failures: Vec<(TestDesc, Vec<u8>)>,
55 pub not_failures: Vec<(TestDesc, Vec<u8>)>,
56 pub ignores: Vec<(TestDesc, Vec<u8>)>,
57 pub time_failures: Vec<(TestDesc, Vec<u8>)>,
61 impl ConsoleTestState {
62 pub fn new(opts: &TestOpts) -> io::Result<ConsoleTestState> {
63 let log_out = match opts.logfile {
64 Some(ref path) => Some(File::create(path)?),
77 metrics: MetricMap::new(),
79 not_failures: Vec::new(),
81 time_failures: Vec::new(),
82 options: opts.options,
86 pub fn write_log<F, S>(&mut self, msg: F) -> io::Result<()>
95 let msg = msg.as_ref();
96 o.write_all(msg.as_bytes())
101 pub fn write_log_result(
105 exec_time: Option<&TestExecTime>,
106 ) -> io::Result<()> {
108 let TestDesc { name, ignore_message, .. } = test;
112 TestResult::TrOk => "ok".to_owned(),
113 TestResult::TrFailed => "failed".to_owned(),
114 TestResult::TrFailedMsg(ref msg) => format!("failed: {msg}"),
115 TestResult::TrIgnored => {
116 if let Some(msg) = ignore_message {
117 format!("ignored: {msg}")
122 TestResult::TrBench(ref bs) => fmt_bench_samples(bs),
123 TestResult::TrTimedFail => "failed (time limit exceeded)".to_owned(),
128 if let Some(exec_time) = exec_time {
129 self.write_log(|| format!(" <{exec_time}>"))?;
131 self.write_log(|| "\n")
134 fn current_test_count(&self) -> usize {
135 self.passed + self.failed + self.ignored + self.measured
139 // List the tests to console, and optionally to logfile. Filters are honored.
140 pub fn list_tests_console(opts: &TestOpts, tests: Vec<TestDescAndFn>) -> io::Result<()> {
141 let mut output = match term::stdout() {
142 None => OutputLocation::Raw(io::stdout().lock()),
143 Some(t) => OutputLocation::Pretty(t),
146 let quiet = opts.format == OutputFormat::Terse;
147 let mut st = ConsoleTestState::new(opts)?;
152 for test in filter_tests(opts, tests).into_iter() {
153 use crate::TestFn::*;
155 let TestDescAndFn { desc: TestDesc { name, .. }, testfn } = test;
157 let fntype = match testfn {
158 StaticTestFn(..) | DynTestFn(..) => {
162 StaticBenchFn(..) | DynBenchFn(..) => {
168 writeln!(output, "{name}: {fntype}")?;
169 st.write_log(|| format!("{fntype} {name}\n"))?;
172 fn plural(count: u32, s: &str) -> String {
174 1 => format!("1 {s}"),
175 n => format!("{n} {s}s"),
180 if ntest != 0 || nbench != 0 {
184 writeln!(output, "{}, {}", plural(ntest, "test"), plural(nbench, "benchmark"))?;
190 // Updates `ConsoleTestState` depending on result of the test execution.
191 fn handle_test_result(st: &mut ConsoleTestState, completed_test: CompletedTest) {
192 let test = completed_test.desc;
193 let stdout = completed_test.stdout;
194 match completed_test.result {
195 TestResult::TrOk => {
197 st.not_failures.push((test, stdout));
199 TestResult::TrIgnored => {
201 st.ignores.push((test, stdout));
203 TestResult::TrBench(bs) => {
204 st.metrics.insert_metric(
205 test.name.as_slice(),
206 bs.ns_iter_summ.median,
207 bs.ns_iter_summ.max - bs.ns_iter_summ.min,
211 TestResult::TrFailed => {
213 st.failures.push((test, stdout));
215 TestResult::TrFailedMsg(msg) => {
217 let mut stdout = stdout;
218 stdout.extend_from_slice(format!("note: {msg}").as_bytes());
219 st.failures.push((test, stdout));
221 TestResult::TrTimedFail => {
223 st.time_failures.push((test, stdout));
228 // Handler for events that occur during test execution.
229 // It is provided as a callback to the `run_tests` function.
232 st: &mut ConsoleTestState,
233 out: &mut dyn OutputFormatter,
234 ) -> io::Result<()> {
235 match (*event).clone() {
236 TestEvent::TeFiltered(filtered_tests, shuffle_seed) => {
237 st.total = filtered_tests;
238 out.write_run_start(filtered_tests, shuffle_seed)?;
240 TestEvent::TeFilteredOut(filtered_out) => {
241 st.filtered_out = filtered_out;
243 TestEvent::TeWait(ref test) => out.write_test_start(test)?,
244 TestEvent::TeTimeout(ref test) => out.write_timeout(test)?,
245 TestEvent::TeResult(completed_test) => {
246 let test = &completed_test.desc;
247 let result = &completed_test.result;
248 let exec_time = &completed_test.exec_time;
249 let stdout = &completed_test.stdout;
251 st.write_log_result(test, result, exec_time.as_ref())?;
252 out.write_result(test, result, exec_time.as_ref(), stdout, st)?;
253 handle_test_result(st, completed_test);
260 /// A simple console test runner.
261 /// Runs provided tests reporting process and results to the stdout.
262 pub fn run_tests_console(opts: &TestOpts, tests: Vec<TestDescAndFn>) -> io::Result<bool> {
263 let output = match term::stdout() {
264 None => OutputLocation::Raw(io::stdout()),
265 Some(t) => OutputLocation::Pretty(t),
268 let max_name_len = tests
270 .max_by_key(|t| len_if_padded(t))
271 .map(|t| t.desc.name.as_slice().len())
274 let is_multithreaded = opts.test_threads.unwrap_or_else(get_concurrency) > 1;
276 let mut out: Box<dyn OutputFormatter> = match opts.format {
277 OutputFormat::Pretty => Box::new(PrettyFormatter::new(
284 OutputFormat::Terse => {
285 Box::new(TerseFormatter::new(output, opts.use_color(), max_name_len, is_multithreaded))
287 OutputFormat::Json => Box::new(JsonFormatter::new(output)),
288 OutputFormat::Junit => Box::new(JunitFormatter::new(output)),
290 let mut st = ConsoleTestState::new(opts)?;
292 // Prevent the usage of `Instant` in some cases:
293 // - It's currently not supported for wasm targets.
294 // - We disable it for miri because it's not available when isolation is enabled.
295 let is_instant_supported = !cfg!(target_family = "wasm") && !cfg!(miri);
297 let start_time = is_instant_supported.then(Instant::now);
298 run_tests(opts, tests, |x| on_test_event(&x, &mut st, &mut *out))?;
299 st.exec_time = start_time.map(|t| TestSuiteExecTime(t.elapsed()));
301 assert!(opts.fail_fast || st.current_test_count() == st.total);
303 out.write_run_finish(&st)
306 // Calculates padding for given test description.
307 fn len_if_padded(t: &TestDescAndFn) -> usize {
308 match t.testfn.padding() {
309 NamePadding::PadNone => 0,
310 NamePadding::PadOnRight => t.desc.name.as_slice().len(),