Skip to content

Commit

Permalink
adds Module#const_source_location specs
Browse files Browse the repository at this point in the history
  • Loading branch information
moofkit committed Nov 7, 2020
1 parent f975ca6 commit 9af4b34
Show file tree
Hide file tree
Showing 3 changed files with 246 additions and 7 deletions.
225 changes: 225 additions & 0 deletions core/module/const_source_location_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
require_relative '../../spec_helper'
require_relative '../../fixtures/constants'
require_relative 'fixtures/constants_autoload'

describe "Module#const_source_location" do
before do
@constants_fixture_path = File.expand_path('../../fixtures/constants.rb', __dir__)
end

ruby_version_is "2.7" do
describe "with dynamically assigned constants" do
it "searches a path in the immediate class or module first" do
ConstantSpecs::ClassA::CS_CONST301 = :const301_1
ConstantSpecs::ClassA.const_source_location(:CS_CONST301).should == [__FILE__, __LINE__ - 1]

ConstantSpecs::ModuleA::CS_CONST301 = :const301_2
ConstantSpecs::ModuleA.const_source_location(:CS_CONST301).should == [__FILE__, __LINE__ - 1]

ConstantSpecs::ParentA::CS_CONST301 = :const301_3
ConstantSpecs::ParentA.const_source_location(:CS_CONST301).should == [__FILE__, __LINE__ - 1]

ConstantSpecs::ContainerA::ChildA::CS_CONST301 = :const301_5
ConstantSpecs::ContainerA::ChildA.const_source_location(:CS_CONST301).should == [__FILE__, __LINE__ - 1]
end

it "searches a path in a module included in the immediate class before the superclass" do
ConstantSpecs::ParentB::CS_CONST302 = :const302_1
ConstantSpecs::ModuleF::CS_CONST302 = :const302_2
ConstantSpecs::ContainerB::ChildB.const_source_location(:CS_CONST302).should == [__FILE__, __LINE__ - 1]
end

it "searches a path in the superclass before a module included in the superclass" do
ConstantSpecs::ModuleE::CS_CONST303 = :const303_1
ConstantSpecs::ParentB::CS_CONST303 = :const303_2
ConstantSpecs::ContainerB::ChildB.const_source_location(:CS_CONST303).should == [__FILE__, __LINE__ - 1]
end

it "searches a path in a module included in the superclass" do
ConstantSpecs::ModuleA::CS_CONST304 = :const304_1
ConstantSpecs::ModuleE::CS_CONST304 = :const304_2
ConstantSpecs::ContainerB::ChildB.const_source_location(:CS_CONST304).should == [__FILE__, __LINE__ - 1]
end

it "searches a path in the superclass chain" do
ConstantSpecs::ModuleA::CS_CONST305 = :const305
ConstantSpecs::ContainerB::ChildB.const_source_location(:CS_CONST305).should == [__FILE__, __LINE__ - 1]
end

it "returns path to a toplevel constant when the receiver is a Class" do
Object::CS_CONST306 = :const306
ConstantSpecs::ContainerB::ChildB.const_source_location(:CS_CONST306).should == [__FILE__, __LINE__ - 1]
end

it "returns path to a toplevel constant when the receiver is a Module" do
Object::CS_CONST308 = :const308
ConstantSpecs.const_source_location(:CS_CONST308).should == [__FILE__, __LINE__ - 1]
ConstantSpecs::ModuleA.const_source_location(:CS_CONST308).should == [__FILE__, __LINE__ - 2]
end

it "returns path to the updated value of a constant" do
ConstantSpecs::ClassB::CS_CONST309 = :const309_1
ConstantSpecs::ClassB.const_source_location(:CS_CONST309).should == [__FILE__, __LINE__ - 1]

-> {
ConstantSpecs::ClassB::CS_CONST309 = :const309_2
}.should complain(/already initialized constant/)
ConstantSpecs::ClassB.const_source_location(:CS_CONST309).should == [__FILE__, __LINE__ - 2]
end
end

describe "with statically assigned constants" do
it "searches location path the immediate class or module first" do
ConstantSpecs::ClassA.const_source_location(:CS_CONST10).should == [@constants_fixture_path, ConstantSpecs::ClassA::CS_CONST10_LINE]
ConstantSpecs::ModuleA.const_source_location(:CS_CONST10).should == [@constants_fixture_path, ConstantSpecs::ModuleA::CS_CONST10_LINE]
ConstantSpecs::ParentA.const_source_location(:CS_CONST10).should == [@constants_fixture_path, ConstantSpecs::ParentA::CS_CONST10_LINE]
ConstantSpecs::ContainerA.const_source_location(:CS_CONST10).should == [@constants_fixture_path, ConstantSpecs::ContainerA::CS_CONST10_LINE]
ConstantSpecs::ContainerA::ChildA.const_source_location(:CS_CONST10).should == [@constants_fixture_path, ConstantSpecs::ContainerA::ChildA::CS_CONST10_LINE]
end

it "searches location path a module included in the immediate class before the superclass" do
ConstantSpecs::ContainerA::ChildA.const_source_location(:CS_CONST15).should == [@constants_fixture_path, ConstantSpecs::ModuleC::CS_CONST15_LINE]
end

it "searches location path the superclass before a module included in the superclass" do
ConstantSpecs::ContainerA::ChildA.const_source_location(:CS_CONST11).should == [@constants_fixture_path, ConstantSpecs::ParentA::CS_CONST11_LINE]
end

it "searches location path a module included in the superclass" do
ConstantSpecs::ContainerA::ChildA.const_source_location(:CS_CONST12).should == [@constants_fixture_path, ConstantSpecs::ModuleB::CS_CONST12_LINE]
end

it "searches location path the superclass chain" do
ConstantSpecs::ContainerA::ChildA.const_source_location(:CS_CONST13).should == [@constants_fixture_path, ConstantSpecs::ModuleA::CS_CONST13_LINE]
end

it "returns location path a toplevel constant when the receiver is a Class" do
ConstantSpecs::ContainerA::ChildA.const_source_location(:CS_CONST1).should == [@constants_fixture_path, CS_CONST1_LINE]
end

it "returns location path a toplevel constant when the receiver is a Module" do
ConstantSpecs.const_source_location(:CS_CONST1).should == [@constants_fixture_path, CS_CONST1_LINE]
ConstantSpecs::ModuleA.const_source_location(:CS_CONST1).should == [@constants_fixture_path, CS_CONST1_LINE]
end
end

it "return empty path if constant defined in C code" do
Object.const_source_location(:String).should == []
end

it "accepts a String or Symbol name" do
Object.const_source_location(:CS_CONST1).should == [@constants_fixture_path, CS_CONST1_LINE]
Object.const_source_location("CS_CONST1").should == [@constants_fixture_path, CS_CONST1_LINE]
end

it "returns nil if no constant is defined in the search path" do
ConstantSpecs.const_source_location(:CS_CONSTX).should == nil
end

it "raises a NameError if the name does not start with a capital letter" do
-> { ConstantSpecs.const_source_location "name" }.should raise_error(NameError)
end

it "raises a NameError if the name starts with a non-alphabetic character" do
-> { ConstantSpecs.const_source_location "__CONSTX__" }.should raise_error(NameError)
-> { ConstantSpecs.const_source_location "@CS_CONST1" }.should raise_error(NameError)
-> { ConstantSpecs.const_source_location "!CS_CONST1" }.should raise_error(NameError)
end

it "raises a NameError if the name contains non-alphabetic characters except '_'" do
Object.const_source_location("CS_CONST1").should == [@constants_fixture_path, CS_CONST1_LINE]
-> { ConstantSpecs.const_source_location "CS_CONST1=" }.should raise_error(NameError)
-> { ConstantSpecs.const_source_location "CS_CONST1?" }.should raise_error(NameError)
end

it "calls #to_str to convert the given name to a String" do
name = mock("ClassA")
name.should_receive(:to_str).and_return("ClassA")
ConstantSpecs.const_source_location(name).should == [@constants_fixture_path, ConstantSpecs::ClassA::CS_CLASS_A_LINE]
end

it "raises a TypeError if conversion to a String by calling #to_str fails" do
name = mock('123')
-> { ConstantSpecs.const_source_location(name) }.should raise_error(TypeError)

name.should_receive(:to_str).and_return(123)
-> { ConstantSpecs.const_source_location(name) }.should raise_error(TypeError)
end

it "does not search the singleton class of a Class or Module" do
ConstantSpecs::ContainerA::ChildA.const_source_location(:CS_CONST14).should == nil
ConstantSpecs.const_source_location(:CS_CONST14).should == nil
end

it "does not search the containing scope" do
ConstantSpecs::ContainerA::ChildA.const_source_location(:CS_CONST20).should == [@constants_fixture_path, ConstantSpecs::ParentA::CS_CONST20_LINE]
ConstantSpecs::ContainerA::ChildA.const_source_location(:CS_CONST5) == nil
end

it "returns nil if the constant is defined in the receiver's superclass and the inherit flag is false" do
ConstantSpecs::ContainerA::ChildA.const_source_location(:CS_CONST4, false).should == nil
end

it "searches into the receiver superclasses if the inherit flag is true" do
ConstantSpecs::ContainerA::ChildA.const_source_location(:CS_CONST4, true).should == [@constants_fixture_path, ConstantSpecs::ParentA::CS_CONST4_LINE]
end

it "returns nil when the receiver is a Module, the constant is defined at toplevel and the inherit flag is false" do
ConstantSpecs::ModuleA.const_source_location(:CS_CONST1, false).should == nil
end

it "returns nil when the receiver is a Class, the constant is defined at toplevel and the inherit flag is false" do
ConstantSpecs::ContainerA::ChildA.const_source_location(:CS_CONST1, false).should == nil
end

it "accepts a toplevel scope qualifier" do
ConstantSpecs.const_source_location("::CS_CONST1").should == [@constants_fixture_path, CS_CONST1_LINE]
end

it "accepts a scoped constant name" do
ConstantSpecs.const_source_location("ClassA::CS_CONST10").should == [@constants_fixture_path, ConstantSpecs::ClassA::CS_CONST10_LINE]
end

it "raises a NameError if the name includes two successive scope separators" do
-> { ConstantSpecs.const_source_location("ClassA::::CS_CONST10") }.should raise_error(NameError)
end

it "raises a NameError if only '::' is passed" do
-> { ConstantSpecs.const_source_location("::") }.should raise_error(NameError)
end

it "raises a NameError if a Symbol has a toplevel scope qualifier" do
-> { ConstantSpecs.const_source_location(:'::CS_CONST1') }.should raise_error(NameError)
end

it "raises a NameError if a Symbol is a scoped constant name" do
-> { ConstantSpecs.const_source_location(:'ClassA::CS_CONST10') }.should raise_error(NameError)
end

it "does search private constants path" do
ConstantSpecs.const_source_location(:CS_PRIVATE).should == [@constants_fixture_path, ConstantSpecs::CS_PRIVATE_LINE]
end

context 'autoload' do
before do
@constants_autoload_path = File.expand_path('fixtures/constants_autoload', __dir__)
end

it 'does search path in autoload declaration' do
Object.const_source_location('CSAutoloadA').should == [@constants_autoload_path + '_a.rb', 1]
end

it 'does search path in autoload declaration a constant with a toplevel scope qualifier' do
Object.const_source_location('::CSAutoloadB').should == [@constants_autoload_path + '_b.rb', 1]
end

it 'does search path in autoload declaration a module and resolve a constant within' do
Object.const_source_location('CSAutoloadC::CONST').should == [@constants_autoload_path + '_c.rb', 2]
end

it 'does autoload a non-toplevel module' do
Object.const_source_location('CSAutoloadD::InnerModule').should == [@constants_autoload_path + '_d.rb', 2]
end
end
end
end
10 changes: 5 additions & 5 deletions core/module/constants_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,31 +43,31 @@ class Module
describe "Module#constants" do
it "returns an array of Symbol names of all constants defined in the module and all included modules" do
ConstantSpecs::ContainerA.constants.sort.should == [
:CS_CONST10, :CS_CONST23, :CS_CONST24, :CS_CONST5, :ChildA
:CS_CONST10, :CS_CONST10_LINE, :CS_CONST23, :CS_CONST24, :CS_CONST5, :ChildA
]
end

it "returns all constants including inherited when passed true" do
ConstantSpecs::ContainerA.constants(true).sort.should == [
:CS_CONST10, :CS_CONST23, :CS_CONST24, :CS_CONST5, :ChildA
:CS_CONST10, :CS_CONST10_LINE, :CS_CONST23, :CS_CONST24, :CS_CONST5, :ChildA
]
end

it "returns all constants including inherited when passed some object" do
ConstantSpecs::ContainerA.constants(Object.new).sort.should == [
:CS_CONST10, :CS_CONST23, :CS_CONST24, :CS_CONST5, :ChildA
:CS_CONST10, :CS_CONST10_LINE, :CS_CONST23, :CS_CONST24, :CS_CONST5, :ChildA
]
end

it "doesn't returns inherited constants when passed false" do
ConstantSpecs::ContainerA.constants(false).sort.should == [
:CS_CONST10, :CS_CONST23, :CS_CONST5, :ChildA
:CS_CONST10, :CS_CONST10_LINE, :CS_CONST23, :CS_CONST5, :ChildA
]
end

it "doesn't returns inherited constants when passed nil" do
ConstantSpecs::ContainerA.constants(nil).sort.should == [
:CS_CONST10, :CS_CONST23, :CS_CONST5, :ChildA
:CS_CONST10, :CS_CONST10_LINE, :CS_CONST23, :CS_CONST5, :ChildA
]
end

Expand Down
18 changes: 16 additions & 2 deletions fixtures/constants.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Contains all static code examples of all constants behavior in language and
# library specs. The specs include language/constants_spec.rb and the specs
# for Module#const_defined?, Module#const_get, Module#const_set,
# Module#remove_const, Module#const_missing and Module#constants.
# for Module#const_defined?, Module#const_get, Module#const_set, Module#remove_const,
# Module#const_source_location, Module#const_missing and Module#constants.
#
# Rather than defining a class structure for each example, a canonical set of
# classes is used along with numerous constants, in most cases, a unique
Expand All @@ -28,14 +28,17 @@
# for completeness. No other constant of this name should be defined in the
# specs.
CS_CONST1 = :const1 # only defined here
CS_CONST1_LINE = __LINE__ - 1

module ConstantSpecs

# Included at toplevel
module ModuleA
CS_CONST10 = :const10_1
CS_CONST10_LINE = __LINE__ - 1
CS_CONST12 = :const12_2
CS_CONST13 = :const13
CS_CONST13_LINE = __LINE__ - 1
CS_CONST21 = :const21_2
end

Expand All @@ -44,12 +47,14 @@ module ModuleB
CS_CONST10 = :const10_9
CS_CONST11 = :const11_2
CS_CONST12 = :const12_1
CS_CONST12_LINE = __LINE__ - 1
end

# Included in ChildA
module ModuleC
CS_CONST10 = :const10_4
CS_CONST15 = :const15_1
CS_CONST15_LINE = __LINE__ - 1
end

# Included in ChildA metaclass
Expand All @@ -75,7 +80,9 @@ module ModuleD
# are run.

class ClassA
CS_CLASS_A_LINE = __LINE__ - 1
CS_CONST10 = :const10_10
CS_CONST10_LINE = __LINE__ - 1
CS_CONST16 = :const16
CS_CONST17 = :const17_2
CS_CONST22 = :const22_1
Expand All @@ -97,10 +104,14 @@ class ParentA
include ModuleB

CS_CONST4 = :const4
CS_CONST4_LINE = __LINE__ - 1
CS_CONST10 = :const10_5
CS_CONST10_LINE = __LINE__ - 1
CS_CONST11 = :const11_1
CS_CONST11_LINE = __LINE__ - 1
CS_CONST15 = :const15_2
CS_CONST20 = :const20_2
CS_CONST20_LINE = __LINE__ - 1
CS_CONST21 = :const21_1
CS_CONST22 = :const22_2

Expand All @@ -118,6 +129,7 @@ class ContainerA

CS_CONST5 = :const5
CS_CONST10 = :const10_2
CS_CONST10_LINE = __LINE__ - 1
CS_CONST23 = :const23

class ChildA < ParentA
Expand All @@ -135,6 +147,7 @@ def const19; CS_CONST19; end

CS_CONST6 = :const6
CS_CONST10 = :const10_3
CS_CONST10_LINE = __LINE__ - 1
CS_CONST19 = :const19_2

def self.const10; CS_CONST10; end
Expand Down Expand Up @@ -282,6 +295,7 @@ class ClassD < ClassC
end

CS_PRIVATE = :cs_private
CS_PRIVATE_LINE = __LINE__ - 1
private_constant :CS_PRIVATE
end

Expand Down

0 comments on commit 9af4b34

Please sign in to comment.