Skip to content
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

Give correct error message for expect {}.not_to raise_error(/foo/) #1456

Merged

Conversation

nevinera
Copy link
Contributor

@nevinera nevinera commented May 8, 2024

Found this while adding coverage - there was a test that ought to have covered the line in question, but didn't because it was actually slightly too loose. Tightening it up shows that it's getting the wrong error message, because the RaiseError matcher is handling expected_error_or_message by checking the type, and it's looking for String, but not Regexp there.

Figured it'd make more sense as its own PR, since it's not just test coverage. (The line is covered afterwards though!)

Failure with just the test change:

  1) expect { ... }.not_to raise_error(/message/) issues a warning
     Failure/Error: expect { raise 'blarg' }.not_to raise_error(/blah/)

       Kernel received :warn with unexpected arguments
         expected: (match /not_to raise_error\(message\)` risks false positives/ and match /Called from \/Users\/ericmueller\/src\/rspec-expectations\/spec\/rspec\/matchers\/built_in\/raise_error_spec.rb:309/)
              got: ("WARNING: Using `expect { }.not_to raise_error(SpecificErrorClass)` risks false positives, since lite...ons/spec/rspec/matchers/built_in/raise_error_spec.rb:309:in `block (2 levels) in <top (required)>'.")
     # ./spec/rspec/matchers/built_in/raise_error_spec.rb:309:in `block (2 levels) in <top (required)>'

@nevinera nevinera marked this pull request as ready for review May 8, 2024 14:51
@nevinera nevinera mentioned this pull request May 8, 2024
Copy link
Member

@pirj pirj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No objections on the changes, just a few notes to spark a discussion on if this can be further improved. Partially caused by the lack of morning coffee.

spec/rspec/matchers/built_in/raise_error_spec.rb Outdated Show resolved Hide resolved
spec/rspec/matchers/built_in/raise_error_spec.rb Outdated Show resolved Hide resolved
expect { raise 'blarg' }.not_to raise_error("blah")
end

it "can supresses the warning when configured to do so", :warn_about_potential_false_positives do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering why ‘warn_about_potential_false_positives’ metadata is needed here to warn, but it warns anyway in other examples? Is it to restore the setting to the default? It just reads confusingly when used this way.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that we do the same in other examples in this file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't know about this until you asked (and I wasn't sure until I tested it out), but adding a shared context like this one (with a tag specified) causes that context to be automatically included in any context that supplies that same tag. So including this metadata causes an automatic cleanup to occur. I, of course, had just copied it from other tests without thinking :-)

lib/rspec/matchers/built_in/raise_error.rb Show resolved Hide resolved
@@ -25,7 +25,7 @@ def initialize(expected_error_or_message, expected_message, &block)
when nil, UndefinedValue
@expected_error = Exception
@expected_message = expected_message
when String
when String, Regexp
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any change in behaviour?
Is looks like that previously a regexp would have been used as an exception?

This change indeed seems the right thing to do. I’m wondering, why it worked the same way before?

Is it because we still were tolerant to whatever it is an exception or something matcheable with a string message?

if values_match?(@expected_error, @actual_error) ||
               values_match?(@expected_error, actual_error_message)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So.. there is a change in "behavior", but it pretty much just affects what failure messages come out. These two ivars are really only used for that purpose, and aren't exposed externally, thankfully; the only impact I can find is that a slightly wrong failure message gets generated (it acts as though you supplied an exception if you supplied only a regex). Could have had larger impact, but the main place we look at the @expected_error, we only check @expected_error != Exception, which is not true for Regexps either (though I suppose if you supplied to raise_error(Exception, /foo/), you might also be able to get some wrong behavior?).

And the reason the specs didn't mind was just that the case checking this had a test that only checked the part of the failure message the outcomes had in common ("risks false positives"), which was still being produced.

Copy link
Member

@pirj pirj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

RSpec.describe "expect { ... }.not_to raise_error(/message/)" do
it "issues a warning when configured to do so", :warn_about_potential_false_positives do
RSpec::Expectations.configuration.warn_about_potential_false_positives = true
expect_warning_with_call_site __FILE__, __LINE__+1, /not_to raise_error\(message\)` risks false positives/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to dissect it. Before the change, this was printing a slightly different warning:

Using `expect { }.not_to raise_error(SpecificErrorClass)` risks false positives

and now it's a more appropriate for not_to raise_error(/regexp/):

Using `expect { }.not_to raise_error(message)` risks false positives

My apologies that it took that long to figure out.

nevinera and others added 5 commits May 11, 2024 10:51
And split off a pair for the String case (versus the Regexp case, since
that was the critical difference in the code here)
If the first argument is a Regexp, it's a message _matcher_, rather than
a message.. but certainly it's not an exception! I'm actually a bit
surprised this doesn't cause any other problems, but it doesn't seem to.
this confused us at a glance, and it probably will again later
@pirj pirj force-pushed the nev--give-correct-message-for-regex-not-raise-case branch from f85ca83 to 32ea28c Compare May 11, 2024 07:54
@pirj pirj merged commit 261d7de into rspec:main May 11, 2024
29 of 30 checks passed
JonRowe pushed a commit that referenced this pull request Jun 13, 2024
…regex-not-raise-case

Give correct error message for `expect {}.not_to raise_error(/foo/)`
@JonRowe
Copy link
Member

JonRowe commented Jun 13, 2024

Released in 3.13.1

@nevinera nevinera deleted the nev--give-correct-message-for-regex-not-raise-case branch June 13, 2024 15:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants