use crate::{Clock, Monotonic};
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{Duration, Instant};
pub struct VirtualStdClock {
start: Instant,
offset: AtomicU64,
}
impl VirtualStdClock {
pub fn new(start: Instant) -> Self {
Self {
start,
offset: AtomicU64::new(0),
}
}
pub fn advance_by(&self, d: Duration) -> Instant {
let d: u128 = d.as_nanos();
let d: u64 = u64::try_from(d).unwrap_or(u64::MAX);
let prev: u64 = self.offset.load(Ordering::SeqCst);
let wanted_offset = prev.max(d);
let prev: u64 = self.offset.fetch_max(wanted_offset, Ordering::SeqCst);
let current_offset = prev.max(wanted_offset);
self.start + Duration::from_nanos(current_offset)
}
pub fn advance_to(&self, t: Instant) -> Instant {
let wanted_offset: u128 = match t.checked_duration_since(self.start) {
Some(d) => d.as_nanos(),
None => 0u128,
};
let wanted_offset: u64 = u64::try_from(wanted_offset).unwrap_or(u64::MAX);
let prev: u64 = self.offset.fetch_max(wanted_offset, Ordering::SeqCst);
let current_offset = prev.max(wanted_offset);
self.start + Duration::from_nanos(current_offset)
}
}
impl Clock for VirtualStdClock {
type Instant = Instant;
fn now(&self) -> Self::Instant {
let current_offset = self.offset.load(Ordering::SeqCst);
self.start + Duration::from_nanos(current_offset)
}
}
unsafe impl Monotonic for VirtualStdClock {}
#[cfg(test)]
mod tests {
use super::*;
use crate::StdClock;
const ONE_YEAR: Duration = Duration::new(365 * 24 * 60 * 60, 0);
#[test]
fn test_now() {
let start = Instant::now();
let clock = VirtualStdClock::new(start);
use_clock(&clock);
use_std_clock(&clock);
}
fn use_clock<TyClock>(clock: &TyClock)
where
TyClock: Clock<Instant = Instant> + Monotonic,
{
let one_year_ago = Instant::now() - ONE_YEAR;
let now = clock.now();
assert!(now > one_year_ago);
use_std_clock(clock);
}
fn use_std_clock<TyClock>(clock: &TyClock)
where
TyClock: StdClock + Monotonic,
{
let one_year_ago = Instant::now() - ONE_YEAR;
let now = clock.now_std();
assert!(now > one_year_ago);
}
#[test]
fn test_advance_by() {
let start = Instant::now();
let clock = VirtualStdClock::new(start);
assert_eq!(clock.now(), start);
clock.advance_by(ONE_YEAR);
assert_eq!(clock.now(), start + ONE_YEAR);
assert_eq!(clock.now(), start + ONE_YEAR);
}
#[test]
fn test_advance_to() {
let start = Instant::now();
let clock = VirtualStdClock::new(start);
assert_eq!(clock.now(), start);
clock.advance_to(start + 2 * ONE_YEAR);
assert_eq!(clock.now(), start + 2 * ONE_YEAR);
assert_eq!(clock.now(), start + 2 * ONE_YEAR);
}
#[test]
fn test_monotonic() {
let start = Instant::now();
let clock = VirtualStdClock::new(start);
assert_eq!(clock.now(), start);
clock.advance_to(start + 2 * ONE_YEAR);
assert_eq!(clock.now(), start + 2 * ONE_YEAR);
clock.advance_to(start + ONE_YEAR);
assert_eq!(clock.now(), start + 2 * ONE_YEAR);
}
}