extern crate crossbeam_channel;
extern crate rayon;
#[macro_use]
extern crate structopt;
extern crate termcolor;
use std::{
process,
};
use rayon::prelude::*;
mod args;
mod printer;
pub use args::{Arguments, ColorSetting, FormatSetting};
#[derive(Clone, Debug)]
pub struct Test<D = ()> {
pub name: String,
pub kind: String,
pub is_ignored: bool,
pub is_bench: bool,
pub data: D,
}
impl<D: Default> Test<D> {
pub fn test(name: impl Into<String>) -> Self {
Self {
name: name.into(),
kind: String::new(),
is_ignored: false,
is_bench: false,
data: D::default(),
}
}
pub fn bench(name: impl Into<String>) -> Self {
Self {
name: name.into(),
kind: String::new(),
is_ignored: false,
is_bench: true,
data: D::default(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Outcome {
Passed,
Failed {
msg: Option<String>,
},
Ignored,
Measured {
avg: u64,
variance: u64,
},
}
#[derive(Debug)]
pub enum RunnerEvent<D> {
Started {
name: String,
kind: String,
},
Completed {
test: Test<D>,
outcome: Outcome,
},
}
#[derive(Clone, Debug)]
#[must_use]
pub struct Conclusion {
has_failed: bool,
num_filtered_out: u64,
num_passed: u64,
num_failed: u64,
num_ignored: u64,
num_benches: u64,
}
impl Conclusion {
pub fn exit(&self) -> ! {
self.exit_if_failed();
process::exit(0);
}
pub fn exit_if_failed(&self) {
if self.has_failed {
process::exit(101)
}
}
pub fn has_failed(&self) -> bool {
self.has_failed
}
pub fn num_filtered_out(&self) -> u64 {
self.num_filtered_out
}
pub fn num_passed(&self) -> u64 {
self.num_passed
}
pub fn num_failed(&self) -> u64 {
self.num_failed
}
pub fn num_ignored(&self) -> u64 {
self.num_ignored
}
pub fn num_benches(&self) -> u64 {
self.num_benches
}
}
fn run_tests_threaded<D: 'static + Send + Sync>(
args: &Arguments,
tests: Vec<Test<D>>,
run_test: impl Fn(&Test<D>) -> Outcome + 'static + Send + Sync,
) -> impl IntoIterator<Item = RunnerEvent<D>> {
let mut builder = rayon::ThreadPoolBuilder::new();
if let Some(n) = args.num_threads {
builder = builder.num_threads(n);
}
let pool = builder.build().expect("Unable to spawn threads");
let args = args.clone();
let (send, recv) = crossbeam_channel::bounded(0);
pool.spawn(move || {
tests.into_par_iter().for_each(|test| {
let _ = send.send(RunnerEvent::Started {
name: test.name.clone(),
kind: test.kind.clone(),
});
let is_ignored = (test.is_ignored && !args.ignored)
|| (test.is_bench && args.test)
|| (!test.is_bench && args.bench);
let outcome = if is_ignored {
Outcome::Ignored
} else {
run_test(&test)
};
let _ = send.send(RunnerEvent::Completed { test, outcome });
});
});
recv
}
pub fn run_tests<D: 'static + Send + Sync>(
args: &Arguments,
tests: Vec<Test<D>>,
run_test: impl Fn(&Test<D>) -> Outcome + 'static + Send + Sync,
) -> Conclusion {
let (tests, num_filtered_out) = if args.filter_string.is_some() || !args.skip.is_empty() {
let len_before = tests.len() as u64;
let mut tests = tests;
tests.retain(|t| {
if let Some(filter) = &args.filter_string {
match args.exact {
true if &t.name != filter => return false,
false if !t.name.contains(filter) => return false,
_ => {}
};
}
for skip_filter in &args.skip {
match args.exact {
true if &t.name == skip_filter => return false,
false if t.name.contains(skip_filter) => return false,
_ => {}
}
}
true
});
let num_filtered_out = len_before - tests.len() as u64;
(tests, num_filtered_out)
} else {
(tests, 0)
};
let mut printer = printer::Printer::new(args, &tests);
if args.list {
printer.print_list(&tests);
return Conclusion {
has_failed: false,
num_filtered_out: 0,
num_passed: 0,
num_failed: 0,
num_ignored: 0,
num_benches: 0,
};
}
printer.print_title(tests.len() as u64);
let mut failed_tests = Vec::new();
let mut num_ignored = 0;
let mut num_benches = 0;
let mut num_passed = 0;
for event in run_tests_threaded(args, tests, run_test) {
match event {
RunnerEvent::Started { name, kind } => {
if args.num_threads == Some(1) {
printer.print_test(&name, &kind);
}
}
RunnerEvent::Completed { test, outcome } => {
if args.num_threads != Some(1) {
printer.print_test(&test.name, &test.kind);
}
printer.print_single_outcome(&outcome);
if test.is_bench {
num_benches += 1;
}
match outcome {
Outcome::Passed => num_passed += 1,
Outcome::Failed { msg } => failed_tests.push((test, msg)),
Outcome::Ignored => num_ignored += 1,
Outcome::Measured { .. } => {}
}
}
}
}
if !failed_tests.is_empty() {
printer.print_failures(&failed_tests);
}
let num_failed = failed_tests.len() as u64;
let conclusion = Conclusion {
has_failed: num_failed != 0,
num_filtered_out,
num_passed,
num_failed,
num_ignored,
num_benches,
};
printer.print_summary(&conclusion);
conclusion
}