diff options
Diffstat (limited to 'spec/ruby/core/module')
64 files changed, 3335 insertions, 683 deletions
diff --git a/spec/ruby/core/module/alias_method_spec.rb b/spec/ruby/core/module/alias_method_spec.rb index 742e289a3f..c36dedd2d8 100644 --- a/spec/ruby/core/module/alias_method_spec.rb +++ b/spec/ruby/core/module/alias_method_spec.rb @@ -81,12 +81,21 @@ describe "Module#alias_method" do -> { @class.make_alias mock('x'), :public_one }.should raise_error(TypeError) end + it "raises a NoMethodError if the given name raises a NoMethodError during type coercion using to_str" do + obj = mock("mock-name") + obj.should_receive(:to_str).and_raise(NoMethodError) + -> { @class.make_alias obj, :public_one }.should raise_error(NoMethodError) + end + it "is a public method" do Module.should have_public_instance_method(:alias_method, false) end - it "returns self" do - @class.send(:alias_method, :checking_return_value, :public_one).should equal(@class) + describe "returned value" do + it "returns symbol of the defined method name" do + @class.send(:alias_method, :checking_return_value, :public_one).should equal(:checking_return_value) + @class.send(:alias_method, 'checking_return_value', :public_one).should equal(:checking_return_value) + end end it "works in module" do diff --git a/spec/ruby/core/module/allocate_spec.rb b/spec/ruby/core/module/allocate_spec.rb deleted file mode 100644 index 3b2c4119c3..0000000000 --- a/spec/ruby/core/module/allocate_spec.rb +++ /dev/null @@ -1,14 +0,0 @@ -require_relative '../../spec_helper' - -describe "Module.allocate" do - it "returns an instance of Module" do - mod = Module.allocate - mod.should be_an_instance_of(Module) - end - - it "returns a fully-formed instance of Module" do - mod = Module.allocate - mod.constants.should_not == nil - mod.methods.should_not == nil - end -end diff --git a/spec/ruby/core/module/ancestors_spec.rb b/spec/ruby/core/module/ancestors_spec.rb index 5e4c196206..90c26941d1 100644 --- a/spec/ruby/core/module/ancestors_spec.rb +++ b/spec/ruby/core/module/ancestors_spec.rb @@ -7,10 +7,17 @@ describe "Module#ancestors" do ModuleSpecs.ancestors.should == [ModuleSpecs] ModuleSpecs::Basic.ancestors.should == [ModuleSpecs::Basic] ModuleSpecs::Super.ancestors.should == [ModuleSpecs::Super, ModuleSpecs::Basic] - ModuleSpecs.without_test_modules(ModuleSpecs::Parent.ancestors).should == - [ModuleSpecs::Parent, Object, Kernel, BasicObject] - ModuleSpecs.without_test_modules(ModuleSpecs::Child.ancestors).should == - [ModuleSpecs::Child, ModuleSpecs::Super, ModuleSpecs::Basic, ModuleSpecs::Parent, Object, Kernel, BasicObject] + if defined?(Ruby::Box) && Ruby::Box.enabled? + ModuleSpecs.without_test_modules(ModuleSpecs::Parent.ancestors).should == + [ModuleSpecs::Parent, Object, Ruby::Box::Loader, Kernel, BasicObject] + ModuleSpecs.without_test_modules(ModuleSpecs::Child.ancestors).should == + [ModuleSpecs::Child, ModuleSpecs::Super, ModuleSpecs::Basic, ModuleSpecs::Parent, Object, Ruby::Box::Loader, Kernel, BasicObject] + else + ModuleSpecs.without_test_modules(ModuleSpecs::Parent.ancestors).should == + [ModuleSpecs::Parent, Object, Kernel, BasicObject] + ModuleSpecs.without_test_modules(ModuleSpecs::Child.ancestors).should == + [ModuleSpecs::Child, ModuleSpecs::Super, ModuleSpecs::Basic, ModuleSpecs::Parent, Object, Kernel, BasicObject] + end end it "returns only modules and classes" do @@ -21,6 +28,17 @@ describe "Module#ancestors" do ModuleSpecs::Parent.ancestors.should == ModuleSpecs::Parent.ancestors.uniq end + it "returns a module that is included later into a nested module as well" do + m1 = Module.new + m2 = Module.new + m3 = Module.new do + include m2 + end + m2.include m1 # should be after m3 includes m2 + + m3.ancestors.should == [m3, m2, m1] + end + describe "when called on a singleton class" do it "includes the singleton classes of ancestors" do parent = Class.new diff --git a/spec/ruby/core/module/append_features_spec.rb b/spec/ruby/core/module/append_features_spec.rb index d960798eef..1724cde5d6 100644 --- a/spec/ruby/core/module/append_features_spec.rb +++ b/spec/ruby/core/module/append_features_spec.rb @@ -47,20 +47,6 @@ describe "Module#append_features" do end - ruby_version_is ''...'2.7' do - it "copies own tainted status to the given module" do - other = Module.new - Module.new.taint.send :append_features, other - other.tainted?.should be_true - end - - it "copies own untrusted status to the given module" do - other = Module.new - Module.new.untrust.send :append_features, other - other.untrusted?.should be_true - end - end - describe "when other is frozen" do before :each do @receiver = Module.new diff --git a/spec/ruby/core/module/attr_accessor_spec.rb b/spec/ruby/core/module/attr_accessor_spec.rb index 6a749341be..503dccc61e 100644 --- a/spec/ruby/core/module/attr_accessor_spec.rb +++ b/spec/ruby/core/module/attr_accessor_spec.rb @@ -1,5 +1,6 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' +require_relative 'shared/attr_added' describe "Module#attr_accessor" do it "creates a getter and setter for each given attribute name" do @@ -33,10 +34,23 @@ describe "Module#attr_accessor" do attr_accessor :spec_attr_accessor end - -> { true.spec_attr_accessor = "a" }.should raise_error(RuntimeError) + -> { true.spec_attr_accessor = "a" }.should raise_error(FrozenError) end - it "converts non string/symbol/fixnum names to strings using to_str" do + it "raises FrozenError if the receiver if frozen" do + c = Class.new do + attr_accessor :foo + end + obj = c.new + obj.foo = 1 + obj.foo.should == 1 + + obj.freeze + -> { obj.foo = 42 }.should raise_error(FrozenError) + obj.foo.should == 1 + end + + it "converts non string/symbol names to strings using to_str" do (o = mock('test')).should_receive(:to_str).any_number_of_times.and_return("test") c = Class.new do attr_accessor o @@ -67,19 +81,25 @@ describe "Module#attr_accessor" do Module.should have_public_instance_method(:attr_accessor, false) end + it "returns an array of defined method names as symbols" do + Class.new do + (attr_accessor :foo, 'bar').should == [:foo, :foo=, :bar, :bar=] + end + end + describe "on immediates" do before :each do - class Fixnum + class Integer attr_accessor :foobar end end after :each do - if Fixnum.method_defined?(:foobar) - Fixnum.send(:remove_method, :foobar) + if Integer.method_defined?(:foobar) + Integer.send(:remove_method, :foobar) end - if Fixnum.method_defined?(:foobar=) - Fixnum.send(:remove_method, :foobar=) + if Integer.method_defined?(:foobar=) + Integer.send(:remove_method, :foobar=) end end @@ -87,4 +107,6 @@ describe "Module#attr_accessor" do 1.foobar.should be_nil end end + + it_behaves_like :module_attr_added, :attr_accessor end diff --git a/spec/ruby/core/module/attr_reader_spec.rb b/spec/ruby/core/module/attr_reader_spec.rb index 238e3db9ea..37fd537ff5 100644 --- a/spec/ruby/core/module/attr_reader_spec.rb +++ b/spec/ruby/core/module/attr_reader_spec.rb @@ -1,5 +1,6 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' +require_relative 'shared/attr_added' describe "Module#attr_reader" do it "creates a getter for each given attribute name" do @@ -32,7 +33,7 @@ describe "Module#attr_reader" do -> { true.instance_variable_set("@spec_attr_reader", "a") }.should raise_error(RuntimeError) end - it "converts non string/symbol/fixnum names to strings using to_str" do + it "converts non string/symbol names to strings using to_str" do (o = mock('test')).should_receive(:to_str).any_number_of_times.and_return("test") c = Class.new do attr_reader o @@ -61,4 +62,12 @@ describe "Module#attr_reader" do it "is a public method" do Module.should have_public_instance_method(:attr_reader, false) end + + it "returns an array of defined method names as symbols" do + Class.new do + (attr_reader :foo, 'bar').should == [:foo, :bar] + end + end + + it_behaves_like :module_attr_added, :attr_reader end diff --git a/spec/ruby/core/module/attr_spec.rb b/spec/ruby/core/module/attr_spec.rb index 8b91e77658..2f9f4e26dc 100644 --- a/spec/ruby/core/module/attr_spec.rb +++ b/spec/ruby/core/module/attr_spec.rb @@ -1,5 +1,6 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' +require_relative 'shared/attr_added' describe "Module#attr" do before :each do @@ -124,7 +125,7 @@ describe "Module#attr" do -> { c.new.bar }.should raise_error(NoMethodError) end - it "converts non string/symbol/fixnum names to strings using to_str" do + it "converts non string/symbol names to strings using to_str" do (o = mock('test')).should_receive(:to_str).any_number_of_times.and_return("test") Class.new { attr o }.new.respond_to?("test").should == true end @@ -145,4 +146,14 @@ describe "Module#attr" do it "is a public method" do Module.should have_public_instance_method(:attr, false) end + + it "returns an array of defined method names as symbols" do + Class.new do + (attr :foo, 'bar').should == [:foo, :bar] + (attr :baz, false).should == [:baz] + (attr :qux, true).should == [:qux, :qux=] + end + end + + it_behaves_like :module_attr_added, :attr end diff --git a/spec/ruby/core/module/attr_writer_spec.rb b/spec/ruby/core/module/attr_writer_spec.rb index e4b193a9d8..5b863ef88c 100644 --- a/spec/ruby/core/module/attr_writer_spec.rb +++ b/spec/ruby/core/module/attr_writer_spec.rb @@ -1,5 +1,6 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' +require_relative 'shared/attr_added' describe "Module#attr_writer" do it "creates a setter for each given attribute name" do @@ -29,10 +30,20 @@ describe "Module#attr_writer" do attr_writer :spec_attr_writer end - -> { true.spec_attr_writer = "a" }.should raise_error(RuntimeError) + -> { true.spec_attr_writer = "a" }.should raise_error(FrozenError) end - it "converts non string/symbol/fixnum names to strings using to_str" do + it "raises FrozenError if the receiver if frozen" do + c = Class.new do + attr_writer :foo + end + obj = c.new + obj.freeze + + -> { obj.foo = 42 }.should raise_error(FrozenError) + end + + it "converts non string/symbol names to strings using to_str" do (o = mock('test')).should_receive(:to_str).any_number_of_times.and_return("test") c = Class.new do attr_writer o @@ -61,4 +72,12 @@ describe "Module#attr_writer" do it "is a public method" do Module.should have_public_instance_method(:attr_writer, false) end + + it "returns an array of defined method names as symbols" do + Class.new do + (attr_writer :foo, 'bar').should == [:foo=, :bar=] + end + end + + it_behaves_like :module_attr_added, :attr_writer end diff --git a/spec/ruby/core/module/autoload_spec.rb b/spec/ruby/core/module/autoload_spec.rb index 85a0d067d1..625d945686 100644 --- a/spec/ruby/core/module/autoload_spec.rb +++ b/spec/ruby/core/module/autoload_spec.rb @@ -1,6 +1,6 @@ require_relative '../../spec_helper' +require_relative '../../fixtures/code_loading' require_relative 'fixtures/classes' -require 'thread' describe "Module#autoload?" do it "returns the name of the file that will be autoloaded" do @@ -17,30 +17,21 @@ describe "Module#autoload?" do ModuleSpecs::Autoload::Child.autoload?(:AnotherAutoload).should == "another_autoload.rb" end - ruby_version_is "2.7" do - it "returns nil if an ancestor defined that autoload but recursion is disabled" do - ModuleSpecs::Autoload::Parent.autoload :InheritedAutoload, "inherited_autoload.rb" - ModuleSpecs::Autoload::Child.autoload?(:InheritedAutoload, false).should be_nil - end + it "returns nil if an ancestor defined that autoload but recursion is disabled" do + ModuleSpecs::Autoload::Parent.autoload :InheritedAutoload, "inherited_autoload.rb" + ModuleSpecs::Autoload::Child.autoload?(:InheritedAutoload, false).should be_nil + end - it "returns the name of the file that will be loaded if recursion is disabled but the autoload is defined on the class itself" do - ModuleSpecs::Autoload::Child.autoload :ChildAutoload, "child_autoload.rb" - ModuleSpecs::Autoload::Child.autoload?(:ChildAutoload, false).should == "child_autoload.rb" - end + it "returns the name of the file that will be loaded if recursion is disabled but the autoload is defined on the class itself" do + ModuleSpecs::Autoload::Child.autoload :ChildAutoload, "child_autoload.rb" + ModuleSpecs::Autoload::Child.autoload?(:ChildAutoload, false).should == "child_autoload.rb" end end describe "Module#autoload" do before :all do @non_existent = fixture __FILE__, "no_autoload.rb" - - # Require RubyGems eagerly, to ensure #require is already the RubyGems - # version, before starting #autoload specs which snapshot #require, and - # could end up redefining #require as the original core Kernel#require. - begin - require "rubygems" - rescue LoadError - end + CodeLoadingSpecs.preload_rubygems end before :each do @@ -210,6 +201,13 @@ describe "Module#autoload" do ModuleSpecs::Autoload.use_ex1.should == :good end + it "considers an autoload constant as loaded when autoload is called for/from the current file" do + filename = fixture(__FILE__, "autoload_during_require_current_file.rb") + require filename + + ScratchPad.recorded.should be_nil + end + describe "interacting with defined?" do it "does not load the file when referring to the constant in defined?" do module ModuleSpecs::Autoload::Dog @@ -344,6 +342,29 @@ describe "Module#autoload" do end end + def check_before_during_thread_after(const, &check) + before = check.call + to_autoload_thread, from_autoload_thread = Queue.new, Queue.new + ScratchPad.record -> { + from_autoload_thread.push check.call + to_autoload_thread.pop + } + t = Thread.new { + in_loading_thread = from_autoload_thread.pop + in_other_thread = check.call + to_autoload_thread.push :done + [in_loading_thread, in_other_thread] + } + in_loading_thread, in_other_thread = nil + begin + ModuleSpecs::Autoload.const_get(const) + ensure + in_loading_thread, in_other_thread = t.value + end + after = check.call + [before, in_loading_thread, in_other_thread, after] + end + describe "during the autoload before the constant is assigned" do before :each do @path = fixture(__FILE__, "autoload_during_autoload.rb") @@ -352,58 +373,83 @@ describe "Module#autoload" do raise unless ModuleSpecs::Autoload.autoload?(:DuringAutoload) == @path end - def check_before_during_thread_after(&check) - before = check.call - to_autoload_thread, from_autoload_thread = Queue.new, Queue.new - ScratchPad.record -> { - from_autoload_thread.push check.call - to_autoload_thread.pop - } - t = Thread.new { - in_loading_thread = from_autoload_thread.pop - in_other_thread = check.call - to_autoload_thread.push :done - [in_loading_thread, in_other_thread] - } - in_loading_thread, in_other_thread = nil - begin - ModuleSpecs::Autoload::DuringAutoload - ensure - in_loading_thread, in_other_thread = t.value - end - after = check.call - [before, in_loading_thread, in_other_thread, after] - end - it "returns nil in autoload thread and 'constant' otherwise for defined?" do - results = check_before_during_thread_after { + results = check_before_during_thread_after(:DuringAutoload) { defined?(ModuleSpecs::Autoload::DuringAutoload) } results.should == ['constant', nil, 'constant', 'constant'] end it "keeps the constant in Module#constants" do - results = check_before_during_thread_after { + results = check_before_during_thread_after(:DuringAutoload) { ModuleSpecs::Autoload.constants(false).include?(:DuringAutoload) } results.should == [true, true, true, true] end it "returns false in autoload thread and true otherwise for Module#const_defined?" do - results = check_before_during_thread_after { + results = check_before_during_thread_after(:DuringAutoload) { ModuleSpecs::Autoload.const_defined?(:DuringAutoload, false) } results.should == [true, false, true, true] end it "returns nil in autoload thread and returns the path in other threads for Module#autoload?" do - results = check_before_during_thread_after { + results = check_before_during_thread_after(:DuringAutoload) { ModuleSpecs::Autoload.autoload?(:DuringAutoload) } results.should == [@path, nil, @path, nil] end end + describe "during the autoload after the constant is assigned" do + before :each do + @path = fixture(__FILE__, "autoload_during_autoload_after_define.rb") + ModuleSpecs::Autoload.autoload :DuringAutoloadAfterDefine, @path + @autoload_location = [__FILE__, __LINE__ - 1] + @const_location = [@path, 2] + @remove << :DuringAutoloadAfterDefine + raise unless ModuleSpecs::Autoload.autoload?(:DuringAutoloadAfterDefine) == @path + end + + it "returns 'constant' in both threads" do + results = check_before_during_thread_after(:DuringAutoloadAfterDefine) { + defined?(ModuleSpecs::Autoload::DuringAutoloadAfterDefine) + } + results.should == ['constant', 'constant', 'constant', 'constant'] + end + + it "Module#constants include the autoloaded in both threads" do + results = check_before_during_thread_after(:DuringAutoloadAfterDefine) { + ModuleSpecs::Autoload.constants(false).include?(:DuringAutoloadAfterDefine) + } + results.should == [true, true, true, true] + end + + it "Module#const_defined? returns true in both threads" do + results = check_before_during_thread_after(:DuringAutoloadAfterDefine) { + ModuleSpecs::Autoload.const_defined?(:DuringAutoloadAfterDefine, false) + } + results.should == [true, true, true, true] + end + + it "returns nil in autoload thread and returns the path in other threads for Module#autoload?" do + results = check_before_during_thread_after(:DuringAutoloadAfterDefine) { + ModuleSpecs::Autoload.autoload?(:DuringAutoloadAfterDefine) + } + results.should == [@path, nil, @path, nil] + end + + ruby_bug("#20188", ""..."3.4") do + it "returns the real constant location in autoload thread and returns the autoload location in other threads for Module#const_source_location" do + results = check_before_during_thread_after(:DuringAutoloadAfterDefine) { + ModuleSpecs::Autoload.const_source_location(:DuringAutoloadAfterDefine) + } + results.should == [@autoload_location, @const_location, @autoload_location, @const_location] + end + end + end + it "does not remove the constant from Module#constants if load fails and keeps it as an autoload" do ModuleSpecs::Autoload.autoload :Fail, @non_existent @@ -440,7 +486,7 @@ describe "Module#autoload" do ScratchPad.recorded.should == [:raise, :raise] end - it "does not remove the constant from Module#constants if the loaded file does not define it, but leaves it as 'undefined'" do + it "removes the constant from Module#constants if the loaded file does not define it" do path = fixture(__FILE__, "autoload_o.rb") ScratchPad.record [] ModuleSpecs::Autoload.autoload :O, path @@ -451,8 +497,8 @@ describe "Module#autoload" do -> { ModuleSpecs::Autoload::O }.should raise_error(NameError) - ModuleSpecs::Autoload.should have_constant(:O) ModuleSpecs::Autoload.const_defined?(:O).should == false + ModuleSpecs::Autoload.should_not have_constant(:O) ModuleSpecs::Autoload.autoload?(:O).should == nil -> { ModuleSpecs::Autoload.const_get(:O) }.should raise_error(NameError) end @@ -492,9 +538,7 @@ describe "Module#autoload" do it "does not load the file when accessing the constants table of the module" do ModuleSpecs::Autoload.autoload :P, @non_existent ModuleSpecs::Autoload.const_defined?(:P).should be_true - ruby_bug "[Bug #15780]", ""..."2.7" do - ModuleSpecs::Autoload.const_defined?("P").should be_true - end + ModuleSpecs::Autoload.const_defined?("P").should be_true end it "loads the file when opening a module that is the autoloaded constant" do @@ -553,14 +597,41 @@ describe "Module#autoload" do # Basically, the parent autoload constant remains in a "undefined" state self.autoload?(:DeclaredInParentDefinedInCurrent).should == nil const_defined?(:DeclaredInParentDefinedInCurrent).should == false - self.should have_constant(:DeclaredInParentDefinedInCurrent) -> { DeclaredInParentDefinedInCurrent }.should raise_error(NameError) ModuleSpecs::Autoload::LexicalScope.send(:remove_const, :DeclaredInParentDefinedInCurrent) end end - it "and fails when finding the undefined autoload constant in the current scope when declared in current and defined in parent" do + it "warns once in verbose mode if the constant was defined in a parent scope" do + ScratchPad.record -> { + ModuleSpecs::DeclaredInCurrentDefinedInParent = :declared_in_current_defined_in_parent + } + + module ModuleSpecs + module Autoload + autoload :DeclaredInCurrentDefinedInParent, fixture(__FILE__, "autoload_callback.rb") + self.autoload?(:DeclaredInCurrentDefinedInParent).should == fixture(__FILE__, "autoload_callback.rb") + const_defined?(:DeclaredInCurrentDefinedInParent).should == true + + -> { + DeclaredInCurrentDefinedInParent + }.should complain( + /Expected .*autoload_callback.rb to define ModuleSpecs::Autoload::DeclaredInCurrentDefinedInParent but it didn't/, + verbose: true, + ) + + -> { + DeclaredInCurrentDefinedInParent + }.should_not complain(/.*/, verbose: true) + self.autoload?(:DeclaredInCurrentDefinedInParent).should == nil + const_defined?(:DeclaredInCurrentDefinedInParent).should == false + ModuleSpecs.const_defined?(:DeclaredInCurrentDefinedInParent).should == true + end + end + end + + it "looks up in parent scope after failed autoload" do @remove << :DeclaredInCurrentDefinedInParent module ModuleSpecs::Autoload ScratchPad.record -> { @@ -569,11 +640,10 @@ describe "Module#autoload" do class LexicalScope autoload :DeclaredInCurrentDefinedInParent, fixture(__FILE__, "autoload_callback.rb") - -> { DeclaredInCurrentDefinedInParent }.should raise_error(NameError) + -> { DeclaredInCurrentDefinedInParent }.should_not raise_error(NameError) # Basically, the autoload constant remains in a "undefined" state self.autoload?(:DeclaredInCurrentDefinedInParent).should == nil const_defined?(:DeclaredInCurrentDefinedInParent).should == false - self.should have_constant(:DeclaredInCurrentDefinedInParent) -> { const_get(:DeclaredInCurrentDefinedInParent) }.should raise_error(NameError) end @@ -648,6 +718,21 @@ describe "Module#autoload" do end end + it "should trigger the autoload when using `private_constant`" do + @remove << :DynClass + module ModuleSpecs::Autoload + autoload :DynClass, fixture(__FILE__, "autoload_c.rb") + private_constant :DynClass + + ScratchPad.recorded.should be_nil + + DynClass::C.new.loaded.should == :dynclass_c + ScratchPad.recorded.should == :loaded + end + + -> { ModuleSpecs::Autoload::DynClass }.should raise_error(NameError, /private constant/) + end + # [ruby-core:19127] [ruby-core:29941] it "does NOT raise a NameError when the autoload file did not define the constant and a module is opened with the same name" do module ModuleSpecs::Autoload diff --git a/spec/ruby/core/module/class_variable_defined_spec.rb b/spec/ruby/core/module/class_variable_defined_spec.rb index f867d383c3..c0f2072a37 100644 --- a/spec/ruby/core/module/class_variable_defined_spec.rb +++ b/spec/ruby/core/module/class_variable_defined_spec.rb @@ -51,7 +51,7 @@ describe "Module#class_variable_defined?" do }.should raise_error(NameError) end - it "converts a non string/symbol/fixnum name to string using to_str" do + it "converts a non string/symbol name to string using to_str" do c = Class.new { class_variable_set :@@class_var, "test" } (o = mock('@@class_var')).should_receive(:to_str).and_return("@@class_var") c.class_variable_defined?(o).should == true diff --git a/spec/ruby/core/module/class_variable_get_spec.rb b/spec/ruby/core/module/class_variable_get_spec.rb index 79d22a506b..e5d06731ec 100644 --- a/spec/ruby/core/module/class_variable_get_spec.rb +++ b/spec/ruby/core/module/class_variable_get_spec.rb @@ -60,7 +60,7 @@ describe "Module#class_variable_get" do -> { c.send(:class_variable_get, "@invalid_name") }.should raise_error(NameError) end - it "converts a non string/symbol/fixnum name to string using to_str" do + it "converts a non string/symbol name to string using to_str" do c = Class.new { class_variable_set :@@class_var, "test" } (o = mock('@@class_var')).should_receive(:to_str).and_return("@@class_var") c.send(:class_variable_get, o).should == "test" diff --git a/spec/ruby/core/module/class_variable_set_spec.rb b/spec/ruby/core/module/class_variable_set_spec.rb index 85759a29d1..63f32f5389 100644 --- a/spec/ruby/core/module/class_variable_set_spec.rb +++ b/spec/ruby/core/module/class_variable_set_spec.rb @@ -45,7 +45,7 @@ describe "Module#class_variable_set" do }.should raise_error(NameError) end - it "converts a non string/symbol/fixnum name to string using to_str" do + it "converts a non string/symbol name to string using to_str" do (o = mock('@@class_var')).should_receive(:to_str).and_return("@@class_var") c = Class.new c.send(:class_variable_set, o, "test") diff --git a/spec/ruby/core/module/class_variables_spec.rb b/spec/ruby/core/module/class_variables_spec.rb index fd7aa93aa8..e155f1deac 100644 --- a/spec/ruby/core/module/class_variables_spec.rb +++ b/spec/ruby/core/module/class_variables_spec.rb @@ -23,4 +23,12 @@ describe "Module#class_variables" do c.extend ModuleSpecs::MVars c.class_variables.should_not include(:@@mvar) end + + it "returns the correct class variables when inherit is given" do + ModuleSpecs::SubCVars.class_variables(false).should == [:@@sub] + ModuleSpecs::SubCVars.new.singleton_class.class_variables(false).should == [] + + ModuleSpecs::SubCVars.class_variables(true).should == [:@@sub, :@@cls, :@@meta] + ModuleSpecs::SubCVars.new.singleton_class.class_variables(true).should == [:@@sub, :@@cls, :@@meta] + end end diff --git a/spec/ruby/core/module/const_added_spec.rb b/spec/ruby/core/module/const_added_spec.rb new file mode 100644 index 0000000000..90cd36551a --- /dev/null +++ b/spec/ruby/core/module/const_added_spec.rb @@ -0,0 +1,238 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'fixtures/const_added' + +describe "Module#const_added" do + it "is a private instance method" do + Module.should have_private_instance_method(:const_added) + end + + it "returns nil in the default implementation" do + Module.new do + const_added(:TEST).should == nil + end + end + + it "for a class defined with the `class` keyword, const_added runs before inherited" do + ScratchPad.record [] + + mod = Module.new do + def self.const_added(_) + ScratchPad << :const_added + end + end + + parent = Class.new do + def self.inherited(_) + ScratchPad << :inherited + end + end + + class mod::C < parent; end + + ScratchPad.recorded.should == [:const_added, :inherited] + end + + it "the superclass of a class assigned to a constant is set before const_added is called" do + ScratchPad.record [] + + parent = Class.new do + def self.const_added(name) + ScratchPad << name + ScratchPad << const_get(name).superclass + end + end + + class parent::C < parent; end + + ScratchPad.recorded.should == [:C, parent] + end + + it "is called when a new constant is assigned on self" do + ScratchPad.record [] + + mod = Module.new do + def self.const_added(name) + ScratchPad << name + end + end + + mod.module_eval(<<-RUBY, __FILE__, __LINE__ + 1) + TEST = 1 + RUBY + + ScratchPad.recorded.should == [:TEST] + end + + it "is called when a new constant is assigned on self through const_set" do + ScratchPad.record [] + + mod = Module.new do + def self.const_added(name) + ScratchPad << name + end + end + + mod.const_set(:TEST, 1) + + ScratchPad.recorded.should == [:TEST] + end + + it "is called when a new module is defined under self" do + ScratchPad.record [] + + mod = Module.new do + def self.const_added(name) + ScratchPad << name + end + end + + mod.module_eval(<<-RUBY, __FILE__, __LINE__ + 1) + module SubModule + end + + module SubModule + end + RUBY + + ScratchPad.recorded.should == [:SubModule] + end + + it "is called when a new module is defined under a named module (assigned to a constant)" do + ScratchPad.record [] + + ModuleSpecs::ConstAddedSpecs::NamedModule = Module.new do + def self.const_added(name) + ScratchPad << name + end + + module self::A + def self.const_added(name) + ScratchPad << name + end + + module self::B + end + end + end + + ScratchPad.recorded.should == [:A, :B] + ModuleSpecs::ConstAddedSpecs.send :remove_const, :NamedModule + end + + it "is called when a new class is defined under self" do + ScratchPad.record [] + + mod = Module.new do + def self.const_added(name) + ScratchPad << name + end + end + + mod.module_eval(<<-RUBY, __FILE__, __LINE__ + 1) + class SubClass + end + + class SubClass + end + RUBY + + ScratchPad.recorded.should == [:SubClass] + end + + it "is called when a new class is defined under a named module (assigned to a constant)" do + ScratchPad.record [] + + ModuleSpecs::ConstAddedSpecs::NamedModuleB = Module.new do + def self.const_added(name) + ScratchPad << name + end + + class self::A + def self.const_added(name) + ScratchPad << name + end + + class self::B + end + end + end + + ScratchPad.recorded.should == [:A, :B] + ModuleSpecs::ConstAddedSpecs.send :remove_const, :NamedModuleB + end + + it "is called when an autoload is defined" do + ScratchPad.record [] + + mod = Module.new do + def self.const_added(name) + ScratchPad << name + end + end + + mod.autoload :Autoload, "foo" + ScratchPad.recorded.should == [:Autoload] + end + + it "is called with a precise caller location with the line of definition" do + ScratchPad.record [] + + mod = Module.new do + def self.const_added(name) + location = caller_locations(1, 1)[0] + ScratchPad << location.lineno + end + end + + line = __LINE__ + mod.module_eval(<<-RUBY, __FILE__, __LINE__ + 1) + TEST = 1 + + module SubModule + end + + class SubClass + end + RUBY + + mod.const_set(:CONST_SET, 1) + + ScratchPad.recorded.should == [line + 2, line + 4, line + 7, line + 11] + end + + it "is called when the constant is already assigned a value" do + ScratchPad.record [] + + mod = Module.new do + def self.const_added(name) + ScratchPad.record const_get(name) + end + end + + mod.module_eval(<<-RUBY, __FILE__, __LINE__ + 1) + TEST = 123 + RUBY + + ScratchPad.recorded.should == 123 + end + + it "records re-definition of existing constants" do + ScratchPad.record [] + + mod = Module.new do + def self.const_added(name) + ScratchPad << const_get(name) + end + end + + -> { + mod.module_eval(<<-RUBY, __FILE__, __LINE__ + 1) + TEST = 123 + TEST = 456 + RUBY + }.should complain(/warning: already initialized constant .+::TEST/) + + ScratchPad.recorded.should == [123, 456] + end +end diff --git a/spec/ruby/core/module/const_defined_spec.rb b/spec/ruby/core/module/const_defined_spec.rb index 0673adca3d..8b137cd134 100644 --- a/spec/ruby/core/module/const_defined_spec.rb +++ b/spec/ruby/core/module/const_defined_spec.rb @@ -17,11 +17,16 @@ describe "Module#const_defined?" do ConstantSpecs::ContainerA::ChildA.const_defined?(:CS_CONST4).should be_true end - it "returns true if the constant is defined in a mixed-in module of the receiver" do + it "returns true if the constant is defined in a mixed-in module of the receiver's parent" do # CS_CONST10 is defined in a module included by ChildA ConstantSpecs::ContainerA::ChildA.const_defined?(:CS_CONST10).should be_true end + it "returns true if the constant is defined in a mixed-in module (with prepends) of the receiver" do + # CS_CONST11 is defined in the module included by ContainerPrepend + ConstantSpecs::ContainerPrepend.const_defined?(:CS_CONST11).should be_true + end + it "returns true if the constant is defined in Object and the receiver is a module" do # CS_CONST1 is defined in Object ConstantSpecs::ModuleA.const_defined?(:CS_CONST1).should be_true @@ -40,6 +45,11 @@ describe "Module#const_defined?" do ConstantSpecs::ContainerA::ChildA.const_defined?(:CS_CONST4, true).should be_true end + it "coerces the inherit flag to a boolean" do + ConstantSpecs::ContainerA::ChildA.const_defined?(:CS_CONST4, nil).should be_false + ConstantSpecs::ContainerA::ChildA.const_defined?(:CS_CONST4, :true).should be_true + end + it "returns true if the given String names a constant defined in the receiver" do ConstantSpecs.const_defined?("CS_CONST2").should == true ConstantSpecs.const_defined?("ModuleA").should == true @@ -55,6 +65,8 @@ describe "Module#const_defined?" do str = "CS_CONSTλ".encode("euc-jp") ConstantSpecs.const_set str, 1 ConstantSpecs.const_defined?(str).should be_true + ensure + ConstantSpecs.send(:remove_const, str) end it "returns false if the constant is not defined in the receiver, its superclass, or any included modules" do @@ -70,10 +82,23 @@ describe "Module#const_defined?" do ConstantSpecs::ClassA.const_defined?(:CS_CONSTX).should == false 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_defined?(name).should == true + describe "converts the given name to a String using #to_str" do + 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_defined?(name).should == true + end + + it "raises a TypeError if the given name can't be converted to a String" do + -> { ConstantSpecs.const_defined?(nil) }.should raise_error(TypeError) + -> { ConstantSpecs.const_defined?([]) }.should raise_error(TypeError) + end + + it "raises a NoMethodError if the given argument raises a NoMethodError during type coercion to a String" do + name = mock("classA") + name.should_receive(:to_str).and_raise(NoMethodError) + -> { ConstantSpecs.const_defined?(name) }.should raise_error(NoMethodError) + end end it "special cases Object and checks it's included Modules" do diff --git a/spec/ruby/core/module/const_get_spec.rb b/spec/ruby/core/module/const_get_spec.rb index 0a6702903f..4b53cbe7b3 100644 --- a/spec/ruby/core/module/const_get_spec.rb +++ b/spec/ruby/core/module/const_get_spec.rb @@ -88,10 +88,28 @@ describe "Module#const_get" do end.should raise_error(NameError) end + it "coerces the inherit flag to a boolean" do + ConstantSpecs::ContainerA::ChildA.const_get(:CS_CONST4, :true).should == :const4 + + -> do + ConstantSpecs::ContainerA::ChildA.const_get(:CS_CONST1, nil) + end.should raise_error(NameError) + end + it "accepts a toplevel scope qualifier" do ConstantSpecs.const_get("::CS_CONST1").should == :const1 end + it "accepts a toplevel scope qualifier when inherit is false" do + ConstantSpecs.const_get("::CS_CONST1", false).should == :const1 + -> { ConstantSpecs.const_get("CS_CONST1", false) }.should raise_error(NameError) + end + + it "returns a constant whose module is defined the toplevel" do + ConstantSpecs.const_get("ConstantSpecsTwo::Foo").should == :cs_two_foo + ConstantSpecsThree.const_get("ConstantSpecsTwo::Foo").should == :cs_three_foo + end + it "accepts a scoped constant name" do ConstantSpecs.const_get("ClassA::CS_CONST10").should == :const10_10 end @@ -113,7 +131,7 @@ describe "Module#const_get" do end it "does read private constants" do - ConstantSpecs.const_get(:CS_PRIVATE).should == :cs_private + ConstantSpecs.const_get(:CS_PRIVATE).should == :cs_private end it 'does autoload a constant' do @@ -132,6 +150,10 @@ describe "Module#const_get" do Object.const_get('CSAutoloadD::InnerModule').name.should == 'CSAutoloadD::InnerModule' end + it "raises a NameError when the nested constant does not exist on the module but exists in Object" do + -> { Object.const_get('ConstantSpecs::CS_CONST1') }.should raise_error(NameError) + end + describe "with statically assigned constants" do it "searches the immediate class or module first" do ConstantSpecs::ClassA.const_get(:CS_CONST10).should == :const10_10 @@ -180,40 +202,60 @@ describe "Module#const_get" do ConstantSpecs::ContainerA::ChildA::CS_CONST301 = :const301_5 ConstantSpecs::ContainerA::ChildA.const_get(:CS_CONST301).should == :const301_5 + ensure + ConstantSpecs::ClassA.send(:remove_const, :CS_CONST301) + ConstantSpecs::ModuleA.send(:remove_const, :CS_CONST301) + ConstantSpecs::ParentA.send(:remove_const, :CS_CONST301) + ConstantSpecs::ContainerA::ChildA.send(:remove_const, :CS_CONST301) end it "searches 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_get(:CS_CONST302).should == :const302_2 + ensure + ConstantSpecs::ParentB.send(:remove_const, :CS_CONST302) + ConstantSpecs::ModuleF.send(:remove_const, :CS_CONST302) end it "searches 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_get(:CS_CONST303).should == :const303_2 + ensure + ConstantSpecs::ModuleE.send(:remove_const, :CS_CONST303) + ConstantSpecs::ParentB.send(:remove_const, :CS_CONST303) end it "searches a module included in the superclass" do ConstantSpecs::ModuleA::CS_CONST304 = :const304_1 ConstantSpecs::ModuleE::CS_CONST304 = :const304_2 ConstantSpecs::ContainerB::ChildB.const_get(:CS_CONST304).should == :const304_2 + ensure + ConstantSpecs::ModuleA.send(:remove_const, :CS_CONST304) + ConstantSpecs::ModuleE.send(:remove_const, :CS_CONST304) end it "searches the superclass chain" do ConstantSpecs::ModuleA::CS_CONST305 = :const305 ConstantSpecs::ContainerB::ChildB.const_get(:CS_CONST305).should == :const305 + ensure + ConstantSpecs::ModuleA.send(:remove_const, :CS_CONST305) end it "returns a toplevel constant when the receiver is a Class" do Object::CS_CONST306 = :const306 ConstantSpecs::ContainerB::ChildB.const_get(:CS_CONST306).should == :const306 + ensure + Object.send(:remove_const, :CS_CONST306) end it "returns a toplevel constant when the receiver is a Module" do Object::CS_CONST308 = :const308 ConstantSpecs.const_get(:CS_CONST308).should == :const308 ConstantSpecs::ModuleA.const_get(:CS_CONST308).should == :const308 + ensure + Object.send(:remove_const, :CS_CONST308) end it "returns the updated value of a constant" do @@ -224,6 +266,8 @@ describe "Module#const_get" do ConstantSpecs::ClassB::CS_CONST309 = :const309_2 }.should complain(/already initialized constant/) ConstantSpecs::ClassB.const_get(:CS_CONST309).should == :const309_2 + ensure + ConstantSpecs::ClassB.send(:remove_const, :CS_CONST309) end end end diff --git a/spec/ruby/core/module/const_set_spec.rb b/spec/ruby/core/module/const_set_spec.rb index 77a2e59a13..823768b882 100644 --- a/spec/ruby/core/module/const_set_spec.rb +++ b/spec/ruby/core/module/const_set_spec.rb @@ -8,22 +8,29 @@ describe "Module#const_set" do ConstantSpecs.const_set "CS_CONST402", :const402 ConstantSpecs.const_get(:CS_CONST402).should == :const402 + ensure + ConstantSpecs.send(:remove_const, :CS_CONST401) + ConstantSpecs.send(:remove_const, :CS_CONST402) end it "returns the value set" do ConstantSpecs.const_set(:CS_CONST403, :const403).should == :const403 + ensure + ConstantSpecs.send(:remove_const, :CS_CONST403) end it "sets the name of an anonymous module" do m = Module.new ConstantSpecs.const_set(:CS_CONST1000, m) m.name.should == "ConstantSpecs::CS_CONST1000" + ensure + ConstantSpecs.send(:remove_const, :CS_CONST1000) end - it "does not set the name of a module scoped by an anonymous module" do + it "sets the name of a module scoped by an anonymous module" do a, b = Module.new, Module.new a.const_set :B, b - b.name.should be_nil + b.name.should.end_with? '::B' end it "sets the name of contained modules when assigning a toplevel anonymous module" do @@ -38,6 +45,8 @@ describe "Module#const_set" do b.name.should == "ModuleSpecs_CS3::B" c.name.should == "ModuleSpecs_CS3::B::C" d.name.should == "ModuleSpecs_CS3::D" + ensure + Object.send(:remove_const, :ModuleSpecs_CS3) end it "raises a NameError if the name does not start with a capital letter" do @@ -55,6 +64,8 @@ describe "Module#const_set" do ConstantSpecs.const_set("CS_CONST404", :const404).should == :const404 -> { ConstantSpecs.const_set "Name=", 1 }.should raise_error(NameError) -> { ConstantSpecs.const_set "Name?", 1 }.should raise_error(NameError) + ensure + ConstantSpecs.send(:remove_const, :CS_CONST404) end it "calls #to_str to convert the given name to a String" do @@ -62,6 +73,8 @@ describe "Module#const_set" do name.should_receive(:to_str).and_return("CS_CONST405") ConstantSpecs.const_set(name, :const405).should == :const405 ConstantSpecs::CS_CONST405.should == :const405 + ensure + ConstantSpecs.send(:remove_const, :CS_CONST405) end it "raises a TypeError if conversion to a String by calling #to_str fails" do @@ -91,7 +104,7 @@ describe "Module#const_set" do mod.const_get(:Foo).should == 1 end - it "does not warn if the previous value was undefined" do + it "does not warn after a failed autoload" do path = fixture(__FILE__, "autoload_o.rb") ScratchPad.record [] mod = Module.new @@ -99,7 +112,6 @@ describe "Module#const_set" do mod.autoload :Foo, path -> { mod::Foo }.should raise_error(NameError) - mod.should have_constant(:Foo) mod.const_defined?(:Foo).should == false mod.autoload?(:Foo).should == nil diff --git a/spec/ruby/core/module/const_source_location_spec.rb b/spec/ruby/core/module/const_source_location_spec.rb new file mode 100644 index 0000000000..96649ea10b --- /dev/null +++ b/spec/ruby/core/module/const_source_location_spec.rb @@ -0,0 +1,281 @@ +require_relative '../../spec_helper' +require_relative '../../fixtures/constants' + +describe "Module#const_source_location" do + before do + @constants_fixture_path = File.expand_path('../../fixtures/constants.rb', __dir__) + end + + describe "with dynamically assigned constants" do + it "searches a path in the immediate class or module first" do + ConstantSpecs::ClassA::CSL_CONST301 = :const301_1 + ConstantSpecs::ClassA.const_source_location(:CSL_CONST301).should == [__FILE__, __LINE__ - 1] + + ConstantSpecs::ModuleA::CSL_CONST301 = :const301_2 + ConstantSpecs::ModuleA.const_source_location(:CSL_CONST301).should == [__FILE__, __LINE__ - 1] + + ConstantSpecs::ParentA::CSL_CONST301 = :const301_3 + ConstantSpecs::ParentA.const_source_location(:CSL_CONST301).should == [__FILE__, __LINE__ - 1] + + ConstantSpecs::ContainerA::ChildA::CSL_CONST301 = :const301_5 + ConstantSpecs::ContainerA::ChildA.const_source_location(:CSL_CONST301).should == [__FILE__, __LINE__ - 1] + ensure + ConstantSpecs::ClassA.send(:remove_const, :CSL_CONST301) + ConstantSpecs::ModuleA.send(:remove_const, :CSL_CONST301) + ConstantSpecs::ParentA.send(:remove_const, :CSL_CONST301) + ConstantSpecs::ContainerA::ChildA.send(:remove_const, :CSL_CONST301) + end + + it "searches a path in a module included in the immediate class before the superclass" do + ConstantSpecs::ParentB::CSL_CONST302 = :const302_1 + ConstantSpecs::ModuleF::CSL_CONST302 = :const302_2 + ConstantSpecs::ContainerB::ChildB.const_source_location(:CSL_CONST302).should == [__FILE__, __LINE__ - 1] + ensure + ConstantSpecs::ParentB.send(:remove_const, :CSL_CONST302) + ConstantSpecs::ModuleF.send(:remove_const, :CSL_CONST302) + end + + it "searches a path in the superclass before a module included in the superclass" do + ConstantSpecs::ModuleE::CSL_CONST303 = :const303_1 + ConstantSpecs::ParentB::CSL_CONST303 = :const303_2 + ConstantSpecs::ContainerB::ChildB.const_source_location(:CSL_CONST303).should == [__FILE__, __LINE__ - 1] + ensure + ConstantSpecs::ModuleE.send(:remove_const, :CSL_CONST303) + ConstantSpecs::ParentB.send(:remove_const, :CSL_CONST303) + end + + it "searches a path in a module included in the superclass" do + ConstantSpecs::ModuleA::CSL_CONST304 = :const304_1 + ConstantSpecs::ModuleE::CSL_CONST304 = :const304_2 + ConstantSpecs::ContainerB::ChildB.const_source_location(:CSL_CONST304).should == [__FILE__, __LINE__ - 1] + ensure + ConstantSpecs::ModuleA.send(:remove_const, :CSL_CONST304) + ConstantSpecs::ModuleE.send(:remove_const, :CSL_CONST304) + end + + it "searches a path in the superclass chain" do + ConstantSpecs::ModuleA::CSL_CONST305 = :const305 + ConstantSpecs::ContainerB::ChildB.const_source_location(:CSL_CONST305).should == [__FILE__, __LINE__ - 1] + ensure + ConstantSpecs::ModuleA.send(:remove_const, :CSL_CONST305) + end + + it "returns path to a toplevel constant when the receiver is a Class" do + Object::CSL_CONST306 = :const306 + ConstantSpecs::ContainerB::ChildB.const_source_location(:CSL_CONST306).should == [__FILE__, __LINE__ - 1] + ensure + Object.send(:remove_const, :CSL_CONST306) + end + + it "returns path to a toplevel constant when the receiver is a Module" do + Object::CSL_CONST308 = :const308 + ConstantSpecs.const_source_location(:CSL_CONST308).should == [__FILE__, __LINE__ - 1] + ConstantSpecs::ModuleA.const_source_location(:CSL_CONST308).should == [__FILE__, __LINE__ - 2] + ensure + Object.send(:remove_const, :CSL_CONST308) + end + + it "returns path to the updated value of a constant" do + ConstantSpecs::ClassB::CSL_CONST309 = :const309_1 + ConstantSpecs::ClassB.const_source_location(:CSL_CONST309).should == [__FILE__, __LINE__ - 1] + + -> { + ConstantSpecs::ClassB::CSL_CONST309 = :const309_2 + }.should complain(/already initialized constant/) + ConstantSpecs::ClassB.const_source_location(:CSL_CONST309).should == [__FILE__, __LINE__ - 2] + ensure + ConstantSpecs::ClassB.send(:remove_const, :CSL_CONST309) + end + end + + describe "with statically assigned constants" do + it "works for the module and class keywords" do + ConstantSpecs.const_source_location(:ModuleB).should == [@constants_fixture_path, ConstantSpecs::ModuleB::LINE] + ConstantSpecs.const_source_location(:ClassA).should == [@constants_fixture_path, ConstantSpecs::ClassA::LINE] + end + + 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::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 "returns updated location from const_set" do + mod = Module.new + const_line = __LINE__ + 1 + mod.const_set :Foo, 1 + mod.const_source_location(:Foo).should == [__FILE__, const_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 + + it "works for eval with a given line" do + c = Class.new do + eval('self::C = 1', nil, "foo", 100) + end + c.const_source_location(:C).should == ["foo", 100] + end + + context 'autoload' do + before :all do + ConstantSpecs.autoload :CSL_CONST1, "#{__dir__}/notexisting.rb" + @line = __LINE__ - 1 + end + + before :each do + @loaded_features = $".dup + end + + after :each do + $".replace @loaded_features + end + + it 'returns the autoload location while not resolved' do + ConstantSpecs.const_source_location('CSL_CONST1').should == [__FILE__, @line] + end + + it 'returns where the constant was resolved when resolved' do + file = fixture(__FILE__, 'autoload_location.rb') + ConstantSpecs.autoload :CONST_LOCATION, file + line = ConstantSpecs::CONST_LOCATION + ConstantSpecs.const_source_location('CONST_LOCATION').should == [file, line] + end + + ruby_bug("#20188", ""..."3.4") do + it 'returns the real constant location as soon as it is defined' do + file = fixture(__FILE__, 'autoload_const_source_location.rb') + ConstantSpecs.autoload :ConstSource, file + autoload_location = [__FILE__, __LINE__ - 1] + + ConstantSpecs.const_source_location(:ConstSource).should == autoload_location + ConstantSpecs::ConstSource::LOCATION.should == ConstantSpecs.const_source_location(:ConstSource) + ConstantSpecs::BEFORE_DEFINE_LOCATION.should == autoload_location + ConstantSpecs.send :remove_const, :ConstSource + ConstantSpecs.send :remove_const, :BEFORE_DEFINE_LOCATION + end + end + end +end diff --git a/spec/ruby/core/module/constants_spec.rb b/spec/ruby/core/module/constants_spec.rb index 4538e828dd..330da1cc88 100644 --- a/spec/ruby/core/module/constants_spec.rb +++ b/spec/ruby/core/module/constants_spec.rb @@ -13,9 +13,9 @@ describe "Module.constants" do it "returns an array of Symbol names" do # This in NOT an exhaustive list - Module.constants.should include(:Array, :Bignum, :Class, :Comparable, :Dir, + Module.constants.should include(:Array, :Class, :Comparable, :Dir, :Enumerable, :ENV, :Exception, :FalseClass, - :File, :Fixnum, :Float, :Hash, :Integer, :IO, + :File, :Float, :Hash, :Integer, :IO, :Kernel, :Math, :Method, :Module, :NilClass, :Numeric, :Object, :Range, :Regexp, :String, :Symbol, :Thread, :Time, :TrueClass) @@ -43,37 +43,43 @@ end 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 it "returns only public constants" do ModuleSpecs::PrivConstModule.constants.should == [:PUBLIC_CONSTANT] end + + it "returns only constants starting with an uppercase letter" do + # e.g. fatal, IO::generic_readable and IO::generic_writable should not be returned by Module#constants + Object.constants.each { |c| c[0].should == c[0].upcase } + IO.constants.each { |c| c[0].should == c[0].upcase } + end end describe "Module#constants" do diff --git a/spec/ruby/core/module/define_method_spec.rb b/spec/ruby/core/module/define_method_spec.rb index 49472c18e1..c5dfc53764 100644 --- a/spec/ruby/core/module/define_method_spec.rb +++ b/spec/ruby/core/module/define_method_spec.rb @@ -88,6 +88,23 @@ describe "Module#define_method when given an UnboundMethod" do end end +describe "Module#define_method" do + describe "when the default definee is not the same as the module" do + it "sets the visibility of the method to public" do + klass = Class.new + class << klass + private + define_method(:meta) do + define_method(:foo) { :foo } + end + end + + klass.send :meta + klass.new.foo.should == :foo + end + end +end + describe "Module#define_method when name is not a special private name" do describe "given an UnboundMethod" do describe "and called from the target module" do @@ -116,6 +133,17 @@ describe "Module#define_method when name is not a special private name" do klass.should have_public_instance_method(:baz) end end + + it "sets the method owner for a dynamically added method with a different original owner" do + mixin_module = Module.new do + def bar; end + end + + foo = Object.new + foo.singleton_class.define_method(:bar, mixin_module.instance_method(:bar)) + + foo.method(:bar).owner.should == foo.singleton_class + end end describe "passed a block" do @@ -202,18 +230,55 @@ describe "Module#define_method" do o.block_test2.should == o end + it "raises TypeError if name cannot converted to String" do + -> { + Class.new { define_method(1001, -> {}) } + }.should raise_error(TypeError, /is not a symbol nor a string/) + + -> { + Class.new { define_method([], -> {}) } + }.should raise_error(TypeError, /is not a symbol nor a string/) + end + + it "converts non-String name to String with #to_str" do + obj = Object.new + def obj.to_str() "foo" end + + new_class = Class.new { define_method(obj, -> { :called }) } + new_class.new.foo.should == :called + end + + it "raises TypeError when #to_str called on non-String name returns non-String value" do + obj = Object.new + def obj.to_str() [] end + + -> { + Class.new { define_method(obj, -> {}) } + }.should raise_error(TypeError, /can't convert Object to String/) + end + it "raises a TypeError when the given method is no Method/Proc" do -> { Class.new { define_method(:test, "self") } - }.should raise_error(TypeError) + }.should raise_error(TypeError, "wrong argument type String (expected Proc/Method/UnboundMethod)") -> { Class.new { define_method(:test, 1234) } - }.should raise_error(TypeError) + }.should raise_error(TypeError, "wrong argument type Integer (expected Proc/Method/UnboundMethod)") -> { Class.new { define_method(:test, nil) } - }.should raise_error(TypeError) + }.should raise_error(TypeError, "wrong argument type NilClass (expected Proc/Method/UnboundMethod)") + end + + it "uses provided Method/Proc even if block is specified" do + new_class = Class.new do + define_method(:test, -> { :method_is_called }) do + :block_is_called + end + end + + new_class.new.test.should == :method_is_called end it "raises an ArgumentError when no block is given" do @@ -333,6 +398,14 @@ describe "Module#define_method" do object2.other_cool_method.should == "data is foo" end + it "accepts a proc from a Symbol" do + symbol_proc = :+.to_proc + klass = Class.new do + define_method :foo, &symbol_proc + end + klass.new.foo(1, 2).should == 3 + end + it "maintains the Proc's scope" do class DefineMethodByProcClass in_scope = true @@ -403,6 +476,9 @@ describe "Module#define_method" do ChildClass = Class.new(ParentClass) { define_method(:foo) { :baz } } ParentClass.send :define_method, :foo, ChildClass.instance_method(:foo) }.should raise_error(TypeError, /bind argument must be a subclass of ChildClass/) + ensure + Object.send(:remove_const, :ParentClass) + Object.send(:remove_const, :ChildClass) end it "raises a TypeError when an UnboundMethod from one class is defined on an unrelated class" do @@ -426,6 +502,33 @@ describe "Module#define_method" do Class.new { define_method :bar, m } }.should raise_error(TypeError, /can't bind singleton method to a different class/) end + + it "defines a new method with public visibility when a Method passed and the class/module of the context isn't equal to the receiver of #define_method" do + c = Class.new do + private def foo + "public" + end + end + + object = c.new + object.singleton_class.define_method(:bar, object.method(:foo)) + + object.bar.should == "public" + end + + it "defines the new method according to the scope visibility when a Method passed and the class/module of the context is equal to the receiver of #define_method" do + c = Class.new do + def foo; end + end + + object = c.new + object.singleton_class.class_eval do + private + define_method(:bar, c.new.method(:foo)) + end + + -> { object.bar }.should raise_error(NoMethodError) + end end describe "Module#define_method" do @@ -491,7 +594,36 @@ describe "Module#define_method" do it "receives the value passed as the argument when passed one argument" do @klass.new.m(1).should == 1 end + end + + describe "passed { |a,| } creates a method that" do + before :each do + @klass = Class.new do + define_method(:m) { |a,| a } + end + end + + it "raises an ArgumentError when passed zero arguments" do + -> { @klass.new.m }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError when passed zero arguments and a block" do + -> { @klass.new.m { :computed } }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError when passed two arguments" do + -> { @klass.new.m 1, 2 }.should raise_error(ArgumentError) + end + + it "receives the value passed as the argument when passed one argument" do + @klass.new.m(1).should == 1 + end + it "does not destructure the passed argument" do + @klass.new.m([1, 2]).should == [1, 2] + # for comparison: + proc { |a,| a }.call([1, 2]).should == 1 + end end describe "passed { |*a| } creates a method that" do @@ -595,7 +727,7 @@ describe "Module#define_method" do end end -describe "Method#define_method when passed a Method object" do +describe "Module#define_method when passed a Method object" do before :each do @klass = Class.new do def m(a, b, *c) @@ -620,7 +752,7 @@ describe "Method#define_method when passed a Method object" do end end -describe "Method#define_method when passed an UnboundMethod object" do +describe "Module#define_method when passed an UnboundMethod object" do before :each do @klass = Class.new do def m(a, b, *c) @@ -645,7 +777,7 @@ describe "Method#define_method when passed an UnboundMethod object" do end end -describe "Method#define_method when passed a Proc object" do +describe "Module#define_method when passed a Proc object" do describe "and a method is defined inside" do it "defines the nested method in the default definee where the Proc was created" do prc = nil @@ -669,3 +801,46 @@ describe "Method#define_method when passed a Proc object" do end end end + +describe "Module#define_method when passed a block" do + describe "behaves exactly like a lambda" do + it "for return" do + Class.new do + define_method(:foo) do + return 42 + end + end.new.foo.should == 42 + end + + it "for break" do + Class.new do + define_method(:foo) do + break 42 + end + end.new.foo.should == 42 + end + + it "for next" do + Class.new do + define_method(:foo) do + next 42 + end + end.new.foo.should == 42 + end + + it "for redo" do + Class.new do + result = [] + define_method(:foo) do + if result.empty? + result << :first + redo + else + result << :second + result + end + end + end.new.foo.should == [:first, :second] + end + end +end diff --git a/spec/ruby/core/module/deprecate_constant_spec.rb b/spec/ruby/core/module/deprecate_constant_spec.rb index 7bcced981b..ec0de6782f 100644 --- a/spec/ruby/core/module/deprecate_constant_spec.rb +++ b/spec/ruby/core/module/deprecate_constant_spec.rb @@ -9,7 +9,6 @@ describe "Module#deprecate_constant" do @module::PRIVATE = @value @module.private_constant :PRIVATE @module.deprecate_constant :PRIVATE - @pattern = /deprecated/ end describe "when accessing the deprecated module" do @@ -19,7 +18,7 @@ describe "Module#deprecate_constant" do value = nil -> { value = @module::PUBLIC1 - }.should complain(@pattern) + }.should complain(/warning: constant .+::PUBLIC1 is deprecated/) value.should equal(@value) -> { @module::PRIVATE }.should raise_error(NameError) @@ -28,16 +27,37 @@ describe "Module#deprecate_constant" do it "warns with a message" do @module.deprecate_constant :PUBLIC1 - -> { @module::PUBLIC1 }.should complain(@pattern) - -> { @module.const_get :PRIVATE }.should complain(@pattern) + -> { @module::PUBLIC1 }.should complain(/warning: constant .+::PUBLIC1 is deprecated/) + -> { @module.const_get :PRIVATE }.should complain(/warning: constant .+::PRIVATE is deprecated/) + end + + it "does not warn if Warning[:deprecated] is false" do + @module.deprecate_constant :PUBLIC1 + + deprecated = Warning[:deprecated] + begin + Warning[:deprecated] = false + -> { @module::PUBLIC1 }.should_not complain + ensure + Warning[:deprecated] = deprecated + end + end + end + + ruby_bug '#20900', ''...'3.4' do + describe "when removing the deprecated module" do + it "warns with a message" do + @module.deprecate_constant :PUBLIC1 + -> { @module.module_eval {remove_const :PUBLIC1} }.should complain(/warning: constant .+::PUBLIC1 is deprecated/) + end end end it "accepts multiple symbols and strings as constant names" do @module.deprecate_constant "PUBLIC1", :PUBLIC2 - -> { @module::PUBLIC1 }.should complain(@pattern) - -> { @module::PUBLIC2 }.should complain(@pattern) + -> { @module::PUBLIC1 }.should complain(/warning: constant .+::PUBLIC1 is deprecated/) + -> { @module::PUBLIC2 }.should complain(/warning: constant .+::PUBLIC2 is deprecated/) end it "returns self" do diff --git a/spec/ruby/core/module/extend_object_spec.rb b/spec/ruby/core/module/extend_object_spec.rb index e66b87efef..1fd1abc0b5 100644 --- a/spec/ruby/core/module/extend_object_spec.rb +++ b/spec/ruby/core/module/extend_object_spec.rb @@ -42,20 +42,6 @@ describe "Module#extend_object" do ScratchPad.recorded.should == :extended end - ruby_version_is ''...'2.7' do - it "does not copy own tainted status to the given object" do - other = Object.new - Module.new.taint.send :extend_object, other - other.tainted?.should be_false - end - - it "does not copy own untrusted status to the given object" do - other = Object.new - Module.new.untrust.send :extend_object, other - other.untrusted?.should be_false - end - end - describe "when given a frozen object" do before :each do @receiver = Module.new diff --git a/spec/ruby/core/module/fixtures/autoload_const_source_location.rb b/spec/ruby/core/module/fixtures/autoload_const_source_location.rb new file mode 100644 index 0000000000..ee0e5a689f --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_const_source_location.rb @@ -0,0 +1,6 @@ +module ConstantSpecs + BEFORE_DEFINE_LOCATION = const_source_location(:ConstSource) + module ConstSource + LOCATION = Object.const_source_location(name) + end +end diff --git a/spec/ruby/core/module/fixtures/autoload_during_autoload_after_define.rb b/spec/ruby/core/module/fixtures/autoload_during_autoload_after_define.rb new file mode 100644 index 0000000000..a9d886dfd6 --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_during_autoload_after_define.rb @@ -0,0 +1,6 @@ +module ModuleSpecs::Autoload + class DuringAutoloadAfterDefine + block = ScratchPad.recorded + ScratchPad.record(block.call) + end +end diff --git a/spec/ruby/core/module/fixtures/autoload_during_require_current_file.rb b/spec/ruby/core/module/fixtures/autoload_during_require_current_file.rb new file mode 100644 index 0000000000..5aa8595065 --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_during_require_current_file.rb @@ -0,0 +1,5 @@ +module ModuleSpecs::Autoload + autoload(:AutoloadCurrentFile, __FILE__) + + ScratchPad.record autoload?(:AutoloadCurrentFile) +end diff --git a/spec/ruby/core/module/fixtures/autoload_location.rb b/spec/ruby/core/module/fixtures/autoload_location.rb new file mode 100644 index 0000000000..318851b2df --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_location.rb @@ -0,0 +1,3 @@ +module ConstantSpecs + CONST_LOCATION = __LINE__ +end diff --git a/spec/ruby/core/module/fixtures/classes.rb b/spec/ruby/core/module/fixtures/classes.rb index 40777cdbbd..964f64c593 100644 --- a/spec/ruby/core/module/fixtures/classes.rb +++ b/spec/ruby/core/module/fixtures/classes.rb @@ -1,6 +1,6 @@ module ModuleSpecs def self.without_test_modules(modules) - ignore = %w[MSpecRSpecAdapter PP::ObjectMixin ModuleSpecs::IncludedInObject MainSpecs::Module ConstantSpecs::ModuleA] + ignore = %w[MSpecRSpecAdapter PP::ObjectMixin MainSpecs::Module ConstantSpecs::ModuleA] modules.reject { |k| ignore.include?(k.name) } end @@ -42,6 +42,14 @@ module ModuleSpecs class LookupChild < Lookup end + module ModuleWithPrepend + prepend LookupMod + end + + class WithPrependedModule + include ModuleWithPrepend + end + class Parent # For private_class_method spec def self.private_method; end @@ -352,6 +360,10 @@ module ModuleSpecs end end + class SubCVars < CVars + @@sub = :sub + end + module MVars @@mvar = :mvar end @@ -376,6 +388,7 @@ module ModuleSpecs # empty modules module M1; end module M2; end + module M3; end module Autoload def self.use_ex1 @@ -583,6 +596,32 @@ module ModuleSpecs private :foo end EmptyFooMethod = m.instance_method(:foo) + + # for undefined_instance_methods spec + module UndefinedInstanceMethods + module Super + def super_included_method; end + end + + class Parent + def undefed_method; end + undef_method :undefed_method + + def parent_method; end + def another_parent_method; end + end + + class Child < Parent + include Super + + undef_method :parent_method + undef_method :another_parent_method + end + + class Grandchild < Child + undef_method :super_included_method + end + end end class Object diff --git a/spec/ruby/core/module/fixtures/const_added.rb b/spec/ruby/core/module/fixtures/const_added.rb new file mode 100644 index 0000000000..0f5baad65d --- /dev/null +++ b/spec/ruby/core/module/fixtures/const_added.rb @@ -0,0 +1,4 @@ +module ModuleSpecs + module ConstAddedSpecs + end +end diff --git a/spec/ruby/core/module/fixtures/module.rb b/spec/ruby/core/module/fixtures/module.rb index 9050a272ec..34543ca2b4 100644 --- a/spec/ruby/core/module/fixtures/module.rb +++ b/spec/ruby/core/module/fixtures/module.rb @@ -1,4 +1,8 @@ module ModuleSpecs module Anonymous + module Child + end + + SameChild = Child end end diff --git a/spec/ruby/core/module/fixtures/name.rb b/spec/ruby/core/module/fixtures/name.rb index fb9e66c309..25c74d3944 100644 --- a/spec/ruby/core/module/fixtures/name.rb +++ b/spec/ruby/core/module/fixtures/name.rb @@ -7,4 +7,7 @@ module ModuleSpecs Cß.name end end + + module NameSpecs + end end diff --git a/spec/ruby/core/module/fixtures/refine.rb b/spec/ruby/core/module/fixtures/refine.rb index 79e2e80197..e8215aa640 100644 --- a/spec/ruby/core/module/fixtures/refine.rb +++ b/spec/ruby/core/module/fixtures/refine.rb @@ -3,6 +3,10 @@ module ModuleSpecs def foo; "foo" end end + class ClassWithSuperFoo + def foo; [:C] end + end + module PrependedModule def foo; "foo from prepended module"; end end @@ -11,7 +15,11 @@ module ModuleSpecs def foo; "foo from included module"; end end - def self.build_refined_class - Class.new(ClassWithFoo) + def self.build_refined_class(for_super: false) + if for_super + Class.new(ClassWithSuperFoo) + else + Class.new(ClassWithFoo) + end end end diff --git a/spec/ruby/core/module/fixtures/set_temporary_name.rb b/spec/ruby/core/module/fixtures/set_temporary_name.rb new file mode 100644 index 0000000000..901b3b94d1 --- /dev/null +++ b/spec/ruby/core/module/fixtures/set_temporary_name.rb @@ -0,0 +1,4 @@ +module ModuleSpecs + module SetTemporaryNameSpec + end +end diff --git a/spec/ruby/core/module/include_spec.rb b/spec/ruby/core/module/include_spec.rb index e7f99a5981..210918b2e7 100644 --- a/spec/ruby/core/module/include_spec.rb +++ b/spec/ruby/core/module/include_spec.rb @@ -44,7 +44,23 @@ describe "Module#include" do end it "does not raise a TypeError when the argument is an instance of a subclass of Module" do - -> { ModuleSpecs::SubclassSpec.include(ModuleSpecs::Subclass.new) }.should_not raise_error(TypeError) + class ModuleSpecs::SubclassSpec::AClass + end + -> { ModuleSpecs::SubclassSpec::AClass.include(ModuleSpecs::Subclass.new) }.should_not raise_error(TypeError) + ensure + ModuleSpecs::SubclassSpec.send(:remove_const, :AClass) + end + + it "raises a TypeError when the argument is a refinement" do + refinement = nil + + Module.new do + refine String do + refinement = self + end + end + + -> { ModuleSpecs::Basic.include(refinement) }.should raise_error(TypeError, "Cannot include refinement") end it "imports constants to modules and classes" do @@ -104,9 +120,9 @@ describe "Module#include" do class A; include M; end class B < A; include M; end - all = [A,B,M] + all = [A, B, M] - (B.ancestors & all).should == [B, A, M] + (B.ancestors.filter { |a| all.include?(a) }).should == [B, A, M] end end @@ -234,6 +250,360 @@ describe "Module#include" do remove_const :C end end + + it "updates the method when an included module is updated" do + a_class = Class.new do + def foo + 'a' + end + end + + m_module = Module.new + + b_class = Class.new(a_class) do + include m_module + end + + b = b_class.new + + foo = -> { b.foo } + + foo.call.should == 'a' + + m_module.module_eval do + def foo + 'm' + end + end + + foo.call.should == 'm' + end + + + it "updates the method when a module included after a call is later updated" do + m_module = Module.new + a_class = Class.new do + def foo + 'a' + end + end + b_class = Class.new(a_class) + b = b_class.new + foo = -> { b.foo } + foo.call.should == 'a' + + b_class.include m_module + foo.call.should == 'a' + + m_module.module_eval do + def foo + "m" + end + end + foo.call.should == 'm' + end + + it "updates the method when a nested included module is updated" do + a_class = Class.new do + def foo + 'a' + end + end + + n_module = Module.new + + m_module = Module.new do + include n_module + end + + b_class = Class.new(a_class) do + include m_module + end + + b = b_class.new + + foo = -> { b.foo } + + foo.call.should == 'a' + + n_module.module_eval do + def foo + 'n' + end + end + + foo.call.should == 'n' + end + + it "updates the method when a new module is included" do + a_class = Class.new do + def foo + 'a' + end + end + + m_module = Module.new do + def foo + 'm' + end + end + + b_class = Class.new(a_class) + b = b_class.new + + foo = -> { b.foo } + + foo.call.should == 'a' + + b_class.class_eval do + include m_module + end + + foo.call.should == 'm' + end + + it "updates the method when a new module with nested module is included" do + a_class = Class.new do + def foo + 'a' + end + end + + n_module = Module.new do + def foo + 'n' + end + end + + m_module = Module.new do + include n_module + end + + b_class = Class.new(a_class) + b = b_class.new + + foo = -> { b.foo } + + foo.call.should == 'a' + + b_class.class_eval do + include m_module + end + + foo.call.should == 'n' + end + + it "updates the constant when an included module is updated" do + module ModuleSpecs::ConstUpdated + module A + FOO = 'a' + end + + module M + end + + module B + include A + include M + def self.foo + FOO + end + end + + B.foo.should == 'a' + + M.const_set(:FOO, 'm') + B.foo.should == 'm' + end + ensure + ModuleSpecs.send(:remove_const, :ConstUpdated) + end + + it "updates the constant when a module included after a call is later updated" do + module ModuleSpecs::ConstLaterUpdated + module A + FOO = 'a' + end + + module B + include A + def self.foo + FOO + end + end + + B.foo.should == 'a' + + module M + end + B.include M + + B.foo.should == 'a' + + M.const_set(:FOO, 'm') + B.foo.should == 'm' + end + ensure + ModuleSpecs.send(:remove_const, :ConstLaterUpdated) + end + + it "updates the constant when a module included in another module after a call is later updated" do + module ModuleSpecs::ConstModuleLaterUpdated + module A + FOO = 'a' + end + + module B + include A + def self.foo + FOO + end + end + + B.foo.should == 'a' + + module M + end + B.include M + + B.foo.should == 'a' + + M.const_set(:FOO, 'm') + B.foo.should == 'm' + end + ensure + ModuleSpecs.send(:remove_const, :ConstModuleLaterUpdated) + end + + it "updates the constant when a nested included module is updated" do + module ModuleSpecs::ConstUpdatedNestedIncludeUpdated + module A + FOO = 'a' + end + + module N + end + + module M + include N + end + + module B + include A + include M + def self.foo + FOO + end + end + + B.foo.should == 'a' + + N.const_set(:FOO, 'n') + B.foo.should == 'n' + end + ensure + ModuleSpecs.send(:remove_const, :ConstUpdatedNestedIncludeUpdated) + end + + it "updates the constant when a new module is included" do + module ModuleSpecs::ConstUpdatedNewInclude + module A + FOO = 'a' + end + + module M + FOO = 'm' + end + + module B + include A + def self.foo + FOO + end + end + + B.foo.should == 'a' + + B.include(M) + B.foo.should == 'm' + end + ensure + ModuleSpecs.send(:remove_const, :ConstUpdatedNewInclude) + end + + it "updates the constant when a new module with nested module is included" do + module ModuleSpecs::ConstUpdatedNestedIncluded + module A + FOO = 'a' + end + + module N + FOO = 'n' + end + + module M + include N + end + + module B + include A + def self.foo + FOO + end + end + + B.foo.should == 'a' + + B.include M + B.foo.should == 'n' + end + ensure + ModuleSpecs.send(:remove_const, :ConstUpdatedNestedIncluded) + end + + it "overrides a previous super method call" do + c1 = Class.new do + def foo + [:c1] + end + end + c2 = Class.new(c1) do + def foo + [:c2] + super + end + end + c2.new.foo.should == [:c2, :c1] + m = Module.new do + def foo + [:m1] + end + end + c2.include(m) + c2.new.foo.should == [:c2, :m1] + end + + it "update a module when a nested module is updated and includes a module on its own" do + m1 = Module.new + m2 = Module.new do + def m2; [:m2]; end + end + m3 = Module.new do + def m3; [:m3]; end + end + m4 = Module.new do + def m4; [:m4]; end + end + c = Class.new + + c.include(m1) + m1.include(m2) + m2.include(m3) + m3.include(m4) + + c.new.m2.should == [:m2] + c.new.m3.should == [:m3] + c.new.m4.should == [:m4] + end end describe "Module#include?" do diff --git a/spec/ruby/core/module/included_modules_spec.rb b/spec/ruby/core/module/included_modules_spec.rb index 40e20953f4..ce94ed1285 100644 --- a/spec/ruby/core/module/included_modules_spec.rb +++ b/spec/ruby/core/module/included_modules_spec.rb @@ -4,9 +4,11 @@ require_relative 'fixtures/classes' describe "Module#included_modules" do it "returns a list of modules included in self" do ModuleSpecs.included_modules.should == [] + ModuleSpecs::Child.included_modules.should include(ModuleSpecs::Super, ModuleSpecs::Basic, Kernel) ModuleSpecs::Parent.included_modules.should include(Kernel) ModuleSpecs::Basic.included_modules.should == [] ModuleSpecs::Super.included_modules.should include(ModuleSpecs::Basic) + ModuleSpecs::WithPrependedModule.included_modules.should include(ModuleSpecs::ModuleWithPrepend) end end diff --git a/spec/ruby/core/module/instance_method_spec.rb b/spec/ruby/core/module/instance_method_spec.rb index b4d6a0d8c8..182cdf5c54 100644 --- a/spec/ruby/core/module/instance_method_spec.rb +++ b/spec/ruby/core/module/instance_method_spec.rb @@ -45,20 +45,41 @@ describe "Module#instance_method" do @parent_um.inspect.should =~ /\bModuleSpecs::InstanceMeth\b/ @child_um.inspect.should =~ /\bfoo\b/ @child_um.inspect.should =~ /\bModuleSpecs::InstanceMeth\b/ - @child_um.inspect.should =~ /\bModuleSpecs::InstanceMethChild\b/ + @mod_um.inspect.should =~ /\bbar\b/ @mod_um.inspect.should =~ /\bModuleSpecs::InstanceMethMod\b/ - @mod_um.inspect.should =~ /\bModuleSpecs::InstanceMethChild\b/ end - it "raises a TypeError if not passed a symbol" do - -> { Object.instance_method([]) }.should raise_error(TypeError) - -> { Object.instance_method(0) }.should raise_error(TypeError) + it "raises a TypeError if the given name is not a String/Symbol" do + -> { Object.instance_method([]) }.should raise_error(TypeError, /is not a symbol nor a string/) + -> { Object.instance_method(0) }.should raise_error(TypeError, /is not a symbol nor a string/) + -> { Object.instance_method(nil) }.should raise_error(TypeError, /is not a symbol nor a string/) + -> { Object.instance_method(mock('x')) }.should raise_error(TypeError, /is not a symbol nor a string/) + end + + it "accepts String name argument" do + method = ModuleSpecs::InstanceMeth.instance_method(:foo) + method.should be_kind_of(UnboundMethod) + end + + it "accepts Symbol name argument" do + method = ModuleSpecs::InstanceMeth.instance_method("foo") + method.should be_kind_of(UnboundMethod) end - it "raises a TypeError if the given name is not a string/symbol" do - -> { Object.instance_method(nil) }.should raise_error(TypeError) - -> { Object.instance_method(mock('x')) }.should raise_error(TypeError) + it "converts non-String name by calling #to_str method" do + obj = Object.new + def obj.to_str() "foo" end + + method = ModuleSpecs::InstanceMeth.instance_method(obj) + method.should be_kind_of(UnboundMethod) + end + + it "raises TypeError when passed non-String name and #to_str returns non-String value" do + obj = Object.new + def obj.to_str() [] end + + -> { ModuleSpecs::InstanceMeth.instance_method(obj) }.should raise_error(TypeError, /can't convert Object to String/) end it "raises a NameError if the method has been undefined" do diff --git a/spec/ruby/core/module/method_added_spec.rb b/spec/ruby/core/module/method_added_spec.rb index e1b1eda430..ec92cddc1e 100644 --- a/spec/ruby/core/module/method_added_spec.rb +++ b/spec/ruby/core/module/method_added_spec.rb @@ -2,6 +2,10 @@ require_relative '../../spec_helper' require_relative 'fixtures/classes' describe "Module#method_added" do + before :each do + ScratchPad.record [] + end + it "is a private instance method" do Module.should have_private_instance_method(:method_added) end @@ -13,8 +17,6 @@ describe "Module#method_added" do end it "is called when a new instance method is defined in self" do - ScratchPad.record [] - Module.new do def self.method_added(name) ScratchPad << name @@ -32,8 +34,6 @@ describe "Module#method_added" do it "is not called when a singleton method is added" do # obj.singleton_method_added is called instead - ScratchPad.record [] - klass = Class.new def klass.method_added(name) ScratchPad << name @@ -59,4 +59,88 @@ describe "Module#method_added" do end m.should_not have_method(:method_to_undef) end + + it "is not called when a method changes visibility" do + Module.new do + def public_method + end + + def private_method + end + + def self.method_added(name) + ScratchPad << name + end + + public :public_method + private :public_method + + private :private_method + public :private_method + end + + ScratchPad.recorded.should == [] + end + + it "is called when using #private in a subclass" do + parent = Class.new do + def foo + end + end + + Class.new(parent) do + def self.method_added(name) + ScratchPad << name + end + + # Create an instance as that might initialize some method lookup caches, which is interesting to test + self.new.foo + + private :foo + public :foo + end + + ScratchPad.recorded.should == [:foo] + end + + it "is not called when a method is copied via module_function, rather #singleton_method_added is called" do + Module.new do + def mod_function + end + + def self.method_added(name) + ScratchPad << [:method_added, name] + end + + def self.singleton_method_added(name) + ScratchPad << [:singleton_method_added, name] + end + + ScratchPad.record [] + + module_function :mod_function + end + + ScratchPad.recorded.should == [[:singleton_method_added, :mod_function]] + end + + it "is called with a precise caller location with the line of the 'def'" do + line = nil + + Module.new do + def self.method_added(name) + location = caller_locations(1, 1)[0] + ScratchPad << location.lineno + end + + line = __LINE__ + def first + end + + def second + end + end + + ScratchPad.recorded.should == [line + 1, line + 4] + end end diff --git a/spec/ruby/core/module/method_defined_spec.rb b/spec/ruby/core/module/method_defined_spec.rb index c2a8702d97..bc5b420e11 100644 --- a/spec/ruby/core/module/method_defined_spec.rb +++ b/spec/ruby/core/module/method_defined_spec.rb @@ -30,7 +30,7 @@ describe "Module#method_defined?" do m.method_defined?(:module_specs_public_method_on_kernel).should be_false end - it "raises a TypeError when the given object is not a string/symbol/fixnum" do + it "raises a TypeError when the given object is not a string/symbol" do c = Class.new o = mock('123') @@ -47,54 +47,52 @@ describe "Module#method_defined?" do c.method_defined?(o).should == true end - ruby_version_is "2.6" do - # works as method_defined?(method_name) - describe "when passed true as a second optional argument" do - it "performs a lookup in ancestors" do - ModuleSpecs::Child.method_defined?(:public_child, true).should == true - ModuleSpecs::Child.method_defined?(:protected_child, true).should == true - ModuleSpecs::Child.method_defined?(:accessor_method, true).should == true - ModuleSpecs::Child.method_defined?(:private_child, true).should == false - - # Defined in Parent - ModuleSpecs::Child.method_defined?(:public_parent, true).should == true - ModuleSpecs::Child.method_defined?(:protected_parent, true).should == true - ModuleSpecs::Child.method_defined?(:private_parent, true).should == false - - # Defined in Module - ModuleSpecs::Child.method_defined?(:public_module, true).should == true - ModuleSpecs::Child.method_defined?(:protected_module, true).should == true - ModuleSpecs::Child.method_defined?(:private_module, true).should == false - - # Defined in SuperModule - ModuleSpecs::Child.method_defined?(:public_super_module, true).should == true - ModuleSpecs::Child.method_defined?(:protected_super_module, true).should == true - ModuleSpecs::Child.method_defined?(:private_super_module, true).should == false - end + # works as method_defined?(method_name) + describe "when passed true as a second optional argument" do + it "performs a lookup in ancestors" do + ModuleSpecs::Child.method_defined?(:public_child, true).should == true + ModuleSpecs::Child.method_defined?(:protected_child, true).should == true + ModuleSpecs::Child.method_defined?(:accessor_method, true).should == true + ModuleSpecs::Child.method_defined?(:private_child, true).should == false + + # Defined in Parent + ModuleSpecs::Child.method_defined?(:public_parent, true).should == true + ModuleSpecs::Child.method_defined?(:protected_parent, true).should == true + ModuleSpecs::Child.method_defined?(:private_parent, true).should == false + + # Defined in Module + ModuleSpecs::Child.method_defined?(:public_module, true).should == true + ModuleSpecs::Child.method_defined?(:protected_module, true).should == true + ModuleSpecs::Child.method_defined?(:private_module, true).should == false + + # Defined in SuperModule + ModuleSpecs::Child.method_defined?(:public_super_module, true).should == true + ModuleSpecs::Child.method_defined?(:protected_super_module, true).should == true + ModuleSpecs::Child.method_defined?(:private_super_module, true).should == false end + end - describe "when passed false as a second optional argument" do - it "checks only the class itself" do - ModuleSpecs::Child.method_defined?(:public_child, false).should == true - ModuleSpecs::Child.method_defined?(:protected_child, false).should == true - ModuleSpecs::Child.method_defined?(:accessor_method, false).should == true - ModuleSpecs::Child.method_defined?(:private_child, false).should == false - - # Defined in Parent - ModuleSpecs::Child.method_defined?(:public_parent, false).should == false - ModuleSpecs::Child.method_defined?(:protected_parent, false).should == false - ModuleSpecs::Child.method_defined?(:private_parent, false).should == false - - # Defined in Module - ModuleSpecs::Child.method_defined?(:public_module, false).should == false - ModuleSpecs::Child.method_defined?(:protected_module, false).should == false - ModuleSpecs::Child.method_defined?(:private_module, false).should == false - - # Defined in SuperModule - ModuleSpecs::Child.method_defined?(:public_super_module, false).should == false - ModuleSpecs::Child.method_defined?(:protected_super_module, false).should == false - ModuleSpecs::Child.method_defined?(:private_super_module, false).should == false - end + describe "when passed false as a second optional argument" do + it "checks only the class itself" do + ModuleSpecs::Child.method_defined?(:public_child, false).should == true + ModuleSpecs::Child.method_defined?(:protected_child, false).should == true + ModuleSpecs::Child.method_defined?(:accessor_method, false).should == true + ModuleSpecs::Child.method_defined?(:private_child, false).should == false + + # Defined in Parent + ModuleSpecs::Child.method_defined?(:public_parent, false).should == false + ModuleSpecs::Child.method_defined?(:protected_parent, false).should == false + ModuleSpecs::Child.method_defined?(:private_parent, false).should == false + + # Defined in Module + ModuleSpecs::Child.method_defined?(:public_module, false).should == false + ModuleSpecs::Child.method_defined?(:protected_module, false).should == false + ModuleSpecs::Child.method_defined?(:private_module, false).should == false + + # Defined in SuperModule + ModuleSpecs::Child.method_defined?(:public_super_module, false).should == false + ModuleSpecs::Child.method_defined?(:protected_super_module, false).should == false + ModuleSpecs::Child.method_defined?(:private_super_module, false).should == false end end end diff --git a/spec/ruby/core/module/module_function_spec.rb b/spec/ruby/core/module/module_function_spec.rb index 407237d48f..51f647142e 100644 --- a/spec/ruby/core/module/module_function_spec.rb +++ b/spec/ruby/core/module/module_function_spec.rb @@ -38,14 +38,12 @@ describe "Module#module_function with specific method names" do m.respond_to?(:test3).should == false end - it "returns the current module" do - x = nil - m = Module.new do - def test() end - x = module_function :test + it "returns argument or arguments if given" do + Module.new do + def foo; end + module_function(:foo).should equal(:foo) + module_function(:foo, :foo).should == [:foo, :foo] end - - x.should == m end it "creates an independent copy of the method, not a redirect" do @@ -146,38 +144,82 @@ describe "Module#module_function with specific method names" do m.foo.should == ["m", "super_m"] end + + context "methods created with define_method" do + context "passed a block" do + it "creates duplicates of the given instance methods" do + m = Module.new do + define_method :test1 do; end + module_function :test1 + end + + m.respond_to?(:test1).should == true + end + end + + context "passed a method" do + it "creates duplicates of the given instance methods" do + module_with_method = Module.new do + def test1; end + end + + c = Class.new do + extend module_with_method + end + + m = Module.new do + define_method :test2, c.method(:test1) + module_function :test2 + end + + m.respond_to?(:test2).should == true + end + end + + context "passed an unbound method" do + it "creates duplicates of the given instance methods" do + module_with_method = Module.new do + def test1; end + end + + m = Module.new do + define_method :test2, module_with_method.instance_method(:test1) + module_function :test2 + end + + m.respond_to?(:test2).should == true + end + end + end end describe "Module#module_function as a toggle (no arguments) in a Module body" do it "makes any subsequently defined methods module functions with the normal semantics" do - m = Module.new { + m = Module.new do module_function def test1() end def test2() end - } + end m.respond_to?(:test1).should == true m.respond_to?(:test2).should == true end - it "returns the current module" do - x = nil - m = Module.new { - x = module_function - } - - x.should == m + it "returns nil" do + Module.new do + module_function.should equal(nil) + end end it "stops creating module functions if the body encounters another toggle " \ "like public/protected/private without arguments" do - m = Module.new { + m = Module.new do module_function def test1() end def test2() end public def test3() end - } + end m.respond_to?(:test1).should == true m.respond_to?(:test2).should == true @@ -186,14 +228,14 @@ describe "Module#module_function as a toggle (no arguments) in a Module body" do it "does not stop creating module functions if the body encounters " \ "public/protected/private WITH arguments" do - m = Module.new { + m = Module.new do def foo() end module_function def test1() end def test2() end public :foo def test3() end - } + end m.respond_to?(:test1).should == true m.respond_to?(:test2).should == true @@ -201,69 +243,116 @@ describe "Module#module_function as a toggle (no arguments) in a Module body" do end it "does not affect module_evaled method definitions also if outside the eval itself" do - m = Module.new { + m = Module.new do module_function module_eval { def test1() end } module_eval " def test2() end " - } + end m.respond_to?(:test1).should == false m.respond_to?(:test2).should == false end it "has no effect if inside a module_eval if the definitions are outside of it" do - m = Module.new { + m = Module.new do module_eval { module_function } def test1() end def test2() end - } + end m.respond_to?(:test1).should == false m.respond_to?(:test2).should == false end it "functions normally if both toggle and definitions inside a module_eval" do - m = Module.new { - module_eval { + m = Module.new do + module_eval do module_function def test1() end def test2() end - } - } + end + end m.respond_to?(:test1).should == true m.respond_to?(:test2).should == true end - it "affects evaled method definitions also even when outside the eval itself" do - m = Module.new { + it "affects eval'ed method definitions also even when outside the eval itself" do + m = Module.new do module_function eval "def test1() end" - } + end m.respond_to?(:test1).should == true end it "doesn't affect definitions when inside an eval even if the definitions are outside of it" do - m = Module.new { + m = Module.new do eval "module_function" def test1() end - } + end m.respond_to?(:test1).should == false end it "functions normally if both toggle and definitions inside a eval" do - m = Module.new { + m = Module.new do eval <<-CODE module_function def test1() end def test2() end CODE - } + end m.respond_to?(:test1).should == true m.respond_to?(:test2).should == true end + + context "methods are defined with define_method" do + context "passed a block" do + it "makes any subsequently defined methods module functions with the normal semantics" do + m = Module.new do + module_function + define_method :test1 do; end + end + + m.respond_to?(:test1).should == true + end + end + + context "passed a method" do + it "makes any subsequently defined methods module functions with the normal semantics" do + module_with_method = Module.new do + def test1; end + end + + c = Class.new do + extend module_with_method + end + + m = Module.new do + module_function + define_method :test2, c.method(:test1) + end + + m.respond_to?(:test2).should == true + end + end + + context "passed an unbound method" do + it "makes any subsequently defined methods module functions with the normal semantics" do + module_with_method = Module.new do + def test1; end + end + + m = Module.new do + module_function + define_method :test2, module_with_method.instance_method(:test1) + end + + m.respond_to?(:test2).should == true + end + end + end end diff --git a/spec/ruby/core/module/name_spec.rb b/spec/ruby/core/module/name_spec.rb index ae8037555b..d3318e1645 100644 --- a/spec/ruby/core/module/name_spec.rb +++ b/spec/ruby/core/module/name_spec.rb @@ -6,10 +6,10 @@ describe "Module#name" do Module.new.name.should be_nil end - it "is nil when assigned to a constant in an anonymous module" do + it "is not nil when assigned to a constant in an anonymous module" do m = Module.new m::N = Module.new - m::N.name.should be_nil + m::N.name.should.end_with? '::N' end it "is not nil for a nested module created with the module keyword" do @@ -30,6 +30,21 @@ describe "Module#name" do m::N.name.should =~ /\A#<Module:0x\h+>::N\z/ ModuleSpecs::Anonymous::WasAnnon = m::N m::N.name.should == "ModuleSpecs::Anonymous::WasAnnon" + ensure + ModuleSpecs::Anonymous.send(:remove_const, :WasAnnon) + end + + it "may be the repeated in different module objects" do + m = Module.new + n = Module.new + + suppress_warning do + ModuleSpecs::Anonymous::SameName = m + ModuleSpecs::Anonymous::SameName = n + end + + m.name.should == "ModuleSpecs::Anonymous::SameName" + n.name.should == "ModuleSpecs::Anonymous::SameName" end it "is set after it is removed from a constant" do @@ -59,10 +74,20 @@ describe "Module#name" do ModuleSpecs::Anonymous.name.should == "ModuleSpecs::Anonymous" end - it "is set when assigning to a constant" do + it "is set when assigning to a constant (constant path matches outer module name)" do m = Module.new ModuleSpecs::Anonymous::A = m m.name.should == "ModuleSpecs::Anonymous::A" + ensure + ModuleSpecs::Anonymous.send(:remove_const, :A) + end + + it "is set when assigning to a constant (constant path does not match outer module name)" do + m = Module.new + ModuleSpecs::Anonymous::SameChild::A = m + m.name.should == "ModuleSpecs::Anonymous::Child::A" + ensure + ModuleSpecs::Anonymous::SameChild.send(:remove_const, :A) end it "is not modified when assigning to a new constant after it has been accessed" do @@ -71,6 +96,9 @@ describe "Module#name" do m.name.should == "ModuleSpecs::Anonymous::B" ModuleSpecs::Anonymous::C = m m.name.should == "ModuleSpecs::Anonymous::B" + ensure + ModuleSpecs::Anonymous.send(:remove_const, :B) + ModuleSpecs::Anonymous.send(:remove_const, :C) end it "is not modified when assigned to a different anonymous module" do @@ -101,34 +129,77 @@ describe "Module#name" do ModuleSpecs::NameEncoding.new.name.encoding.should == Encoding::UTF_8 end - it "is set when the anonymous outer module name is set" do + it "is set when the anonymous outer module name is set (module in one single constant)" do m = Module.new m::N = Module.new ModuleSpecs::Anonymous::E = m m::N.name.should == "ModuleSpecs::Anonymous::E::N" + ensure + ModuleSpecs::Anonymous.send(:remove_const, :E) end - ruby_version_is ""..."2.7" do - it "returns a mutable string" do - ModuleSpecs.name.frozen?.should be_false - end + # https://bugs.ruby-lang.org/issues/19681 + it "is set when the anonymous outer module name is set (module in several constants)" do + m = Module.new + m::N = Module.new + m::O = m::N + ModuleSpecs::Anonymous::StoredInMultiplePlaces = m + valid_names = [ + "ModuleSpecs::Anonymous::StoredInMultiplePlaces::N", + "ModuleSpecs::Anonymous::StoredInMultiplePlaces::O" + ] + valid_names.should include(m::N.name) # You get one of the two, but you don't know which one. + ensure + ModuleSpecs::Anonymous.send(:remove_const, :StoredInMultiplePlaces) + end + + it "is set in #const_added callback when a module defined in the top-level scope" do + ruby_exe(<<~RUBY, args: "2>&1").chomp.should == "TEST1\nTEST2" + class Module + def const_added(name) + puts const_get(name).name + end + end - it "returns a mutable string that when mutated does not modify the original module name" do - ModuleSpecs.name << "foo" + # module with name + module TEST1 + end - ModuleSpecs.name.should == "ModuleSpecs" - end + # anonymous module + TEST2 = Module.new + RUBY end - ruby_version_is "2.7" do - it "returns a frozen String" do - ModuleSpecs.name.should.frozen? - end + it "is set in #const_added callback for a nested module when an outer module defined in the top-level scope" do + ScratchPad.record [] + + ModuleSpecs::NameSpecs::NamedModule = Module.new do + def self.const_added(name) + ScratchPad << const_get(name).name + end + + module self::A + def self.const_added(name) + ScratchPad << const_get(name).name + end - it "always returns the same String for a given Module" do - s1 = ModuleSpecs.name - s2 = ModuleSpecs.name - s1.should equal(s2) + module self::B + end + end end + + ScratchPad.recorded.should.one?(/#<Module.+>::A$/) + ScratchPad.recorded.should.one?(/#<Module.+>::A::B$/) + ModuleSpecs::NameSpecs.send :remove_const, :NamedModule + end + + it "returns a frozen String" do + ModuleSpecs.name.should.frozen? + end + + it "always returns the same String for a given Module" do + s1 = ModuleSpecs.name + s2 = ModuleSpecs.name + s1.should equal(s2) end end diff --git a/spec/ruby/core/module/new_spec.rb b/spec/ruby/core/module/new_spec.rb index da7f3b8720..ec521360bd 100644 --- a/spec/ruby/core/module/new_spec.rb +++ b/spec/ruby/core/module/new_spec.rb @@ -6,6 +6,10 @@ describe "Module.new" do Module.new.is_a?(Module).should == true end + it "creates a module without a name" do + Module.new.name.should be_nil + end + it "creates a new Module and passes it to the provided block" do test_mod = nil m = Module.new do |mod| diff --git a/spec/ruby/core/module/prepend_features_spec.rb b/spec/ruby/core/module/prepend_features_spec.rb index 2d1fa713b7..09c15c5c15 100644 --- a/spec/ruby/core/module/prepend_features_spec.rb +++ b/spec/ruby/core/module/prepend_features_spec.rb @@ -28,20 +28,6 @@ describe "Module#prepend_features" do }.should raise_error(ArgumentError) end - ruby_version_is ''...'2.7' do - it "copies own tainted status to the given module" do - other = Module.new - Module.new.taint.send :prepend_features, other - other.tainted?.should be_true - end - - it "copies own untrusted status to the given module" do - other = Module.new - Module.new.untrust.send :prepend_features, other - other.untrusted?.should be_true - end - end - it "clears caches of the given module" do parent = Class.new do def bar; :bar; end diff --git a/spec/ruby/core/module/prepend_spec.rb b/spec/ruby/core/module/prepend_spec.rb index 1905021cf7..71e82c513e 100644 --- a/spec/ruby/core/module/prepend_spec.rb +++ b/spec/ruby/core/module/prepend_spec.rb @@ -36,12 +36,435 @@ describe "Module#prepend" do ScratchPad.recorded.should == [ [ m3, c], [ m2, c ], [ m, c ] ] end + it "updates the method when a module is prepended" do + m_module = Module.new do + def foo + "m" + end + end + a_class = Class.new do + def foo + 'a' + end + end + a = a_class.new + foo = -> { a.foo } + foo.call.should == 'a' + a_class.class_eval do + prepend m_module + end + foo.call.should == 'm' + end + + it "updates the method when a prepended module is updated" do + m_module = Module.new + a_class = Class.new do + prepend m_module + def foo + 'a' + end + end + a = a_class.new + foo = -> { a.foo } + foo.call.should == 'a' + m_module.module_eval do + def foo + "m" + end + end + foo.call.should == 'm' + end + + it "updates the optimized method when a prepended module is updated" do + out = ruby_exe(<<~RUBY) + module M; end + class Integer + prepend M + end + l = -> { 1 + 2 } + p l.call + M.module_eval do + def +(o) + $called = true + super(o) + end + end + p l.call + p $called + RUBY + out.should == "3\n3\ntrue\n" + end + + it "updates the method when there is a base included method and the prepended module overrides it" do + base_module = Module.new do + def foo + 'a' + end + end + a_class = Class.new do + include base_module + end + a = a_class.new + foo = -> { a.foo } + foo.call.should == 'a' + + m_module = Module.new do + def foo + "m" + end + end + a_class.prepend m_module + foo.call.should == 'm' + end + + it "updates the method when there is a base included method and the prepended module is later updated" do + base_module = Module.new do + def foo + 'a' + end + end + a_class = Class.new do + include base_module + end + a = a_class.new + foo = -> { a.foo } + foo.call.should == 'a' + + m_module = Module.new + a_class.prepend m_module + foo.call.should == 'a' + + m_module.module_eval do + def foo + "m" + end + end + foo.call.should == 'm' + end + + it "updates the method when a module prepended after a call is later updated" do + m_module = Module.new + a_class = Class.new do + def foo + 'a' + end + end + a = a_class.new + foo = -> { a.foo } + foo.call.should == 'a' + + a_class.prepend m_module + foo.call.should == 'a' + + m_module.module_eval do + def foo + "m" + end + end + foo.call.should == 'm' + end + + it "updates the method when a module is prepended after another and the method is defined later on that module" do + m_module = Module.new do + def foo + 'a' + end + end + a_class = Class.new + a_class.prepend m_module + a = a_class.new + foo = -> { a.foo } + foo.call.should == 'a' + + n_module = Module.new + a_class.prepend n_module + foo.call.should == 'a' + + n_module.module_eval do + def foo + "n" + end + end + foo.call.should == 'n' + end + + it "updates the method when a module is included in a prepended module and the method is defined later" do + a_class = Class.new + base_module = Module.new do + def foo + 'a' + end + end + a_class.prepend base_module + a = a_class.new + foo = -> { a.foo } + foo.call.should == 'a' + + m_module = Module.new + n_module = Module.new + m_module.include n_module + a_class.prepend m_module + + n_module.module_eval do + def foo + "n" + end + end + foo.call.should == 'n' + end + + it "updates the method when a new module with an included module is prepended" do + a_class = Class.new do + def foo + 'a' + end + end + + n_module = Module.new do + def foo + 'n' + end + end + + m_module = Module.new do + include n_module + end + + a = a_class.new + foo = -> { a.foo } + + foo.call.should == 'a' + + a_class.class_eval do + prepend m_module + end + + foo.call.should == 'n' + end + + it "updates the constant when a module is prepended" do + module ModuleSpecs::ConstUpdatePrepended + module M + FOO = 'm' + end + module A + FOO = 'a' + end + module B + include A + def self.foo + FOO + end + end + + B.foo.should == 'a' + B.prepend M + B.foo.should == 'm' + end + ensure + ModuleSpecs.send(:remove_const, :ConstUpdatePrepended) + end + + it "updates the constant when a prepended module is updated" do + module ModuleSpecs::ConstPrependedUpdated + module M + end + module A + FOO = 'a' + end + module B + include A + prepend M + def self.foo + FOO + end + end + B.foo.should == 'a' + M.const_set(:FOO, 'm') + B.foo.should == 'm' + end + ensure + ModuleSpecs.send(:remove_const, :ConstPrependedUpdated) + end + + it "updates the constant when there is a base included constant and the prepended module overrides it" do + module ModuleSpecs::ConstIncludedPrependedOverride + module Base + FOO = 'a' + end + module A + include Base + def self.foo + FOO + end + end + A.foo.should == 'a' + + module M + FOO = 'm' + end + A.prepend M + A.foo.should == 'm' + end + ensure + ModuleSpecs.send(:remove_const, :ConstIncludedPrependedOverride) + end + + it "updates the constant when there is a base included constant and the prepended module is later updated" do + module ModuleSpecs::ConstIncludedPrependedLaterUpdated + module Base + FOO = 'a' + end + module A + include Base + def self.foo + FOO + end + end + A.foo.should == 'a' + + module M + end + A.prepend M + A.foo.should == 'a' + + M.const_set(:FOO, 'm') + A.foo.should == 'm' + end + ensure + ModuleSpecs.send(:remove_const, :ConstIncludedPrependedLaterUpdated) + end + + it "updates the constant when a module prepended after a constant is later updated" do + module ModuleSpecs::ConstUpdatedPrependedAfterLaterUpdated + module M + end + module A + FOO = 'a' + end + module B + include A + def self.foo + FOO + end + end + B.foo.should == 'a' + + B.prepend M + B.foo.should == 'a' + + M.const_set(:FOO, 'm') + B.foo.should == 'm' + end + ensure + ModuleSpecs.send(:remove_const, :ConstUpdatedPrependedAfterLaterUpdated) + end + + it "updates the constant when a module is prepended after another and the constant is defined later on that module" do + module ModuleSpecs::ConstUpdatedPrependedAfterConstDefined + module M + FOO = 'm' + end + module A + prepend M + def self.foo + FOO + end + end + + A.foo.should == 'm' + + module N + end + A.prepend N + A.foo.should == 'm' + + N.const_set(:FOO, 'n') + A.foo.should == 'n' + end + ensure + ModuleSpecs.send(:remove_const, :ConstUpdatedPrependedAfterConstDefined) + end + + it "updates the constant when a module is included in a prepended module and the constant is defined later" do + module ModuleSpecs::ConstUpdatedIncludedInPrependedConstDefinedLater + module A + def self.foo + FOO + end + end + module Base + FOO = 'a' + end + + A.prepend Base + A.foo.should == 'a' + + module N + end + module M + include N + end + + A.prepend M + + N.const_set(:FOO, 'n') + A.foo.should == 'n' + end + ensure + ModuleSpecs.send(:remove_const, :ConstUpdatedIncludedInPrependedConstDefinedLater) + end + + it "updates the constant when a new module with an included module is prepended" do + module ModuleSpecs::ConstUpdatedNewModuleIncludedPrepended + module A + FOO = 'a' + end + module B + include A + def self.foo + FOO + end + end + module N + FOO = 'n' + end + + module M + include N + end + + B.foo.should == 'a' + + B.prepend M + B.foo.should == 'n' + end + ensure + ModuleSpecs.send(:remove_const, :ConstUpdatedNewModuleIncludedPrepended) + end + it "raises a TypeError when the argument is not a Module" do -> { ModuleSpecs::Basic.prepend(Class.new) }.should raise_error(TypeError) end it "does not raise a TypeError when the argument is an instance of a subclass of Module" do - -> { ModuleSpecs::SubclassSpec.prepend(ModuleSpecs::Subclass.new) }.should_not raise_error(TypeError) + class ModuleSpecs::SubclassSpec::AClass + end + -> { ModuleSpecs::SubclassSpec::AClass.prepend(ModuleSpecs::Subclass.new) }.should_not raise_error(TypeError) + ensure + ModuleSpecs::SubclassSpec.send(:remove_const, :AClass) + end + + it "raises a TypeError when the argument is a refinement" do + refinement = nil + + Module.new do + refine String do + refinement = self + end + end + + -> { ModuleSpecs::Basic.prepend(refinement) }.should raise_error(TypeError, "Cannot prepend refinement") end it "imports constants" do @@ -128,34 +551,17 @@ describe "Module#prepend" do c.dup.new.should be_kind_of(m) end - ruby_version_is '0'...'2.8' do - it "keeps the module in the chain when dupping an intermediate module" do - m1 = Module.new { def calc(x) x end } - m2 = Module.new { prepend(m1) } - c1 = Class.new { prepend(m2) } - m2dup = m2.dup - m2dup.ancestors.should == [m2dup,m1,m2] - c2 = Class.new { prepend(m2dup) } - c1.ancestors[0,3].should == [m1,m2,c1] - c1.new.should be_kind_of(m1) - c2.ancestors[0,4].should == [m2dup,m1,m2,c2] - c2.new.should be_kind_of(m1) - end - end - - ruby_version_is '2.8' do - it "uses only new module when dupping the module" do - m1 = Module.new { def calc(x) x end } - m2 = Module.new { prepend(m1) } - c1 = Class.new { prepend(m2) } - m2dup = m2.dup - m2dup.ancestors.should == [m1,m2dup] - c2 = Class.new { prepend(m2dup) } - c1.ancestors[0,3].should == [m1,m2,c1] - c1.new.should be_kind_of(m1) - c2.ancestors[0,3].should == [m1,m2dup,c2] - c2.new.should be_kind_of(m1) - end + it "uses only new module when dupping the module" do + m1 = Module.new { def calc(x) x end } + m2 = Module.new { prepend(m1) } + c1 = Class.new { prepend(m2) } + m2dup = m2.dup + m2dup.ancestors.should == [m1,m2dup] + c2 = Class.new { prepend(m2dup) } + c1.ancestors[0,3].should == [m1,m2,c1] + c1.new.should be_kind_of(m1) + c2.ancestors[0,3].should == [m1,m2dup,c2] + c2.new.should be_kind_of(m1) end it "depends on prepend_features to add the module" do @@ -240,6 +646,18 @@ describe "Module#prepend" do ScratchPad.recorded.should == [[:prepend_features, c], [:prepended, c]] end + it "prepends a module if it is included in a super class" do + module ModuleSpecs::M3 + module M; end + class A; include M; end + class B < A; prepend M; end + + all = [A, B, M] + + (B.ancestors.filter { |a| all.include?(a) }).should == [M, B, A, M] + end + end + it "detects cyclic prepends" do -> { module ModuleSpecs::P @@ -360,6 +778,33 @@ describe "Module#prepend" do ary.should == [3, 2, 1] end + it "does not prepend a second copy if the module already indirectly exists in the hierarchy" do + mod = Module.new do; end + submod = Module.new do; end + klass = Class.new do; end + klass.include(mod) + mod.prepend(submod) + klass.include(mod) + + klass.ancestors.take(4).should == [klass, submod, mod, Object] + end + + # https://bugs.ruby-lang.org/issues/17423 + describe "when module already exists in ancestor chain" do + it "modifies the ancestor chain" do + m = Module.new do; end + a = Module.new do; end + b = Class.new do; end + + b.include(a) + a.prepend(m) + b.ancestors.take(4).should == [b, m, a, Object] + + b.prepend(m) + b.ancestors.take(5).should == [m, b, m, a, Object] + end + end + describe "called on a module" do describe "included into a class" it "does not obscure the module's methods from reflective access" do diff --git a/spec/ruby/core/module/private_class_method_spec.rb b/spec/ruby/core/module/private_class_method_spec.rb index e35b50d986..f899c71a57 100644 --- a/spec/ruby/core/module/private_class_method_spec.rb +++ b/spec/ruby/core/module/private_class_method_spec.rb @@ -78,4 +78,14 @@ describe "Module#private_class_method" do end end.should raise_error(NameError) end + + context "when single argument is passed and is an array" do + it "sets the visibility of the given methods to private" do + c = Class.new do + def self.foo() "foo" end + private_class_method [:foo] + end + -> { c.foo }.should raise_error(NoMethodError) + end + end end diff --git a/spec/ruby/core/module/private_method_defined_spec.rb b/spec/ruby/core/module/private_method_defined_spec.rb index 515542d9df..01fc8f8fb9 100644 --- a/spec/ruby/core/module/private_method_defined_spec.rb +++ b/spec/ruby/core/module/private_method_defined_spec.rb @@ -31,7 +31,7 @@ describe "Module#private_method_defined?" do ModuleSpecs::CountsMixin.private_method_defined?(:private_3).should == true end - it "raises a TypeError if passed a Fixnum" do + it "raises a TypeError if passed an Integer" do -> do ModuleSpecs::CountsMixin.private_method_defined?(1) end.should raise_error(TypeError) @@ -70,53 +70,51 @@ describe "Module#private_method_defined?" do ModuleSpecs::CountsMixin.private_method_defined?(str).should == true end - ruby_version_is "2.6" do - describe "when passed true as a second optional argument" do - it "performs a lookup in ancestors" do - ModuleSpecs::Child.private_method_defined?(:public_child, true).should == false - ModuleSpecs::Child.private_method_defined?(:protected_child, true).should == false - ModuleSpecs::Child.private_method_defined?(:accessor_method, true).should == false - ModuleSpecs::Child.private_method_defined?(:private_child, true).should == true - - # Defined in Parent - ModuleSpecs::Child.private_method_defined?(:public_parent, true).should == false - ModuleSpecs::Child.private_method_defined?(:protected_parent, true).should == false - ModuleSpecs::Child.private_method_defined?(:private_parent, true).should == true - - # Defined in Module - ModuleSpecs::Child.private_method_defined?(:public_module, true).should == false - ModuleSpecs::Child.private_method_defined?(:protected_module, true).should == false - ModuleSpecs::Child.private_method_defined?(:private_module, true).should == true - - # Defined in SuperModule - ModuleSpecs::Child.private_method_defined?(:public_super_module, true).should == false - ModuleSpecs::Child.private_method_defined?(:protected_super_module, true).should == false - ModuleSpecs::Child.private_method_defined?(:private_super_module, true).should == true - end + describe "when passed true as a second optional argument" do + it "performs a lookup in ancestors" do + ModuleSpecs::Child.private_method_defined?(:public_child, true).should == false + ModuleSpecs::Child.private_method_defined?(:protected_child, true).should == false + ModuleSpecs::Child.private_method_defined?(:accessor_method, true).should == false + ModuleSpecs::Child.private_method_defined?(:private_child, true).should == true + + # Defined in Parent + ModuleSpecs::Child.private_method_defined?(:public_parent, true).should == false + ModuleSpecs::Child.private_method_defined?(:protected_parent, true).should == false + ModuleSpecs::Child.private_method_defined?(:private_parent, true).should == true + + # Defined in Module + ModuleSpecs::Child.private_method_defined?(:public_module, true).should == false + ModuleSpecs::Child.private_method_defined?(:protected_module, true).should == false + ModuleSpecs::Child.private_method_defined?(:private_module, true).should == true + + # Defined in SuperModule + ModuleSpecs::Child.private_method_defined?(:public_super_module, true).should == false + ModuleSpecs::Child.private_method_defined?(:protected_super_module, true).should == false + ModuleSpecs::Child.private_method_defined?(:private_super_module, true).should == true end + end - describe "when passed false as a second optional argument" do - it "checks only the class itself" do - ModuleSpecs::Child.private_method_defined?(:public_child, false).should == false - ModuleSpecs::Child.private_method_defined?(:protected_child, false).should == false - ModuleSpecs::Child.private_method_defined?(:accessor_method, false).should == false - ModuleSpecs::Child.private_method_defined?(:private_child, false).should == true - - # Defined in Parent - ModuleSpecs::Child.private_method_defined?(:public_parent, false).should == false - ModuleSpecs::Child.private_method_defined?(:protected_parent, false).should == false - ModuleSpecs::Child.private_method_defined?(:private_parent, false).should == false - - # Defined in Module - ModuleSpecs::Child.private_method_defined?(:public_module, false).should == false - ModuleSpecs::Child.private_method_defined?(:protected_module, false).should == false - ModuleSpecs::Child.private_method_defined?(:private_module, false).should == false - - # Defined in SuperModule - ModuleSpecs::Child.private_method_defined?(:public_super_module, false).should == false - ModuleSpecs::Child.private_method_defined?(:protected_super_module, false).should == false - ModuleSpecs::Child.private_method_defined?(:private_super_module, false).should == false - end + describe "when passed false as a second optional argument" do + it "checks only the class itself" do + ModuleSpecs::Child.private_method_defined?(:public_child, false).should == false + ModuleSpecs::Child.private_method_defined?(:protected_child, false).should == false + ModuleSpecs::Child.private_method_defined?(:accessor_method, false).should == false + ModuleSpecs::Child.private_method_defined?(:private_child, false).should == true + + # Defined in Parent + ModuleSpecs::Child.private_method_defined?(:public_parent, false).should == false + ModuleSpecs::Child.private_method_defined?(:protected_parent, false).should == false + ModuleSpecs::Child.private_method_defined?(:private_parent, false).should == false + + # Defined in Module + ModuleSpecs::Child.private_method_defined?(:public_module, false).should == false + ModuleSpecs::Child.private_method_defined?(:protected_module, false).should == false + ModuleSpecs::Child.private_method_defined?(:private_module, false).should == false + + # Defined in SuperModule + ModuleSpecs::Child.private_method_defined?(:public_super_module, false).should == false + ModuleSpecs::Child.private_method_defined?(:protected_super_module, false).should == false + ModuleSpecs::Child.private_method_defined?(:private_super_module, false).should == false end end end diff --git a/spec/ruby/core/module/private_spec.rb b/spec/ruby/core/module/private_spec.rb index e893c24f38..9e1a297eea 100644 --- a/spec/ruby/core/module/private_spec.rb +++ b/spec/ruby/core/module/private_spec.rb @@ -38,11 +38,13 @@ describe "Module#private" do :module_specs_public_method_on_object_for_kernel_private) end - it "returns self" do + it "returns argument or arguments if given" do (class << Object.new; self; end).class_eval do def foo; end - private(:foo).should equal(self) - private.should equal(self) + private(:foo).should equal(:foo) + private([:foo, :foo]).should == [:foo, :foo] + private(:foo, :foo).should == [:foo, :foo] + private.should equal(nil) end end diff --git a/spec/ruby/core/module/protected_method_defined_spec.rb b/spec/ruby/core/module/protected_method_defined_spec.rb index 7be7bdd3c9..31e24a16c1 100644 --- a/spec/ruby/core/module/protected_method_defined_spec.rb +++ b/spec/ruby/core/module/protected_method_defined_spec.rb @@ -31,7 +31,7 @@ describe "Module#protected_method_defined?" do ModuleSpecs::CountsMixin.protected_method_defined?(:protected_3).should == true end - it "raises a TypeError if passed a Fixnum" do + it "raises a TypeError if passed an Integer" do -> do ModuleSpecs::CountsMixin.protected_method_defined?(1) end.should raise_error(TypeError) @@ -70,53 +70,51 @@ describe "Module#protected_method_defined?" do ModuleSpecs::CountsMixin.protected_method_defined?(str).should == true end - ruby_version_is "2.6" do - describe "when passed true as a second optional argument" do - it "performs a lookup in ancestors" do - ModuleSpecs::Child.protected_method_defined?(:public_child, true).should == false - ModuleSpecs::Child.protected_method_defined?(:protected_child, true).should == true - ModuleSpecs::Child.protected_method_defined?(:accessor_method, true).should == false - ModuleSpecs::Child.protected_method_defined?(:private_child, true).should == false - - # Defined in Parent - ModuleSpecs::Child.protected_method_defined?(:public_parent, true).should == false - ModuleSpecs::Child.protected_method_defined?(:protected_parent, true).should == true - ModuleSpecs::Child.protected_method_defined?(:private_parent, true).should == false - - # Defined in Module - ModuleSpecs::Child.protected_method_defined?(:public_module, true).should == false - ModuleSpecs::Child.protected_method_defined?(:protected_module, true).should == true - ModuleSpecs::Child.protected_method_defined?(:private_module, true).should == false - - # Defined in SuperModule - ModuleSpecs::Child.protected_method_defined?(:public_super_module, true).should == false - ModuleSpecs::Child.protected_method_defined?(:protected_super_module, true).should == true - ModuleSpecs::Child.protected_method_defined?(:private_super_module, true).should == false - end + describe "when passed true as a second optional argument" do + it "performs a lookup in ancestors" do + ModuleSpecs::Child.protected_method_defined?(:public_child, true).should == false + ModuleSpecs::Child.protected_method_defined?(:protected_child, true).should == true + ModuleSpecs::Child.protected_method_defined?(:accessor_method, true).should == false + ModuleSpecs::Child.protected_method_defined?(:private_child, true).should == false + + # Defined in Parent + ModuleSpecs::Child.protected_method_defined?(:public_parent, true).should == false + ModuleSpecs::Child.protected_method_defined?(:protected_parent, true).should == true + ModuleSpecs::Child.protected_method_defined?(:private_parent, true).should == false + + # Defined in Module + ModuleSpecs::Child.protected_method_defined?(:public_module, true).should == false + ModuleSpecs::Child.protected_method_defined?(:protected_module, true).should == true + ModuleSpecs::Child.protected_method_defined?(:private_module, true).should == false + + # Defined in SuperModule + ModuleSpecs::Child.protected_method_defined?(:public_super_module, true).should == false + ModuleSpecs::Child.protected_method_defined?(:protected_super_module, true).should == true + ModuleSpecs::Child.protected_method_defined?(:private_super_module, true).should == false end + end - describe "when passed false as a second optional argument" do - it "checks only the class itself" do - ModuleSpecs::Child.protected_method_defined?(:public_child, false).should == false - ModuleSpecs::Child.protected_method_defined?(:protected_child, false).should == true - ModuleSpecs::Child.protected_method_defined?(:accessor_method, false).should == false - ModuleSpecs::Child.protected_method_defined?(:private_child, false).should == false - - # Defined in Parent - ModuleSpecs::Child.protected_method_defined?(:public_parent, false).should == false - ModuleSpecs::Child.protected_method_defined?(:protected_parent, false).should == false - ModuleSpecs::Child.protected_method_defined?(:private_parent, false).should == false - - # Defined in Module - ModuleSpecs::Child.protected_method_defined?(:public_module, false).should == false - ModuleSpecs::Child.protected_method_defined?(:protected_module, false).should == false - ModuleSpecs::Child.protected_method_defined?(:private_module, false).should == false - - # Defined in SuperModule - ModuleSpecs::Child.protected_method_defined?(:public_super_module, false).should == false - ModuleSpecs::Child.protected_method_defined?(:protected_super_module, false).should == false - ModuleSpecs::Child.protected_method_defined?(:private_super_module, false).should == false - end + describe "when passed false as a second optional argument" do + it "checks only the class itself" do + ModuleSpecs::Child.protected_method_defined?(:public_child, false).should == false + ModuleSpecs::Child.protected_method_defined?(:protected_child, false).should == true + ModuleSpecs::Child.protected_method_defined?(:accessor_method, false).should == false + ModuleSpecs::Child.protected_method_defined?(:private_child, false).should == false + + # Defined in Parent + ModuleSpecs::Child.protected_method_defined?(:public_parent, false).should == false + ModuleSpecs::Child.protected_method_defined?(:protected_parent, false).should == false + ModuleSpecs::Child.protected_method_defined?(:private_parent, false).should == false + + # Defined in Module + ModuleSpecs::Child.protected_method_defined?(:public_module, false).should == false + ModuleSpecs::Child.protected_method_defined?(:protected_module, false).should == false + ModuleSpecs::Child.protected_method_defined?(:private_module, false).should == false + + # Defined in SuperModule + ModuleSpecs::Child.protected_method_defined?(:public_super_module, false).should == false + ModuleSpecs::Child.protected_method_defined?(:protected_super_module, false).should == false + ModuleSpecs::Child.protected_method_defined?(:private_super_module, false).should == false end end end diff --git a/spec/ruby/core/module/protected_spec.rb b/spec/ruby/core/module/protected_spec.rb index aa04a42fb8..9e37223e18 100644 --- a/spec/ruby/core/module/protected_spec.rb +++ b/spec/ruby/core/module/protected_spec.rb @@ -39,11 +39,13 @@ describe "Module#protected" do :module_specs_public_method_on_object_for_kernel_protected) end - it "returns self" do + it "returns argument or arguments if given" do (class << Object.new; self; end).class_eval do def foo; end - protected(:foo).should equal(self) - protected.should equal(self) + protected(:foo).should equal(:foo) + protected([:foo, :foo]).should == [:foo, :foo] + protected(:foo, :foo).should == [:foo, :foo] + protected.should equal(nil) end end diff --git a/spec/ruby/core/module/public_class_method_spec.rb b/spec/ruby/core/module/public_class_method_spec.rb index c09cc64863..71b20acda5 100644 --- a/spec/ruby/core/module/public_class_method_spec.rb +++ b/spec/ruby/core/module/public_class_method_spec.rb @@ -77,4 +77,18 @@ describe "Module#public_class_method" do end end.should raise_error(NameError) end + + context "when single argument is passed and is an array" do + it "makes a class method public" do + c = Class.new do + class << self + private + def foo() "foo" end + end + public_class_method [:foo] + end + + c.foo.should == "foo" + end + end end diff --git a/spec/ruby/core/module/public_method_defined_spec.rb b/spec/ruby/core/module/public_method_defined_spec.rb index d2fcfd1e72..5c9bdf1ccc 100644 --- a/spec/ruby/core/module/public_method_defined_spec.rb +++ b/spec/ruby/core/module/public_method_defined_spec.rb @@ -31,7 +31,7 @@ describe "Module#public_method_defined?" do ModuleSpecs::CountsMixin.public_method_defined?(:public_3).should == true end - it "raises a TypeError if passed a Fixnum" do + it "raises a TypeError if passed an Integer" do -> do ModuleSpecs::CountsMixin.public_method_defined?(1) end.should raise_error(TypeError) diff --git a/spec/ruby/core/module/public_spec.rb b/spec/ruby/core/module/public_spec.rb index e7059f6aa6..ce31eb5d0e 100644 --- a/spec/ruby/core/module/public_spec.rb +++ b/spec/ruby/core/module/public_spec.rb @@ -27,12 +27,13 @@ describe "Module#public" do :module_specs_private_method_on_object_for_kernel_public) end - it "returns self" do + it "returns argument or arguments if given" do (class << Object.new; self; end).class_eval do def foo; end - private :foo - public(:foo).should equal(self) - public.should equal(self) + public(:foo).should equal(:foo) + public([:foo, :foo]).should == [:foo, :foo] + public(:foo, :foo).should == [:foo, :foo] + public.should equal(nil) end end diff --git a/spec/ruby/core/module/refine_spec.rb b/spec/ruby/core/module/refine_spec.rb index ca12d5d13b..d219b98825 100644 --- a/spec/ruby/core/module/refine_spec.rb +++ b/spec/ruby/core/module/refine_spec.rb @@ -71,7 +71,7 @@ describe "Module#refine" do Module.new do refine("foo") {} end - end.should raise_error(TypeError) + end.should raise_error(TypeError, "wrong argument type String (expected Class or Module)") end it "accepts a module as argument" do @@ -243,34 +243,10 @@ describe "Module#refine" do result.should == "foo from singleton class" end - it "looks in the included modules for builtin methods" do - result = ruby_exe(<<-RUBY) - a = Module.new do - def /(other) quo(other) end - end - - refinement = Module.new do - refine Integer do - include a - end - end - - result = nil - Module.new do - using refinement - result = 1 / 2 - end - - print result.class - RUBY - - result.should == 'Rational' - end - it "looks in later included modules of the refined module first" do a = Module.new do def foo - "foo from A" + "foo from A" end end @@ -298,65 +274,6 @@ describe "Module#refine" do result.should == "foo from IncludeMeLater" end - it "looks in prepended modules from the refinement first" do - refined_class = ModuleSpecs.build_refined_class - - refinement = Module.new do - refine refined_class do - include ModuleSpecs::IncludedModule - prepend ModuleSpecs::PrependedModule - - def foo; "foo from refinement"; end - end - end - - result = nil - Module.new do - using refinement - result = refined_class.new.foo - end - - result.should == "foo from prepended module" - end - - it "looks in refinement then" do - refined_class = ModuleSpecs.build_refined_class - - refinement = Module.new do - refine(refined_class) do - include ModuleSpecs::IncludedModule - - def foo; "foo from refinement"; end - end - end - - result = nil - Module.new do - using refinement - result = refined_class.new.foo - end - - result.should == "foo from refinement" - end - - it "looks in included modules from the refinement then" do - refined_class = ModuleSpecs.build_refined_class - - refinement = Module.new do - refine refined_class do - include ModuleSpecs::IncludedModule - end - end - - result = nil - Module.new do - using refinement - result = refined_class.new.foo - end - - result.should == "foo from included module" - end - it "looks in the class then" do refined_class = ModuleSpecs.build_refined_class @@ -453,44 +370,22 @@ describe "Module#refine" do result.should == ["(1)", "(2)", "(3)"] end - ruby_version_is "" ... "2.6" do - it "is not honored by Kernel#public_send" do - refined_class = ModuleSpecs.build_refined_class - - refinement = Module.new do - refine refined_class do - def foo; "foo from refinement"; end - end - end + it "is honored by Kernel#public_send" do + refined_class = ModuleSpecs.build_refined_class - result = nil - Module.new do - using refinement - result = refined_class.new.public_send :foo + refinement = Module.new do + refine refined_class do + def foo; "foo from refinement"; end end - - result.should == "foo" end - end - ruby_version_is "2.6" do - it "is honored by Kernel#public_send" do - refined_class = ModuleSpecs.build_refined_class - - refinement = Module.new do - refine refined_class do - def foo; "foo from refinement"; end - end - end - - result = nil - Module.new do - using refinement - result = refined_class.new.public_send :foo - end - - result.should == "foo from refinement" + result = nil + Module.new do + using refinement + result = refined_class.new.public_send :foo end + + result.should == "foo from refinement" end it "is honored by string interpolation" do @@ -536,166 +431,99 @@ describe "Module#refine" do result.should == "hello from refinement" end - ruby_version_is "" ... "2.7" do - it "is not honored by Kernel#method" do - klass = Class.new - refinement = Module.new do - refine klass do - def foo; end - end + it "is honored by Kernel#method" do + klass = Class.new + refinement = Module.new do + refine klass do + def foo; end end - - -> { - Module.new do - using refinement - klass.new.method(:foo) - end - }.should raise_error(NameError, /undefined method `foo'/) end - end - ruby_version_is "2.7" do - it "is honored by Kernel#method" do - klass = Class.new - refinement = Module.new do - refine klass do - def foo; end - end - end - - result = nil - Module.new do - using refinement - result = klass.new.method(:foo).class - end - - result.should == Method + result = nil + Module.new do + using refinement + result = klass.new.method(:foo).class end - end - - ruby_version_is "" ... "2.7" do - it "is not honored by Kernel#instance_method" do - klass = Class.new - refinement = Module.new do - refine klass do - def foo; end - end - end - -> { - Module.new do - using refinement - klass.instance_method(:foo) - end - }.should raise_error(NameError, /undefined method `foo'/) - end + result.should == Method end - ruby_version_is "2.7" do - it "is honored by Kernel#method" do - klass = Class.new - refinement = Module.new do - refine klass do - def foo; end - end - end - - result = nil - Module.new do - using refinement - result = klass.instance_method(:foo).class + it "is honored by Kernel#public_method" do + klass = Class.new + refinement = Module.new do + refine klass do + def foo; end end - - result.should == UnboundMethod end - end - ruby_version_is "" ... "2.6" do - it "is not honored by Kernel#respond_to?" do - klass = Class.new - refinement = Module.new do - refine klass do - def foo; end - end - end - - result = nil - Module.new do - using refinement - result = klass.new.respond_to?(:foo) - end - - result.should == false + result = nil + Module.new do + using refinement + result = klass.new.public_method(:foo).class end - end - ruby_version_is "2.6" do - it "is honored by Kernel#respond_to?" do - klass = Class.new - refinement = Module.new do - refine klass do - def foo; end - end - end + result.should == Method + end - result = nil - Module.new do - using refinement - result = klass.new.respond_to?(:foo) + it "is honored by Kernel#instance_method" do + klass = Class.new + refinement = Module.new do + refine klass do + def foo; end end + end - result.should == true + result = nil + Module.new do + using refinement + result = klass.instance_method(:foo).class end + + result.should == UnboundMethod end - ruby_version_is ""..."2.6" do - it "is not honored by &" do - refinement = Module.new do - refine String do - def to_proc(*args) - -> * { 'foo' } - end - end + it "is honored by Kernel#respond_to?" do + klass = Class.new + refinement = Module.new do + refine klass do + def foo; end end + end - -> do - Module.new do - using refinement - ["hola"].map(&"upcase") - end - end.should raise_error(TypeError, /wrong argument type String \(expected Proc\)/) + result = nil + Module.new do + using refinement + result = klass.new.respond_to?(:foo) end + + result.should == true end - ruby_version_is "2.6" do - it "is honored by &" do - refinement = Module.new do - refine String do - def to_proc(*args) - -> * { 'foo' } - end + it "is honored by &" do + refinement = Module.new do + refine String do + def to_proc(*args) + -> * { 'foo' } end end + end - result = nil - Module.new do - using refinement - result = ["hola"].map(&"upcase") - end - - result.should == ['foo'] + result = nil + Module.new do + using refinement + result = ["hola"].map(&"upcase") end + + result.should == ['foo'] end end context "when super is called in a refinement" do - it "looks in the included to refinery module" do + it "looks in the refined class" do refined_class = ModuleSpecs.build_refined_class refinement = Module.new do refine refined_class do - include ModuleSpecs::IncludedModule - def foo super end @@ -708,16 +536,27 @@ describe "Module#refine" do result = refined_class.new.foo end - result.should == "foo from included module" + result.should == "foo" end - it "looks in the refined class" do - refined_class = ModuleSpecs.build_refined_class + # super in a method of a refinement invokes the method in the refined + # class even if there is another refinement which has been activated + # in the same context. + it "looks in the refined class first if called from refined method" do + refined_class = ModuleSpecs.build_refined_class(for_super: true) refinement = Module.new do refine refined_class do def foo - super + [:R1] + end + end + end + + refinement_with_super = Module.new do + refine refined_class do + def foo + [:R2] + super end end end @@ -725,42 +564,40 @@ describe "Module#refine" do result = nil Module.new do using refinement + using refinement_with_super result = refined_class.new.foo end - result.should == "foo" + result.should == [:R2, :C] end - # super in a method of a refinement invokes the method in the refined - # class even if there is another refinement which has been activated - # in the same context. - it "looks in the refined class even if there is another active refinement" do - refined_class = ModuleSpecs.build_refined_class + it "looks only in the refined class even if there is another active refinement" do + refined_class = ModuleSpecs.build_refined_class(for_super: true) refinement = Module.new do refine refined_class do - def foo - "foo from refinement" + def bar + "you cannot see me from super because I belong to another active R" end end end refinement_with_super = Module.new do refine refined_class do - def foo + def bar super end end end - result = nil + Module.new do using refinement using refinement_with_super - result = refined_class.new.foo + -> { + refined_class.new.bar + }.should raise_error(NoMethodError) end - - result.should == "foo" end end diff --git a/spec/ruby/core/module/refinements_spec.rb b/spec/ruby/core/module/refinements_spec.rb new file mode 100644 index 0000000000..05658a8b0e --- /dev/null +++ b/spec/ruby/core/module/refinements_spec.rb @@ -0,0 +1,43 @@ +require_relative '../../spec_helper' + +describe "Module#refinements" do + it "returns refinements defined in a module" do + ScratchPad.record [] + + m = Module.new do + refine String do + ScratchPad << self + end + + refine Array do + ScratchPad << self + end + end + + m.refinements.sort_by(&:object_id).should == ScratchPad.recorded.sort_by(&:object_id) + end + + it "does not return refinements defined in the included module" do + ScratchPad.record [] + + m1 = Module.new do + refine Integer do + nil + end + end + + m2 = Module.new do + include m1 + + refine String do + ScratchPad << self + end + end + + m2.refinements.should == ScratchPad.recorded + end + + it "returns an empty array if no refinements defined in a module" do + Module.new.refinements.should == [] + end +end diff --git a/spec/ruby/core/module/remove_const_spec.rb b/spec/ruby/core/module/remove_const_spec.rb index 20eb2dc3ed..35a9d65105 100644 --- a/spec/ruby/core/module/remove_const_spec.rb +++ b/spec/ruby/core/module/remove_const_spec.rb @@ -81,4 +81,27 @@ describe "Module#remove_const" do ConstantSpecs.autoload :AutoloadedConstant, 'a_file' ConstantSpecs.send(:remove_const, :AutoloadedConstant).should be_nil end + + it "updates the constant value" do + module ConstantSpecs::RemovedConstantUpdate + module M + FOO = 'm' + end + + module A + include M + FOO = 'a' + def self.foo + FOO + end + end + + A.foo.should == 'a' + + A.send(:remove_const,:FOO) + A.foo.should == 'm' + end + ensure + ConstantSpecs.send(:remove_const, :RemovedConstantUpdate) + end end diff --git a/spec/ruby/core/module/remove_method_spec.rb b/spec/ruby/core/module/remove_method_spec.rb index b6ae02078f..94b255df62 100644 --- a/spec/ruby/core/module/remove_method_spec.rb +++ b/spec/ruby/core/module/remove_method_spec.rb @@ -43,6 +43,28 @@ describe "Module#remove_method" do x.method_to_remove.should == 1 end + it "updates the method implementation" do + m_module = Module.new do + def foo + 'm' + end + end + + a_class = Class.new do + include m_module + + def foo + 'a' + end + end + + a = a_class.new + foo = -> { a.foo } + foo.call.should == 'a' + a_class.remove_method(:foo) + foo.call.should == 'm' + end + it "removes multiple methods with 1 call" do klass = Class.new do def method_to_remove_1; 1; end diff --git a/spec/ruby/core/module/ruby2_keywords_spec.rb b/spec/ruby/core/module/ruby2_keywords_spec.rb new file mode 100644 index 0000000000..652f9f7083 --- /dev/null +++ b/spec/ruby/core/module/ruby2_keywords_spec.rb @@ -0,0 +1,248 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Module#ruby2_keywords" do + class << self + ruby2_keywords def mark(*args) + args + end + end + + it "marks the final hash argument as keyword hash" do + last = mark(1, 2, a: "a").last + Hash.ruby2_keywords_hash?(last).should == true + end + + it "makes a copy of the hash and only marks the copy as keyword hash" do + obj = Object.new + obj.singleton_class.class_exec do + def regular(*args) + args.last + end + end + + h = {a: 1} + + last = mark(**h).last + Hash.ruby2_keywords_hash?(last).should == true + Hash.ruby2_keywords_hash?(h).should == false + + last2 = mark(**last).last # last is already marked + Hash.ruby2_keywords_hash?(last2).should == true + Hash.ruby2_keywords_hash?(last).should == true + last2.should_not.equal?(last) + Hash.ruby2_keywords_hash?(h).should == false + end + + it "makes a copy and unmark the Hash when calling a method taking (arg)" do + obj = Object.new + obj.singleton_class.class_exec do + def single(arg) + arg + end + end + + h = { a: 1 } + args = mark(**h) + marked = args.last + Hash.ruby2_keywords_hash?(marked).should == true + + after_usage = obj.single(*args) + after_usage.should == h + after_usage.should_not.equal?(h) + after_usage.should_not.equal?(marked) + Hash.ruby2_keywords_hash?(after_usage).should == false + Hash.ruby2_keywords_hash?(marked).should == true + end + + it "makes a copy and unmark the Hash when calling a method taking (**kw)" do + obj = Object.new + obj.singleton_class.class_exec do + def kwargs(**kw) + kw + end + end + + h = { a: 1 } + args = mark(**h) + marked = args.last + Hash.ruby2_keywords_hash?(marked).should == true + + after_usage = obj.kwargs(*args) + after_usage.should == h + after_usage.should_not.equal?(h) + after_usage.should_not.equal?(marked) + Hash.ruby2_keywords_hash?(after_usage).should == false + Hash.ruby2_keywords_hash?(marked).should == true + end + + it "makes a copy and unmark the Hash when calling a method taking (*args)" do + obj = Object.new + obj.singleton_class.class_exec do + def splat(*args) + args.last + end + + def splat1(arg, *args) + args.last + end + + def proc_call(*args) + -> *a { a.last }.call(*args) + end + end + + h = { a: 1 } + args = mark(**h) + marked = args.last + Hash.ruby2_keywords_hash?(marked).should == true + + after_usage = obj.splat(*args) + after_usage.should == h + after_usage.should_not.equal?(h) + after_usage.should_not.equal?(marked) + Hash.ruby2_keywords_hash?(after_usage).should == false + Hash.ruby2_keywords_hash?(marked).should == true + + args = mark(1, **h) + marked = args.last + after_usage = obj.splat1(*args) + after_usage.should == h + after_usage.should_not.equal?(h) + after_usage.should_not.equal?(marked) + Hash.ruby2_keywords_hash?(after_usage).should == false + Hash.ruby2_keywords_hash?(marked).should == true + + args = mark(**h) + marked = args.last + after_usage = obj.proc_call(*args) + after_usage.should == h + after_usage.should_not.equal?(h) + after_usage.should_not.equal?(marked) + Hash.ruby2_keywords_hash?(after_usage).should == false + Hash.ruby2_keywords_hash?(marked).should == true + + args = mark(**h) + marked = args.last + after_usage = obj.send(:splat, *args) + after_usage.should == h + after_usage.should_not.equal?(h) + after_usage.should_not.equal?(marked) + Hash.ruby2_keywords_hash?(after_usage).should == false + Hash.ruby2_keywords_hash?(marked).should == true + end + + it "applies to the underlying method and applies across aliasing" do + obj = Object.new + + obj.singleton_class.class_exec do + def foo(*a) a.last end + alias_method :bar, :foo + ruby2_keywords :foo + + def baz(*a) a.last end + ruby2_keywords :baz + alias_method :bob, :baz + end + + last = obj.foo(1, 2, a: "a") + Hash.ruby2_keywords_hash?(last).should == true + + last = obj.bar(1, 2, a: "a") + Hash.ruby2_keywords_hash?(last).should == true + + last = obj.baz(1, 2, a: "a") + Hash.ruby2_keywords_hash?(last).should == true + + last = obj.bob(1, 2, a: "a") + Hash.ruby2_keywords_hash?(last).should == true + end + + it "returns nil" do + obj = Object.new + + obj.singleton_class.class_exec do + def foo(*a) end + + ruby2_keywords(:foo).should == nil + end + end + + it "raises NameError when passed not existing method name" do + obj = Object.new + + -> { + obj.singleton_class.class_exec do + ruby2_keywords :not_existing + end + }.should raise_error(NameError, /undefined method [`']not_existing'/) + end + + it "accepts String as well" do + obj = Object.new + + obj.singleton_class.class_exec do + def foo(*a) a.last end + ruby2_keywords "foo" + end + + last = obj.foo(1, 2, a: "a") + Hash.ruby2_keywords_hash?(last).should == true + end + + it "raises TypeError when passed not Symbol or String" do + obj = Object.new + + -> { + obj.singleton_class.class_exec do + ruby2_keywords Object.new + end + }.should raise_error(TypeError, /is not a symbol nor a string/) + end + + it "prints warning when a method does not accept argument splat" do + obj = Object.new + def obj.foo(a, b, c) end + + -> { + obj.singleton_class.class_exec do + ruby2_keywords :foo + end + }.should complain(/Skipping set of ruby2_keywords flag for/) + end + + it "prints warning when a method accepts keywords" do + obj = Object.new + def obj.foo(*a, b:) end + + -> { + obj.singleton_class.class_exec do + ruby2_keywords :foo + end + }.should complain(/Skipping set of ruby2_keywords flag for/) + end + + it "prints warning when a method accepts keyword splat" do + obj = Object.new + def obj.foo(*a, **b) end + + -> { + obj.singleton_class.class_exec do + ruby2_keywords :foo + end + }.should complain(/Skipping set of ruby2_keywords flag for/) + end + + ruby_version_is "4.0" do + it "prints warning when a method accepts post arguments" do + obj = Object.new + def obj.foo(*a, b) end + + -> { + obj.singleton_class.class_exec do + ruby2_keywords :foo + end + }.should complain(/Skipping set of ruby2_keywords flag for/) + end + end +end diff --git a/spec/ruby/core/module/set_temporary_name_spec.rb b/spec/ruby/core/module/set_temporary_name_spec.rb new file mode 100644 index 0000000000..46605ed675 --- /dev/null +++ b/spec/ruby/core/module/set_temporary_name_spec.rb @@ -0,0 +1,147 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/set_temporary_name' + +ruby_version_is "3.3" do + describe "Module#set_temporary_name" do + it "can assign a temporary name" do + m = Module.new + m.name.should be_nil + + m.set_temporary_name("fake_name") + m.name.should == "fake_name" + + m.set_temporary_name(nil) + m.name.should be_nil + end + + it "returns self" do + m = Module.new + m.set_temporary_name("fake_name").should.equal? m + end + + it "can assign a temporary name which is not a valid constant path" do + m = Module.new + + m.set_temporary_name("name") + m.name.should == "name" + + m.set_temporary_name("Template['foo.rb']") + m.name.should == "Template['foo.rb']" + + m.set_temporary_name("a::B") + m.name.should == "a::B" + + m.set_temporary_name("A::b") + m.name.should == "A::b" + + m.set_temporary_name("A::B::") + m.name.should == "A::B::" + + m.set_temporary_name("A::::B") + m.name.should == "A::::B" + + m.set_temporary_name("A=") + m.name.should == "A=" + end + + it "can't assign empty string as name" do + m = Module.new + -> { m.set_temporary_name("") }.should raise_error(ArgumentError, "empty class/module name") + end + + it "can't assign a constant name as a temporary name" do + m = Module.new + -> { m.set_temporary_name("Object") }.should raise_error(ArgumentError, "the temporary name must not be a constant path to avoid confusion") + end + + it "can't assign a constant path as a temporary name" do + m = Module.new + -> { m.set_temporary_name("A::B") }.should raise_error(ArgumentError, "the temporary name must not be a constant path to avoid confusion") + -> { m.set_temporary_name("::A") }.should raise_error(ArgumentError, "the temporary name must not be a constant path to avoid confusion") + -> { m.set_temporary_name("::A::B") }.should raise_error(ArgumentError, "the temporary name must not be a constant path to avoid confusion") + end + + it "can't assign name to permanent module" do + -> { Object.set_temporary_name("fake_name") }.should raise_error(RuntimeError, "can't change permanent name") + end + + it "can assign a temporary name to a module nested into an anonymous module" do + m = Module.new + module m::N; end + m::N.name.should =~ /\A#<Module:0x\h+>::N\z/ + + m::N.set_temporary_name("fake_name") + m::N.name.should == "fake_name" + + m::N.set_temporary_name(nil) + m::N.name.should be_nil + end + + it "discards a temporary name when an outer anonymous module gets a permanent name" do + m = Module.new + module m::N; end + + m::N.set_temporary_name("fake_name") + m::N.name.should == "fake_name" + + ModuleSpecs::SetTemporaryNameSpec::M = m + m::N.name.should == "ModuleSpecs::SetTemporaryNameSpec::M::N" + ModuleSpecs::SetTemporaryNameSpec.send :remove_const, :M + end + + it "can update the name when assigned to a constant" do + m = Module.new + m::N = Module.new + m::N.name.should =~ /\A#<Module:0x\h+>::N\z/ + m::N.set_temporary_name(nil) + + m::M = m::N + m::M.name.should =~ /\A#<Module:0x\h+>::M\z/m + end + + it "can reassign a temporary name repeatedly" do + m = Module.new + + m.set_temporary_name("fake_name") + m.name.should == "fake_name" + + m.set_temporary_name("fake_name_2") + m.name.should == "fake_name_2" + end + + ruby_bug "#21094", ""..."4.0" do + it "also updates a name of a nested module" do + m = Module.new + m::N = Module.new + m::N.name.should =~ /\A#<Module:0x\h+>::N\z/ + + m.set_temporary_name "m" + m::N.name.should == "m::N" + + m.set_temporary_name nil + m::N.name.should == nil + end + end + + it "keeps temporary name when assigned in an anonymous module" do + outer = Module.new + m = Module.new + m.set_temporary_name "m" + m.name.should == "m" + outer::M = m + m.name.should == "m" + m.inspect.should == "m" + end + + it "keeps temporary name when assigned in an anonymous module and nested before" do + outer = Module.new + m = Module.new + outer::A = m + m.set_temporary_name "m" + m.name.should == "m" + outer::M = m + m.name.should == "m" + m.inspect.should == "m" + end + end +end diff --git a/spec/ruby/core/module/shared/attr_added.rb b/spec/ruby/core/module/shared/attr_added.rb new file mode 100644 index 0000000000..ce832cdcff --- /dev/null +++ b/spec/ruby/core/module/shared/attr_added.rb @@ -0,0 +1,34 @@ +describe :module_attr_added, shared: true do + it "calls method_added for normal classes" do + ScratchPad.record [] + + cls = Class.new do + class << self + def method_added(name) + ScratchPad.recorded << name + end + end + end + + cls.send(@method, :foo) + + ScratchPad.recorded.each {|name| name.to_s.should =~ /foo[=]?/} + end + + it "calls singleton_method_added for singleton classes" do + ScratchPad.record [] + cls = Class.new do + class << self + def singleton_method_added(name) + # called for this def so ignore it + return if name == :singleton_method_added + ScratchPad.recorded << name + end + end + end + + cls.singleton_class.send(@method, :foo) + + ScratchPad.recorded.each {|name| name.to_s.should =~ /foo[=]?/} + end +end diff --git a/spec/ruby/core/module/shared/class_eval.rb b/spec/ruby/core/module/shared/class_eval.rb index 224078ae54..b1d5cb3814 100644 --- a/spec/ruby/core/module/shared/class_eval.rb +++ b/spec/ruby/core/module/shared/class_eval.rb @@ -52,43 +52,58 @@ describe :module_class_eval, shared: true do ModuleSpecs.send(@method, "[__FILE__, __LINE__]", "test", 102).should == ["test", 102] end + ruby_version_is "3.3" do + it "uses the caller location as default filename" do + ModuleSpecs.send(@method, "[__FILE__, __LINE__]").should == ["(eval at #{__FILE__}:#{__LINE__})", 1] + end + end + it "converts a non-string filename to a string using to_str" do (file = mock(__FILE__)).should_receive(:to_str).and_return(__FILE__) ModuleSpecs.send(@method, "1+1", file) + + (file = mock(__FILE__)).should_receive(:to_str).and_return(__FILE__) + ModuleSpecs.send(@method, "1+1", file, 15) end it "raises a TypeError when the given filename can't be converted to string using to_str" do (file = mock('123')).should_receive(:to_str).and_return(123) - -> { ModuleSpecs.send(@method, "1+1", file) }.should raise_error(TypeError) + -> { ModuleSpecs.send(@method, "1+1", file) }.should raise_error(TypeError, /can't convert MockObject to String/) end it "converts non string eval-string to string using to_str" do (o = mock('1 + 1')).should_receive(:to_str).and_return("1 + 1") ModuleSpecs.send(@method, o).should == 2 + + (o = mock('1 + 1')).should_receive(:to_str).and_return("1 + 1") + ModuleSpecs.send(@method, o, "file.rb").should == 2 + + (o = mock('1 + 1')).should_receive(:to_str).and_return("1 + 1") + ModuleSpecs.send(@method, o, "file.rb", 15).should == 2 end it "raises a TypeError when the given eval-string can't be converted to string using to_str" do o = mock('x') - -> { ModuleSpecs.send(@method, o) }.should raise_error(TypeError) + -> { ModuleSpecs.send(@method, o) }.should raise_error(TypeError, "no implicit conversion of MockObject into String") (o = mock('123')).should_receive(:to_str).and_return(123) - -> { ModuleSpecs.send(@method, o) }.should raise_error(TypeError) + -> { ModuleSpecs.send(@method, o) }.should raise_error(TypeError, /can't convert MockObject to String/) end it "raises an ArgumentError when no arguments and no block are given" do - -> { ModuleSpecs.send(@method) }.should raise_error(ArgumentError) + -> { ModuleSpecs.send(@method) }.should raise_error(ArgumentError, "wrong number of arguments (given 0, expected 1..3)") end it "raises an ArgumentError when more than 3 arguments are given" do -> { ModuleSpecs.send(@method, "1 + 1", "some file", 0, "bogus") - }.should raise_error(ArgumentError) + }.should raise_error(ArgumentError, "wrong number of arguments (given 4, expected 1..3)") end it "raises an ArgumentError when a block and normal arguments are given" do -> { ModuleSpecs.send(@method, "1 + 1") { 1 + 1 } - }.should raise_error(ArgumentError) + }.should raise_error(ArgumentError, "wrong number of arguments (given 1, expected 0)") end # This case was found because Rubinius was caching the compiled diff --git a/spec/ruby/core/module/shared/set_visibility.rb b/spec/ruby/core/module/shared/set_visibility.rb index a04b1a54a0..a1586dd2bd 100644 --- a/spec/ruby/core/module/shared/set_visibility.rb +++ b/spec/ruby/core/module/shared/set_visibility.rb @@ -6,6 +6,38 @@ describe :set_visibility, shared: true do end describe "with argument" do + describe "one or more arguments" do + it "sets visibility of given method names" do + visibility = @method + old_visibility = [:protected, :private].find {|vis| vis != visibility } + + mod = Module.new { + send old_visibility + def test1() end + def test2() end + send visibility, :test1, :test2 + } + mod.should send(:"have_#{visibility}_instance_method", :test1, false) + mod.should send(:"have_#{visibility}_instance_method", :test2, false) + end + end + + describe "array as a single argument" do + it "sets visibility of given method names" do + visibility = @method + old_visibility = [:protected, :private].find {|vis| vis != visibility } + + mod = Module.new { + send old_visibility + def test1() end + def test2() end + send visibility, [:test1, :test2] + } + mod.should send(:"have_#{visibility}_instance_method", :test1, false) + mod.should send(:"have_#{visibility}_instance_method", :test2, false) + end + end + it "does not clone method from the ancestor when setting to the same visibility in a child" do visibility = @method parent = Module.new { diff --git a/spec/ruby/core/module/to_s_spec.rb b/spec/ruby/core/module/to_s_spec.rb index 29f6ecf726..83c0ae0825 100644 --- a/spec/ruby/core/module/to_s_spec.rb +++ b/spec/ruby/core/module/to_s_spec.rb @@ -42,4 +42,29 @@ describe "Module#to_s" do obj = ModuleSpecs::NamedClass.new obj.singleton_class.to_s.should =~ /\A#<Class:#<ModuleSpecs::NamedClass:0x\h+>>\z/ end + + it "always show the refinement name, even if the module is named" do + module ModuleSpecs::RefinementInspect + R = refine String do + end + end + + ModuleSpecs::RefinementInspect::R.name.should == 'ModuleSpecs::RefinementInspect::R' + ModuleSpecs::RefinementInspect::R.to_s.should == '#<refinement:String@ModuleSpecs::RefinementInspect>' + ensure + ModuleSpecs.send(:remove_const, :RefinementInspect) + end + + it 'does not call #inspect or #to_s for singleton classes' do + klass = Class.new + obj = klass.new + def obj.to_s + "to_s" + end + def obj.inspect + "inspect" + end + sclass = obj.singleton_class + sclass.to_s.should =~ /\A#<Class:#<#{Regexp.escape klass.to_s}:0x\h+>>\z/ + end end diff --git a/spec/ruby/core/module/undef_method_spec.rb b/spec/ruby/core/module/undef_method_spec.rb index c2ad200536..d4efcd51cb 100644 --- a/spec/ruby/core/module/undef_method_spec.rb +++ b/spec/ruby/core/module/undef_method_spec.rb @@ -50,7 +50,7 @@ describe "Module#undef_method" do end it "raises a NameError when passed a missing name for a module" do - -> { @module.send :undef_method, :not_exist }.should raise_error(NameError, /undefined method `not_exist' for module `#{@module}'/) { |e| + -> { @module.send :undef_method, :not_exist }.should raise_error(NameError, /undefined method [`']not_exist' for module [`']#{@module}'/) { |e| # a NameError and not a NoMethodError e.class.should == NameError } @@ -58,7 +58,7 @@ describe "Module#undef_method" do it "raises a NameError when passed a missing name for a class" do klass = Class.new - -> { klass.send :undef_method, :not_exist }.should raise_error(NameError, /undefined method `not_exist' for class `#{klass}'/) { |e| + -> { klass.send :undef_method, :not_exist }.should raise_error(NameError, /undefined method [`']not_exist' for class [`']#{klass}'/) { |e| # a NameError and not a NoMethodError e.class.should == NameError } @@ -69,8 +69,8 @@ describe "Module#undef_method" do obj = klass.new sclass = obj.singleton_class - -> { sclass.send :undef_method, :not_exist }.should raise_error(NameError, /undefined method `not_exist' for class `#{sclass}'/) { |e| - e.message.should include('`#<Class:#<#<Class:') + -> { sclass.send :undef_method, :not_exist }.should raise_error(NameError, /undefined method [`']not_exist' for class [`']#{sclass}'/) { |e| + e.message.should =~ /[`']#<Class:#<#<Class:/ # a NameError and not a NoMethodError e.class.should == NameError @@ -79,7 +79,7 @@ describe "Module#undef_method" do it "raises a NameError when passed a missing name for a metaclass" do klass = String.singleton_class - -> { klass.send :undef_method, :not_exist }.should raise_error(NameError, /undefined method `not_exist' for class `String'/) { |e| + -> { klass.send :undef_method, :not_exist }.should raise_error(NameError, /undefined method [`']not_exist' for class [`']String'/) { |e| # a NameError and not a NoMethodError e.class.should == NameError } diff --git a/spec/ruby/core/module/undefined_instance_methods_spec.rb b/spec/ruby/core/module/undefined_instance_methods_spec.rb new file mode 100644 index 0000000000..d33ee93fc1 --- /dev/null +++ b/spec/ruby/core/module/undefined_instance_methods_spec.rb @@ -0,0 +1,24 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Module#undefined_instance_methods" do + it "returns methods undefined in the class" do + methods = ModuleSpecs::UndefinedInstanceMethods::Parent.undefined_instance_methods + methods.should == [:undefed_method] + end + + it "returns inherited methods undefined in the class" do + methods = ModuleSpecs::UndefinedInstanceMethods::Child.undefined_instance_methods + methods.should include(:parent_method, :another_parent_method) + end + + it "returns methods from an included module that are undefined in the class" do + methods = ModuleSpecs::UndefinedInstanceMethods::Grandchild.undefined_instance_methods + methods.should include(:super_included_method) + end + + it "does not returns ancestors undefined methods" do + methods = ModuleSpecs::UndefinedInstanceMethods::Grandchild.undefined_instance_methods + methods.should_not include(:parent_method, :another_parent_method) + end +end diff --git a/spec/ruby/core/module/used_refinements_spec.rb b/spec/ruby/core/module/used_refinements_spec.rb new file mode 100644 index 0000000000..40dd4a444e --- /dev/null +++ b/spec/ruby/core/module/used_refinements_spec.rb @@ -0,0 +1,85 @@ +require_relative '../../spec_helper' + +describe "Module.used_refinements" do + it "returns list of all refinements imported in the current scope" do + refinement_int = nil + refinement_str = nil + ScratchPad.record [] + + m1 = Module.new do + refine Integer do + refinement_int = self + end + end + + m2 = Module.new do + refine String do + refinement_str = self + end + end + + Module.new do + using m1 + using m2 + + Module.used_refinements.each { |r| ScratchPad << r } + end + + ScratchPad.recorded.sort_by(&:object_id).should == [refinement_int, refinement_str].sort_by(&:object_id) + end + + it "returns empty array if does not have any refinements imported" do + used_refinements = nil + + Module.new do + used_refinements = Module.used_refinements + end + + used_refinements.should == [] + end + + it "ignores refinements imported in a module that is included into the current one" do + used_refinements = nil + + m1 = Module.new do + refine Integer do + nil + end + end + + m2 = Module.new do + using m1 + end + + Module.new do + include m2 + + used_refinements = Module.used_refinements + end + + used_refinements.should == [] + end + + it "returns refinements even not defined directly in a module refinements are imported from" do + used_refinements = nil + ScratchPad.record [] + + m1 = Module.new do + refine Integer do + ScratchPad << self + end + end + + m2 = Module.new do + include m1 + end + + Module.new do + using m2 + + used_refinements = Module.used_refinements + end + + used_refinements.should == ScratchPad.recorded + end +end diff --git a/spec/ruby/core/module/using_spec.rb b/spec/ruby/core/module/using_spec.rb index 4781b99bb7..a908363c96 100644 --- a/spec/ruby/core/module/using_spec.rb +++ b/spec/ruby/core/module/using_spec.rb @@ -316,7 +316,7 @@ describe "Module#using" do using refinement def initialize - @a = "1703" + @a = +"1703" @a.instance_eval do def abc |
