use std::{
collections::VecDeque,
fmt::Debug,
sync::{Arc, Mutex},
thread,
};
#[derive(Debug, Clone)]
pub struct Generic<T: Clone + Debug + PartialEq> {
expected: Arc<Mutex<VecDeque<T>>>,
done_called: Arc<Mutex<DoneCallDetector>>,
}
impl<'a, T: 'a> Generic<T>
where
T: Clone + Debug + PartialEq,
{
pub fn new<E>(expected: E) -> Generic<T>
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())),
};
g.update_expectations(expected);
g
}
pub fn update_expectations<E>(&mut self, expected: E)
where
E: IntoIterator<Item = &'a T>,
{
self.done_impl(false);
let new_expectations: VecDeque<T> = expected.into_iter().cloned().collect();
let mut expected = self.expected.lock().unwrap();
let mut done_called = self.done_called.lock().unwrap();
*expected = new_expectations;
done_called.reset();
}
#[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)
}
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");
}
}
impl<T> Iterator for Generic<T>
where
T: Clone + Debug + PartialEq,
{
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
self.expected.lock().unwrap().pop_front()
}
}
#[derive(Debug)]
pub(crate) struct DoneCallDetector {
called: bool,
}
impl DoneCallDetector {
pub(crate) fn new() -> Self {
Self { called: false }
}
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;
}
pub(crate) fn reset(&mut self) {
self.called = false;
}
}
impl Drop for DoneCallDetector {
fn drop(&mut self) {
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.";
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!("{}", 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();
}
}
}