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

adds Module#const_source_location specs #815

Merged
merged 1 commit into from
Nov 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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