embedded_hal_mock/eh0/
timer.rs

1//! Provides a mocked [embedded_time::Clock] that can be used for host-side
2//! testing crates that use [embedded_hal::timer].
3//!
4//! The provided [embedded_time::Clock] implementation is thread safe and can
5//! be freely skipped forward with nanosecond precision.
6//!
7//! # Usage
8//!
9//! ```rust
10//! # use eh0 as embedded_hal;
11//! use embedded_hal::timer::CountDown;
12//! use embedded_hal_mock::eh0::timer::MockClock;
13//! use embedded_time::duration::*;
14//!
15//! let mut clock = MockClock::new();
16//! let mut timer = clock.get_timer();
17//! timer.start(100.nanoseconds());
18//! // hand over timer to embedded-hal based driver
19//! // continue to tick clock
20//! clock.tick(50.nanoseconds());
21//! assert_eq!(timer.wait(), Err(nb::Error::WouldBlock));
22//! clock.tick(50.nanoseconds());
23//! assert_eq!(timer.wait(), Ok(()));
24//! clock.tick(50.nanoseconds());
25//! assert_eq!(timer.wait(), Err(nb::Error::WouldBlock));
26//! clock.tick(50.nanoseconds());
27//! assert_eq!(timer.wait(), Ok(()));
28//! ```
29
30use std::{
31    convert::Infallible,
32    sync::{
33        atomic::{AtomicU64, Ordering},
34        Arc,
35    },
36};
37
38use eh0 as embedded_hal;
39use embedded_hal::timer::{Cancel, CountDown, Periodic};
40pub use embedded_time::Clock;
41use embedded_time::{clock, duration::*, fraction::Fraction, Instant};
42use void::Void;
43
44/// A simulated clock that can be used in tests.
45#[derive(Clone, Debug)]
46pub struct MockClock {
47    ticks: Arc<AtomicU64>,
48}
49
50impl Clock for MockClock {
51    type T = u64;
52    const SCALING_FACTOR: Fraction = Fraction::new(1, 1_000_000_000);
53
54    fn try_now(&self) -> Result<Instant<Self>, clock::Error> {
55        let ticks: u64 = self.ticks.load(Ordering::Relaxed);
56        Ok(Instant::<Self>::new(ticks))
57    }
58}
59
60impl Default for MockClock {
61    fn default() -> Self {
62        MockClock {
63            ticks: Arc::new(AtomicU64::new(0)),
64        }
65    }
66}
67
68impl MockClock {
69    /// Creates a new simulated clock.
70    pub fn new() -> Self {
71        Self::default()
72    }
73
74    /// Returns the number of elapsed nanoseconds.
75    pub fn elapsed(&self) -> Nanoseconds<u64> {
76        Nanoseconds(self.ticks.load(Ordering::Relaxed))
77    }
78
79    /// Forward the clock by `ticks` amount.
80    pub fn tick<T>(&mut self, ticks: T)
81    where
82        T: Into<Nanoseconds<u64>>,
83    {
84        self.ticks.fetch_add(ticks.into().0, Ordering::Relaxed);
85    }
86
87    /// Get a new timer based on the clock.
88    pub fn get_timer(&self) -> MockTimer {
89        let clock = self.clone();
90        let duration = Nanoseconds(1);
91        let expiration = clock.try_now().unwrap();
92        MockTimer {
93            clock: self.clone(),
94            duration,
95            expiration,
96            started: false,
97        }
98    }
99}
100
101/// A simulated timer that can be used in tests.
102pub struct MockTimer {
103    clock: MockClock,
104    duration: Nanoseconds<u64>,
105    expiration: Instant<MockClock>,
106    started: bool,
107}
108
109impl CountDown for MockTimer {
110    type Time = Nanoseconds<u64>;
111
112    fn start<T>(&mut self, count: T)
113    where
114        T: Into<Self::Time>,
115    {
116        let now = self.clock.try_now().unwrap();
117        self.duration = count.into();
118        self.expiration = now + self.duration;
119        self.started = true;
120    }
121
122    fn wait(&mut self) -> nb::Result<(), Void> {
123        let now = self.clock.try_now().unwrap();
124        if self.started && now >= self.expiration {
125            self.expiration = now + self.duration;
126            Ok(())
127        } else {
128            Err(nb::Error::WouldBlock)
129        }
130    }
131}
132
133impl Periodic for MockTimer {}
134
135impl Cancel for MockTimer {
136    type Error = Infallible;
137
138    fn cancel(&mut self) -> Result<(), Self::Error> {
139        self.started = false;
140        Ok(())
141    }
142}
143
144#[cfg(test)]
145mod test {
146    use super::*;
147
148    #[test]
149    fn count_down() {
150        let mut clock = MockClock::new();
151        let mut timer = clock.get_timer();
152        timer.start(100.nanoseconds());
153        clock.tick(50.nanoseconds());
154        assert_eq!(timer.wait(), Err(nb::Error::WouldBlock));
155        clock.tick(50.nanoseconds());
156        assert_eq!(timer.wait(), Ok(()));
157        clock.tick(50.nanoseconds());
158        assert_eq!(timer.wait(), Err(nb::Error::WouldBlock));
159        clock.tick(50.nanoseconds());
160        assert_eq!(timer.wait(), Ok(()));
161    }
162}