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
Ensure negative expectations raise during RSpec verification time. #884
Conversation
Thanks for tackling this, @samphippen. Thinking about this some more, I realized that a negative message expectation is just one case where we eagerly fail rather than failing on def verify_messages_received
return if expected_messages_received?
InsertOntoBacktrace.line(@expected_from) { generate_error }
end Much simpler, eh? I tried out that change and noticed that it caused about 30 failures but the handful I looked at looked correct since they were cases that failed eagerly that the spec itself caught, so a simple |
@myronmarston 👍 I'll continue to work on this over the coming week. |
8b57533
to
a60bbf5
Compare
@myronmarston I used your implementation and started fixing the specs up.
I think that's it? |
I'll try to look at this tomorrow...ran out of time today. |
ec8b088
to
accd614
Compare
Sorry it took me so long to get back to you; I had to do an audit to answer your question! Here are the cases I've found that raise errors eagerly (e.g. when the message is received):
Initially, I was thinking we would want So I think the cases to re-raise for are:
...which I guess is the same list you had plus the ordering one. |
This is necessary because in rspec/rspec-mocks#884 we've added behaviour that raises even if an eager raise occured. The call to generated description happens outside of the test, but before the mock is reset, so this resets it.
@myronmarston I started writing specs for all the cases. I think having them in their own file makes sense, do you agree? I'm aware the new spec file is a bit duplicated right now, so don't feel the need to leave a review note to that effect (I'll fix it up). I'm thinking I'm going to put the cases that don't reraise into their own context, and the ones that do also into their own context. Do you see the current implementation causing any of the cases that shouldn't raise (that you've outlined above) causing any issues? |
@myronmarston can you take a look at this when you get a chance. Thanks in advance :) |
@@ -79,6 +79,7 @@ module Mocks | |||
expect { | |||
@double.do_something | |||
}.to raise_error(/expected: at most 2 times.*received: 3 times/m) | |||
reset @double |
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.
So this expect { }.to raise_error; reset double
pattern is showing up over, and over and over again through the spec suite. I'm thinking we should come up with a common abstraction to handle that.
WDYT of this?
expect_fast_failure_from(double, /expected: at most 2 times.*received: 3 times/m) do
double.do_something
end
expect_fast_failure_from
would take of expect { }.to raise_error
and the reset
.
Specifically, in #1826, we changed the conditional for whether or not to assign a generated description from RSpec::Matchers for examples with no doc string. Before #1826, the conditional was: assign_generated_description if RSpec.configuration.expecting_with_rspec? In #1826 it changed to: assign_generated_description if defined?(::RSpec::Matchers) We didn’t update the spec meant for that case to match, but it continued to pass (as a false positive) due to rspec/rspec-mocks#874. @samphippen’s fix in rspec/rspec-mocks#884 surfaced the issue (as the spec now failed) so I decided to improve the tests. - The spec now simulates the `RSpec::Matchers` constant being undefined to simulate the correct condition. We also have to prevent it from being autoloaded. - The cukes for minitest/test-unit did not sufficiently cover this case, because the aforementioned autoload would autoload RSpec::Matchers, so we have to simulate rspec-expectations being completely uninstalled. Then the cukes properly fail if we break the `if defined?(::RSpec::Matchers)` conditional.
I just kicked the build now that rspec/rspec-core#1879 has been merged...hopefully this will go green now. |
Seems not, :/ |
It's much better, though -- in the last build (https://travis-ci.org/rspec/rspec-mocks/builds/49933519) it was failing when running rspec-core's specs. Now it's not. It's just 1.8.7 that's failing on some rspec-mocks specs now. |
@samphippen, are you planning to get back to this? |
yup. I'm gonna spend some time this weekend on this. |
d574d30
to
9fde951
Compare
https://travis-ci.org/rspec/rspec-mocks/jobs/54355732#L2334 💀 I will see if I can debug this locally. |
I'm having trouble getting REE set up on my mac. @myronmarston can you look at the failure and reckon if it's legit. I'm going to see if I can get a linux box set up with REE inside it. |
reset d | ||
end | ||
|
||
def expect_fast_failure_from(double, class_or_message=Exception, message=/.*/, &blk) |
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.
Exception
seems like a poor choice for the default here, given that at virtually all the call sites above you pass RSpec::Mocks::MockExpectationError
(and in the places you don't, I'm wondering if you should!). It seems like the norm is RSpec::Mocks::MockExpectationError
-- after all, in rspec-mocks, that's who we signal failures -- so we should make that the default and only make the caller pass a different class if it really needs something different.
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.
Actually, I suspect that there is no need to specify a class other than RSpec::Mocks::MockExpectationError
. If that's the case, this can be simplified to:
def expect_fast_failure_from(double, *fail_with_args)
expect { yield double }.to fail_with(*fail_with_args)
reset double
end
@samphippen -- The problem isn't ree-specific. It's failing on MRI 1.8.7 for me. I'm realizing that the failure has to do with hash ordering -- on 1.9+ it's deterministic and this spec consistently passes, but on 1.8.7 the hash ordering is non-deterministic, and as a result, in
Note, however, that the message you are expecting ( I think you want something like this instead: it "reraises when an expectation is called out of order" do
with_unfulfilled_double do |dbl|
expect(dbl).to receive(:foo).ordered
expect(dbl).to receive(:bar).ordered
expect { dbl.bar }.to fail
dbl.foo # satisfy the `foo` expectation so that only the bar one fails below
expect { RSpec::Mocks.verify }.to fail_with(/received :bar out of order/)
end
end However, that doesn't pass on any ruby; instead we get:
...which is simply wrong. |
@myronmarston how's this looking? I went through the feedback and made edits. |
LGTM. Can you squash it into a smaller number of commits before merging? |
4266b76
to
f3f3d3f
Compare
f3f3d3f
to
fb2e0f1
Compare
Ensure negative expectations raise during RSpec verification time.
Specifically, in rspec#1826, we changed the conditional for whether or not to assign a generated description from RSpec::Matchers for examples with no doc string. Before rspec#1826, the conditional was: assign_generated_description if RSpec.configuration.expecting_with_rspec? In rspec#1826 it changed to: assign_generated_description if defined?(::RSpec::Matchers) We didn’t update the spec meant for that case to match, but it continued to pass (as a false positive) due to rspec/rspec-mocks#874. @samphippen’s fix in rspec/rspec-mocks#884 surfaced the issue (as the spec now failed) so I decided to improve the tests. - The spec now simulates the `RSpec::Matchers` constant being undefined to simulate the correct condition. We also have to prevent it from being autoloaded. - The cukes for minitest/test-unit did not sufficiently cover this case, because the aforementioned autoload would autoload RSpec::Matchers, so we have to simulate rspec-expectations being completely uninstalled. Then the cukes properly fail if we break the `if defined?(::RSpec::Matchers)` conditional.
During the execution of an example (as noted in #874) it is possible
for a framework that wraps a mocked object to catch the
RSpec::Mocks::MockExpectationError raised by a negative expectation. This
exception is thrown immediately and so if it is caught the running
example will pass.
This patch causes message expectations to also raise the same error at
verification time.
This commit does not have a passing spec suite, that is intentional. I
want to get feedback on the implementation before I go through all the
broken tests and fix them. The relevant new test can be found on lines
228 to 239 of receive_spec.rb