use std::sync::{Arc, LazyLock};
use std::time::SystemTime;
pub(crate) trait Clock: Send + Sync + std::fmt::Debug + 'static {
fn now(&self) -> SystemTime;
}
#[derive(Debug, Default, Clone, Copy)]
pub(crate) struct SystemClock;
impl Clock for SystemClock {
fn now(&self) -> SystemTime {
SystemTime::now()
}
}
pub(crate) type SharedClock = Arc<dyn Clock>;
static SYSTEM: LazyLock<SharedClock> = LazyLock::new(|| Arc::new(SystemClock));
pub(crate) fn system() -> SharedClock {
Arc::clone(&SYSTEM)
}
#[cfg(test)]
#[derive(Debug)]
pub(crate) struct ManualClock {
now: std::sync::Mutex<SystemTime>,
}
#[cfg(test)]
impl ManualClock {
pub(crate) fn at(t: SystemTime) -> Arc<Self> {
Arc::new(Self {
now: std::sync::Mutex::new(t),
})
}
pub(crate) fn set(&self, t: SystemTime) {
*self.now.lock().expect("manual clock poisoned") = t;
}
pub(crate) fn advance(&self, by: std::time::Duration) {
*self.now.lock().expect("manual clock poisoned") += by;
}
}
#[cfg(test)]
impl Clock for ManualClock {
fn now(&self) -> SystemTime {
*self.now.lock().expect("manual clock poisoned")
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::{Duration, UNIX_EPOCH};
#[test]
fn manual_clock_set_and_advance() {
let t0 = UNIX_EPOCH + Duration::from_secs(1_700_000_000);
let clock = ManualClock::at(t0);
assert_eq!(clock.now(), t0);
clock.advance(Duration::from_secs(90));
assert_eq!(clock.now(), t0 + Duration::from_secs(90));
let t1 = UNIX_EPOCH + Duration::from_secs(42);
clock.set(t1);
assert_eq!(clock.now(), t1);
let shared: SharedClock = clock.clone();
assert_eq!(shared.now(), t1);
}
#[test]
fn system_clock_is_close_to_now() {
let before = SystemTime::now();
let read = system().now();
let after = SystemTime::now();
assert!(
before <= read && read <= after,
"{before:?} {read:?} {after:?}"
);
assert!(SystemClock.now() >= read);
}
#[test]
fn system_handles_are_one_clock_and_manual_clocks_differ() {
assert!(Arc::ptr_eq(&system(), &system()));
let manual: SharedClock = ManualClock::at(UNIX_EPOCH);
assert!(!Arc::ptr_eq(&manual, &system()));
}
}