pub mod tokio;
#[cfg(any(test, feature = "testing"))]
use std::sync::{
Arc,
atomic::{AtomicU64, Ordering},
};
use std::time::{Duration, Instant, SystemTime};
pub trait Clock {
fn now(&self) -> Instant;
}
#[cfg(not(loom))]
pub type SharedClock = std::sync::Arc<dyn Clock + Send + Sync + std::panic::RefUnwindSafe>;
#[cfg(not(loom))]
#[derive(Clone)]
pub struct SyncClock(SharedClock);
#[cfg(not(loom))]
impl SyncClock {
#[must_use]
pub fn new(clock: SharedClock) -> Self {
SyncClock(clock)
}
#[must_use]
pub fn system() -> Self {
SyncClock(std::sync::Arc::new(SystemClock))
}
}
#[cfg(not(loom))]
impl std::ops::Deref for SyncClock {
type Target = dyn Clock + Send + Sync + std::panic::RefUnwindSafe;
fn deref(&self) -> &Self::Target {
&*self.0
}
}
#[cfg(not(loom))]
impl std::fmt::Debug for SyncClock {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("SyncClock")
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct SystemClock;
impl Clock for SystemClock {
#[inline]
fn now(&self) -> Instant {
Instant::now()
}
}
#[derive(Clone, Copy, Debug)]
pub struct Stopwatch {
started: Instant,
}
impl Stopwatch {
#[inline]
#[must_use]
pub fn start<C: Clock + ?Sized>(clock: &C) -> Self {
Self {
started: clock.now(),
}
}
#[inline]
#[must_use]
pub fn elapsed<C: Clock + ?Sized>(&self, clock: &C) -> Duration {
clock.now().saturating_duration_since(self.started)
}
}
#[must_use]
pub fn wall_now() -> SystemTime {
SystemTime::now()
}
#[cfg(any(test, feature = "testing"))]
#[doc(hidden)]
#[derive(Debug)]
pub struct TestClock {
base: Instant,
offset_nanos: AtomicU64,
}
#[cfg(any(test, feature = "testing"))]
impl TestClock {
#[must_use]
pub fn frozen() -> Arc<TestClock> {
Arc::new(TestClock {
base: Instant::now(),
offset_nanos: AtomicU64::new(0),
})
}
pub fn advance(&self, by: Duration) {
let nanos = u64::try_from(by.as_nanos()).expect("test clock advance fits in u64 nanos");
self.offset_nanos.fetch_add(nanos, Ordering::Relaxed);
}
pub fn advance_stepped(&self, total: Duration, step: Duration, mut between: impl FnMut()) {
assert!(!step.is_zero(), "advance_stepped needs a non-zero step");
let mut moved = Duration::ZERO;
while moved < total {
let chunk = step.min(total - moved);
self.advance(chunk);
moved += chunk;
between();
}
}
}
#[cfg(any(test, feature = "testing"))]
impl Clock for TestClock {
fn now(&self) -> Instant {
self.base + Duration::from_nanos(self.offset_nanos.load(Ordering::Relaxed))
}
}
#[cfg(any(test, feature = "testing"))]
impl Clock for &TestClock {
fn now(&self) -> Instant {
(**self).now()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(not(loom))]
#[test]
fn a_sync_clock_debugs_as_its_name() {
assert_eq!(format!("{:?}", SyncClock::system()), "SyncClock");
}
#[test]
fn advance_stepped_reaches_total_in_steps() {
let clock = TestClock::frozen();
let start = clock.now();
let mut seen = Vec::new();
clock.advance_stepped(Duration::from_millis(25), Duration::from_millis(10), || {
seen.push(clock.now() - start);
});
assert_eq!(
seen,
[10, 20, 25].map(Duration::from_millis),
"one reading per step, the last one short"
);
}
#[test]
fn a_stopwatch_reports_the_time_its_clock_moved() {
let clock = TestClock::frozen();
let watch = Stopwatch::start(&*clock);
clock.advance(Duration::from_millis(250));
assert_eq!(watch.elapsed(&*clock), Duration::from_millis(250));
}
#[test]
#[should_panic(expected = "non-zero step")]
fn advance_stepped_rejects_a_zero_step() {
TestClock::frozen().advance_stepped(Duration::from_secs(1), Duration::ZERO, || {});
}
}