embedded_hal_mock/
common.rs

1//! Common functionality used by the mock implementations.
2
3use std::{
4    collections::VecDeque,
5    fmt::Debug,
6    sync::{Arc, Mutex},
7    thread,
8};
9
10/// Generic mock implementation.
11///
12/// ⚠️ **Do not create this directly as end user! This is only a building block
13/// for creating mocks.**
14///
15/// This type supports the specification and evaluation of expectations to
16/// allow automated testing of hal drivers. Mismatches between expectations
17/// will cause runtime assertions to assist in locating the source of the
18/// fault.
19///
20/// Note that the implementation uses an `Arc<Mutex<...>>` internally, so a
21/// cloned instance of the mock can be used to check the expectations of the
22/// original instance that has been moved into a driver.
23#[derive(Debug, Clone)]
24pub struct Generic<T: Clone + Debug + PartialEq> {
25    expected: Arc<Mutex<VecDeque<T>>>,
26    done_called: Arc<Mutex<DoneCallDetector>>,
27}
28
29impl<'a, T: 'a> Generic<T>
30where
31    T: Clone + Debug + PartialEq,
32{
33    /// Create a new mock interface
34    ///
35    /// This creates a new generic mock interface with initial expectations
36    pub fn new<E>(expected: E) -> Generic<T>
37    where
38        E: IntoIterator<Item = &'a T>,
39    {
40        let mut g = Generic {
41            expected: Arc::new(Mutex::new(VecDeque::new())),
42            done_called: Arc::new(Mutex::new(DoneCallDetector::new())),
43        };
44
45        g.update_expectations(expected);
46
47        g
48    }
49
50    /// Update expectations on the interface
51    ///
52    /// When this method is called, first it is ensured that existing
53    /// expectations are all consumed by calling [`done()`](#method.done)
54    /// internally (if not called already). Afterwards, the new expectations
55    /// are set.
56    pub fn update_expectations<E>(&mut self, expected: E)
57    where
58        E: IntoIterator<Item = &'a T>,
59    {
60        // Ensure that existing expectations are consumed
61        self.done_impl(false);
62
63        // Collect new expectations into vector
64        let new_expectations: VecDeque<T> = expected.into_iter().cloned().collect();
65
66        // Lock internal state
67        let mut expected = self.expected.lock().unwrap();
68        let mut done_called = self.done_called.lock().unwrap();
69
70        // Update expectations
71        *expected = new_expectations;
72
73        // Reset done call detector
74        done_called.reset();
75    }
76
77    /// Deprecated alias of `update_expectations`.
78    #[deprecated(
79        since = "0.10.0",
80        note = "The method 'expect' was renamed to 'update_expectations'"
81    )]
82    pub fn expect<E>(&mut self, expected: E)
83    where
84        E: IntoIterator<Item = &'a T>,
85    {
86        self.update_expectations(expected)
87    }
88
89    /// Assert that all expectations on a given mock have been consumed.
90    pub fn done(&mut self) {
91        self.done_impl(true);
92    }
93
94    fn done_impl(&mut self, panic_if_already_done: bool) {
95        self.done_called
96            .lock()
97            .unwrap()
98            .mark_as_called(panic_if_already_done);
99        let e = self.expected.lock().unwrap();
100        assert!(e.is_empty(), "Not all expectations consumed");
101    }
102}
103
104/// Iterator impl for use in mock impls
105impl<T> Iterator for Generic<T>
106where
107    T: Clone + Debug + PartialEq,
108{
109    type Item = T;
110    fn next(&mut self) -> Option<Self::Item> {
111        self.expected.lock().unwrap().pop_front()
112    }
113}
114
115/// Struct used to detect whether or not the `.done()` method was called.
116#[derive(Debug)]
117pub(crate) struct DoneCallDetector {
118    called: bool,
119}
120
121impl DoneCallDetector {
122    pub(crate) fn new() -> Self {
123        Self { called: false }
124    }
125
126    /// Mark the `.done()` method as called.
127    ///
128    /// Note: When calling this method twice, an assertion failure will be
129    /// triggered if `panic_if_already_done` is true.
130    pub(crate) fn mark_as_called(&mut self, panic_if_already_done: bool) {
131        if panic_if_already_done {
132            assert!(!self.called, "The `.done()` method was called twice!");
133        }
134        self.called = true;
135    }
136
137    /// Reset the detector.
138    pub(crate) fn reset(&mut self) {
139        self.called = false;
140    }
141}
142
143impl Drop for DoneCallDetector {
144    fn drop(&mut self) {
145        // Ensure that the `.done()` method was called on the mock before
146        // dropping.
147        if !self.called && !thread::panicking() {
148            let msg = "WARNING: A mock (from embedded-hal-mock) was dropped \
149                       without calling the `.done()` method. \
150                       See https://github.com/dbrgn/embedded-hal-mock/issues/34 \
151                       for more details.";
152
153            // Note: We cannot use the print macros here, since they get
154            // captured by the Cargo test runner. Instead, write to stderr
155            // directly.
156            use std::io::Write;
157            let mut stderr = std::io::stderr();
158            stderr.write_all(b"\x1b[31m").ok();
159            stderr.write_all(msg.as_bytes()).ok();
160            stderr.write_all(b"\x1b[m\n").ok();
161            stderr.flush().ok();
162
163            // Panic!
164            //
165            // (Note: Inside a `Drop` implementation, panic should only be used
166            // if not already panicking:
167            // https://doc.rust-lang.org/std/ops/trait.Drop.html#panics
168            // This is ensured by checking `!thread::panicking()`.)
169            panic!("{}", msg);
170        }
171    }
172}
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177
178    mod generic_mock {
179        use super::*;
180
181        #[test]
182        fn success() {
183            let expectations = [0u8, 1u8];
184            let mut mock: Generic<u8> = Generic::new(&expectations);
185
186            assert_eq!(mock.next(), Some(0u8));
187            assert_eq!(mock.next(), Some(1u8));
188            assert_eq!(mock.next(), None);
189            assert_eq!(mock.next(), None);
190
191            mock.done();
192        }
193
194        #[test]
195        #[should_panic(
196            expected = "WARNING: A mock (from embedded-hal-mock) was dropped without calling the `.done()` method. See https://github.com/dbrgn/embedded-hal-mock/issues/34 for more details."
197        )]
198        fn panic_if_drop_not_called() {
199            let expectations = [0u8, 1u8];
200            let mut mock: Generic<u8> = Generic::new(&expectations);
201            assert_eq!(mock.next(), Some(0u8));
202            assert_eq!(mock.next(), Some(1u8));
203        }
204
205        #[test]
206        #[should_panic(expected = "The `.done()` method was called twice!")]
207        fn panic_if_drop_called_twice() {
208            let expectations = [0u8, 1u8];
209            let mut mock: Generic<u8> = Generic::new(&expectations);
210            assert_eq!(mock.next(), Some(0u8));
211            assert_eq!(mock.next(), Some(1u8));
212            mock.done();
213            mock.done();
214        }
215    }
216}