use std::sync::Arc;
use std::time::{Duration, Instant, SystemTime};
pub(crate) const CLOCK_UNIX_ENV_VAR: &str = "AGENTTY_CLOCK_UNIX";
pub(crate) trait Clock: Send + Sync {
fn now_instant(&self) -> Instant;
fn now_system_time(&self) -> SystemTime;
}
pub(crate) fn from_environment() -> Arc<dyn Clock> {
match pinned_system_time() {
Some(system_time) => Arc::new(FixedClock { system_time }),
None => Arc::new(RealClock),
}
}
pub(crate) struct RealClock;
impl Clock for RealClock {
fn now_instant(&self) -> Instant {
Instant::now()
}
fn now_system_time(&self) -> SystemTime {
SystemTime::now()
}
}
struct FixedClock {
system_time: SystemTime,
}
impl Clock for FixedClock {
fn now_instant(&self) -> Instant {
Instant::now()
}
fn now_system_time(&self) -> SystemTime {
self.system_time
}
}
fn pinned_system_time() -> Option<SystemTime> {
let raw = std::env::var(CLOCK_UNIX_ENV_VAR).ok()?;
let unix_seconds = raw.trim().parse::<u64>().ok()?;
Some(SystemTime::UNIX_EPOCH + Duration::from_secs(unix_seconds))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fixed_clock_freezes_wall_clock_time() {
let system_time = SystemTime::UNIX_EPOCH + Duration::from_secs(90);
let clock = FixedClock { system_time };
let first_read = clock.now_system_time();
let second_read = clock.now_system_time();
assert_eq!(first_read, system_time);
assert_eq!(second_read, system_time);
}
#[test]
fn fixed_clock_keeps_monotonic_time_advancing() {
let clock = FixedClock {
system_time: SystemTime::UNIX_EPOCH,
};
let earlier = clock.now_instant();
let later = clock.now_instant();
assert!(later >= earlier);
}
#[test]
fn real_clock_advances_wall_clock_time() {
let clock = RealClock;
let now = clock.now_system_time();
assert!(now > SystemTime::UNIX_EPOCH);
}
}