libtest: Store pending timeouts in a deque

This reduces the total complexity of checking timeouts from quadratic
to linear, and should also fix an unwrap of None on completion of an
already timed-out test.

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
This commit is contained in:
Anders Kaseorg 2021-01-25 12:19:25 -08:00
parent 57c72ab846
commit b05788e859

View file

@ -62,6 +62,7 @@ pub mod test {
}
use std::{
collections::VecDeque,
env, io,
io::prelude::Write,
panic::{self, catch_unwind, AssertUnwindSafe, PanicInfo},
@ -211,7 +212,6 @@ where
use std::sync::mpsc::RecvTimeoutError;
struct RunningTest {
timeout: Instant,
join_handle: Option<thread::JoinHandle<()>>,
}
@ -219,6 +219,11 @@ where
type TestMap =
HashMap<TestDesc, RunningTest, BuildHasherDefault<collections::hash_map::DefaultHasher>>;
struct TimeoutEntry {
desc: TestDesc,
timeout: Instant,
}
let tests_len = tests.len();
let mut filtered_tests = filter_tests(opts, tests);
@ -262,25 +267,28 @@ where
};
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, running_test)| {
if now >= running_test.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<Duration> {
running_tests.values().map(|running_test| running_test.timeout).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) }
})
@ -305,7 +313,7 @@ where
let timeout = time::get_default_test_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
let join_handle = run_test(
opts,
@ -315,15 +323,16 @@ where
tx.clone(),
Concurrent::Yes,
);
running_tests.insert(desc, RunningTest { timeout, join_handle });
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)?;
}