rate_guard/time_source/
mock.rs

1//! Mock time source implementation for deterministic testing.
2//!
3//! This module provides MockTimeSource, a controllable time source that allows
4//! manual manipulation of time for testing rate limiting behavior. It maintains
5//! the elapsed time model while providing full control over time progression.
6//!
7//! # Design Features
8//!
9//! - **Deterministic**: Time only advances when explicitly requested
10//! - **Monotonic**: Time never goes backward, even with manual control
11//! - **Thread-local**: Each thread has independent mock time state
12//! - **Simple API**: Easy to use in tests and examples
13//!
14//! # Examples
15//!
16//! ## Basic Time Control
17//!
18//! ```rust
19//! use rate_guard::time_source::{TimeSource, MockTimeSource};
20//! use std::time::Duration;
21//!
22//! let time_source = MockTimeSource::new();
23//!
24//! // Initially at zero
25//! assert_eq!(time_source.now(), Duration::ZERO);
26//!
27//! // Advance time manually
28//! time_source.advance(Duration::from_millis(100));
29//! assert_eq!(time_source.now(), Duration::from_millis(100));
30//!
31//! // Set absolute time
32//! time_source.set_elapsed(Duration::from_secs(5));
33//! assert_eq!(time_source.now(), Duration::from_secs(5));
34//! ```
35//!
36//! ## Rate Limiter Testing
37//!
38//! ```rust
39//! use rate_guard::time_source::{TimeSource, MockTimeSource};
40//! use rate_guard::precision::{Precision, Millis};
41//! use std::time::Duration;
42//!
43//! let time_source = MockTimeSource::new();
44//!
45//! // Simulate rate limiter behavior
46//! let initial_ticks = Millis::to_ticks(time_source.now());
47//! assert_eq!(initial_ticks, 0);
48//!
49//! // Advance time to trigger refill
50//! time_source.advance(Duration::from_millis(100));
51//! let later_ticks = Millis::to_ticks(time_source.now());
52//! assert_eq!(later_ticks, 100);
53//! ```
54
55use std::time::Duration;
56use std::cell::Cell;
57use crate::time_source::TimeSource;
58
59thread_local! {
60    /// Thread-local mock time state.
61    ///
62    /// This thread-local variable holds the current elapsed time value that can
63    /// be controlled in tests. Each thread has its own independent mock time,
64    /// ensuring test isolation and avoiding synchronization overhead.
65    static MOCK_ELAPSED: Cell<Duration> = Cell::new(Duration::ZERO);
66}
67
68/// Mock time source for deterministic testing.
69///
70/// MockTimeSource provides a controllable time source where elapsed time can
71/// be manually set and advanced. This enables deterministic testing of rate
72/// limiting behavior without depending on real system time.
73///
74/// # Time Model
75///
76/// MockTimeSource maintains an elapsed Duration since an implicit starting point.
77/// Time starts at `Duration::ZERO` and can be advanced or set to specific values.
78/// Time is always monotonic - attempting to set time backward will be ignored.
79///
80/// # Thread Safety
81///
82/// MockTimeSource uses thread-local storage, so each thread has its own
83/// independent mock time. This is perfect for testing scenarios where you
84/// want isolated time control per test.
85///
86/// # Examples
87///
88/// ## Basic Usage
89///
90/// ```rust
91/// use rate_guard::time_source::{TimeSource, MockTimeSource};
92/// use std::time::Duration;
93///
94/// let time_source = MockTimeSource::new();
95///
96/// // Check initial state
97/// assert_eq!(time_source.now(), Duration::ZERO);
98///
99/// // Advance time
100/// time_source.advance(Duration::from_millis(50));
101/// assert_eq!(time_source.now(), Duration::from_millis(50));
102///
103/// // Advance more
104/// time_source.advance(Duration::from_millis(30));
105/// assert_eq!(time_source.now(), Duration::from_millis(80));
106/// ```
107///
108/// ## Absolute Time Setting
109///
110/// ```rust
111/// use rate_guard::time_source::{TimeSource, MockTimeSource};
112/// use std::time::Duration;
113///
114/// let time_source = MockTimeSource::new();
115///
116/// // Jump to specific time
117/// time_source.set_elapsed(Duration::from_secs(10));
118/// assert_eq!(time_source.now(), Duration::from_secs(10));
119///
120/// // Reset to zero
121/// time_source.reset();
122/// assert_eq!(time_source.now(), Duration::ZERO);
123/// ```
124///
125/// ## Rate Limiter Integration
126///
127/// ```rust
128/// use rate_guard::time_source::{TimeSource, MockTimeSource};
129/// use std::time::Duration;
130///
131/// let time_source = MockTimeSource::new();
132///
133/// // Simulate token bucket refill timing
134/// let refill_interval = Duration::from_millis(100);
135/// let initial_time = time_source.now();
136///
137/// // Advance past refill interval
138/// time_source.advance(refill_interval);
139/// let elapsed = time_source.now() - initial_time;
140/// assert!(elapsed >= refill_interval);
141/// ```
142#[derive(Debug, Clone)]
143pub struct MockTimeSource;
144
145impl MockTimeSource {
146    /// Creates a new MockTimeSource.
147    ///
148    /// The time source starts with elapsed time of `Duration::ZERO`.
149    /// Each instance shares the same thread-local time state within
150    /// the current thread.
151    ///
152    /// # Examples
153    ///
154    /// ```rust
155    /// use rate_guard::time_source::{TimeSource, MockTimeSource};
156    /// use std::time::Duration;
157    ///
158    /// let time_source = MockTimeSource::new();
159    /// assert_eq!(time_source.now(), Duration::ZERO);
160    /// ```
161    pub fn new() -> Self {
162        Self
163    }
164
165    /// Sets the elapsed time to a specific value.
166    ///
167    /// This method allows tests to jump to any point in time. The time
168    /// must be greater than or equal to the current elapsed time to
169    /// maintain monotonicity. Attempts to set time backward are ignored.
170    ///
171    /// # Arguments
172    /// * `elapsed` - The new elapsed time value
173    ///
174    /// # Examples
175    ///
176    /// ```rust
177    /// use rate_guard::time_source::{TimeSource, MockTimeSource};
178    /// use std::time::Duration;
179    ///
180    /// let time_source = MockTimeSource::new();
181    ///
182    /// time_source.set_elapsed(Duration::from_millis(1000));
183    /// assert_eq!(time_source.now(), Duration::from_millis(1000));
184    ///
185    /// // Attempting to go backward is ignored
186    /// time_source.set_elapsed(Duration::from_millis(500));
187    /// assert_eq!(time_source.now(), Duration::from_millis(1000));
188    /// ```
189    pub fn set_elapsed(&self, elapsed: Duration) {
190        MOCK_ELAPSED.with(|cell| {
191            let current = cell.get();
192            if elapsed >= current {
193                cell.set(elapsed);
194            }
195        });
196    }
197
198    /// Advances the elapsed time by the specified duration.
199    ///
200    /// This method adds the specified duration to the current elapsed time,
201    /// simulating the passage of time in tests. The advancement is always
202    /// forward, maintaining monotonicity.
203    ///
204    /// # Arguments
205    /// * `duration` - Duration to advance the time by
206    ///
207    /// # Examples
208    ///
209    /// ```rust
210    /// use rate_guard::time_source::{TimeSource, MockTimeSource};
211    /// use std::time::Duration;
212    ///
213    /// let time_source = MockTimeSource::new();
214    ///
215    /// time_source.advance(Duration::from_millis(50));
216    /// assert_eq!(time_source.now(), Duration::from_millis(50));
217    ///
218    /// time_source.advance(Duration::from_millis(25));
219    /// assert_eq!(time_source.now(), Duration::from_millis(75));
220    /// ```
221    pub fn advance(&self, duration: Duration) {
222        MOCK_ELAPSED.with(|cell| {
223            let current = cell.get();
224            cell.set(current + duration);
225        });
226    }
227
228    /// Resets the elapsed time to zero.
229    ///
230    /// This is a convenience method equivalent to `set_elapsed(Duration::ZERO)`.
231    /// Useful for resetting time state between tests.
232    ///
233    /// # Examples
234    ///
235    /// ```rust
236    /// use rate_guard::time_source::{TimeSource, MockTimeSource};
237    /// use std::time::Duration;
238    ///
239    /// let time_source = MockTimeSource::new();
240    /// time_source.advance(Duration::from_secs(5));
241    /// assert_eq!(time_source.now(), Duration::from_secs(5));
242    ///
243    /// time_source.reset();
244    /// assert_eq!(time_source.now(), Duration::ZERO);
245    /// ```
246    pub fn reset(&self) {
247        MOCK_ELAPSED.with(|cell| {
248            cell.set(Duration::ZERO);
249        });
250    }
251
252    /// Returns the current elapsed time.
253    ///
254    /// This is equivalent to calling the `now()` method from the TimeSource
255    /// trait, but provided as a convenience method with a more descriptive name.
256    ///
257    /// # Examples
258    ///
259    /// ```rust
260    /// use rate_guard::time_source::MockTimeSource;
261    /// use std::time::Duration;
262    ///
263    /// let time_source = MockTimeSource::new();
264    /// time_source.advance(Duration::from_millis(100));
265    ///
266    /// assert_eq!(time_source.elapsed(), Duration::from_millis(100));
267    /// assert_eq!(time_source.elapsed(), time_source.now());
268    /// ```
269    pub fn elapsed(&self) -> Duration {
270        self.now()
271    }
272
273    pub fn now(&self) -> Duration {
274        MOCK_ELAPSED.with(|cell| cell.get())
275    }
276}
277
278impl Default for MockTimeSource {
279    /// Creates a new MockTimeSource with default settings.
280    ///
281    /// Equivalent to `MockTimeSource::new()`.
282    fn default() -> Self {
283        Self::new()
284    }
285}
286
287impl TimeSource for MockTimeSource {
288    /// Returns the current elapsed time since the mock starting point.
289    ///
290    /// This method returns the elapsed Duration that has been set or advanced
291    /// through the mock time control methods.
292    ///
293    /// # Examples
294    ///
295    /// ```rust
296    /// use rate_guard::time_source::{TimeSource, MockTimeSource};
297    /// use std::time::Duration;
298    ///
299    /// let time_source = MockTimeSource::new();
300    /// assert_eq!(time_source.now(), Duration::ZERO);
301    ///
302    /// time_source.advance(Duration::from_millis(123));
303    /// assert_eq!(time_source.now(), Duration::from_millis(123));
304    /// ```
305    #[inline(always)]
306    fn now(&self) -> Duration {
307        MOCK_ELAPSED.with(|cell| cell.get())
308    }
309}