#[cfg(all(test, not(axtest)))]
mod tests {
use super::*;
#[test]
fn rearming_physically_replaces_the_previous_alarm_node() {
let slot = AlarmSlot::new();
let mut queue = AlarmQueue::new();
let first = slot.replace(Some(ClockDeadline::Monotonic(Duration::from_nanos(10))));
let second = slot.replace(Some(ClockDeadline::Monotonic(Duration::from_nanos(20))));
let AlarmChange::Schedule {
deadline: first_deadline,
token: first_token,
} = first
else {
unreachable!("armed slot must produce a schedule action")
};
queue.schedule(first_deadline, first_token, ());
let AlarmChange::Schedule {
deadline: second_deadline,
token: second_token,
} = second
else {
unreachable!("rearmed slot must produce a schedule action")
};
queue.schedule(second_deadline, second_token, ());
assert_eq!(queue.entries.len(), 1);
assert!(
matches!(queue.next_action(ClockSnapshot::new(Duration::ZERO, Duration::ZERO)), AlarmQueueAction::Wait(deadline) if deadline == Duration::from_nanos(20))
);
}
#[test]
fn stale_generation_cannot_replace_the_current_alarm() {
let slot = AlarmSlot::new();
let mut queue = AlarmQueue::new();
let stale = slot.replace(Some(ClockDeadline::Monotonic(Duration::from_nanos(10))));
let current = slot.replace(Some(ClockDeadline::Monotonic(Duration::from_nanos(20))));
let AlarmChange::Schedule {
deadline: current_deadline,
token: current_token,
} = current
else {
unreachable!("armed slot must produce a schedule action")
};
queue.schedule(current_deadline, current_token, ());
let AlarmChange::Schedule {
deadline: stale_deadline,
token: stale_token,
} = stale
else {
unreachable!("armed slot must produce a schedule action")
};
queue.schedule(stale_deadline, stale_token, ());
assert_eq!(queue.entries.len(), 1);
assert!(
matches!(queue.next_action(ClockSnapshot::new(Duration::ZERO, Duration::ZERO)), AlarmQueueAction::Wait(deadline) if deadline == Duration::from_nanos(20))
);
}
#[test]
fn disarming_physically_removes_the_alarm_node() {
let slot = AlarmSlot::new();
let mut queue = AlarmQueue::new();
let schedule = slot.replace(Some(ClockDeadline::Monotonic(Duration::from_nanos(10))));
let AlarmChange::Schedule {
deadline: deadline,
token,
} = schedule
else {
unreachable!("armed slot must produce a schedule action")
};
queue.schedule(deadline, token, ());
let cancellation = slot.replace(None);
let AlarmChange::Cancel(cancellation_token) = cancellation else {
unreachable!("disarmed slot must produce a cancellation")
};
queue.cancel(&cancellation_token);
assert!(queue.is_empty());
}
#[test]
fn stale_cancellation_does_not_remove_a_newer_alarm_generation() {
let slot = AlarmSlot::new();
let mut queue = AlarmQueue::new();
let stale_schedule = slot.replace(Some(ClockDeadline::Monotonic(Duration::from_nanos(10))));
let AlarmChange::Schedule {
deadline: stale_deadline,
token: stale_token,
} = stale_schedule
else {
unreachable!("armed slot must produce a schedule action")
};
queue.schedule(stale_deadline, stale_token, ());
let stale_cancellation = slot.replace(None);
let current_schedule =
slot.replace(Some(ClockDeadline::Monotonic(Duration::from_nanos(20))));
let AlarmChange::Schedule {
deadline: current_deadline,
token: current_token,
} = current_schedule
else {
unreachable!("rearmed slot must produce a schedule action")
};
queue.schedule(current_deadline, current_token, ());
let AlarmChange::Cancel(cancellation_token) = stale_cancellation else {
unreachable!("disarmed slot must produce a cancellation")
};
queue.cancel(&cancellation_token);
assert_eq!(queue.entries.len(), 1);
assert!(
matches!(queue.next_action(ClockSnapshot::new(Duration::ZERO, Duration::ZERO)), AlarmQueueAction::Wait(deadline) if deadline == Duration::from_nanos(20))
);
}
#[test]
fn pruning_a_stale_due_node_reclassifies_the_new_future_head() {
let stale_slot = AlarmSlot::new();
let future_slot = AlarmSlot::new();
let mut queue = AlarmQueue::new();
let stale = stale_slot.replace(Some(ClockDeadline::Monotonic(Duration::from_nanos(10))));
let future = future_slot.replace(Some(ClockDeadline::Monotonic(Duration::from_nanos(20))));
let AlarmChange::Schedule {
deadline: stale_deadline,
token: stale_token,
} = stale
else {
unreachable!("armed slot must produce a schedule action")
};
let AlarmChange::Schedule {
deadline: future_deadline,
token: future_token,
} = future
else {
unreachable!("armed slot must produce a schedule action")
};
queue.schedule(stale_deadline, stale_token, ());
queue.schedule(future_deadline, future_token, ());
let _pending_cancellation = stale_slot.replace(None);
assert!(matches!(
queue.next_action(ClockSnapshot::new(Duration::from_nanos(15), Duration::ZERO)),
AlarmQueueAction::Wait(deadline) if deadline == Duration::from_nanos(20)
));
assert_eq!(queue.entries.len(), 1);
}
#[test]
fn alarm_worker_does_not_absorb_a_publish_during_its_queue_snapshot() {
let epoch = AtomicU64::new(0);
let snapshot = take_alarm_worker_snapshot(&epoch, || {
epoch.fetch_add(1, Ordering::AcqRel);
AlarmAction::AwaitNewTimer
});
assert_ne!(
epoch.load(Ordering::Acquire),
snapshot.epoch,
"a queue update racing the snapshot must make the worker's wait predicate true",
);
}
}
#[cfg(axtest)]
pub(super) fn stale_alarm_cancellation_preserves_new_generation_for_test() -> bool {
let slot = AlarmSlot::new();
let mut queue = AlarmQueue::new();
let stale_schedule = slot.replace(Some(ClockDeadline::Monotonic(Duration::from_nanos(10))));
let AlarmChange::Schedule {
deadline: stale_deadline,
token: stale_token,
} = stale_schedule
else {
return false;
};
queue.schedule(stale_deadline, stale_token, ());
let stale_cancellation = slot.replace(None);
let current_schedule = slot.replace(Some(ClockDeadline::Monotonic(Duration::from_nanos(20))));
let AlarmChange::Schedule {
deadline: current_deadline,
token: current_token,
} = current_schedule
else {
return false;
};
queue.schedule(current_deadline, current_token, ());
let AlarmChange::Cancel(cancellation_token) = stale_cancellation else {
return false;
};
queue.cancel(&cancellation_token);
queue.entries.len() == 1
&& matches!(queue.next_action(ClockSnapshot::new(Duration::ZERO, Duration::ZERO)), AlarmQueueAction::Wait(deadline) if deadline == Duration::from_nanos(20))
}