ppoppo-clock 0.1.1

Universal Clock + Timer port for ppoppo workspace — chat-as-infrastructure primitive
Documentation
use crate::{Clock, Timer};
use futures::future::BoxFuture;
use jiff::Timestamp;
use std::time::Duration;

/// Real wall clock backed by the system clock.
pub struct WallClock;

impl Clock for WallClock {
    fn now(&self) -> Timestamp {
        Timestamp::now()
    }
}

/// Tokio-backed async timer.
pub struct TokioTimer;

impl Timer for TokioTimer {
    fn sleep(&self, dur: Duration) -> BoxFuture<'static, ()> {
        Box::pin(async move {
            tokio::time::sleep(dur).await;
        })
    }

    fn next_tick(&self) -> BoxFuture<'static, ()> {
        Box::pin(async {
            tokio::task::yield_now().await;
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::Clock as ClockTrait;
    use crate::Timer as TimerTrait;
    use std::sync::Arc;
    use std::time::Instant;

    fn assert_send_sync<T: Send + Sync>() {}

    #[test]
    fn wall_clock_and_tokio_timer_are_send_sync() {
        assert_send_sync::<WallClock>();
        assert_send_sync::<TokioTimer>();
    }

    #[test]
    fn now_is_recent() {
        let before = Timestamp::now();
        let clock = WallClock;
        let t = clock.now();
        let after = Timestamp::now();
        assert!(t >= before - jiff::SignedDuration::from_secs(1));
        assert!(t <= after + jiff::SignedDuration::from_secs(1));
    }

    #[test]
    fn now_unix_millis_positive_and_recent() {
        let before_ms = Timestamp::now().as_millisecond();
        let clock = WallClock;
        let ms = clock.now_unix_millis();
        let after_ms = Timestamp::now().as_millisecond();
        assert!(ms > 0, "unix millis must be positive");
        assert!(ms >= before_ms - 100, "must be >= before sample");
        assert!(ms <= after_ms + 100, "must be <= after sample");
    }

    #[tokio::test]
    async fn tokio_timer_sleep_completes() {
        let timer = TokioTimer;
        let start = Instant::now();
        timer.sleep(Duration::from_millis(50)).await;
        let elapsed = start.elapsed();
        assert!(elapsed.as_millis() >= 45, "sleep must take at least 45ms");
        assert!(elapsed.as_millis() < 500, "sleep must not take more than 500ms");
    }

    #[tokio::test]
    async fn tokio_timer_next_tick_completes_immediately() {
        let timer = TokioTimer;
        let start = Instant::now();
        timer.next_tick().await;
        assert!(start.elapsed().as_millis() < 100);
    }

    #[test]
    fn wall_clock_arc_dyn_dispatch() {
        let c: Arc<dyn ClockTrait> = Arc::new(WallClock);
        let _ = c.now();
    }

    #[test]
    fn tokio_timer_arc_dyn_dispatch() {
        let t: Arc<dyn TimerTrait> = Arc::new(TokioTimer);
        let _ = t.sleep(Duration::ZERO);
    }
}