Skip to content
This repository was archived by the owner on Nov 30, 2024. It is now read-only.
This repository was archived by the owner on Nov 30, 2024. It is now read-only.

Wonky constant resolution on 1.9.2 combined w/ our example group constant assignment can lead to users getting unexpected constants #1697

@myronmarston

Description

@myronmarston

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
end

On 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)
end

This 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?

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions