pub trait Clock: Send + Sync {
fn now_ms(&self) -> i64;
}
pub struct SystemClock;
impl Clock for SystemClock {
fn now_ms(&self) -> i64 {
if let Ok(v) = std::env::var("AREEV_TRIGGER_NOW_MS") {
return v.parse::<i64>().unwrap_or_else(|_| {
panic!("AREEV_TRIGGER_NOW_MS is set but not an integer: {v:?}")
});
}
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_millis() as i64)
.unwrap_or(0)
}
}
pub struct FixedClock(pub std::sync::atomic::AtomicI64);
impl FixedClock {
pub fn new(now_ms: i64) -> Self {
FixedClock(std::sync::atomic::AtomicI64::new(now_ms))
}
pub fn set(&self, now_ms: i64) {
self.0.store(now_ms, std::sync::atomic::Ordering::SeqCst);
}
pub fn advance(&self, ms: i64) {
assert!(ms >= 0, "time does not run backwards");
self.0.fetch_add(ms, std::sync::atomic::Ordering::SeqCst);
}
}
impl Clock for FixedClock {
fn now_ms(&self) -> i64 {
self.0.load(std::sync::atomic::Ordering::SeqCst)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fixed_clock_reads_and_advances() {
let c = FixedClock::new(1_000);
assert_eq!(c.now_ms(), 1_000);
c.advance(500);
assert_eq!(c.now_ms(), 1_500);
c.set(42);
assert_eq!(c.now_ms(), 42);
}
#[test]
#[should_panic(expected = "time does not run backwards")]
fn fixed_clock_refuses_to_rewind() {
FixedClock::new(10).advance(-1);
}
#[test]
fn the_simulation_seam_is_honoured() {
std::env::set_var("AREEV_TRIGGER_NOW_MS", "1755600000000");
assert_eq!(SystemClock.now_ms(), 1_755_600_000_000);
std::env::remove_var("AREEV_TRIGGER_NOW_MS");
std::env::set_var("AREEV_TRIGGER_NOW_MS", "soon");
let panicked = std::panic::catch_unwind(|| SystemClock.now_ms()).is_err();
std::env::remove_var("AREEV_TRIGGER_NOW_MS");
assert!(panicked, "a set-but-unparseable override must panic");
}
}