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, 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 allowed_fail
: usize,
51 pub filtered_out
: usize,
53 pub exec_time
: Option
<TestSuiteExecTime
>,
54 pub metrics
: MetricMap
,
55 pub failures
: Vec
<(TestDesc
, Vec
<u8>)>,
56 pub not_failures
: 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
)?
),
78 metrics
: MetricMap
::new(),
80 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
<()> {
111 TestResult
::TrOk
=> "ok".to_owned(),
112 TestResult
::TrFailed
=> "failed".to_owned(),
113 TestResult
::TrFailedMsg(ref msg
) => format
!("failed: {}", msg
),
114 TestResult
::TrIgnored
=> "ignored".to_owned(),
115 TestResult
::TrAllowedFail
=> "failed (allowed)".to_owned(),
116 TestResult
::TrBench(ref bs
) => fmt_bench_samples(bs
),
117 TestResult
::TrTimedFail
=> "failed (time limit exceeded)".to_owned(),
122 if let Some(exec_time
) = exec_time
{
123 self.write_log(|| format
!(" <{}>", exec_time
))?
;
125 self.write_log(|| "\n")
128 fn current_test_count(&self) -> usize {
129 self.passed
+ self.failed
+ self.ignored
+ self.measured
+ self.allowed_fail
133 // List the tests to console, and optionally to logfile. Filters are honored.
134 pub fn list_tests_console(opts
: &TestOpts
, tests
: Vec
<TestDescAndFn
>) -> io
::Result
<()> {
135 let mut output
= match term
::stdout() {
136 None
=> OutputLocation
::Raw(io
::stdout()),
137 Some(t
) => OutputLocation
::Pretty(t
),
140 let quiet
= opts
.format
== OutputFormat
::Terse
;
141 let mut st
= ConsoleTestState
::new(opts
)?
;
146 for test
in filter_tests(&opts
, tests
) {
147 use crate::TestFn
::*;
149 let TestDescAndFn { desc: TestDesc { name, .. }
, testfn
} = test
;
151 let fntype
= match testfn
{
152 StaticTestFn(..) | DynTestFn(..) => {
156 StaticBenchFn(..) | DynBenchFn(..) => {
162 writeln
!(output
, "{}: {}", name
, fntype
)?
;
163 st
.write_log(|| format
!("{} {}\n", fntype
, name
))?
;
166 fn plural(count
: u32, s
: &str) -> String
{
168 1 => format
!("{} {}", 1, s
),
169 n
=> format
!("{} {}s", n
, s
),
174 if ntest
!= 0 || nbench
!= 0 {
178 writeln
!(output
, "{}, {}", plural(ntest
, "test"), plural(nbench
, "benchmark"))?
;
184 // Updates `ConsoleTestState` depending on result of the test execution.
185 fn handle_test_result(st
: &mut ConsoleTestState
, completed_test
: CompletedTest
) {
186 let test
= completed_test
.desc
;
187 let stdout
= completed_test
.stdout
;
188 match completed_test
.result
{
189 TestResult
::TrOk
=> {
191 st
.not_failures
.push((test
, stdout
));
193 TestResult
::TrIgnored
=> st
.ignored
+= 1,
194 TestResult
::TrAllowedFail
=> st
.allowed_fail
+= 1,
195 TestResult
::TrBench(bs
) => {
196 st
.metrics
.insert_metric(
197 test
.name
.as_slice(),
198 bs
.ns_iter_summ
.median
,
199 bs
.ns_iter_summ
.max
- bs
.ns_iter_summ
.min
,
203 TestResult
::TrFailed
=> {
205 st
.failures
.push((test
, stdout
));
207 TestResult
::TrFailedMsg(msg
) => {
209 let mut stdout
= stdout
;
210 stdout
.extend_from_slice(format
!("note: {}", msg
).as_bytes());
211 st
.failures
.push((test
, stdout
));
213 TestResult
::TrTimedFail
=> {
215 st
.time_failures
.push((test
, stdout
));
220 // Handler for events that occur during test execution.
221 // It is provided as a callback to the `run_tests` function.
224 st
: &mut ConsoleTestState
,
225 out
: &mut dyn OutputFormatter
,
226 ) -> io
::Result
<()> {
227 match (*event
).clone() {
228 TestEvent
::TeFiltered(ref filtered_tests
) => {
229 st
.total
= filtered_tests
.len();
230 out
.write_run_start(filtered_tests
.len())?
;
232 TestEvent
::TeFilteredOut(filtered_out
) => {
233 st
.filtered_out
= filtered_out
;
235 TestEvent
::TeWait(ref test
) => out
.write_test_start(test
)?
,
236 TestEvent
::TeTimeout(ref test
) => out
.write_timeout(test
)?
,
237 TestEvent
::TeResult(completed_test
) => {
238 let test
= &completed_test
.desc
;
239 let result
= &completed_test
.result
;
240 let exec_time
= &completed_test
.exec_time
;
241 let stdout
= &completed_test
.stdout
;
243 st
.write_log_result(test
, result
, exec_time
.as_ref())?
;
244 out
.write_result(test
, result
, exec_time
.as_ref(), &*stdout
, st
)?
;
245 handle_test_result(st
, completed_test
);
252 /// A simple console test runner.
253 /// Runs provided tests reporting process and results to the stdout.
254 pub fn run_tests_console(opts
: &TestOpts
, tests
: Vec
<TestDescAndFn
>) -> io
::Result
<bool
> {
255 let output
= match term
::stdout() {
256 None
=> OutputLocation
::Raw(io
::stdout()),
257 Some(t
) => OutputLocation
::Pretty(t
),
260 let max_name_len
= tests
262 .max_by_key(|t
| len_if_padded(*t
))
263 .map(|t
| t
.desc
.name
.as_slice().len())
266 let is_multithreaded
= opts
.test_threads
.unwrap_or_else(get_concurrency
) > 1;
268 let mut out
: Box
<dyn OutputFormatter
> = match opts
.format
{
269 OutputFormat
::Pretty
=> Box
::new(PrettyFormatter
::new(
276 OutputFormat
::Terse
=> {
277 Box
::new(TerseFormatter
::new(output
, opts
.use_color(), max_name_len
, is_multithreaded
))
279 OutputFormat
::Json
=> Box
::new(JsonFormatter
::new(output
)),
281 let mut st
= ConsoleTestState
::new(opts
)?
;
283 // Prevent the usage of `Instant` in some cases:
284 // - It's currently not supported for wasm targets.
285 // - We disable it for miri because it's not available when isolation is enabled.
286 let is_instant_supported
= !cfg
!(target_arch
= "wasm32") && !cfg
!(miri
);
288 let start_time
= is_instant_supported
.then(Instant
::now
);
289 run_tests(opts
, tests
, |x
| on_test_event(&x
, &mut st
, &mut *out
))?
;
290 st
.exec_time
= start_time
.map(|t
| TestSuiteExecTime(t
.elapsed()));
292 assert
!(st
.current_test_count() == st
.total
);
294 out
.write_run_finish(&st
)
297 // Calculates padding for given test description.
298 fn len_if_padded(t
: &TestDescAndFn
) -> usize {
299 match t
.testfn
.padding() {
300 NamePadding
::PadNone
=> 0,
301 NamePadding
::PadOnRight
=> t
.desc
.name
.as_slice().len(),