#[cfg(feature = "galvanic_assert_integration")] extern crate galvanic_assert;
use std::collections::HashMap;
use std::cell::RefCell;
pub trait MockControl {
fn should_verify_on_drop(&mut self, flag: bool);
fn add_given_behaviour(&self,
requested_trait: &'static str,
method: &'static str,
behaviour: GivenBehaviour);
fn reset_given_behaviours(&mut self);
fn add_expect_behaviour(&self,
requested_trait: &'static str,
method: &'static str,
behaviour: ExpectBehaviour);
fn reset_expected_behaviours(&mut self);
fn are_expected_behaviours_satisfied(&self) -> bool;
fn verify(&self);
}
pub struct MockState {
pub given_behaviours: RefCell<HashMap<(&'static str, &'static str), Vec<GivenBehaviour>>>,
pub expect_behaviours: RefCell<HashMap<(&'static str, &'static str), Vec<ExpectBehaviour>>>,
verify_on_drop: bool,
}
impl MockState {
pub fn new() -> Self {
Self {
given_behaviours: RefCell::new(HashMap::new()),
expect_behaviours: RefCell::new(HashMap::new()),
verify_on_drop: true,
}
}
}
impl MockControl for MockState {
fn should_verify_on_drop(&mut self, flag: bool) {
self.verify_on_drop = flag;
}
fn add_given_behaviour(&self,
requested_trait: &'static str,
method: &'static str,
behaviour: GivenBehaviour) {
self.given_behaviours
.borrow_mut()
.entry((requested_trait, method))
.or_insert_with(|| Vec::new())
.push(behaviour);
}
fn reset_given_behaviours(&mut self) {
self.given_behaviours.borrow_mut().clear();
}
fn add_expect_behaviour(&self,
requested_trait: &'static str,
method: &'static str,
behaviour: ExpectBehaviour) {
self.expect_behaviours
.borrow_mut()
.entry((requested_trait, method))
.or_insert_with(|| Vec::new())
.push(behaviour);
}
fn reset_expected_behaviours(&mut self) {
self.expect_behaviours.borrow_mut().clear();
}
fn are_expected_behaviours_satisfied(&self) -> bool {
let mut unsatisfied_messages: Vec<String> = Vec::new();
for behaviour in self.expect_behaviours.borrow().values().flat_map(|vs| vs) {
if !behaviour.is_saturated() {
unsatisfied_messages
.push(format!("Behaviour unsatisfied with {} matching invocations: {}",
behaviour.num_matches.get(),
behaviour.describe()));
}
}
if !unsatisfied_messages.is_empty() {
for message in unsatisfied_messages {
eprintln!("{}", message);
}
false
} else {
true
}
}
fn verify(&self) {
if !std::thread::panicking() && !self.are_expected_behaviours_satisfied() {
panic!("There are unsatisfied expected behaviours for mocked traits.");
}
}
}
impl std::ops::Drop for MockState {
fn drop(&mut self) {
if self.verify_on_drop {
self.verify();
}
}
}
pub trait ArgMatcher<'a, T: 'a> {
fn match_args(&self, actual: &'a T) -> bool;
}
impl<'a, T: 'a, F> ArgMatcher<'a, T> for F
where F: Fn(&'a T) -> bool
{
fn match_args(&self, actual: &'a T) -> bool {
self(actual)
}
}
#[cfg(feature = "galvanic_assert_integration")]
impl<'a, T: 'a> ArgMatcher<'a, T> for Box<::galvanic_assert::Matcher<'a, T> + 'a> {
fn match_args(&self, actual: &'a T) -> bool {
self.check(actual).into()
}
}
pub struct GivenBehaviour {
pub stmt_id: usize,
num_matches: std::cell::Cell<usize>,
expected_matches: Option<usize>,
pub bound: std::rc::Rc<std::any::Any>,
stmt_repr: String,
}
impl GivenBehaviour {
pub fn with(stmt_id: usize, bound: std::rc::Rc<std::any::Any>, stmt_repr: &str) -> Self {
Self {
stmt_id: stmt_id,
num_matches: std::cell::Cell::new(0),
expected_matches: None,
bound: bound,
stmt_repr: stmt_repr.to_string(),
}
}
pub fn with_times(times: usize,
stmt_id: usize,
bound: std::rc::Rc<std::any::Any>,
stmt_repr: &str)
-> Self {
Self {
stmt_id: stmt_id,
num_matches: std::cell::Cell::new(0),
expected_matches: Some(times),
bound: bound,
stmt_repr: stmt_repr.to_string(),
}
}
pub fn matched(&self) {
self.num_matches.set(self.num_matches.get() + 1);
}
pub fn is_saturated(&self) -> bool {
match self.expected_matches {
Some(limit) => self.num_matches.get() >= limit,
None => false,
}
}
pub fn describe(&self) -> &str {
&self.stmt_repr
}
}
pub struct ExpectBehaviour {
pub stmt_id: usize,
num_matches: std::cell::Cell<usize>,
expected_min_matches: Option<usize>,
expected_max_matches: Option<usize>,
#[allow(dead_code)] in_order: Option<bool>,
pub bound: std::rc::Rc<std::any::Any>,
stmt_repr: String,
}
impl ExpectBehaviour {
pub fn with_times(times: usize,
stmt_id: usize,
bound: std::rc::Rc<std::any::Any>,
stmt_repr: &str)
-> Self {
Self {
stmt_id: stmt_id,
num_matches: std::cell::Cell::new(0),
expected_min_matches: Some(times),
expected_max_matches: Some(times),
in_order: None,
bound: bound,
stmt_repr: stmt_repr.to_string(),
}
}
pub fn with_at_least(at_least_times: usize,
stmt_id: usize,
bound: std::rc::Rc<std::any::Any>,
stmt_repr: &str)
-> Self {
Self {
stmt_id: stmt_id,
num_matches: std::cell::Cell::new(0),
expected_min_matches: Some(at_least_times),
expected_max_matches: None,
in_order: None,
bound: bound,
stmt_repr: stmt_repr.to_string(),
}
}
pub fn with_at_most(at_most_times: usize,
stmt_id: usize,
bound: std::rc::Rc<std::any::Any>,
stmt_repr: &str)
-> Self {
Self {
stmt_id: stmt_id,
num_matches: std::cell::Cell::new(0),
expected_min_matches: None,
expected_max_matches: Some(at_most_times),
in_order: None,
bound: bound,
stmt_repr: stmt_repr.to_string(),
}
}
pub fn with_between(at_least_times: usize,
at_most_times: usize,
stmt_id: usize,
bound: std::rc::Rc<std::any::Any>,
stmt_repr: &str)
-> Self {
Self {
stmt_id: stmt_id,
num_matches: std::cell::Cell::new(0),
expected_min_matches: Some(at_least_times),
expected_max_matches: Some(at_most_times),
in_order: None,
bound: bound,
stmt_repr: stmt_repr.to_string(),
}
}
pub fn matched(&self) {
self.num_matches.set(self.num_matches.get() + 1);
}
pub fn is_saturated(&self) -> bool {
self.expected_min_matches.unwrap_or(0) <= self.num_matches.get() &&
self.num_matches.get() <= self.expected_max_matches.unwrap_or(std::usize::MAX)
}
pub fn describe(&self) -> &str {
&self.stmt_repr
}
}