Skip to main content

term_test_support/
poll_sync.rs

1//! Deadline-bounded synchronous condition polling.
2
3use std::thread::sleep;
4use std::time::{Duration, Instant};
5
6/// Interval between probe attempts in [`wait_for`].
7pub const DEFAULT_POLL_INTERVAL: Duration = Duration::from_millis(20);
8
9/// Poll `probe` until it returns `Some(value)`, or panic once `deadline`
10/// elapses.
11///
12/// This is the workspace's standard replacement for blind sleeps: the wait
13/// observes real state, succeeds as soon as possible, and fails with a
14/// descriptive message instead of a misleading downstream assert.
15///
16/// ```ignore
17/// let value = wait_for(Duration::from_secs(5), "reader applied resize", || {
18///     state.lock().unwrap_or_else(|err| err.into_inner()).applied.then_some(())
19/// });
20/// ```
21///
22/// # Panics
23/// Panics if `probe` never returns `Some` within `deadline`.
24pub fn wait_for<T>(deadline: Duration, desc: &str, mut probe: impl FnMut() -> Option<T>) -> T {
25    let start = Instant::now();
26    loop {
27        if let Some(value) = probe() {
28            return value;
29        }
30        assert!(
31            start.elapsed() < deadline,
32            "condition not met within {deadline:?}: {desc}"
33        );
34        sleep(DEFAULT_POLL_INTERVAL);
35    }
36}
37
38#[allow(clippy::unwrap_used)]
39#[cfg(test)]
40mod tests {
41    use super::*;
42    use std::sync::Arc;
43    use std::sync::atomic::{AtomicU64, Ordering};
44
45    #[test]
46    fn returns_probe_value_when_immediately_satisfied() {
47        let value = wait_for(Duration::from_secs(1), "always true", || Some(42));
48        assert_eq!(value, 42);
49    }
50
51    #[test]
52    fn polls_until_condition_becomes_true() {
53        let ticks = Arc::new(AtomicU64::new(0));
54        let seen = Arc::clone(&ticks);
55        let value = wait_for(Duration::from_secs(2), "third tick", move || {
56            let n = seen.fetch_add(1, Ordering::Release);
57            (n >= 2).then_some(n)
58        });
59        assert_eq!(value, 2);
60    }
61
62    #[test]
63    #[should_panic(expected = "condition not met within")]
64    fn panics_with_description_after_deadline() {
65        wait_for::<()>(Duration::from_millis(50), "impossible condition", || None);
66    }
67}