#![crate_name = "test"]
#![unstable(feature = "test", issue = "50297")]
-#![doc(html_root_url = "https://doc.rust-lang.org/nightly/", test(attr(deny(warnings))))]
-#![cfg_attr(any(unix, target_os = "cloudabi"), feature(libc))]
+#![doc(test(attr(deny(warnings))))]
+#![feature(libc)]
#![feature(rustc_private)]
#![feature(nll)]
-#![feature(bool_to_option)]
-#![feature(set_stdio)]
+#![feature(available_parallelism)]
+#![feature(bench_black_box)]
+#![feature(internal_output_capture)]
#![feature(panic_unwind)]
#![feature(staged_api)]
#![feature(termination_trait_lib)]
#![feature(test)]
+#![feature(total_cmp)]
// Public reexports
pub use self::bench::{black_box, Bencher};
cli::{parse_opts, TestOpts},
filter_tests,
helpers::metrics::{Metric, MetricMap},
- options::{Options, RunIgnored, RunStrategy, ShouldPanic},
+ options::{Concurrent, Options, RunIgnored, RunStrategy, ShouldPanic},
run_test, test_main, test_main_static,
test_result::{TestResult, TrFailed, TrFailedMsg, TrIgnored, TrOk},
time::{TestExecTime, TestTimeOptions},
types::{
DynTestFn, DynTestName, StaticBenchFn, StaticTestFn, StaticTestName, TestDesc,
- TestDescAndFn, TestName, TestType,
+ TestDescAndFn, TestId, TestName, TestType,
},
};
}
use std::{
+ collections::VecDeque,
env, io,
io::prelude::Write,
panic::{self, catch_unwind, AssertUnwindSafe, PanicInfo},
mod helpers;
mod options;
pub mod stats;
+mod term;
mod test_result;
mod time;
mod types;
use event::{CompletedTest, TestEvent};
use helpers::concurrency::get_concurrency;
use helpers::exit_code::get_exit_code;
-use helpers::sink::Sink;
+use helpers::shuffle::{get_shuffle_seed, shuffle_tests};
use options::{Concurrent, RunStrategy};
use test_result::*;
use time::TestExecTime;
use std::collections::{self, HashMap};
use std::hash::BuildHasherDefault;
use std::sync::mpsc::RecvTimeoutError;
+
+ struct RunningTest {
+ join_handle: Option<thread::JoinHandle<()>>,
+ }
+
// Use a deterministic hasher
type TestMap =
- HashMap<TestDesc, Instant, BuildHasherDefault<collections::hash_map::DefaultHasher>>;
+ HashMap<TestId, RunningTest, BuildHasherDefault<collections::hash_map::DefaultHasher>>;
+
+ struct TimeoutEntry {
+ id: TestId,
+ desc: TestDesc,
+ timeout: Instant,
+ }
let tests_len = tests.len();
let filtered_descs = filtered_tests.iter().map(|t| t.desc.clone()).collect();
- let event = TestEvent::TeFiltered(filtered_descs);
+ let shuffle_seed = get_shuffle_seed(opts);
+
+ let event = TestEvent::TeFiltered(filtered_descs, shuffle_seed);
notify_about_test_event(event)?;
- let (filtered_tests, filtered_benchs): (Vec<_>, _) =
- filtered_tests.into_iter().partition(|e| match e.testfn {
- StaticTestFn(_) | DynTestFn(_) => true,
- _ => false,
- });
+ let (filtered_tests, filtered_benchs): (Vec<_>, _) = filtered_tests
+ .into_iter()
+ .enumerate()
+ .map(|(i, e)| (TestId(i), e))
+ .partition(|(_, e)| matches!(e.testfn, StaticTestFn(_) | DynTestFn(_)));
let concurrency = opts.test_threads.unwrap_or_else(get_concurrency);
let mut remaining = filtered_tests;
- remaining.reverse();
+ if let Some(shuffle_seed) = shuffle_seed {
+ shuffle_tests(shuffle_seed, &mut remaining);
+ } else {
+ remaining.reverse();
+ }
let mut pending = 0;
let (tx, rx) = channel::<CompletedTest>();
};
let mut running_tests: TestMap = HashMap::default();
+ let mut timeout_queue: VecDeque<TimeoutEntry> = VecDeque::new();
- fn get_timed_out_tests(running_tests: &mut TestMap) -> Vec<TestDesc> {
+ fn get_timed_out_tests(
+ running_tests: &TestMap,
+ timeout_queue: &mut VecDeque<TimeoutEntry>,
+ ) -> Vec<TestDesc> {
let now = Instant::now();
- let timed_out = running_tests
- .iter()
- .filter_map(|(desc, timeout)| if &now >= timeout { Some(desc.clone()) } else { None })
- .collect();
- for test in &timed_out {
- running_tests.remove(test);
+ let mut timed_out = Vec::new();
+ while let Some(timeout_entry) = timeout_queue.front() {
+ if now < timeout_entry.timeout {
+ break;
+ }
+ let timeout_entry = timeout_queue.pop_front().unwrap();
+ if running_tests.contains_key(&timeout_entry.id) {
+ timed_out.push(timeout_entry.desc);
+ }
}
timed_out
- };
+ }
- fn calc_timeout(running_tests: &TestMap) -> Option<Duration> {
- running_tests.values().min().map(|next_timeout| {
+ fn calc_timeout(timeout_queue: &VecDeque<TimeoutEntry>) -> Option<Duration> {
+ timeout_queue.front().map(|&TimeoutEntry { timeout: next_timeout, .. }| {
let now = Instant::now();
- if *next_timeout >= now { *next_timeout - now } else { Duration::new(0, 0) }
+ if next_timeout >= now { next_timeout - now } else { Duration::new(0, 0) }
})
- };
+ }
if concurrency == 1 {
while !remaining.is_empty() {
- let test = remaining.pop().unwrap();
+ let (id, test) = remaining.pop().unwrap();
let event = TestEvent::TeWait(test.desc.clone());
notify_about_test_event(event)?;
- run_test(opts, !opts.run_tests, test, run_strategy, tx.clone(), Concurrent::No);
+ let join_handle =
+ run_test(opts, !opts.run_tests, id, test, run_strategy, tx.clone(), Concurrent::No);
+ assert!(join_handle.is_none());
let completed_test = rx.recv().unwrap();
let event = TestEvent::TeResult(completed_test);
} else {
while pending > 0 || !remaining.is_empty() {
while pending < concurrency && !remaining.is_empty() {
- let test = remaining.pop().unwrap();
+ let (id, test) = remaining.pop().unwrap();
let timeout = time::get_default_test_timeout();
- running_tests.insert(test.desc.clone(), timeout);
+ let desc = test.desc.clone();
- let event = TestEvent::TeWait(test.desc.clone());
+ let event = TestEvent::TeWait(desc.clone());
notify_about_test_event(event)?; //here no pad
- run_test(opts, !opts.run_tests, test, run_strategy, tx.clone(), Concurrent::Yes);
+ let join_handle = run_test(
+ opts,
+ !opts.run_tests,
+ id,
+ test,
+ run_strategy,
+ tx.clone(),
+ Concurrent::Yes,
+ );
+ running_tests.insert(id, RunningTest { join_handle });
+ timeout_queue.push_back(TimeoutEntry { id, desc, timeout });
pending += 1;
}
let mut res;
loop {
- if let Some(timeout) = calc_timeout(&running_tests) {
+ if let Some(timeout) = calc_timeout(&timeout_queue) {
res = rx.recv_timeout(timeout);
- for test in get_timed_out_tests(&mut running_tests) {
+ for test in get_timed_out_tests(&running_tests, &mut timeout_queue) {
let event = TestEvent::TeTimeout(test);
notify_about_test_event(event)?;
}
}
}
- let completed_test = res.unwrap();
- running_tests.remove(&completed_test.desc);
+ let mut completed_test = res.unwrap();
+ let running_test = running_tests.remove(&completed_test.id).unwrap();
+ if let Some(join_handle) = running_test.join_handle {
+ if let Err(_) = join_handle.join() {
+ if let TrOk = completed_test.result {
+ completed_test.result =
+ TrFailedMsg("panicked after reporting success".to_string());
+ }
+ }
+ }
let event = TestEvent::TeResult(completed_test);
notify_about_test_event(event)?;
if opts.bench_benchmarks {
// All benchmarks run at the end, in serial.
- for b in filtered_benchs {
+ for (id, b) in filtered_benchs {
let event = TestEvent::TeWait(b.desc.clone());
notify_about_test_event(event)?;
- run_test(opts, false, b, run_strategy, tx.clone(), Concurrent::No);
+ run_test(opts, false, id, b, run_strategy, tx.clone(), Concurrent::No);
let completed_test = rx.recv().unwrap();
let event = TestEvent::TeResult(completed_test);
};
// Remove tests that don't match the test filter
- if let Some(ref filter) = opts.filter {
- filtered.retain(|test| matches_filter(test, filter));
+ if !opts.filters.is_empty() {
+ filtered.retain(|test| opts.filters.iter().any(|filter| matches_filter(test, filter)));
}
// Skip tests that match any of the skip filters
pub fn run_test(
opts: &TestOpts,
force_ignore: bool,
+ id: TestId,
test: TestDescAndFn,
strategy: RunStrategy,
monitor_ch: Sender<CompletedTest>,
concurrency: Concurrent,
-) {
+) -> Option<thread::JoinHandle<()>> {
let TestDescAndFn { desc, testfn } = test;
// Emscripten can catch panics but other wasm targets cannot
let ignore_because_no_process_support = desc.should_panic != ShouldPanic::No
- && cfg!(target_arch = "wasm32")
+ && cfg!(target_family = "wasm")
&& !cfg!(target_os = "emscripten");
if force_ignore || desc.ignore || ignore_because_no_process_support {
- let message = CompletedTest::new(desc, TrIgnored, None, Vec::new());
+ let message = CompletedTest::new(id, desc, TrIgnored, None, Vec::new());
monitor_ch.send(message).unwrap();
- return;
+ return None;
}
struct TestRunOpts {
}
fn run_test_inner(
+ id: TestId,
desc: TestDesc,
monitor_ch: Sender<CompletedTest>,
testfn: Box<dyn FnOnce() + Send>,
opts: TestRunOpts,
- ) {
+ ) -> Option<thread::JoinHandle<()>> {
let concurrency = opts.concurrency;
let name = desc.name.clone();
let runtest = move || match opts.strategy {
RunStrategy::InProcess => run_test_in_process(
+ id,
desc,
opts.nocapture,
opts.time.is_some(),
opts.time,
),
RunStrategy::SpawnPrimary => spawn_test_subprocess(
+ id,
desc,
opts.nocapture,
opts.time.is_some(),
// If the platform is single-threaded we're just going to run
// the test synchronously, regardless of the concurrency
// level.
- let supports_threads = !cfg!(target_os = "emscripten") && !cfg!(target_arch = "wasm32");
+ let supports_threads = !cfg!(target_os = "emscripten") && !cfg!(target_family = "wasm");
if concurrency == Concurrent::Yes && supports_threads {
let cfg = thread::Builder::new().name(name.as_slice().to_owned());
- cfg.spawn(runtest).unwrap();
+ let mut runtest = Arc::new(Mutex::new(Some(runtest)));
+ let runtest2 = runtest.clone();
+ match cfg.spawn(move || runtest2.lock().unwrap().take().unwrap()()) {
+ Ok(handle) => Some(handle),
+ Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
+ // `ErrorKind::WouldBlock` means hitting the thread limit on some
+ // platforms, so run the test synchronously here instead.
+ Arc::get_mut(&mut runtest).unwrap().get_mut().unwrap().take().unwrap()();
+ None
+ }
+ Err(e) => panic!("failed to spawn thread to run test: {}", e),
+ }
} else {
runtest();
+ None
}
}
match testfn {
DynBenchFn(bencher) => {
// Benchmarks aren't expected to panic, so we run them all in-process.
- crate::bench::benchmark(desc, monitor_ch, opts.nocapture, |harness| {
+ crate::bench::benchmark(id, desc, monitor_ch, opts.nocapture, |harness| {
bencher.run(harness)
});
+ None
}
StaticBenchFn(benchfn) => {
// Benchmarks aren't expected to panic, so we run them all in-process.
- crate::bench::benchmark(desc, monitor_ch, opts.nocapture, benchfn);
+ crate::bench::benchmark(id, desc, monitor_ch, opts.nocapture, benchfn);
+ None
}
DynTestFn(f) => {
match strategy {
_ => panic!("Cannot run dynamic test fn out-of-process"),
};
run_test_inner(
+ id,
desc,
monitor_ch,
Box::new(move || __rust_begin_short_backtrace(f)),
test_run_opts,
- );
+ )
}
StaticTestFn(f) => run_test_inner(
+ id,
desc,
monitor_ch,
Box::new(move || __rust_begin_short_backtrace(f)),
}
fn run_test_in_process(
+ id: TestId,
desc: TestDesc,
nocapture: bool,
report_time: bool,
// Buffer for capturing standard I/O
let data = Arc::new(Mutex::new(Vec::new()));
- let oldio = if !nocapture {
- Some((
- io::set_print(Some(Sink::new_boxed(&data))),
- io::set_panic(Some(Sink::new_boxed(&data))),
- ))
- } else {
- None
- };
+ if !nocapture {
+ io::set_output_capture(Some(data.clone()));
+ }
let start = report_time.then(Instant::now);
let result = catch_unwind(AssertUnwindSafe(testfn));
TestExecTime(duration)
});
- if let Some((printio, panicio)) = oldio {
- io::set_print(printio);
- io::set_panic(panicio);
- }
+ io::set_output_capture(None);
let test_result = match result {
Ok(()) => calc_result(&desc, Ok(()), &time_opts, &exec_time),
Err(e) => calc_result(&desc, Err(e.as_ref()), &time_opts, &exec_time),
};
- let stdout = data.lock().unwrap().to_vec();
- let message = CompletedTest::new(desc, test_result, exec_time, stdout);
+ let stdout = data.lock().unwrap_or_else(|e| e.into_inner()).to_vec();
+ let message = CompletedTest::new(id, desc, test_result, exec_time, stdout);
monitor_ch.send(message).unwrap();
}
fn spawn_test_subprocess(
+ id: TestId,
desc: TestDesc,
nocapture: bool,
report_time: bool,
(result, test_output, exec_time)
})();
- let message = CompletedTest::new(desc, result, exec_time, test_output);
+ let message = CompletedTest::new(id, desc, result, exec_time, test_output);
monitor_ch.send(message).unwrap();
}