rtic/rtic-time/tests/timer_queue.rs

208 lines
5.4 KiB
Rust
Raw Permalink Normal View History

//! A test that verifies the correctness of the [`TimerQueue`].
//!
//! To run this test, you need to activate the `critical-section/std` feature.
2023-04-10 13:39:22 +02:00
use cassette::Cassette;
2023-04-10 10:34:00 +02:00
use parking_lot::Mutex;
use rtic_time::timer_queue::{TimerQueue, TimerQueueBackend};
2023-04-10 13:05:36 +02:00
mod peripheral {
use parking_lot::Mutex;
use std::{
sync::atomic::{AtomicU64, Ordering},
task::{Poll, Waker},
};
2023-04-10 13:05:36 +02:00
use super::TestMonoBackend;
2023-04-10 10:34:00 +02:00
static NOW: AtomicU64 = AtomicU64::new(0);
static WAKERS: Mutex<Vec<Waker>> = Mutex::new(Vec::new());
2023-04-10 13:05:36 +02:00
pub fn tick() -> bool {
NOW.fetch_add(1, Ordering::Release);
2023-04-10 13:05:36 +02:00
let had_wakers = !WAKERS.lock().is_empty();
// Wake up all things waiting for a specific time to happen.
for waker in WAKERS.lock().drain(..) {
waker.wake_by_ref();
}
let had_interrupt = TestMonoBackend::tick(false);
2023-04-10 13:05:36 +02:00
had_interrupt || had_wakers
2023-04-10 10:34:00 +02:00
}
pub fn now() -> u64 {
NOW.load(Ordering::Acquire)
2023-04-10 13:05:36 +02:00
}
pub async fn wait_until(time: u64) {
2023-04-10 13:05:36 +02:00
core::future::poll_fn(|ctx| {
if now() >= time {
2023-04-10 13:05:36 +02:00
Poll::Ready(())
} else {
WAKERS.lock().push(ctx.waker().clone());
Poll::Pending
}
})
.await;
}
}
static COMPARE: Mutex<Option<u64>> = Mutex::new(None);
static TIMER_QUEUE: TimerQueue<TestMonoBackend> = TimerQueue::new();
2023-04-10 10:34:00 +02:00
pub struct TestMonoBackend;
2023-04-10 10:34:00 +02:00
impl TestMonoBackend {
2023-04-10 13:05:36 +02:00
pub fn tick(force_interrupt: bool) -> bool {
let now = peripheral::now();
2023-04-10 10:34:00 +02:00
2023-04-10 13:05:36 +02:00
let compare_reached = Some(now) == Self::compare();
let interrupt = compare_reached || force_interrupt;
2023-04-10 10:34:00 +02:00
2023-04-10 13:05:36 +02:00
if interrupt {
unsafe {
TestMonoBackend::timer_queue().on_monotonic_interrupt();
2023-04-10 10:34:00 +02:00
}
2023-04-10 13:05:36 +02:00
true
} else {
false
}
}
2023-04-10 10:34:00 +02:00
pub fn compare() -> Option<u64> {
2023-04-10 13:05:36 +02:00
COMPARE.lock().clone()
}
2023-04-10 10:34:00 +02:00
}
impl TestMonoBackend {
fn init() {
Self::timer_queue().initialize(Self);
}
}
2023-04-10 10:34:00 +02:00
impl TimerQueueBackend for TestMonoBackend {
type Ticks = u64;
2023-04-10 10:34:00 +02:00
fn now() -> Self::Ticks {
peripheral::now()
2023-04-10 10:34:00 +02:00
}
fn set_compare(instant: Self::Ticks) {
*COMPARE.lock() = Some(instant);
2023-04-10 10:34:00 +02:00
}
fn clear_compare_flag() {}
fn pend_interrupt() {
2023-04-10 13:05:36 +02:00
Self::tick(true);
2023-04-10 10:34:00 +02:00
}
fn timer_queue() -> &'static TimerQueue<Self> {
&TIMER_QUEUE
}
2023-04-10 10:34:00 +02:00
}
2023-04-10 13:05:36 +02:00
#[test]
fn timer_queue() {
TestMonoBackend::init();
let start = 0;
2023-04-10 10:34:00 +02:00
2023-04-10 13:05:36 +02:00
let build_delay_test = |pre_delay: Option<u64>, delay: u64| {
2023-04-10 10:34:00 +02:00
let total = if let Some(pre_delay) = pre_delay {
pre_delay + delay
} else {
delay
};
2023-04-10 13:05:36 +02:00
2023-04-10 10:34:00 +02:00
async move {
2023-04-10 13:09:50 +02:00
// A `pre_delay` simulates a delay in scheduling,
// without the `pre_delay` being present in the timer
// queue
2023-04-10 10:34:00 +02:00
if let Some(pre_delay) = pre_delay {
peripheral::wait_until(start + pre_delay).await;
2023-04-10 10:34:00 +02:00
}
TestMonoBackend::timer_queue().delay(delay).await;
2023-04-10 10:34:00 +02:00
let elapsed = peripheral::now() - start;
println!("{total} ticks delay reached after {elapsed} ticks");
2023-04-10 10:34:00 +02:00
// Expect a delay of one longer, to compensate for timer uncertainty
if elapsed != total + 1 {
panic!("{total} ticks delay was not on time ({elapsed} ticks passed instead)");
2023-04-10 10:34:00 +02:00
}
}
};
2023-04-10 13:39:22 +02:00
macro_rules! cassette {
($($x:ident),* $(,)?) => { $(
// Move the value to ensure that it is owned
let mut $x = $x;
// Shadow the original binding so that it can't be directly accessed
// ever again.
#[allow(unused_mut)]
let mut $x = unsafe {
core::pin::Pin::new_unchecked(&mut $x)
};
let mut $x = Cassette::new($x);
)* }
}
2023-04-10 13:05:36 +02:00
let d1 = build_delay_test(Some(100), 100);
2023-04-10 13:39:22 +02:00
cassette!(d1);
2023-04-10 13:05:36 +02:00
let d2 = build_delay_test(None, 300);
2023-04-10 13:39:22 +02:00
cassette!(d2);
2023-04-10 13:05:36 +02:00
let d3 = build_delay_test(None, 400);
2023-04-10 13:39:22 +02:00
cassette!(d3);
2023-04-10 13:05:36 +02:00
2023-04-10 13:39:22 +02:00
macro_rules! poll {
($($fut:ident),*) => {
$(if !$fut.is_done() {
$fut.poll_on();
})*
2023-04-10 13:05:36 +02:00
};
}
// Do an initial poll to set up all of the waiting futures
2023-04-10 13:39:22 +02:00
poll!(d1, d2, d3);
2023-04-10 13:05:36 +02:00
for _ in 0..500 {
// We only poll the waiting futures if an
// interrupt occured or if an artificial delay
// has passed.
if peripheral::tick() {
2023-04-10 13:39:22 +02:00
poll!(d1, d2, d3);
2023-04-10 13:05:36 +02:00
}
if peripheral::now() == 0 {
2023-04-10 13:05:36 +02:00
// First, we want to be waiting for our 300 tick delay
assert_eq!(TestMonoBackend::compare(), Some(301));
2023-04-10 13:05:36 +02:00
}
if peripheral::now() == 100 {
2023-04-10 13:05:36 +02:00
// After 100 ticks, we enqueue a new delay that is supposed to last
// until the 200-tick-mark
assert_eq!(TestMonoBackend::compare(), Some(201));
2023-04-10 13:05:36 +02:00
}
2023-04-10 10:34:00 +02:00
if peripheral::now() == 201 {
2023-04-10 13:05:36 +02:00
// After 200 ticks, we dequeue the 200-tick-mark delay and
// requeue the 300 tick delay
assert_eq!(TestMonoBackend::compare(), Some(301));
2023-04-10 13:05:36 +02:00
}
if peripheral::now() == 301 {
2023-04-10 13:05:36 +02:00
// After 300 ticks, we dequeue the 300-tick-mark delay and
// go to the 400 tick delay that is already enqueued
assert_eq!(TestMonoBackend::compare(), Some(401));
2023-04-10 13:05:36 +02:00
}
}
2023-04-10 10:34:00 +02:00
2023-04-10 13:05:36 +02:00
assert!(d1.is_done() && d2.is_done() && d3.is_done());
2023-04-10 10:34:00 +02:00
}