Skip to content

Commit b05788e

Browse files
committed
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 <[email protected]>
1 parent 57c72ab commit b05788e

File tree

1 file changed

+27
-18
lines changed

1 file changed

+27
-18
lines changed

library/test/src/lib.rs

+27-18
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ pub mod test {
6262
}
6363

6464
use std::{
65+
collections::VecDeque,
6566
env, io,
6667
io::prelude::Write,
6768
panic::{self, catch_unwind, AssertUnwindSafe, PanicInfo},
@@ -211,14 +212,18 @@ where
211212
use std::sync::mpsc::RecvTimeoutError;
212213

213214
struct RunningTest {
214-
timeout: Instant,
215215
join_handle: Option<thread::JoinHandle<()>>,
216216
}
217217

218218
// Use a deterministic hasher
219219
type TestMap =
220220
HashMap<TestDesc, RunningTest, BuildHasherDefault<collections::hash_map::DefaultHasher>>;
221221

222+
struct TimeoutEntry {
223+
desc: TestDesc,
224+
timeout: Instant,
225+
}
226+
222227
let tests_len = tests.len();
223228

224229
let mut filtered_tests = filter_tests(opts, tests);
@@ -262,25 +267,28 @@ where
262267
};
263268

264269
let mut running_tests: TestMap = HashMap::default();
270+
let mut timeout_queue: VecDeque<TimeoutEntry> = VecDeque::new();
265271

266-
fn get_timed_out_tests(running_tests: &mut TestMap) -> Vec<TestDesc> {
272+
fn get_timed_out_tests(
273+
running_tests: &TestMap,
274+
timeout_queue: &mut VecDeque<TimeoutEntry>,
275+
) -> Vec<TestDesc> {
267276
let now = Instant::now();
268-
let timed_out = running_tests
269-
.iter()
270-
.filter_map(
271-
|(desc, running_test)| {
272-
if now >= running_test.timeout { Some(desc.clone()) } else { None }
273-
},
274-
)
275-
.collect();
276-
for test in &timed_out {
277-
running_tests.remove(test);
277+
let mut timed_out = Vec::new();
278+
while let Some(timeout_entry) = timeout_queue.front() {
279+
if now < timeout_entry.timeout {
280+
break;
281+
}
282+
let timeout_entry = timeout_queue.pop_front().unwrap();
283+
if running_tests.contains_key(&timeout_entry.desc) {
284+
timed_out.push(timeout_entry.desc);
285+
}
278286
}
279287
timed_out
280288
}
281289

282-
fn calc_timeout(running_tests: &TestMap) -> Option<Duration> {
283-
running_tests.values().map(|running_test| running_test.timeout).min().map(|next_timeout| {
290+
fn calc_timeout(timeout_queue: &VecDeque<TimeoutEntry>) -> Option<Duration> {
291+
timeout_queue.front().map(|&TimeoutEntry { timeout: next_timeout, .. }| {
284292
let now = Instant::now();
285293
if next_timeout >= now { next_timeout - now } else { Duration::new(0, 0) }
286294
})
@@ -305,7 +313,7 @@ where
305313
let timeout = time::get_default_test_timeout();
306314
let desc = test.desc.clone();
307315

308-
let event = TestEvent::TeWait(test.desc.clone());
316+
let event = TestEvent::TeWait(desc.clone());
309317
notify_about_test_event(event)?; //here no pad
310318
let join_handle = run_test(
311319
opts,
@@ -315,15 +323,16 @@ where
315323
tx.clone(),
316324
Concurrent::Yes,
317325
);
318-
running_tests.insert(desc, RunningTest { timeout, join_handle });
326+
running_tests.insert(desc.clone(), RunningTest { join_handle });
327+
timeout_queue.push_back(TimeoutEntry { desc, timeout });
319328
pending += 1;
320329
}
321330

322331
let mut res;
323332
loop {
324-
if let Some(timeout) = calc_timeout(&running_tests) {
333+
if let Some(timeout) = calc_timeout(&timeout_queue) {
325334
res = rx.recv_timeout(timeout);
326-
for test in get_timed_out_tests(&mut running_tests) {
335+
for test in get_timed_out_tests(&running_tests, &mut timeout_queue) {
327336
let event = TestEvent::TeTimeout(test);
328337
notify_about_test_event(event)?;
329338
}

0 commit comments

Comments
 (0)