diff options
| -rw-r--r-- | embassy-time/src/driver_mock.rs | 134 |
1 files changed, 116 insertions, 18 deletions
diff --git a/embassy-time/src/driver_mock.rs b/embassy-time/src/driver_mock.rs index c255615c7..128f48af9 100644 --- a/embassy-time/src/driver_mock.rs +++ b/embassy-time/src/driver_mock.rs | |||
| @@ -1,4 +1,4 @@ | |||
| 1 | use core::cell::Cell; | 1 | use core::cell::RefCell; |
| 2 | 2 | ||
| 3 | use critical_section::Mutex as CsMutex; | 3 | use critical_section::Mutex as CsMutex; |
| 4 | 4 | ||
| @@ -8,7 +8,7 @@ use crate::{Duration, Instant}; | |||
| 8 | /// A mock driver that can be manually advanced. | 8 | /// A mock driver that can be manually advanced. |
| 9 | /// This is useful for testing code that works with [`Instant`] and [`Duration`]. | 9 | /// This is useful for testing code that works with [`Instant`] and [`Duration`]. |
| 10 | /// | 10 | /// |
| 11 | /// This driver cannot currently be used to test runtime functionality, such as | 11 | /// This driver can also be used to test runtime functionality, such as |
| 12 | /// timers, delays, etc. | 12 | /// timers, delays, etc. |
| 13 | /// | 13 | /// |
| 14 | /// # Example | 14 | /// # Example |
| @@ -26,43 +26,141 @@ use crate::{Duration, Instant}; | |||
| 26 | /// assert_eq!(true, has_a_second_passed(reference)); | 26 | /// assert_eq!(true, has_a_second_passed(reference)); |
| 27 | /// } | 27 | /// } |
| 28 | /// ``` | 28 | /// ``` |
| 29 | pub struct MockDriver { | 29 | pub struct MockDriver(CsMutex<RefCell<InnerMockDriver>>); |
| 30 | now: CsMutex<Cell<Instant>>, | ||
| 31 | } | ||
| 32 | 30 | ||
| 33 | crate::time_driver_impl!(static DRIVER: MockDriver = MockDriver { | 31 | crate::time_driver_impl!(static DRIVER: MockDriver = MockDriver::new()); |
| 34 | now: CsMutex::new(Cell::new(Instant::from_ticks(0))), | ||
| 35 | }); | ||
| 36 | 32 | ||
| 37 | impl MockDriver { | 33 | impl MockDriver { |
| 34 | /// Creates a new mock driver. | ||
| 35 | pub const fn new() -> Self { | ||
| 36 | Self(CsMutex::new(RefCell::new(InnerMockDriver::new()))) | ||
| 37 | } | ||
| 38 | |||
| 38 | /// Gets a reference to the global mock driver. | 39 | /// Gets a reference to the global mock driver. |
| 39 | pub fn get() -> &'static MockDriver { | 40 | pub fn get() -> &'static MockDriver { |
| 40 | &DRIVER | 41 | &DRIVER |
| 41 | } | 42 | } |
| 42 | 43 | ||
| 43 | /// Advances the time by the specified [`Duration`]. | 44 | /// Resets the internal state of the mock driver |
| 44 | pub fn advance(&self, duration: Duration) { | 45 | /// This will clear and deallocate all alarms, and reset the current time to 0. |
| 46 | fn reset(&self) { | ||
| 45 | critical_section::with(|cs| { | 47 | critical_section::with(|cs| { |
| 46 | let now = self.now.borrow(cs).get().as_ticks(); | 48 | self.0.borrow(cs).replace(InnerMockDriver::new()); |
| 47 | self.now.borrow(cs).set(Instant::from_ticks(now + duration.as_ticks())); | ||
| 48 | }); | 49 | }); |
| 49 | } | 50 | } |
| 51 | |||
| 52 | /// Advances the time by the specified [`Duration`]. | ||
| 53 | /// Calling any alarm callbacks that are due. | ||
| 54 | pub fn advance(&self, duration: Duration) { | ||
| 55 | let notify = { | ||
| 56 | critical_section::with(|cs| { | ||
| 57 | let mut inner = self.0.borrow_ref_mut(cs); | ||
| 58 | |||
| 59 | // TODO: store as Instant? | ||
| 60 | let now = (Instant::from_ticks(inner.now) + duration).as_ticks(); | ||
| 61 | |||
| 62 | |||
| 63 | inner.now = now; | ||
| 64 | |||
| 65 | if inner.alarm <= now { | ||
| 66 | inner.alarm = u64::MAX; | ||
| 67 | |||
| 68 | Some((inner.callback, inner.ctx)) | ||
| 69 | } else { | ||
| 70 | None | ||
| 71 | } | ||
| 72 | }) | ||
| 73 | }; | ||
| 74 | |||
| 75 | if let Some((callback, ctx)) = notify { | ||
| 76 | (callback)(ctx); | ||
| 77 | } | ||
| 78 | } | ||
| 50 | } | 79 | } |
| 51 | 80 | ||
| 52 | impl Driver for MockDriver { | 81 | impl Driver for MockDriver { |
| 53 | fn now(&self) -> u64 { | 82 | fn now(&self) -> u64 { |
| 54 | critical_section::with(|cs| self.now.borrow(cs).get().as_ticks() as u64) | 83 | critical_section::with(|cs| self.0.borrow_ref(cs).now) |
| 55 | } | 84 | } |
| 56 | 85 | ||
| 57 | unsafe fn allocate_alarm(&self) -> Option<AlarmHandle> { | 86 | unsafe fn allocate_alarm(&self) -> Option<AlarmHandle> { |
| 58 | unimplemented!("MockDriver does not support runtime features that require an executor"); | 87 | Some(AlarmHandle::new(0)) |
| 88 | } | ||
| 89 | |||
| 90 | fn set_alarm_callback(&self, _alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) { | ||
| 91 | critical_section::with(|cs| { | ||
| 92 | let mut inner = self.0.borrow_ref_mut(cs); | ||
| 93 | |||
| 94 | inner.callback = callback; | ||
| 95 | inner.ctx = ctx; | ||
| 96 | }); | ||
| 97 | } | ||
| 98 | |||
| 99 | fn set_alarm(&self, _alarm: AlarmHandle, timestamp: u64) -> bool { | ||
| 100 | critical_section::with(|cs| { | ||
| 101 | let mut inner = self.0.borrow_ref_mut(cs); | ||
| 102 | |||
| 103 | if timestamp <= inner.now { | ||
| 104 | false | ||
| 105 | } else { | ||
| 106 | inner.alarm = timestamp; | ||
| 107 | true | ||
| 108 | } | ||
| 109 | }) | ||
| 110 | } | ||
| 111 | } | ||
| 112 | |||
| 113 | struct InnerMockDriver { | ||
| 114 | now: u64, | ||
| 115 | alarm: u64, | ||
| 116 | callback: fn(*mut ()), | ||
| 117 | ctx: *mut (), | ||
| 118 | } | ||
| 119 | |||
| 120 | impl InnerMockDriver { | ||
| 121 | const fn new() -> Self { | ||
| 122 | Self { | ||
| 123 | now: 0, | ||
| 124 | alarm: u64::MAX, | ||
| 125 | callback: Self::noop, | ||
| 126 | ctx: core::ptr::null_mut(), | ||
| 127 | } | ||
| 128 | } | ||
| 129 | |||
| 130 | fn noop(_ctx: *mut ()) {} | ||
| 131 | } | ||
| 132 | |||
| 133 | unsafe impl Send for InnerMockDriver {} | ||
| 134 | |||
| 135 | #[cfg(test)] | ||
| 136 | mod tests { | ||
| 137 | use super::*; | ||
| 138 | |||
| 139 | #[test] | ||
| 140 | fn test_advance() { | ||
| 141 | let driver = MockDriver::get(); | ||
| 142 | let reference = driver.now(); | ||
| 143 | driver.advance(Duration::from_secs(1)); | ||
| 144 | assert_eq!(Duration::from_secs(1).as_ticks(), driver.now() - reference); | ||
| 59 | } | 145 | } |
| 60 | 146 | ||
| 61 | fn set_alarm_callback(&self, _alarm: AlarmHandle, _callback: fn(*mut ()), _ctx: *mut ()) { | 147 | #[test] |
| 62 | unimplemented!("MockDriver does not support runtime features that require an executor"); | 148 | fn test_set_alarm_not_in_future() { |
| 149 | let driver = MockDriver::get(); | ||
| 150 | let alarm = unsafe { AlarmHandle::new(0) }; | ||
| 151 | assert_eq!(false, driver.set_alarm(alarm, driver.now())); | ||
| 63 | } | 152 | } |
| 64 | 153 | ||
| 65 | fn set_alarm(&self, _alarm: AlarmHandle, _timestamp: u64) -> bool { | 154 | #[test] |
| 66 | unimplemented!("MockDriver does not support runtime features that require an executor"); | 155 | fn test_alarm() { |
| 156 | let driver = MockDriver::get(); | ||
| 157 | let alarm = unsafe { driver.allocate_alarm() }.expect("No alarms available"); | ||
| 158 | static mut CALLBACK_CALLED: bool = false; | ||
| 159 | let ctx = &mut () as *mut (); | ||
| 160 | driver.set_alarm_callback(alarm, |_| unsafe { CALLBACK_CALLED = true }, ctx); | ||
| 161 | driver.set_alarm(alarm, driver.now() + 1); | ||
| 162 | assert_eq!(false, unsafe { CALLBACK_CALLED }); | ||
| 163 | driver.advance(Duration::from_secs(1)); | ||
| 164 | assert_eq!(true, unsafe { CALLBACK_CALLED }); | ||
| 67 | } | 165 | } |
| 68 | } | 166 | } |
