aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorUlf Lilleengen <[email protected]>2024-01-10 10:51:28 +0000
committerGitHub <[email protected]>2024-01-10 10:51:28 +0000
commit0027a76bb61f19fcae1fe588ba3ff62660d7f7e3 (patch)
tree518c63af122389b958b5fb5e635abb91434512d1
parentc223697e288369ebf1bb02393e55f0ade3581d8a (diff)
parent3db8655e25bf40d0f0b72e557a8d6e3d156119c8 (diff)
Merge pull request #2420 from chrisprice/mock-driver
Extend mock time driver to support alarms
-rwxr-xr-x.github/ci/test.sh2
-rwxr-xr-xci.sh2
-rw-r--r--embassy-time/src/driver.rs4
-rw-r--r--embassy-time/src/driver_mock.rs189
-rw-r--r--embassy-time/src/queue.rs4
-rw-r--r--embassy-time/src/queue_generic.rs102
6 files changed, 186 insertions, 117 deletions
diff --git a/.github/ci/test.sh b/.github/ci/test.sh
index 369f6d221..b16b3c20d 100755
--- a/.github/ci/test.sh
+++ b/.github/ci/test.sh
@@ -11,7 +11,7 @@ export CARGO_TARGET_DIR=/ci/cache/target
11cargo test --manifest-path ./embassy-sync/Cargo.toml 11cargo test --manifest-path ./embassy-sync/Cargo.toml
12cargo test --manifest-path ./embassy-embedded-hal/Cargo.toml 12cargo test --manifest-path ./embassy-embedded-hal/Cargo.toml
13cargo test --manifest-path ./embassy-hal-internal/Cargo.toml 13cargo test --manifest-path ./embassy-hal-internal/Cargo.toml
14cargo test --manifest-path ./embassy-time/Cargo.toml --features generic-queue 14cargo test --manifest-path ./embassy-time/Cargo.toml --features generic-queue,mock-driver
15 15
16cargo test --manifest-path ./embassy-boot/boot/Cargo.toml 16cargo test --manifest-path ./embassy-boot/boot/Cargo.toml
17cargo test --manifest-path ./embassy-boot/boot/Cargo.toml --features ed25519-dalek 17cargo test --manifest-path ./embassy-boot/boot/Cargo.toml --features ed25519-dalek
diff --git a/ci.sh b/ci.sh
index 84a724ede..dd028ddda 100755
--- a/ci.sh
+++ b/ci.sh
@@ -34,7 +34,7 @@ cargo batch \
34 --- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features arch-riscv32,executor-thread \ 34 --- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features arch-riscv32,executor-thread \
35 --- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features arch-riscv32,executor-thread,integrated-timers \ 35 --- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features arch-riscv32,executor-thread,integrated-timers \
36 --- build --release --manifest-path embassy-sync/Cargo.toml --target thumbv6m-none-eabi --features defmt \ 36 --- build --release --manifest-path embassy-sync/Cargo.toml --target thumbv6m-none-eabi --features defmt \
37 --- build --release --manifest-path embassy-time/Cargo.toml --target thumbv6m-none-eabi --features defmt,defmt-timestamp-uptime,tick-hz-32_768,generic-queue-8 \ 37 --- build --release --manifest-path embassy-time/Cargo.toml --target thumbv6m-none-eabi --features defmt,defmt-timestamp-uptime,generic-queue-8,mock-driver \
38 --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,medium-ethernet \ 38 --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,medium-ethernet \
39 --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,igmp,medium-ethernet \ 39 --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,igmp,medium-ethernet \
40 --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet \ 40 --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet \
diff --git a/embassy-time/src/driver.rs b/embassy-time/src/driver.rs
index 2afa454fe..f966386b7 100644
--- a/embassy-time/src/driver.rs
+++ b/embassy-time/src/driver.rs
@@ -42,7 +42,6 @@
42//! use embassy_time::driver::{Driver, AlarmHandle}; 42//! use embassy_time::driver::{Driver, AlarmHandle};
43//! 43//!
44//! struct MyDriver{} // not public! 44//! struct MyDriver{} // not public!
45//! embassy_time::time_driver_impl!(static DRIVER: MyDriver = MyDriver{});
46//! 45//!
47//! impl Driver for MyDriver { 46//! impl Driver for MyDriver {
48//! fn now(&self) -> u64 { 47//! fn now(&self) -> u64 {
@@ -59,6 +58,9 @@
59//! } 58//! }
60//! } 59//! }
61//! ``` 60//! ```
61//! ```ignore
62//! embassy_time::time_driver_impl!(static DRIVER: MyDriver = MyDriver{});
63//! ```
62 64
63/// Alarm handle, assigned by the driver. 65/// Alarm handle, assigned by the driver.
64#[derive(Clone, Copy)] 66#[derive(Clone, Copy)]
diff --git a/embassy-time/src/driver_mock.rs b/embassy-time/src/driver_mock.rs
index c255615c7..7533d51e5 100644
--- a/embassy-time/src/driver_mock.rs
+++ b/embassy-time/src/driver_mock.rs
@@ -1,4 +1,4 @@
1use core::cell::Cell; 1use core::cell::RefCell;
2 2
3use critical_section::Mutex as CsMutex; 3use 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,196 @@ 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/// ```
29pub struct MockDriver { 29pub struct MockDriver(CsMutex<RefCell<InnerMockDriver>>);
30 now: CsMutex<Cell<Instant>>,
31}
32 30
33crate::time_driver_impl!(static DRIVER: MockDriver = MockDriver { 31crate::time_driver_impl!(static DRIVER: MockDriver = MockDriver::new());
34 now: CsMutex::new(Cell::new(Instant::from_ticks(0))),
35});
36 32
37impl MockDriver { 33impl 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 pub 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 inner.now += duration;
60
61 let now = inner.now.as_ticks();
62
63 inner
64 .alarm
65 .as_mut()
66 .filter(|alarm| alarm.timestamp <= now)
67 .map(|alarm| {
68 alarm.timestamp = u64::MAX;
69
70 (alarm.callback, alarm.ctx)
71 })
72 })
73 };
74
75 if let Some((callback, ctx)) = notify {
76 (callback)(ctx);
77 }
78 }
50} 79}
51 80
52impl Driver for MockDriver { 81impl 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).as_ticks()
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 critical_section::with(|cs| {
88 let mut inner = self.0.borrow_ref_mut(cs);
89
90 if inner.alarm.is_some() {
91 None
92 } else {
93 inner.alarm.replace(AlarmState::new());
94
95 Some(AlarmHandle::new(0))
96 }
97 })
59 } 98 }
60 99
61 fn set_alarm_callback(&self, _alarm: AlarmHandle, _callback: fn(*mut ()), _ctx: *mut ()) { 100 fn set_alarm_callback(&self, _alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) {
62 unimplemented!("MockDriver does not support runtime features that require an executor"); 101 critical_section::with(|cs| {
102 let mut inner = self.0.borrow_ref_mut(cs);
103
104 let Some(alarm) = inner.alarm.as_mut() else {
105 panic!("Alarm not allocated");
106 };
107
108 alarm.callback = callback;
109 alarm.ctx = ctx;
110 });
63 } 111 }
64 112
65 fn set_alarm(&self, _alarm: AlarmHandle, _timestamp: u64) -> bool { 113 fn set_alarm(&self, _alarm: AlarmHandle, timestamp: u64) -> bool {
66 unimplemented!("MockDriver does not support runtime features that require an executor"); 114 critical_section::with(|cs| {
115 let mut inner = self.0.borrow_ref_mut(cs);
116
117 if timestamp <= inner.now.as_ticks() {
118 false
119 } else {
120 let Some(alarm) = inner.alarm.as_mut() else {
121 panic!("Alarm not allocated");
122 };
123
124 alarm.timestamp = timestamp;
125 true
126 }
127 })
128 }
129}
130
131struct InnerMockDriver {
132 now: Instant,
133 alarm: Option<AlarmState>,
134}
135
136impl InnerMockDriver {
137 const fn new() -> Self {
138 Self {
139 now: Instant::from_ticks(0),
140 alarm: None,
141 }
142 }
143}
144
145struct AlarmState {
146 timestamp: u64,
147 callback: fn(*mut ()),
148 ctx: *mut (),
149}
150
151impl AlarmState {
152 const fn new() -> Self {
153 Self {
154 timestamp: u64::MAX,
155 callback: Self::noop,
156 ctx: core::ptr::null_mut(),
157 }
158 }
159
160 fn noop(_ctx: *mut ()) {}
161}
162
163unsafe impl Send for AlarmState {}
164
165#[cfg(test)]
166mod tests {
167 use serial_test::serial;
168
169 use super::*;
170
171 fn setup() {
172 DRIVER.reset();
173 }
174
175 #[test]
176 #[serial]
177 fn test_advance() {
178 setup();
179
180 let driver = MockDriver::get();
181 let reference = driver.now();
182 driver.advance(Duration::from_secs(1));
183 assert_eq!(Duration::from_secs(1).as_ticks(), driver.now() - reference);
184 }
185
186 #[test]
187 #[serial]
188 fn test_set_alarm_not_in_future() {
189 setup();
190
191 let driver = MockDriver::get();
192 let alarm = unsafe { AlarmHandle::new(0) };
193 assert_eq!(false, driver.set_alarm(alarm, driver.now()));
194 }
195
196 #[test]
197 #[serial]
198 fn test_alarm() {
199 setup();
200
201 let driver = MockDriver::get();
202 let alarm = unsafe { driver.allocate_alarm() }.expect("No alarms available");
203 static mut CALLBACK_CALLED: bool = false;
204 let ctx = &mut () as *mut ();
205 driver.set_alarm_callback(alarm, |_| unsafe { CALLBACK_CALLED = true }, ctx);
206 driver.set_alarm(alarm, driver.now() + 1);
207 assert_eq!(false, unsafe { CALLBACK_CALLED });
208 driver.advance(Duration::from_secs(1));
209 assert_eq!(true, unsafe { CALLBACK_CALLED });
210 }
211
212 #[test]
213 #[serial]
214 fn test_allocate_alarm() {
215 setup();
216
217 let driver = MockDriver::get();
218 assert!(unsafe { driver.allocate_alarm() }.is_some());
219 assert!(unsafe { driver.allocate_alarm() }.is_none());
67 } 220 }
68} 221}
diff --git a/embassy-time/src/queue.rs b/embassy-time/src/queue.rs
index c6f8b440a..d65197c54 100644
--- a/embassy-time/src/queue.rs
+++ b/embassy-time/src/queue.rs
@@ -23,7 +23,6 @@
23//! use embassy_time::queue::{TimerQueue}; 23//! use embassy_time::queue::{TimerQueue};
24//! 24//!
25//! struct MyTimerQueue{}; // not public! 25//! struct MyTimerQueue{}; // not public!
26//! embassy_time::timer_queue_impl!(static QUEUE: MyTimerQueue = MyTimerQueue{});
27//! 26//!
28//! impl TimerQueue for MyTimerQueue { 27//! impl TimerQueue for MyTimerQueue {
29//! fn schedule_wake(&'static self, at: Instant, waker: &Waker) { 28//! fn schedule_wake(&'static self, at: Instant, waker: &Waker) {
@@ -31,6 +30,9 @@
31//! } 30//! }
32//! } 31//! }
33//! ``` 32//! ```
33//! ```ignore
34//! embassy_time::timer_queue_impl!(static QUEUE: MyTimerQueue = MyTimerQueue{});
35//! ```
34use core::task::Waker; 36use core::task::Waker;
35 37
36use crate::Instant; 38use crate::Instant;
diff --git a/embassy-time/src/queue_generic.rs b/embassy-time/src/queue_generic.rs
index 77947ab29..829368ffc 100644
--- a/embassy-time/src/queue_generic.rs
+++ b/embassy-time/src/queue_generic.rs
@@ -175,103 +175,17 @@ impl TimerQueue for Queue {
175crate::timer_queue_impl!(static QUEUE: Queue = Queue::new()); 175crate::timer_queue_impl!(static QUEUE: Queue = Queue::new());
176 176
177#[cfg(test)] 177#[cfg(test)]
178#[cfg(feature = "mock-driver")]
178mod tests { 179mod tests {
179 use core::cell::Cell; 180 use core::cell::Cell;
180 use core::task::{RawWaker, RawWakerVTable, Waker}; 181 use core::task::{RawWaker, RawWakerVTable, Waker};
181 use std::rc::Rc; 182 use std::rc::Rc;
182 use std::sync::Mutex;
183 183
184 use serial_test::serial; 184 use serial_test::serial;
185 185
186 use crate::driver::{AlarmHandle, Driver}; 186 use crate::driver_mock::MockDriver;
187 use crate::queue_generic::QUEUE; 187 use crate::queue_generic::QUEUE;
188 use crate::Instant; 188 use crate::{Duration, Instant};
189
190 struct InnerTestDriver {
191 now: u64,
192 alarm: u64,
193 callback: fn(*mut ()),
194 ctx: *mut (),
195 }
196
197 impl InnerTestDriver {
198 const fn new() -> Self {
199 Self {
200 now: 0,
201 alarm: u64::MAX,
202 callback: Self::noop,
203 ctx: core::ptr::null_mut(),
204 }
205 }
206
207 fn noop(_ctx: *mut ()) {}
208 }
209
210 unsafe impl Send for InnerTestDriver {}
211
212 struct TestDriver(Mutex<InnerTestDriver>);
213
214 impl TestDriver {
215 const fn new() -> Self {
216 Self(Mutex::new(InnerTestDriver::new()))
217 }
218
219 fn reset(&self) {
220 *self.0.lock().unwrap() = InnerTestDriver::new();
221 }
222
223 fn set_now(&self, now: u64) {
224 let notify = {
225 let mut inner = self.0.lock().unwrap();
226
227 if inner.now < now {
228 inner.now = now;
229
230 if inner.alarm <= now {
231 inner.alarm = u64::MAX;
232
233 Some((inner.callback, inner.ctx))
234 } else {
235 None
236 }
237 } else {
238 panic!("Going back in time?");
239 }
240 };
241
242 if let Some((callback, ctx)) = notify {
243 (callback)(ctx);
244 }
245 }
246 }
247
248 impl Driver for TestDriver {
249 fn now(&self) -> u64 {
250 self.0.lock().unwrap().now
251 }
252
253 unsafe fn allocate_alarm(&self) -> Option<AlarmHandle> {
254 Some(AlarmHandle::new(0))
255 }
256
257 fn set_alarm_callback(&self, _alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) {
258 let mut inner = self.0.lock().unwrap();
259
260 inner.callback = callback;
261 inner.ctx = ctx;
262 }
263
264 fn set_alarm(&self, _alarm: AlarmHandle, timestamp: u64) -> bool {
265 let mut inner = self.0.lock().unwrap();
266
267 if timestamp <= inner.now {
268 false
269 } else {
270 inner.alarm = timestamp;
271 true
272 }
273 }
274 }
275 189
276 struct TestWaker { 190 struct TestWaker {
277 pub awoken: Rc<Cell<bool>>, 191 pub awoken: Rc<Cell<bool>>,
@@ -312,10 +226,8 @@ mod tests {
312 } 226 }
313 } 227 }
314 228
315 crate::time_driver_impl!(static DRIVER: TestDriver = TestDriver::new());
316
317 fn setup() { 229 fn setup() {
318 DRIVER.reset(); 230 MockDriver::get().reset();
319 critical_section::with(|cs| *QUEUE.inner.borrow_ref_mut(cs) = None); 231 critical_section::with(|cs| *QUEUE.inner.borrow_ref_mut(cs) = None);
320 } 232 }
321 233
@@ -382,13 +294,13 @@ mod tests {
382 294
383 assert!(!waker.awoken.get()); 295 assert!(!waker.awoken.get());
384 296
385 DRIVER.set_now(Instant::from_secs(99).as_ticks()); 297 MockDriver::get().advance(Duration::from_secs(99));
386 298
387 assert!(!waker.awoken.get()); 299 assert!(!waker.awoken.get());
388 300
389 assert_eq!(queue_len(), 1); 301 assert_eq!(queue_len(), 1);
390 302
391 DRIVER.set_now(Instant::from_secs(100).as_ticks()); 303 MockDriver::get().advance(Duration::from_secs(1));
392 304
393 assert!(waker.awoken.get()); 305 assert!(waker.awoken.get());
394 306
@@ -404,7 +316,7 @@ mod tests {
404 316
405 QUEUE.schedule_wake(Instant::from_secs(100), &waker.waker); 317 QUEUE.schedule_wake(Instant::from_secs(100), &waker.waker);
406 318
407 DRIVER.set_now(Instant::from_secs(50).as_ticks()); 319 MockDriver::get().advance(Duration::from_secs(50));
408 320
409 let waker2 = TestWaker::new(); 321 let waker2 = TestWaker::new();
410 322