#[cfg(any(test, feature = "test-clock"))]
use core::sync::atomic::{AtomicU64, Ordering};
pub trait Clock: Send + Sync + 'static {
fn now_ms(&self) -> u64;
}
pub struct SystemClock;
impl Clock for SystemClock {
fn now_ms(&self) -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(saturating_millis)
.unwrap_or(0)
}
}
fn saturating_millis(d: std::time::Duration) -> u64 {
u64::try_from(d.as_millis()).unwrap_or(u64::MAX)
}
#[cfg(any(test, feature = "test-clock"))]
pub mod testing {
use super::*;
use std::sync::Arc;
#[derive(Clone, Default)]
pub struct MockClock {
ms: Arc<AtomicU64>,
}
impl MockClock {
pub fn new(start_ms: u64) -> Self {
MockClock {
ms: Arc::new(AtomicU64::new(start_ms)),
}
}
pub fn advance(&self, by_ms: u64) {
self.ms.fetch_add(by_ms, Ordering::AcqRel);
}
pub fn set(&self, to_ms: u64) {
self.ms.store(to_ms, Ordering::Release);
}
}
impl Clock for MockClock {
fn now_ms(&self) -> u64 {
self.ms.load(Ordering::Acquire)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use testing::MockClock;
#[test]
fn system_clock_returns_nonzero() {
let clock = SystemClock;
let now = clock.now_ms();
assert!(now > 1_700_000_000_000, "current time after 2023-11");
}
#[test]
fn saturating_millis_passes_through_in_range() {
use std::time::Duration;
assert_eq!(saturating_millis(Duration::from_millis(123)), 123);
}
#[test]
fn saturating_millis_saturates_instead_of_truncating() {
use std::time::Duration;
assert_eq!(saturating_millis(Duration::from_secs(u64::MAX)), u64::MAX);
}
#[test]
fn mock_clock_starts_at_seed() {
let clock = MockClock::new(42);
assert_eq!(clock.now_ms(), 42);
}
#[test]
fn mock_clock_advance() {
let clock = MockClock::new(100);
clock.advance(50);
assert_eq!(clock.now_ms(), 150);
}
#[test]
fn mock_clock_set() {
let clock = MockClock::new(100);
clock.set(999);
assert_eq!(clock.now_ms(), 999);
}
}