diff --git a/src/libtest/lib.rs b/src/libtest/lib.rs index 248f6f98650a5..64515b900d5b1 100644 --- a/src/libtest/lib.rs +++ b/src/libtest/lib.rs @@ -42,6 +42,7 @@ #![feature(staged_api)] #![feature(question_mark)] #![feature(panic_unwind)] +#![feature(mpsc_recv_timeout)] extern crate getopts; extern crate term; @@ -73,6 +74,8 @@ use std::sync::{Arc, Mutex}; use std::thread; use std::time::{Instant, Duration}; +const TEST_WARN_TIMEOUT_S: u64 = 60; + // to be used by rustc to compile tests in libtest pub mod test { pub use {Bencher, TestName, TestResult, TestDesc, TestDescAndFn, TestOpts, TrFailed, @@ -592,6 +595,12 @@ impl ConsoleTestState { } } + pub fn write_timeout(&mut self, desc: &TestDesc) -> io::Result<()> { + self.write_plain(&format!("test {} has been running for over {} seconds\n", + desc.name, + TEST_WARN_TIMEOUT_S)) + } + pub fn write_log(&mut self, test: &TestDesc, result: &TestResult) -> io::Result<()> { match self.log_out { None => Ok(()), @@ -709,6 +718,7 @@ pub fn run_tests_console(opts: &TestOpts, tests: Vec) -> io::Resu match (*event).clone() { TeFiltered(ref filtered_tests) => st.write_run_start(filtered_tests.len()), TeWait(ref test, padding) => st.write_test_start(test, padding), + TeTimeout(ref test) => st.write_timeout(test), TeResult(test, result, stdout) => { st.write_log(&test, &result)?; st.write_result(&result)?; @@ -830,6 +840,7 @@ enum TestEvent { TeFiltered(Vec), TeWait(TestDesc, NamePadding), TeResult(TestDesc, TestResult, Vec), + TeTimeout(TestDesc), } pub type MonitorMsg = (TestDesc, TestResult, Vec); @@ -838,6 +849,9 @@ pub type MonitorMsg = (TestDesc, TestResult, Vec); fn run_tests(opts: &TestOpts, tests: Vec, mut callback: F) -> io::Result<()> where F: FnMut(TestEvent) -> io::Result<()> { + use std::collections::HashMap; + use std::sync::mpsc::RecvTimeoutError; + let mut filtered_tests = filter_tests(opts, tests); if !opts.bench_benchmarks { filtered_tests = convert_benchmarks_to_tests(filtered_tests); @@ -867,6 +881,29 @@ fn run_tests(opts: &TestOpts, tests: Vec, mut callback: F) -> let (tx, rx) = channel::(); + let mut running_tests: HashMap = HashMap::new(); + + fn get_timed_out_tests(running_tests: &mut HashMap) -> 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); + } + timed_out + }; + + fn calc_timeout(running_tests: &HashMap) -> Option { + running_tests.values().min().map(|next_timeout| { + let now = Instant::now(); + if *next_timeout >= now { + *next_timeout - now + } else { + Duration::new(0, 0) + }}) + }; + while pending > 0 || !remaining.is_empty() { while pending < concurrency && !remaining.is_empty() { let test = remaining.pop().unwrap(); @@ -876,11 +913,31 @@ fn run_tests(opts: &TestOpts, tests: Vec, mut callback: F) -> // that hang forever. callback(TeWait(test.desc.clone(), test.testfn.padding()))?; } + let timeout = Instant::now() + Duration::from_secs(TEST_WARN_TIMEOUT_S); + running_tests.insert(test.desc.clone(), timeout); run_test(opts, !opts.run_tests, test, tx.clone()); pending += 1; } - let (desc, result, stdout) = rx.recv().unwrap(); + let mut res; + loop { + if let Some(timeout) = calc_timeout(&running_tests) { + res = rx.recv_timeout(timeout); + for test in get_timed_out_tests(&mut running_tests) { + callback(TeTimeout(test))?; + } + if res != Err(RecvTimeoutError::Timeout) { + break; + } + } else { + res = rx.recv().map_err(|_| RecvTimeoutError::Disconnected); + break; + } + } + + let (desc, result, stdout) = res.unwrap(); + running_tests.remove(&desc); + if concurrency != 1 { callback(TeWait(desc.clone(), PadNone))?; }