led_client/rp-hal/on-target-tests/tests/i2c_tests/test_executor.rs
2024-12-21 00:48:45 -05:00

80 lines
2.7 KiB
Rust

//! Simplistic test executor
//!
//! Compared to a real executor, this has some limitations:
//!
//! - Can only run to completion (like block_on, but without busy polling)
//! - Can't spawn additional tasks
//! - Must not be called multiple times concurrently
use core::{
future::Future,
pin::{self, Pin},
ptr,
sync::atomic::{AtomicBool, Ordering},
task::{Context, Poll, RawWaker, RawWakerVTable, Waker},
};
use once_cell::sync::OnceCell;
static WOKE: AtomicBool = AtomicBool::new(false);
static POLLING: AtomicBool = AtomicBool::new(false);
static VTABLE: RawWakerVTable = RawWakerVTable::new(clone_fn, wake_fn, wake_fn, drop_fn);
fn wake_fn(_data: *const ()) {
if !POLLING.load(Ordering::Relaxed) {
defmt::info!("waker called while not polling");
}
WOKE.store(true, Ordering::Relaxed);
}
fn clone_fn(data: *const ()) -> RawWaker {
RawWaker::new(data, &VTABLE)
}
fn drop_fn(_data: *const ()) {}
fn context() -> Context<'static> {
static WAKER: OnceCell<Waker> = OnceCell::new();
// Safety: The functions in the vtable of this executor only modify static atomics.
let waker =
WAKER.get_or_init(|| unsafe { Waker::from_raw(RawWaker::new(ptr::null(), &VTABLE)) });
// Starting from rust 1.82.0, this could be used:
// static WAKER: Waker = unsafe { Waker::from_raw(RawWaker::new(ptr::null(), &VTABLE)) };
// (stabilized by https://github.com/rust-lang/rust/pull/128228)
Context::from_waker(waker)
}
/// Run future to completion
///
/// poll() will only be called when the waker was invoked, so this is suitable to test
/// if the waker is properly triggered from an interrupt.
///
/// This won't work as expected of multiple calls to `execute` happen concurrently.
///
/// (Calling this function from multiple threads concurrently doesn't violate any
/// safety guarantees, but wakers may wake the wrong task, making futures stall.)
pub fn execute<T>(future: impl Future<Output = T>) -> T {
let mut pinned: Pin<&mut _> = pin::pin!(future);
if WOKE.load(Ordering::Relaxed) {
defmt::info!("woken before poll - ignoring");
}
POLLING.store(true, Ordering::Relaxed);
loop {
WOKE.store(false, Ordering::Relaxed);
if let Poll::Ready(result) = pinned.as_mut().poll(&mut context()) {
WOKE.store(false, Ordering::Relaxed);
POLLING.store(false, Ordering::Relaxed);
break result;
}
while !WOKE.load(Ordering::Relaxed) {
// In a real executor, there should be a WFI/WFE or similar here, to avoid
// busy looping.
// As this is only a test executor, we don't care.
core::hint::spin_loop();
}
}
}