From 7d7040378d8739229314fbdce7b10959c8df3e4b Mon Sep 17 00:00:00 2001 From: vikz95 Date: Sun, 26 Mar 2023 19:23:41 +0200 Subject: [PATCH] Ensure that checkpoint works if the method call limit is exceeded (#472) 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. --- CHANGELOG.md | 4 ++++ mockall/src/lib.rs | 29 +++++++++++++++++++++++---- mockall/tests/mock_struct.rs | 15 ++++++++++++++ mockall_derive/src/mock_function.rs | 31 +++++++++++++++++++---------- 4 files changed, 65 insertions(+), 14 deletions(-) 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()); + }, + _ => () + } } } }