diff --git a/library/test/src/lib.rs b/library/test/src/lib.rs index 656d9669e81d28201b3e206054d89d15c5deaf22..3ff79eaea49aba18fa749b91c12e600de52d8cbe 100644 --- a/library/test/src/lib.rs +++ b/library/test/src/lib.rs @@ -25,6 +25,7 @@ #![feature(nll)] #![feature(available_concurrency)] #![feature(internal_output_capture)] +#![feature(option_unwrap_none)] #![feature(panic_unwind)] #![feature(staged_api)] #![feature(termination_trait_lib)] @@ -61,6 +62,7 @@ pub mod test { } use std::{ + collections::VecDeque, env, io, io::prelude::Write, panic::{self, catch_unwind, AssertUnwindSafe, PanicInfo}, @@ -208,9 +210,19 @@ pub fn run_tests( use std::collections::{self, HashMap}; use std::hash::BuildHasherDefault; use std::sync::mpsc::RecvTimeoutError; + + struct RunningTest { + join_handle: Option>, + } + // Use a deterministic hasher type TestMap = - HashMap>; + HashMap>; + + struct TimeoutEntry { + desc: TestDesc, + timeout: Instant, + } let tests_len = tests.len(); @@ -255,23 +267,30 @@ pub fn run_tests( }; let mut running_tests: TestMap = HashMap::default(); + let mut timeout_queue: VecDeque = VecDeque::new(); - fn get_timed_out_tests(running_tests: &mut TestMap) -> Vec { + fn get_timed_out_tests( + running_tests: &TestMap, + timeout_queue: &mut VecDeque, + ) -> Vec { 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.desc) { + timed_out.push(timeout_entry.desc); + } } timed_out } - fn calc_timeout(running_tests: &TestMap) -> Option { - running_tests.values().min().map(|next_timeout| { + fn calc_timeout(timeout_queue: &VecDeque) -> Option { + 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) } }) } @@ -280,7 +299,8 @@ fn calc_timeout(running_tests: &TestMap) -> Option { let 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); + run_test(opts, !opts.run_tests, test, run_strategy, tx.clone(), Concurrent::No) + .unwrap_none(); let completed_test = rx.recv().unwrap(); let event = TestEvent::TeResult(completed_test); @@ -291,19 +311,28 @@ fn calc_timeout(running_tests: &TestMap) -> Option { while pending < concurrency && !remaining.is_empty() { let 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, + test, + run_strategy, + tx.clone(), + Concurrent::Yes, + ); + running_tests.insert(desc.clone(), RunningTest { join_handle }); + timeout_queue.push_back(TimeoutEntry { 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)?; } @@ -323,8 +352,16 @@ fn calc_timeout(running_tests: &TestMap) -> Option { } } - 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.desc).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)?; @@ -415,7 +452,7 @@ pub fn run_test( strategy: RunStrategy, monitor_ch: Sender, concurrency: Concurrent, -) { +) -> Option> { let TestDescAndFn { desc, testfn } = test; // Emscripten can catch panics but other wasm targets cannot @@ -426,7 +463,7 @@ pub fn run_test( if force_ignore || desc.ignore || ignore_because_no_process_support { let message = CompletedTest::new(desc, TrIgnored, None, Vec::new()); monitor_ch.send(message).unwrap(); - return; + return None; } struct TestRunOpts { @@ -441,7 +478,7 @@ fn run_test_inner( monitor_ch: Sender, testfn: Box, opts: TestRunOpts, - ) { + ) -> Option> { let concurrency = opts.concurrency; let name = desc.name.clone(); @@ -469,9 +506,10 @@ fn run_test_inner( let supports_threads = !cfg!(target_os = "emscripten") && !cfg!(target_arch = "wasm32"); if concurrency == Concurrent::Yes && supports_threads { let cfg = thread::Builder::new().name(name.as_slice().to_owned()); - cfg.spawn(runtest).unwrap(); + Some(cfg.spawn(runtest).unwrap()) } else { runtest(); + None } } @@ -484,10 +522,12 @@ fn run_test_inner( crate::bench::benchmark(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); + None } DynTestFn(f) => { match strategy { @@ -499,7 +539,7 @@ fn run_test_inner( monitor_ch, Box::new(move || __rust_begin_short_backtrace(f)), test_run_opts, - ); + ) } StaticTestFn(f) => run_test_inner( desc,