use std::fmt;
use std::future::Future;
use std::pin::Pin;
use std::time::Duration;
use crate::cost::CalendarDate;
use crate::error::Error;
pub trait Clock: Send + Sync + fmt::Debug {
fn elapsed(&self) -> Duration;
fn sleep(&self, duration: Duration) -> Pin<Box<dyn Future<Output = ()> + Send + '_>>;
fn unix_now(&self) -> Duration;
fn today_utc(&self) -> Result<CalendarDate, Error> {
CalendarDate::from_unix_secs(self.unix_now().as_secs())
}
}
#[cfg(any(test, feature = "test-support"))]
#[cfg_attr(test, mutants::skip)] pub(crate) mod testing {
use std::sync::{Mutex, PoisonError};
use super::*;
const START_UNIX_SECS: u64 = 1_767_225_600;
#[derive(Debug)]
struct Readings {
elapsed: Duration,
unix: Duration,
}
#[derive(Debug)]
pub struct TestClock {
readings: Mutex<Readings>,
}
impl Default for TestClock {
fn default() -> Self {
Self {
readings: Mutex::new(Readings {
elapsed: Duration::ZERO,
unix: Duration::from_secs(START_UNIX_SECS),
}),
}
}
}
impl TestClock {
pub fn new() -> Self {
Self::default()
}
pub fn advance(&self, duration: Duration) {
let mut readings = self.readings.lock().unwrap_or_else(PoisonError::into_inner);
readings.elapsed += duration;
readings.unix += duration;
}
pub fn now(&self) -> Duration {
self.readings
.lock()
.unwrap_or_else(PoisonError::into_inner)
.elapsed
}
pub fn set_unix_now(&self, unix: Duration) {
self.readings
.lock()
.unwrap_or_else(PoisonError::into_inner)
.unix = unix;
}
}
impl Clock for TestClock {
fn elapsed(&self) -> Duration {
self.now()
}
fn sleep(&self, duration: Duration) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
self.advance(duration);
Box::pin(std::future::ready(()))
}
fn unix_now(&self) -> Duration {
self.readings
.lock()
.unwrap_or_else(PoisonError::into_inner)
.unix
}
}
#[derive(Debug, Default)]
pub struct YieldingClock {
inner: TestClock,
}
impl YieldingClock {
pub fn new() -> Self {
Self::default()
}
}
impl std::ops::Deref for YieldingClock {
type Target = TestClock;
fn deref(&self) -> &TestClock {
&self.inner
}
}
impl Clock for YieldingClock {
fn elapsed(&self) -> Duration {
self.inner.elapsed()
}
fn sleep(&self, duration: Duration) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
self.inner.advance(duration);
Box::pin(tokio::task::yield_now())
}
fn unix_now(&self) -> Duration {
self.inner.unix_now()
}
}
}
#[cfg(test)]
mod tests {
use super::testing::TestClock;
use super::*;
#[test]
fn today_comes_from_the_clock_s_wall_reading() {
let clock = TestClock::new();
clock.set_unix_now(Duration::from_secs(1_709_208_000));
assert_eq!(
clock.today_utc().expect("a representable date"),
CalendarDate::from_ymd(2024, 2, 29)
);
}
#[test]
fn a_wall_reading_past_9999_is_refused() {
let clock = TestClock::new();
clock.set_unix_now(Duration::from_secs(253_402_300_800));
assert!(clock.today_utc().is_err());
}
#[test]
fn advancing_the_test_clock_moves_the_wall_reading_too() {
let clock = TestClock::new();
let (elapsed, unix) = (clock.elapsed(), clock.unix_now());
clock.advance(Duration::from_secs(86_400));
assert_eq!(clock.elapsed() - elapsed, Duration::from_secs(86_400));
assert_eq!(clock.unix_now() - unix, Duration::from_secs(86_400));
}
}