diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f89b1d7..f084ffdb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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)) diff --git a/mockall/src/lib.rs b/mockall/src/lib.rs index 7062b722..32ee89c9 100644 --- a/mockall/src/lib.rs +++ b/mockall/src/lib.rs @@ -1480,6 +1480,14 @@ impl From> for TimesRange { } } +#[derive(PartialEq)] +#[doc(hidden)] +pub enum ExpectedCalls { + Satisfied, + TooMany, + TooFew, +} + #[derive(Debug, Default)] #[doc(hidden)] pub struct Times{ @@ -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 diff --git a/mockall/tests/mock_struct.rs b/mockall/tests/mock_struct.rs index bff87414..02e31def 100644 --- a/mockall/tests/mock_struct.rs +++ b/mockall/tests/mock_struct.rs @@ -24,6 +24,7 @@ mock!{ } mod checkpoint { + use std::panic; use super::*; #[test] @@ -52,6 +53,20 @@ mod checkpoint { panic!("Shouldn't get here!"); } + #[test] + #[should_panic(expected = + "MockFoo::foo: Expectation() 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() { diff --git a/mockall_derive/src/mock_function.rs b/mockall_derive/src/mock_function.rs index da59afe9..0c9dbfd1 100644 --- a/mockall_derive/src/mock_function.rs +++ b/mockall_derive/src/mock_function.rs @@ -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, - /// 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, @@ -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() } } @@ -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()); + }, + _ => () + } } } }