diff --git a/embassy-time/Cargo.toml b/embassy-time/Cargo.toml index 62404863d..8b5d31ee1 100644 --- a/embassy-time/Cargo.toml +++ b/embassy-time/Cargo.toml @@ -59,6 +59,9 @@ generic-queue-32 = ["generic-queue"] generic-queue-64 = ["generic-queue"] generic-queue-128 = ["generic-queue"] +# Create a `MockDriver` that can be manually advanced for testing purposes. +mock-driver = ["tick-hz-1_000_000"] + # Set the `embassy_time` tick rate. # # At most 1 `tick-*` feature can be enabled. If none is enabled, a default of 1MHz is used. diff --git a/embassy-time/src/driver_mock.rs b/embassy-time/src/driver_mock.rs new file mode 100644 index 000000000..becb6bfdd --- /dev/null +++ b/embassy-time/src/driver_mock.rs @@ -0,0 +1,68 @@ +use core::cell::Cell; + +use critical_section::Mutex as CsMutex; + +use crate::driver::{AlarmHandle, Driver}; +use crate::{Duration, Instant}; + +/// A mock driver that can be manually advanced. +/// This is useful for testing code that works with [`Instant`] and [`Duration`]. +/// +/// This driver cannot currently be used to test runtime functionality, such as +/// timers, delays, etc. +/// +/// # Example +/// +/// ```ignore +/// fn has_a_second_passed(reference: Instant) -> bool { +/// Instant::now().duration_since(reference) >= Duration::from_secs(1) +/// } +/// +/// fn test_second_passed() { +/// let driver = embassy_time::MockDriver::get(); +/// let reference = Instant::now(); +/// assert_eq!(false, has_a_second_passed(reference)); +/// driver.advance(Duration::from_secs(1)); +/// assert_eq!(true, has_a_second_passed(reference)); +/// } +/// ``` +pub struct MockDriver { + now: CsMutex>, +} + +crate::time_driver_impl!(static DRIVER: MockDriver = MockDriver { + now: CsMutex::new(Cell::new(Instant::from_ticks(0))), +}); + +impl MockDriver { + /// Gets a reference to the global mock driver. + pub fn get() -> &'static MockDriver { + &DRIVER + } + + /// Advances the time by the specified [`Duration`]. + pub fn advance(&self, duration: Duration) { + critical_section::with(|cs| { + let now = self.now.borrow(cs).get().as_ticks(); + self.now.borrow(cs).set(Instant::from_ticks(now + duration.as_ticks())); + }); + } +} + +impl Driver for MockDriver { + fn now(&self) -> u64 { + critical_section::with(|cs| self.now.borrow(cs).get().as_micros() as u64) + } + + unsafe fn allocate_alarm(&self) -> Option { + unimplemented!("MockDriver does not support runtime features that require an executor"); + } + + fn set_alarm_callback(&self, _alarm: AlarmHandle, _callback: fn(*mut ()), _ctx: *mut ()) { + unimplemented!("MockDriver does not support runtime features that require an executor"); + } + + fn set_alarm(&self, _alarm: AlarmHandle, _timestamp: u64) -> bool { + unimplemented!("MockDriver does not support runtime features that require an executor"); + } +} diff --git a/embassy-time/src/lib.rs b/embassy-time/src/lib.rs index 8f57eabcb..45c1e882b 100644 --- a/embassy-time/src/lib.rs +++ b/embassy-time/src/lib.rs @@ -15,6 +15,12 @@ pub mod queue; mod tick; mod timer; +#[cfg(feature = "mock-driver")] +mod driver_mock; + +#[cfg(feature = "mock-driver")] +pub use driver_mock::MockDriver; + #[cfg(feature = "std")] mod driver_std; #[cfg(feature = "wasm")]