Skip to main content

sipx_testkit/
time.rs

1//! Virtual time for deterministic test drivers.
2
3use std::ops::Add;
4use std::time::Duration;
5
6/// Nanoseconds since a harness began.
7///
8/// There is deliberately no `now()` constructor and no conversion from a wall clock. Tests move
9/// this value explicitly, so a loaded machine cannot change a call flow's result.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
11pub struct Virtual(u64);
12
13impl Virtual {
14    /// The instant a harness starts.
15    #[must_use]
16    pub const fn epoch() -> Self {
17        Self(0)
18    }
19
20    /// This many milliseconds after the harness started.
21    #[must_use]
22    pub const fn at_millis(millis: u64) -> Self {
23        Self(millis.saturating_mul(1_000_000))
24    }
25
26    /// This many nanoseconds after the harness started.
27    #[must_use]
28    pub const fn at_nanos(nanos: u64) -> Self {
29        Self(nanos)
30    }
31
32    /// Milliseconds since the harness started.
33    #[must_use]
34    pub const fn millis(self) -> u64 {
35        self.0 / 1_000_000
36    }
37
38    /// Nanoseconds since the harness started.
39    #[must_use]
40    pub const fn nanos(self) -> u64 {
41        self.0
42    }
43}
44
45impl Add<Duration> for Virtual {
46    type Output = Self;
47
48    fn add(self, after: Duration) -> Self {
49        Self(
50            self.0
51                .saturating_add(u64::try_from(after.as_nanos()).unwrap_or(u64::MAX)),
52        )
53    }
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59
60    #[test]
61    fn adding_sub_millisecond_time_preserves_every_nanosecond() {
62        let instant = Virtual::epoch() + Duration::from_nanos(999_999);
63
64        assert_eq!(instant.nanos(), 999_999);
65        assert_eq!(instant.millis(), 0);
66    }
67}