Skip to main content

reifydb_testing/util/
wait.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2025 ReifyDB
3
4use std::time::{Duration, Instant};
5
6use tokio::time::sleep;
7
8/// Default timeout for wait operations (5 seconds)
9pub const DEFAULT_TIMEOSVT: Duration = Duration::from_secs(5);
10
11/// Default poll interval (1 millisecond)
12pub const DEFAULT_POLL_INTERVAL: Duration = Duration::from_millis(1);
13
14/// Wait for a condition to become true, polling at regular intervals
15///
16/// # Arguments
17/// * `condition` - A closure that returns true when the wait should end
18/// * `timeout` - Maximum time to wait before panicking
19/// * `poll_interval` - How often to check the condition
20/// * `timeout_message` - Message to display if timeout occurs
21///
22/// # Panics
23/// Panics if the condition doesn't become true within the timeout period
24pub async fn wait_for_condition<F>(condition: F, timeout: Duration, poll_interval: Duration, timeout_message: &str)
25where
26	F: Fn() -> bool,
27{
28	#[allow(clippy::disallowed_methods)]
29	let start = Instant::now();
30	let mut poll_count = 0u64;
31
32	while !condition() {
33		if start.elapsed() > timeout {
34			println!(
35				"[DEBUG:await] TIMEOUT elapsed={:.1}s polls={poll_count} msg={timeout_message}",
36				start.elapsed().as_secs_f64()
37			);
38			panic!("Timeout after {:?}: {}", timeout, timeout_message);
39		}
40		poll_count += 1;
41		if poll_count.is_multiple_of(1000) {
42			println!(
43				"[DEBUG:await] poll #{poll_count} elapsed={:.1}s msg={timeout_message}",
44				start.elapsed().as_secs_f64()
45			);
46		}
47		sleep(poll_interval).await;
48	}
49	println!(
50		"[DEBUG:await] condition met after {poll_count} polls elapsed={:.3}s msg={timeout_message}",
51		start.elapsed().as_secs_f64()
52	);
53}
54
55/// Wait for a condition with default timeout and poll interval
56///
57/// Uses a 1-second timeout and 1ms poll interval
58pub async fn wait_for<F>(condition: F, message: &str)
59where
60	F: Fn() -> bool,
61{
62	wait_for_condition(condition, DEFAULT_TIMEOSVT, DEFAULT_POLL_INTERVAL, message).await;
63}
64
65#[cfg(test)]
66pub mod tests {
67	use std::{
68		sync::{Arc, Mutex},
69		thread,
70	};
71
72	use super::*;
73
74	#[tokio::test]
75	async fn test_wait_for_immediate() {
76		// Condition is already true
77		wait_for(|| true, "Should not timeout").await;
78	}
79
80	#[tokio::test]
81	async fn test_wait_for_becomes_true() {
82		let counter = Arc::new(Mutex::new(0));
83		let counter_clone = counter.clone();
84
85		thread::spawn(move || {
86			thread::sleep(Duration::from_millis(50));
87			*counter_clone.lock().unwrap() = 5;
88		});
89
90		wait_for(|| *counter.lock().unwrap() == 5, "Counter should reach 5").await;
91
92		assert_eq!(*counter.lock().unwrap(), 5);
93	}
94
95	#[tokio::test]
96	#[should_panic(expected = "Timeout after")]
97	async fn test_wait_for_timeout() {
98		wait_for_condition(
99			|| false,
100			Duration::from_millis(10),
101			Duration::from_millis(1),
102			"Condition never becomes true",
103		)
104		.await;
105	}
106}