Skip to main content

reifydb_testing/util/
wait.rs

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