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