use std::sync::atomic::{AtomicU64, Ordering};
use std::time::Duration;
pub trait Clock: Send + Sync {
fn now(&self) -> Duration;
fn advance(&self, duration: Duration);
fn elapsed(&self, since: Duration) -> Duration {
self.now().saturating_sub(since)
}
}
pub trait WallClock: Send + Sync {
fn now_unix_ns(&self) -> u64;
}
#[derive(Debug)]
pub struct MockClock {
offset_ns: AtomicU64,
}
impl MockClock {
#[must_use]
pub fn new() -> Self {
Self {
offset_ns: AtomicU64::new(0),
}
}
pub fn set_offset(&self, offset: Duration) {
let offset_ns = u64::try_from(offset.as_nanos()).unwrap_or(u64::MAX);
self.offset_ns.store(offset_ns, Ordering::SeqCst);
}
#[must_use]
pub fn offset_ns(&self) -> u64 {
self.offset_ns.load(Ordering::SeqCst)
}
}
impl Default for MockClock {
fn default() -> Self {
Self::new()
}
}
impl Clock for MockClock {
fn now(&self) -> Duration {
Duration::from_nanos(self.offset_ns.load(Ordering::SeqCst))
}
#[allow(clippy::let_underscore_must_use)] fn advance(&self, duration: Duration) {
let delta = u64::try_from(duration.as_nanos()).unwrap_or(u64::MAX);
let _ = self
.offset_ns
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |current| {
Some(current.saturating_add(delta))
});
}
}
impl WallClock for MockClock {
fn now_unix_ns(&self) -> u64 {
self.offset_ns.load(Ordering::SeqCst)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mock_clock_starts_at_zero() {
let clock = MockClock::new();
assert_eq!(clock.now(), Duration::ZERO);
}
#[test]
fn test_mock_clock_advance() {
let clock = MockClock::new();
let start = clock.now();
clock.advance(Duration::from_secs(1));
assert_eq!(clock.elapsed(start), Duration::from_secs(1));
clock.advance(Duration::from_millis(500));
assert_eq!(clock.elapsed(start), Duration::from_millis(1500));
}
#[test]
fn test_mock_clock_set_offset() {
let clock = MockClock::new();
clock.set_offset(Duration::from_secs(100));
assert_eq!(clock.now(), Duration::from_secs(100));
}
#[test]
fn test_mock_wall_clock() {
let clock = MockClock::new();
assert_eq!(clock.now_unix_ns(), 0);
clock.advance(Duration::from_millis(2));
assert_eq!(clock.now_unix_ns(), 2_000_000);
}
}