use std::time::{Duration, Instant};
#[cfg(feature = "native")]
fn block_on<F: std::future::Future>(fut: F) -> F::Output {
use std::pin::pin;
use std::sync::Arc;
use std::task::{Context, Poll, Wake};
struct NoopWaker;
impl Wake for NoopWaker {
fn wake(self: Arc<Self>) {}
}
let waker = Arc::new(NoopWaker).into();
let mut cx = Context::from_waker(&waker);
let mut fut = pin!(fut);
loop {
match fut.as_mut().poll(&mut cx) {
Poll::Ready(v) => return v,
Poll::Pending => std::thread::yield_now(),
}
}
}
const SLEEP_DUR: Duration = Duration::from_millis(50);
const SLACK: Duration = Duration::from_millis(200);
#[cfg(feature = "native")]
mod native_tests {
use super::*;
use dtact_util::timer::{DtactInterval, DtactTimeout, MissedTickBehavior, sleep, timeout};
#[test]
fn sleep_waits_roughly_the_requested_duration() {
let start = Instant::now();
block_on(sleep(SLEEP_DUR));
let elapsed = start.elapsed();
assert!(
elapsed >= SLEEP_DUR,
"sleep returned early: elapsed={elapsed:?}, wanted >= {SLEEP_DUR:?}"
);
assert!(
elapsed <= SLEEP_DUR + SLACK,
"sleep took far too long: elapsed={elapsed:?}"
);
}
#[test]
fn interval_fires_multiple_times_with_roughly_correct_spacing() {
const N: usize = 5;
let period = Duration::from_millis(20);
let mut interval = DtactInterval::new(period);
let start = Instant::now();
let mut ticks = Vec::with_capacity(N);
block_on(async {
for _ in 0..N {
ticks.push(interval.tick().await);
}
});
assert_eq!(ticks.len(), N);
let total_elapsed = start.elapsed();
let expected_min = period * (N as u32 - 1);
assert!(
total_elapsed >= expected_min,
"interval ticked too fast: elapsed={total_elapsed:?}, expected >= {expected_min:?}"
);
}
#[test]
fn timeout_fires_for_a_future_that_never_completes() {
block_on(async {
let never = std::future::pending::<()>();
let result = DtactTimeout::new(Duration::from_millis(30), never).await;
assert!(result.is_err(), "expected timeout to elapse");
});
}
#[test]
fn timeout_does_not_fire_for_a_fast_future() {
block_on(async {
let fast = async { 42 };
let result = timeout(Duration::from_millis(500), fast).await;
assert_eq!(result.unwrap(), 42);
});
}
#[test]
fn zero_duration_sleep_resolves() {
let start = Instant::now();
block_on(sleep(Duration::ZERO));
assert!(
start.elapsed() < SLACK,
"a zero-duration sleep should resolve almost immediately"
);
}
#[test]
fn zero_duration_timeout_always_fires_for_a_pending_future() {
block_on(async {
let never = std::future::pending::<()>();
let result = DtactTimeout::new(Duration::ZERO, never).await;
assert!(result.is_err(), "a zero-duration timeout must elapse");
});
}
#[test]
fn missed_tick_behavior_defaults_to_burst_and_is_settable() {
let mut interval = DtactInterval::new(Duration::from_millis(20));
assert_eq!(interval.missed_tick_behavior(), MissedTickBehavior::Burst);
interval.set_missed_tick_behavior(MissedTickBehavior::Delay);
assert_eq!(interval.missed_tick_behavior(), MissedTickBehavior::Delay);
}
#[test]
fn missed_tick_behavior_burst_does_not_skip_ticks() {
let period = Duration::from_millis(15);
let mut interval = DtactInterval::new(period);
interval.set_missed_tick_behavior(MissedTickBehavior::Burst);
block_on(async {
sleep(period * 4).await;
let start = Instant::now();
for _ in 0..4 {
interval.tick().await;
}
assert!(
start.elapsed() < period * 2,
"Burst must fire backlog ticks immediately, took {:?}",
start.elapsed()
);
});
}
}
#[cfg(all(feature = "tokio", not(feature = "native")))]
mod tokio_tests {
use super::*;
use dtact_util::timer::{DtactInterval, DtactTimeout, MissedTickBehavior, sleep, timeout};
#[tokio::test]
async fn sleep_waits_roughly_the_requested_duration() {
let start = Instant::now();
sleep(SLEEP_DUR).await;
let elapsed = start.elapsed();
assert!(
elapsed >= SLEEP_DUR,
"sleep returned early: elapsed={elapsed:?}, wanted >= {SLEEP_DUR:?}"
);
assert!(
elapsed <= SLEEP_DUR + SLACK,
"sleep took far too long: elapsed={elapsed:?}"
);
}
#[tokio::test]
async fn interval_fires_multiple_times_with_roughly_correct_spacing() {
const N: usize = 5;
let period = Duration::from_millis(20);
let mut interval = DtactInterval::new(period);
let start = Instant::now();
let mut ticks = Vec::with_capacity(N);
for _ in 0..N {
ticks.push(interval.tick().await);
}
assert_eq!(ticks.len(), N);
let total_elapsed = start.elapsed();
let expected_min = period * (N as u32 - 1);
assert!(
total_elapsed >= expected_min,
"interval ticked too fast: elapsed={total_elapsed:?}, expected >= {expected_min:?}"
);
}
#[tokio::test]
async fn timeout_fires_for_a_future_that_never_completes() {
let never = std::future::pending::<()>();
let result = DtactTimeout::new(Duration::from_millis(30), never).await;
assert!(result.is_err(), "expected timeout to elapse");
}
#[tokio::test]
async fn timeout_does_not_fire_for_a_fast_future() {
let fast = async { 42 };
let result = timeout(Duration::from_millis(500), fast).await;
assert_eq!(result.unwrap(), 42);
}
#[tokio::test]
async fn zero_duration_sleep_resolves() {
let start = Instant::now();
sleep(Duration::ZERO).await;
assert!(
start.elapsed() < SLACK,
"a zero-duration sleep should resolve almost immediately"
);
}
#[tokio::test]
async fn zero_duration_timeout_always_fires_for_a_pending_future() {
let never = std::future::pending::<()>();
let result = DtactTimeout::new(Duration::ZERO, never).await;
assert!(result.is_err(), "a zero-duration timeout must elapse");
}
#[tokio::test]
async fn missed_tick_behavior_defaults_to_burst_and_is_settable() {
let mut interval = DtactInterval::new(Duration::from_millis(20));
assert_eq!(interval.missed_tick_behavior(), MissedTickBehavior::Burst);
interval.set_missed_tick_behavior(MissedTickBehavior::Delay);
assert_eq!(interval.missed_tick_behavior(), MissedTickBehavior::Delay);
}
}