#![allow(clippy::unwrap_used, clippy::panic)]
use std::{
sync::{Arc, mpsc},
thread,
time::{Duration, Instant},
};
use super::{WatchdogOutcome, WatchdogSignal};
#[test]
fn a_finished_invocation_wakes_the_watchdog_at_once() {
const ROUNDS: u32 = 10;
let mut wake_gaps = Vec::new();
for round in 0..ROUNDS {
let signal = Arc::new(WatchdogSignal::new());
let deadline = Instant::now() + Duration::from_mins(1);
let (started_tx, started_rx) = mpsc::channel();
let waiter = {
let signal = Arc::clone(&signal);
thread::spawn(move || {
started_tx.send(Instant::now()).unwrap();
let outcome = signal.wait_until(deadline);
(outcome, Instant::now())
})
};
let waiter_started = started_rx.recv().unwrap();
let finish_at = waiter_started + Duration::from_millis(11 + u64::from(round % 3));
thread::sleep(finish_at.saturating_duration_since(Instant::now()));
let finished_at = Instant::now();
signal.finish();
let (outcome, woke_at) = waiter.join().unwrap();
assert_eq!(outcome, WatchdogOutcome::Finished, "round {round}");
wake_gaps.push(woke_at.saturating_duration_since(finished_at));
}
wake_gaps.sort_unstable();
let median_wake_gap = wake_gaps[wake_gaps.len() / 2];
assert!(
median_wake_gap < Duration::from_millis(4),
"the watchdog must wake on the signal, not on a poll interval — the median of \
{ROUNDS} wake gaps after `finish` had already been called was {median_wake_gap:?} \
(all gaps: {wake_gaps:?})"
);
}
#[test]
fn an_unfinished_invocation_waits_out_its_deadline() {
let signal = WatchdogSignal::new();
let budget = Duration::from_millis(120);
let started = Instant::now();
let outcome = signal.wait_until(started + budget);
let waited = started.elapsed();
assert_eq!(outcome, WatchdogOutcome::DeadlineReached);
assert!(
waited >= budget,
"the watchdog must not fire early — waited {waited:?} of a {budget:?} budget"
);
assert!(
waited < budget * 4,
"…nor drift far past it: waited {waited:?} of a {budget:?} budget"
);
}
#[test]
fn a_completion_before_the_wait_is_not_lost() {
let signal = WatchdogSignal::new();
signal.finish();
let started = Instant::now();
let outcome = signal.wait_until(started + Duration::from_mins(1));
assert_eq!(outcome, WatchdogOutcome::Finished);
assert!(started.elapsed() < Duration::from_millis(50), "it must not wait at all");
}
#[test]
fn an_expired_deadline_returns_at_once() {
let signal = WatchdogSignal::new();
let started = Instant::now();
let outcome =
signal.wait_until(started.checked_sub(Duration::from_secs(1)).expect("past instant"));
assert_eq!(outcome, WatchdogOutcome::DeadlineReached);
assert!(started.elapsed() < Duration::from_millis(50));
}
#[test]
fn finishing_twice_and_finishing_late_are_both_safe() {
let signal = WatchdogSignal::new();
assert!(!signal.is_finished());
let outcome = signal.wait_until(Instant::now() + Duration::from_millis(20));
assert_eq!(outcome, WatchdogOutcome::DeadlineReached);
signal.finish();
signal.finish();
assert!(signal.is_finished());
}