-
Notifications
You must be signed in to change notification settings - Fork 65
Protect mkspec against accessing SortedSet without the gem #78
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
Conversation
f213c44 to
04727d6
Compare
lib/mspec/utils/name_map.rb
Outdated
| return nil unless Module === const | ||
|
|
||
| filtered = @filter && EXCLUDED.include?(const.name) | ||
| return const if Module === const and !filtered | ||
| rescue NameError | ||
| return const unless filtered | ||
| rescue NameError, RuntimeError | ||
| # Either the constant doesn't exist or it is | ||
| # explicitly raising an error, like `SortedSet`. |
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, this can be reorganized using rescue-else to preserve nesting. Wow, that's a rare feature to remember.
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.
Option 1:
def class_or_module(c)
const = Object.const_get(c, false)
return nil unless Module === const
filtered = @filter && EXCLUDED.include?(const.name)
return const unless filtered
rescue NameError, RuntimeError
# Either the constant doesn't exist or it is
# explicitly raising an error, like `SortedSet`.
endOption 2:
def class_or_module(c)
const = Object.const_get(c, false)
rescue NameError, RuntimeError
# Either the constant doesn't exist or it is
# explicitly raising an error, like `SortedSet`.
else
return nil unless Module === const
filtered = @filter && EXCLUDED.include?(const.name)
return const unless filtered
endThere 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.
Option 2 looks OK, but I'm not sure it works on Ruby 2.6 (which mspec still supports for now).
Also some early Ruby implementations might not support such a fancy method-level rescue/else, but this matters less for mkspec as it's meant primarily to be run on CRuby.
I would go with just being simple and explicit:
def class_or_module(c)
begin
const = Object.const_get(c, false)
rescue NameError, RuntimeError
# Either the constant doesn't exist or it is
# explicitly raising an error, like `SortedSet`.
end
return nil unless Module === const
filtered = @filter && EXCLUDED.include?(const.name)
return const unless filtered
end| end | ||
|
|
||
| it "returns nil if accessing the constant raises RuntimeError" do | ||
| expect { NameMapSpecs::BadFile }.to raise_error(RuntimeError) |
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 remove this line?
The issue is after triggering the autoload, given that it fails, the constant might be removed or be in a weird state, so it's better to only trigger the autoload once from class_or_module->const_get.
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 completely understand the reservation but I'm reluctant to remove it. Without it the test can easily regress to any of the other cases which return nil.
Actually, there is a test for this exact situtation, so a broken constant would fail the spec.
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 completely understand the reservation but I'm reluctant to remove it. Without it the test can easily regress to any of the other cases which return
nil.
How could it? The autoload clearly raises the error.
Actually, there is a test for this exact situtation, so a broken constant would fail the spec.
Yes, but that has been the subject of discussions on the CRuby tracker and there have been changes in this area, there used to be "undefined constants" for example.
I suppose one way to meet both of our concerns is to just test both cases, I'll add a second test which doesn't trigger the autoload first.
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.
How could it? The autoload clearly raises the error.
I'm worried that in the case someone changes the tests, it would be very easy to break this one accidentally. A typo, removing the autoload, or removing raise in the file would all cause the method to return nil, so without the sanity check the test would pass.
Yes, but that has been the subject of discussions on the CRuby tracker and there have been changes in this area, there used to be "undefined constants" for example.
Oh, that's concerning. To be fair, I also intuitively expected that the constant would be in a strange state, but current behavior is clearly superior.
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.
without the sanity check the test would pass.
We could have something like expect(NameMapSpecs.autoload?(:BadFile).to be_a(String) or so, that checks the autoload is registered at least and there is no typo in the constant name.
04727d6 to
7b0a156
Compare
|
Thank you for the PR! |
Closes #77.
Special handling for
SortedSetitself would probably be wrong, as it may genuinely be available, possibly as a gem. Other constants may also raise in the future.Per suggestion, I tried to reduce scope of
rescue, but it made the method very annoying to look at. However, it was rescuingNoMethodErrorwhich is not obvious, so I added an extra return if the constant is not a Module.