Skip to main content

rtime_clock/
mock.rs

1use std::sync::{Arc, Mutex};
2
3use rtime_core::clock::{Clock, ClockError};
4use rtime_core::timestamp::{NtpDuration, NtpTimestamp};
5
6/// Deterministic mock clock for testing.
7/// Time advances only when explicitly set or stepped.
8#[derive(Clone)]
9pub struct MockClock {
10    state: Arc<Mutex<MockClockState>>,
11}
12
13struct MockClockState {
14    current_time: NtpTimestamp,
15    frequency_ppm: f64,
16    adjustable: bool,
17}
18
19impl MockClock {
20    pub fn new(initial_time: NtpTimestamp) -> Self {
21        Self {
22            state: Arc::new(Mutex::new(MockClockState {
23                current_time: initial_time,
24                frequency_ppm: 0.0,
25                adjustable: true,
26            })),
27        }
28    }
29
30    /// Set the current time directly.
31    pub fn set_time(&self, time: NtpTimestamp) {
32        self.state.lock().unwrap().current_time = time;
33    }
34
35    /// Advance time by a duration (in nanoseconds).
36    pub fn advance(&self, nanos: i64) {
37        let mut state = self.state.lock().unwrap();
38        let raw = state.current_time.raw() as i128;
39        // Convert nanos to NTP fraction: nanos * 2^32 / 1_000_000_000
40        let ntp_delta = (nanos as i128) * (1i128 << 32) / 1_000_000_000;
41        state.current_time = NtpTimestamp::from_raw((raw + ntp_delta) as u64);
42    }
43
44    /// Set whether this clock can be adjusted.
45    pub fn set_adjustable(&self, adjustable: bool) {
46        self.state.lock().unwrap().adjustable = adjustable;
47    }
48
49    /// Get current frequency offset.
50    pub fn get_frequency(&self) -> f64 {
51        self.state.lock().unwrap().frequency_ppm
52    }
53}
54
55impl Clock for MockClock {
56    fn now(&self) -> Result<NtpTimestamp, ClockError> {
57        Ok(self.state.lock().unwrap().current_time)
58    }
59
60    fn step(&self, offset: NtpDuration) -> Result<(), ClockError> {
61        let mut state = self.state.lock().unwrap();
62        if !state.adjustable {
63            return Err(ClockError::PermissionDenied);
64        }
65        let raw = state.current_time.raw() as i128;
66        let ntp_delta = (offset.to_nanos() as i128) * (1i128 << 32) / 1_000_000_000;
67        state.current_time = NtpTimestamp::from_raw((raw + ntp_delta) as u64);
68        Ok(())
69    }
70
71    fn adjust_frequency(&self, ppm: f64) -> Result<(), ClockError> {
72        let mut state = self.state.lock().unwrap();
73        if !state.adjustable {
74            return Err(ClockError::PermissionDenied);
75        }
76        state.frequency_ppm = ppm;
77        Ok(())
78    }
79
80    fn frequency_offset(&self) -> Result<f64, ClockError> {
81        Ok(self.state.lock().unwrap().frequency_ppm)
82    }
83
84    fn resolution(&self) -> NtpDuration {
85        NtpDuration::from_nanos(1)
86    }
87
88    fn max_frequency_adjustment(&self) -> f64 {
89        500.0
90    }
91
92    fn is_adjustable(&self) -> bool {
93        self.state.lock().unwrap().adjustable
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100
101    #[test]
102    fn mock_clock_basic() {
103        let clock = MockClock::new(NtpTimestamp::new(1000, 0));
104        let t = clock.now().unwrap();
105        assert_eq!(t.seconds(), 1000);
106    }
107
108    #[test]
109    fn mock_clock_advance() {
110        let clock = MockClock::new(NtpTimestamp::new(1000, 0));
111        clock.advance(1_000_000_000); // 1 second
112        let t = clock.now().unwrap();
113        assert_eq!(t.seconds(), 1001);
114    }
115
116    #[test]
117    fn mock_clock_step() {
118        let clock = MockClock::new(NtpTimestamp::new(1000, 0));
119        clock.step(NtpDuration::from_nanos(2_000_000_000)).unwrap();
120        let t = clock.now().unwrap();
121        assert_eq!(t.seconds(), 1002);
122    }
123
124    #[test]
125    fn mock_clock_not_adjustable() {
126        let clock = MockClock::new(NtpTimestamp::new(1000, 0));
127        clock.set_adjustable(false);
128        assert!(!clock.is_adjustable());
129        assert!(clock.step(NtpDuration::from_nanos(1000)).is_err());
130        assert!(clock.adjust_frequency(1.0).is_err());
131    }
132
133    #[test]
134    fn mock_clock_frequency() {
135        let clock = MockClock::new(NtpTimestamp::new(1000, 0));
136        clock.adjust_frequency(10.5).unwrap();
137        assert!((clock.frequency_offset().unwrap() - 10.5).abs() < f64::EPSILON);
138    }
139}