use std::{
future::Future,
pin::Pin,
sync::{Arc, Mutex},
time::{Duration, Instant, SystemTime, UNIX_EPOCH},
};
pub trait Clock: Send + Sync + std::fmt::Debug {
fn now(&self) -> Instant;
fn timestamp(&self) -> u64;
fn sleep(&self, duration: Duration) -> Pin<Box<dyn Future<Output = ()> + Send + '_>>;
}
#[derive(Debug, Clone, Copy, Default)]
pub struct SystemClock;
impl Clock for SystemClock {
fn now(&self) -> Instant {
Instant::now()
}
fn timestamp(&self) -> u64 {
SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or(Duration::ZERO).as_secs()
}
fn sleep(&self, duration: Duration) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
Box::pin(tokio::time::sleep(duration))
}
}
#[derive(Debug, Clone)]
pub struct MockClock {
offset: Arc<Mutex<Duration>>,
base: Instant,
base_system: SystemTime,
}
impl MockClock {
pub fn new() -> Self {
Self { offset: Arc::new(Mutex::new(Duration::ZERO)), base: Instant::now(), base_system: SystemTime::now() }
}
pub fn advance(&self, duration: Duration) {
let mut offset = self.offset.lock().expect("mutex poisoned");
*offset += duration;
}
pub fn set_offset(&self, offset: Duration) {
let mut current = self.offset.lock().expect("mutex poisoned");
*current = offset;
}
pub fn current_offset(&self) -> Duration {
*self.offset.lock().expect("mutex poisoned")
}
pub fn reset(&self) {
let mut offset = self.offset.lock().expect("mutex poisoned");
*offset = Duration::ZERO;
}
}
impl Default for MockClock {
fn default() -> Self {
Self::new()
}
}
impl Clock for MockClock {
fn now(&self) -> Instant {
let offset = self.offset.lock().expect("mutex poisoned");
self.base + *offset
}
fn timestamp(&self) -> u64 {
let offset = self.offset.lock().expect("mutex poisoned");
self.base_system.checked_add(*offset).unwrap_or(self.base_system).duration_since(UNIX_EPOCH).unwrap_or(Duration::ZERO).as_secs()
}
fn sleep(&self, duration: Duration) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
self.advance(duration);
Box::pin(std::future::ready(()))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_system_clock_advances() {
let clock = SystemClock;
let t1 = clock.now();
std::thread::sleep(Duration::from_millis(10));
let t2 = clock.now();
assert!(t2 > t1);
}
#[test]
fn test_system_clock_timestamp() {
let clock = SystemClock;
let t1 = clock.timestamp();
std::thread::sleep(Duration::from_millis(1100));
let t2 = clock.timestamp();
assert!(t2 > t1);
}
#[test]
fn test_mock_clock_initial_time() {
let clock = MockClock::new();
let t1 = clock.now();
let t2 = clock.now();
assert_eq!(t1, t2);
}
#[test]
fn test_mock_clock_advance() {
let clock = MockClock::new();
let t1 = clock.now();
clock.advance(Duration::from_secs(10));
let t2 = clock.now();
assert_eq!(t2 - t1, Duration::from_secs(10));
}
#[test]
fn test_mock_clock_timestamp_advances() {
let clock = MockClock::new();
let t1 = clock.timestamp();
clock.advance(Duration::from_secs(10));
let t2 = clock.timestamp();
assert_eq!(t2, t1 + 10);
}
#[test]
fn test_mock_clock_multiple_advances() {
let clock = MockClock::new();
let t1 = clock.now();
clock.advance(Duration::from_secs(5));
clock.advance(Duration::from_secs(3));
let t2 = clock.now();
assert_eq!(t2 - t1, Duration::from_secs(8));
}
#[test]
fn test_mock_clock_set_offset() {
let clock = MockClock::new();
clock.set_offset(Duration::from_secs(100));
assert_eq!(clock.current_offset(), Duration::from_secs(100));
}
#[test]
fn test_mock_clock_reset() {
let clock = MockClock::new();
clock.advance(Duration::from_secs(50));
clock.reset();
assert_eq!(clock.current_offset(), Duration::ZERO);
}
#[test]
fn test_mock_clock_clone_shares_state() {
let clock = MockClock::new();
let clone = clock.clone();
let t1 = clock.now();
clone.advance(Duration::from_secs(20));
let t2 = clock.now();
assert_eq!(t2 - t1, Duration::from_secs(20));
}
#[tokio::test]
async fn test_system_clock_sleep() {
let clock = SystemClock;
let start = clock.now();
clock.sleep(Duration::from_millis(10)).await;
let elapsed = clock.now() - start;
assert!(elapsed >= Duration::from_millis(10));
}
#[tokio::test]
async fn test_mock_clock_sleep_advances_time() {
let clock = MockClock::new();
assert_eq!(clock.current_offset(), Duration::ZERO);
clock.sleep(Duration::from_secs(10)).await;
assert_eq!(clock.current_offset(), Duration::from_secs(10));
clock.sleep(Duration::from_secs(5)).await;
assert_eq!(clock.current_offset(), Duration::from_secs(15));
}
#[tokio::test]
async fn test_mock_clock_sleep_is_instant() {
let clock = MockClock::new();
let real_start = std::time::Instant::now();
clock.sleep(Duration::from_secs(3600)).await; let real_elapsed = real_start.elapsed();
assert!(real_elapsed < Duration::from_secs(1));
assert_eq!(clock.current_offset(), Duration::from_secs(3600));
}
}