-
Notifications
You must be signed in to change notification settings - Fork 69
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Check if actual calls exceed the expected number on mock object drop #472
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs a CHANGELOG and a test case too.
mockall/src/lib.rs
Outdated
@@ -1605,7 +1605,8 @@ impl Times { | |||
/// Has this expectation already been called the minimum required number of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should update doc comment too, because you've changed the function's behavior.
@@ -416,9 +416,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? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please do not mix formatting changes and content changes in the same commit.
mockall_derive/src/mock_function.rs
Outdated
desc, | ||
self.times.count(), | ||
self.times.minimum()); | ||
if (self.times.count() < self.times.minimum()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is very confusing because you're checking times.count() here, and also implicitly up above in is_satisfied. It makes the code hard to read. You should only do that check on one level.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you elaborate on this point?
Now is_satisfied checks both expected boundaries of the actual number of calls and returns a boolean.
From a boolean I'm not able to determine what error message to print in the panic, this is why the second check.
Instead of a boolean I could return an Enum with values: SATISFIED_LOWER_BOUND, SATISFIED_UPPER_BOUND, SATISFIED, NOT_SATISFIED.
Then I could differentiate the cases. But I don't know if this is what you had in mind...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could do something like that, returning an enum. Or you could inline the is_satisfied logic into the proc macro. Either could work. I just don't want that logic to be repeated in two places.
You seem to be using Windows, and as you can see that example is empty on Windows. Would you like to submit a separate PR to fix it? |
Created a new pull request for the test failure on Windows: #473 I'll fix the other issues tomorrow. |
mockall_derive/src/mock_function.rs
Outdated
self.times.count(), | ||
self.times.minimum()); | ||
if !::std::thread::panicking() { | ||
match self.times.is_satisfied(); { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will remove ; in next commit
Why cargo build doesn't catch there errors?
How should I compile?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, if you just did cargo build
, that would build the proc macros and the small amount of code in the mockall crate, but it wouldn't evaluate any of the proc macros, anywhere. You should do cargo test
before pushing.
If the implementation with enums is ok, then I will create a test where the panics are raised in a subthread and then assure that checkpoint() makes the test fail. I also need to figure why now cargo test causes so many errors, maybe it's due to the new enum:
Update: should have used: |
No need to get all fancy with threads. Just use
Because you forgot to import that type in the generated code. Best to refer to it by its absolute path, |
mockall_derive/src/mock_function.rs
Outdated
self.times.minimum()); | ||
}, | ||
::mockall::ExpectedCalls::SatisfiedOnlyLowerBound => { | ||
panic!("Exceeded number of expected calls panic propagation"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's this about panic propagation? A better message would be:
panic!("Exceeded number of expected calls panic propagation"); | |
panic!("{}: Expectation({}) called {} time(s) which is more than the permitted {}", | |
#funcname, | |
desc, | |
self.times.count(), | |
self.times.maximum() - 1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This message is already printed by the panic in the call() method.
So in a subthread scenario there will be 2 equal panic messages.
I wanted to differentiate the second one.
Or did you expected to remove completely the panic in the call() method? And have only one in drop()?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will it be? The second message will only be printed if for some reason the user is ignoring panics. In that case, he'll need all the info in the second one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
when calls happen in subthread and are more than expected fitst there is the panic from the check in call() method.
but it's ignored by main thread.
then there is the panic from the check in drop() method that makes the test fail.
but even if the messages are the same and are printed 2 times in this scenario it's not a problem, so I changed the message as suggested.
mockall/src/lib.rs
Outdated
Satisfied, | ||
SatisfiedOnlyLowerBound, | ||
SatisfiedOnlyUpperBound, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find this nomenclature confusing, because it focuses on which bounds are satisfied, rather than which aren't, which is more interesting. I suggest
Satisfied, | |
SatisfiedOnlyLowerBound, | |
SatisfiedOnlyUpperBound, | |
Satisfied, | |
TooFew, | |
TooMany, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fine, will update
Now that the tests failures are fixed I need to write the 3 test cases:
But if I don't use threads then in case 3 the call() method will panic, and not the drop(). |
That's what |
cargo doc is failing:
I don't know if it's due to my changes, and what is the issue. Update: I see that the issue is also on master, so it's unrelated. |
In the end I choose to use threads in the tests to reproduce the issue that I encountered, and because I don't know how to use catch_unwind() to ignore the panic in call() and fail on the panic in drop() when calls > expectation I think having tests that check expectations with calls that happen in subthreads is useful, this pull request aims to fill a gap exactly for this scenario. |
You should learn how to use catch_unwind. It's more direct than using threads. I'll ensure that the master branch works. |
Ok, I used |
Ok, I fixed master. The problem was a change in the new syn-2.0.9 crate. Could you please rebase your PR now? |
# Conflicts: # CHANGELOG.md
CHANGELOG.md
Outdated
- Before dropping the mock object an additional check will verify if the | ||
actual number of times a method was called is above the expectation. | ||
This is needed because if the original check, that happens at every call, | ||
panics in a subthread, then the caller thread will ignore the panic and | ||
the test will succeed while should have failed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or to put it much more simply:
- Before dropping the mock object an additional check will verify if the | |
actual number of times a method was called is above the expectation. | |
This is needed because if the original check, that happens at every call, | |
panics in a subthread, then the caller thread will ignore the panic and | |
the test will succeed while should have failed. | |
- The `checkpoint` method now works correctly even after a panic due to too many | |
method calls. |
mockall/src/lib.rs
Outdated
/// Has this expectation already been called a number of times above the minimum, | ||
/// below the maximum, both cases, or neither? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let me suggest some more natural English:
/// Has this expectation already been called a number of times above the minimum, | |
/// below the maximum, both cases, or neither? | |
/// Has this expectation already been called the expected number of times? | |
/// If not, was it too many or too few? |
@@ -0,0 +1,54 @@ | |||
// vim: tw=80 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't need a new test program for this. The reason that Mockall's tests are split into so many separate test programs is so that most of them will still compile successfully even if one does not. And when investigating a compile failure, it helps to narrow the search for the problem.
For this PR, you aren't changing mockall's syntax at all. So I suggest that you delete this file and delete the expectation_satisfied
and too_few_calls
methods, which are redundant with tests elsewhere. Then, move too_many_calls
into tests/mock_struct.rs
, in the checkpoint
module.
Thanks for your contribution @vikz95 . |
Thank you for the support and guidance. |
I'm planning to make a patch release soon. But not a feature release, because I'm still not comfortable with the implementation of the concretize feature. I still haven't found a non-hacky way to do it. |
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.
This pull request will allow to check again if the actual number of function calls is greater than the expected during the mock object dropout.
So if the check executed at every call panics in a sub task, this additional check at object dropout will cause another panic and will make the test fail.
I was not able to run
cargo test
because I got this error: