-
-
Notifications
You must be signed in to change notification settings - Fork 752
Wonky constant resolution on 1.9.2 combined w/ our example group constant assignment can lead to users getting unexpected constants #1697
Description
I just discovered a problem with our example group on 1.9.2 (and only 1.9.2). Ruby has two ways to resolve constants; lexically, by moving up the open module and class definitions (as identified by Module.nesting) and by inheritance, by moving up the ancestor chain. Ruby 1.9.2 seems to resolve to an inherited constant before a lexical one when using instance_eval, and this has some unfortunate consequences.
Example script:
module RSpec
module ExampleGroups
class Nested
File = Module.new
end
end
end
RSpec::ExampleGroups::Nested.new.instance_eval do
puts File.name
endOn ruby 1.8.7, 1.9.3, and 2.0+, this prints File. On 1.9.2, it prints RSpec::ExampleGroups::Nested::File. This can manifest itself by causing a user's reference to File (which they intend to be ::File) to resolve to RSpec::ExampleGroups::Nested::File, which can cause all sorts of problems.
I think the best/simplest fix is to append _ to each of the generated example group const names on 1.9.2. For this specific case, the example group constant would be RSpec::ExampleGroups::Nested_::File_ which would not conflict with ::File. Of course, there could still be a top level ::File_ constant but that's unlikely. Given that this behavior is limited to 1.9.2 and is apparently a bug in 1.9.2 (given that it's the only version like this) I think it doesn't make sense to put a more complicated full solution in place that would totally prevent the possibility.
Unfortunately, I've not been able to write a failing spec for this and I'm not sure why. Here's what I tried (this would go in the describe "constant naming" context in example_group_spec.rb):
it 'does not cause problems when users reference a top level constant of the same name' do
file_in_outer_group = file_in_inner_group = file_in_inner_example = file_in_outer_example = nil
RSpec.describe "A group containing a nested `File` group" do
describe "File" do
file_in_inner_group = File
example { file_in_inner_example = File }
end
file_in_outer_group = File
example { file_in_outer_example = File }
end.run
expect(file_in_inner_group).to be(::File)
expect(file_in_outer_group).to be(::File)
expect(file_in_inner_example).to be(::File)
expect(file_in_outer_example).to be(::File)
endThis passes on 1.9.2 even though I've observed the problem in a real project (VCR) and also in the standalone script from above. I've also tried this simpler spec:
context "within a nested `File` group" do
describe "File" do
specify "`File` still resolves to ::File" do
expect(File).to be(::File)
end
end
end...but it passes, too. I can't figure out why these are passing on 1.9.2. Anyone want to take a stab at this one?