rtic/rtic-time/tests/timer_queue.rs
Finomnis 612a47ef4d
Fix mono delay (#843)
* rtic-time: Compenstate for timer uncertainty

* Update changelog and incorrect cargo.lock in an example

* Fix Monotonic impls

* Fix tests

* Fix other monotonics, again

* Update changelog

* Fix example

* Fix DelayUs and DelayMs impls

* Minor coding style fix in u64 conversions

* Fix all changelogs

* Fix changelog

* Fix blocking DelayUs

* Minor monotonic rework

* Add delay precision test

* Add more tests

* Add rust-version tags to Cargo.toml

* Fix imxrt, rp2040 and systick timer

* Fix more monotonics

* Fix systick monotonic

* Some reverts

* Fix imxrt

* Fix nrf

* Fix rp2040

* Fix stm32

* Fix systick

* Fix rtic-time tests

* Bump to e-h.rc2

* Apply e-h.rc2 fixes to rtic-time

* Apply fixes from arbiter

* Fix clippy warning

* Minor beautification

* Revert previous changes

* Fix variable name

* Add blocking tests, but disable them by default
2023-12-01 07:59:22 +00:00

291 lines
7.3 KiB
Rust

//! A test that verifies the correctness of the [`TimerQueue`].
//!
//! To run this test, you need to activate the `critical-section/std` feature.
use std::{
fmt::Debug,
task::{Poll, Waker},
};
use cassette::Cassette;
use parking_lot::Mutex;
use rtic_time::{Monotonic, TimerQueue};
static NOW: Mutex<Option<Instant>> = Mutex::new(None);
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug)]
pub struct Duration(u64);
impl Duration {
pub const fn from_ticks(millis: u64) -> Self {
Self(millis)
}
pub fn as_ticks(&self) -> u64 {
self.0
}
}
impl core::ops::Add<Duration> for Duration {
type Output = Duration;
fn add(self, rhs: Duration) -> Self::Output {
Self(self.0 + rhs.0)
}
}
impl From<Duration> for Instant {
fn from(value: Duration) -> Self {
Instant(value.0)
}
}
static WAKERS: Mutex<Vec<Waker>> = Mutex::new(Vec::new());
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug)]
pub struct Instant(u64);
impl Instant {
const ZERO: Self = Self(0);
pub fn tick() -> bool {
// If we've never ticked before, initialize the clock.
if NOW.lock().is_none() {
*NOW.lock() = Some(Instant::ZERO);
}
// We've ticked before, add one to the clock
else {
let now = Instant::now();
let new_time = now + Duration(1);
*NOW.lock() = Some(new_time);
}
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 = TestMono::tick(false);
had_interrupt || had_wakers
}
pub fn now() -> Self {
NOW.lock().clone().unwrap_or(Instant::ZERO)
}
pub fn elapsed(&self) -> Duration {
Duration(Self::now().0 - self.0)
}
pub async fn wait_until(time: Instant) {
core::future::poll_fn(|ctx| {
if Instant::now() >= time {
Poll::Ready(())
} else {
WAKERS.lock().push(ctx.waker().clone());
Poll::Pending
}
})
.await;
}
}
impl From<u64> for Instant {
fn from(value: u64) -> Self {
Self(value)
}
}
impl core::ops::Add<Duration> for Instant {
type Output = Instant;
fn add(self, rhs: Duration) -> Self::Output {
Self(self.0 + rhs.0)
}
}
impl core::ops::Sub<Duration> for Instant {
type Output = Instant;
fn sub(self, rhs: Duration) -> Self::Output {
Self(self.0 - rhs.0)
}
}
impl core::ops::Sub<Instant> for Instant {
type Output = Duration;
fn sub(self, rhs: Instant) -> Self::Output {
Duration(self.0 - rhs.0)
}
}
static COMPARE: Mutex<Option<Instant>> = Mutex::new(None);
static TIMER_QUEUE: TimerQueue<TestMono> = TimerQueue::new();
pub struct TestMono;
impl TestMono {
pub fn tick(force_interrupt: bool) -> bool {
let now = Instant::now();
let compare_reached = Some(now) == Self::compare();
let interrupt = compare_reached || force_interrupt;
if interrupt {
unsafe {
TestMono::queue().on_monotonic_interrupt();
}
true
} else {
false
}
}
/// Initialize the monotonic.
pub fn init() {
Self::queue().initialize(Self);
}
/// Used to access the underlying timer queue
pub fn queue() -> &'static TimerQueue<TestMono> {
&TIMER_QUEUE
}
pub fn compare() -> Option<Instant> {
COMPARE.lock().clone()
}
}
impl Monotonic for TestMono {
const ZERO: Self::Instant = Instant::ZERO;
const TICK_PERIOD: Self::Duration = Duration::from_ticks(1);
type Instant = Instant;
type Duration = Duration;
fn now() -> Self::Instant {
Instant::now()
}
fn set_compare(instant: Self::Instant) {
let _ = COMPARE.lock().insert(instant);
}
fn clear_compare_flag() {}
fn pend_interrupt() {
Self::tick(true);
}
}
#[test]
fn timer_queue() {
TestMono::init();
let start = Instant::ZERO;
let build_delay_test = |pre_delay: Option<u64>, delay: u64| {
let delay = Duration::from_ticks(delay);
let pre_delay = pre_delay.map(Duration::from_ticks);
let total = if let Some(pre_delay) = pre_delay {
pre_delay + delay
} else {
delay
};
let total_millis = total.as_ticks();
async move {
// A `pre_delay` simulates a delay in scheduling,
// without the `pre_delay` being present in the timer
// queue
if let Some(pre_delay) = pre_delay {
Instant::wait_until(start + pre_delay).await;
}
TestMono::queue().delay(delay).await;
let elapsed = start.elapsed().as_ticks();
println!("{total_millis} ticks delay reached after {elapsed} ticks");
// Expect a delay of one longer, to compensate for timer uncertainty
if elapsed != total_millis + 1 {
panic!(
"{total_millis} ticks delay was not on time ({elapsed} ticks passed instead)"
);
}
}
};
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);
)* }
}
let d1 = build_delay_test(Some(100), 100);
cassette!(d1);
let d2 = build_delay_test(None, 300);
cassette!(d2);
let d3 = build_delay_test(None, 400);
cassette!(d3);
macro_rules! poll {
($($fut:ident),*) => {
$(if !$fut.is_done() {
$fut.poll_on();
})*
};
}
// Do an initial poll to set up all of the waiting futures
poll!(d1, d2, d3);
for _ in 0..500 {
// We only poll the waiting futures if an
// interrupt occured or if an artificial delay
// has passed.
if Instant::tick() {
poll!(d1, d2, d3);
}
if Instant::now() == 0.into() {
// First, we want to be waiting for our 300 tick delay
assert_eq!(TestMono::compare(), Some(301.into()));
}
if Instant::now() == 100.into() {
// After 100 ticks, we enqueue a new delay that is supposed to last
// until the 200-tick-mark
assert_eq!(TestMono::compare(), Some(201.into()));
}
if Instant::now() == 201.into() {
// After 200 ticks, we dequeue the 200-tick-mark delay and
// requeue the 300 tick delay
assert_eq!(TestMono::compare(), Some(301.into()));
}
if Instant::now() == 301.into() {
// After 300 ticks, we dequeue the 300-tick-mark delay and
// go to the 400 tick delay that is already enqueued
assert_eq!(TestMono::compare(), Some(401.into()));
}
}
assert!(d1.is_done() && d2.is_done() && d3.is_done());
}