Skip to content

Commit

Permalink
Ensure that checkpoint works if the method call limit is exceeded (#472)
Browse files Browse the repository at this point in the history
This is usually not an issue, since the program should already have paniced as soon as it attempted the first method call beyond the expected limits.  However, it can still be useful if the method call is made in a context where panics are ignored, but checkpoint() is called in a context where they are not.
  • Loading branch information
vikz95 authored and asomers committed Mar 26, 2023
1 parent 1be901c commit 7d70403
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 14 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Static methods' expectations will now be cleared during a panic.
([#443](https://github.com/asomers/mockall/pull/443))

- The `checkpoint` method now works correctly even after a panic due to too many
method calls.
([#472](https://github.com/asomers/mockall/pull/472))

- Methods with unknown size type bounds can now be mocked.
([#421](https://github.com/asomers/mockall/pull/421))

Expand Down
29 changes: 25 additions & 4 deletions mockall/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1480,6 +1480,14 @@ impl From<RangeToInclusive<usize>> for TimesRange {
}
}

#[derive(PartialEq)]
#[doc(hidden)]
pub enum ExpectedCalls {
Satisfied,
TooMany,
TooFew,
}

#[derive(Debug, Default)]
#[doc(hidden)]
pub struct Times{
Expand Down Expand Up @@ -1528,10 +1536,23 @@ impl Times {
(self.range.0.end - self.range.0.start) == 1
}

/// Has this expectation already been called the minimum required number of
/// times?
pub fn is_satisfied(&self) -> bool {
self.count.load(Ordering::Relaxed) >= self.range.0.start
/// Has this expectation already been called the expected number of times?
/// If not, was it too many or too few?
pub fn is_satisfied(&self) -> ExpectedCalls {
let satisfied_lower_bound = self.count.load(Ordering::Relaxed) >= self.range.0.start;
let satisfied_upper_bound = self.count.load(Ordering::Relaxed) < self.range.0.end;
if satisfied_lower_bound && satisfied_upper_bound {
ExpectedCalls::Satisfied
} else if satisfied_lower_bound {
ExpectedCalls::TooMany
} else {
ExpectedCalls::TooFew
}
}

/// The maximum number of times that this expectation must be called
pub fn maximum(&self) -> usize {
self.range.0.end - 1
}

/// The minimum number of times that this expectation must be called
Expand Down
15 changes: 15 additions & 0 deletions mockall/tests/mock_struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ mock!{
}

mod checkpoint {
use std::panic;
use super::*;

#[test]
Expand Down Expand Up @@ -52,6 +53,20 @@ mod checkpoint {
panic!("Shouldn't get here!");
}

#[test]
#[should_panic(expected =
"MockFoo::foo: Expectation(<anything>) called 1 time(s) which is more than expected 0")]
fn too_many_calls() {
let mut mock = MockFoo::default();
mock.expect_foo()
.returning(|_| 42)
.times(0);
let _ = panic::catch_unwind(|| {
mock.foo(0);
});
mock.checkpoint();
panic!("Shouldn't get here!");
}

#[test]
fn ok() {
Expand Down
31 changes: 21 additions & 10 deletions mockall_derive/src/mock_function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -406,9 +406,9 @@ pub(crate) struct MockFunction {
/// Types used for Predicates. Will be almost the same as args, but every
/// type will be a non-reference type.
predty: Vec<Type>,
/// Does the function return a non-'static reference?
/// Does the function return a non-'static reference?
return_ref: bool,
/// Does the function return a mutable reference?
/// Does the function return a mutable reference?
return_refmut: bool,
/// References to every type in `predty`.
refpredty: Vec<Type>,
Expand Down Expand Up @@ -885,7 +885,7 @@ impl<'a> ToTokens for Common<'a> {
m);
});
self.verify_sequence(desc);
if self.times.is_satisfied() {
if ::mockall::ExpectedCalls::TooFew != self.times.is_satisfied() {
self.satisfy_sequence()
}
}
Expand Down Expand Up @@ -963,15 +963,26 @@ impl<'a> ToTokens for Common<'a> {

impl #ig Drop for Common #tg #wc {
fn drop(&mut self) {
if !::std::thread::panicking() && !self.times.is_satisfied()
{
if !::std::thread::panicking() {
let desc = std::format!(
"{}", self.matcher.lock().unwrap());
panic!("{}: Expectation({}) called {} time(s) which is fewer than expected {}",
#funcname,
desc,
self.times.count(),
self.times.minimum());
match self.times.is_satisfied() {
::mockall::ExpectedCalls::TooFew => {
panic!("{}: Expectation({}) called {} time(s) which is fewer than expected {}",
#funcname,
desc,
self.times.count(),
self.times.minimum());
},
::mockall::ExpectedCalls::TooMany => {
panic!("{}: Expectation({}) called {} time(s) which is more than expected {}",
#funcname,
desc,
self.times.count(),
self.times.maximum());
},
_ => ()
}
}
}
}
Expand Down

0 comments on commit 7d70403

Please sign in to comment.