use std::collections::HashMap;
use proptest::prelude::*;
const MAX_TIMERS: usize = 16;
#[derive(Debug, Clone)]
enum Op {
Register(u8, u8, u64),
Advance(u64),
}
fn op_strategy() -> impl Strategy<Value = Op> {
prop_oneof![
3 => (0..=31u8, 0..=15u8, 1..10_000u64).prop_map(|(p, i, d)| Op::Register(p, i, d)),
5 => (1..=1_000u64).prop_map(Op::Advance),
]
}
fn run_ops(ops: &[Op]) {
rivet::kernel_test! {
let mut pending: HashMap<(u8, u8), u64> = HashMap::new();
let mut now: u64 = 0;
for op in ops {
match *op {
Op::Register(prio, idx, deadline) => {
if pending.contains_key(&(prio, idx)) {
continue;
}
if pending.len() >= MAX_TIMERS {
continue;
}
rivet::timer::register_deadline(deadline, rivet::task::TaskId::new(prio, idx))
.expect("timer queue full during model");
pending.insert((prio, idx), deadline);
}
Op::Advance(dt) => {
now = now.saturating_add(dt);
rivet::timer::poll_timers(now);
let expected: Vec<(u8, u8)> = pending
.iter()
.filter(|(_, &d)| d <= now)
.map(|(&k, _)| k)
.collect();
let mut actual = Vec::new();
while let Some(id) = rivet::waker::next_ready() {
actual.push((id.priority(), id.index()));
}
for &(p, i) in &actual {
let d = pending[&(p, i)];
assert!(
d <= now,
"spurious wake: ({p}, {i}) fired at {now}, deadline {d}"
);
}
let mut sorted_expected = expected.clone();
sorted_expected.sort();
let mut sorted_actual = actual.clone();
sorted_actual.sort();
assert_eq!(
sorted_actual, sorted_expected,
"wake set mismatch at now={now}: actual {actual:?}, expected {expected:?}"
);
for &(p, i) in &expected {
pending.remove(&(p, i));
}
assert_eq!(
rivet::timer::slots_in_use(),
pending.len(),
"slot leak at now={now}"
);
}
}
}
let max_deadline = pending.values().copied().max().unwrap_or(0);
now = now.max(max_deadline).saturating_add(1);
rivet::timer::poll_timers(now);
while let Some(id) = rivet::waker::next_ready() {
assert!(
pending.remove(&(id.priority(), id.index())).is_some(),
"unexpected final wake ({}, {})",
id.priority(),
id.index()
);
}
assert!(pending.is_empty(), "deadlines never fired: {pending:?}");
assert_eq!(
rivet::timer::slots_in_use(),
0,
"slots leaked after final flush"
);
}
}
proptest! {
#[test]
fn timer_queue_fires_each_deadline_exactly_once(
ops in prop::collection::vec(op_strategy(), 0..300),
) {
run_ops(&ops);
}
}