diff options
Diffstat (limited to 'spec/ruby/core/module')
119 files changed, 4955 insertions, 1051 deletions
diff --git a/spec/ruby/core/module/alias_method_spec.rb b/spec/ruby/core/module/alias_method_spec.rb index d3c0529418..c36dedd2d8 100644 --- a/spec/ruby/core/module/alias_method_spec.rb +++ b/spec/ruby/core/module/alias_method_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Module#alias_method" do before :each do @@ -14,7 +14,7 @@ describe "Module#alias_method" do @object.double(12).should == @object.public_two(12) end - it "creates methods that are == to eachother" do + it "creates methods that are == to each other" do @class.make_alias :uno, :public_one @object.method(:uno).should == @object.method(:public_one) end @@ -28,12 +28,12 @@ describe "Module#alias_method" do it "retains method visibility" do @class.make_alias :private_ichi, :private_one - lambda { @object.private_one }.should raise_error(NameError) - lambda { @object.private_ichi }.should raise_error(NameError) + -> { @object.private_one }.should raise_error(NameError) + -> { @object.private_ichi }.should raise_error(NameError) @class.make_alias :public_ichi, :public_one @object.public_ichi.should == @object.public_one @class.make_alias :protected_ichi, :protected_one - lambda { @object.protected_ichi }.should raise_error(NameError) + -> { @object.protected_ichi }.should raise_error(NameError) end it "handles aliasing a stub that changes visibility" do @@ -42,16 +42,28 @@ describe "Module#alias_method" do @object.was_private_one.should == 1 end + it "handles aliasing a method only present in a refinement" do + c = @class + Module.new do + refine c do + def uno_refined_method + end + alias_method :double_refined_method, :uno_refined_method + instance_method(:uno_refined_method).should == instance_method(:double_refined_method) + end + end + end + it "fails if origin method not found" do - lambda { @class.make_alias :ni, :san }.should raise_error(NameError) { |e| + -> { @class.make_alias :ni, :san }.should raise_error(NameError) { |e| # a NameError and not a NoMethodError e.class.should == NameError } end - it "raises RuntimeError if frozen" do + it "raises FrozenError if frozen" do @class.freeze - lambda { @class.make_alias :uno, :public_one }.should raise_error(RuntimeError) + -> { @class.make_alias :uno, :public_one }.should raise_error(FrozenError) end it "converts the names using #to_str" do @@ -66,22 +78,24 @@ describe "Module#alias_method" do end it "raises a TypeError when the given name can't be converted using to_str" do - lambda { @class.make_alias mock('x'), :public_one }.should raise_error(TypeError) + -> { @class.make_alias mock('x'), :public_one }.should raise_error(TypeError) end - ruby_version_is ''...'2.5' do - it "is a private method" do - lambda { @class.alias_method :ichi, :public_one }.should raise_error(NoMethodError) - 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 - ruby_version_is '2.5' do - it "is a public method" do - Module.should have_public_instance_method(:alias_method, false) - 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 @@ -90,7 +104,7 @@ describe "Module#alias_method" do it "works on private module methods in a module that has been reopened" do ModuleSpecs::ReopeningModule.foo.should == true - lambda { ModuleSpecs::ReopeningModule.foo2 }.should_not raise_error(NoMethodError) + -> { ModuleSpecs::ReopeningModule.foo2 }.should_not raise_error(NoMethodError) end it "accesses a method defined on Object from Kernel" do diff --git a/spec/ruby/core/module/allocate_spec.rb b/spec/ruby/core/module/allocate_spec.rb deleted file mode 100644 index 306426881a..0000000000 --- a/spec/ruby/core/module/allocate_spec.rb +++ /dev/null @@ -1,14 +0,0 @@ -require File.expand_path('../../../spec_helper', __FILE__) - -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 1cd537f7a0..90c26941d1 100644 --- a/spec/ruby/core/module/ancestors_spec.rb +++ b/spec/ruby/core/module/ancestors_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Module#ancestors" do it "returns a list of modules included in self (including self)" do @@ -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 ceb8c3f8eb..1724cde5d6 100644 --- a/spec/ruby/core/module/append_features_spec.rb +++ b/spec/ruby/core/module/append_features_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Module#append_features" do it "is a private method" do @@ -12,7 +12,7 @@ describe "Module#append_features" do end it "raises a TypeError if calling after rebinded to Class" do - lambda { + -> { Module.instance_method(:append_features).bind(Class.new).call Module.new }.should raise_error(TypeError) end @@ -37,36 +37,24 @@ describe "Module#append_features" do end it "raises an ArgumentError on a cyclic include" do - lambda { + -> { ModuleSpecs::CyclicAppendA.send(:append_features, ModuleSpecs::CyclicAppendA) }.should raise_error(ArgumentError) - lambda { + -> { ModuleSpecs::CyclicAppendB.send(:append_features, ModuleSpecs::CyclicAppendA) }.should raise_error(ArgumentError) end - 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 - describe "when other is frozen" do before :each do @receiver = Module.new @other = Module.new.freeze end - it "raises a RuntimeError before appending self" do - lambda { @receiver.send(:append_features, @other) }.should raise_error(RuntimeError) + it "raises a FrozenError before appending self" do + -> { @receiver.send(:append_features, @other) }.should raise_error(FrozenError) @other.ancestors.should_not include(@receiver) end end diff --git a/spec/ruby/core/module/attr_accessor_spec.rb b/spec/ruby/core/module/attr_accessor_spec.rb index c6f7aad6af..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 File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +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 - lambda { 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 @@ -48,9 +62,9 @@ describe "Module#attr_accessor" do it "raises a TypeError when the given names can't be converted to strings using to_str" do o = mock('o') - lambda { Class.new { attr_accessor o } }.should raise_error(TypeError) + -> { Class.new { attr_accessor o } }.should raise_error(TypeError) (o = mock('123')).should_receive(:to_str).and_return(123) - lambda { Class.new { attr_accessor o } }.should raise_error(TypeError) + -> { Class.new { attr_accessor o } }.should raise_error(TypeError) end it "applies current visibility to methods created" do @@ -59,34 +73,33 @@ describe "Module#attr_accessor" do attr_accessor :foo end - lambda { c.new.foo }.should raise_error(NoMethodError) - lambda { c.new.foo=1 }.should raise_error(NoMethodError) + -> { c.new.foo }.should raise_error(NoMethodError) + -> { c.new.foo=1 }.should raise_error(NoMethodError) end - ruby_version_is ''...'2.5' do - it "is a private method" do - Module.should have_private_instance_method(:attr_accessor, false) - end + it "is a public method" do + Module.should have_public_instance_method(:attr_accessor, false) end - ruby_version_is '2.5' do - it "is a public method" do - Module.should have_public_instance_method(:attr_accessor, false) + + 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 @@ -94,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 65cafdba9f..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 File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +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 @@ -29,10 +30,10 @@ describe "Module#attr_reader" do attr_reader :spec_attr_reader end - lambda { true.instance_variable_set("@spec_attr_reader", "a") }.should raise_error(RuntimeError) + -> { 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 @@ -44,9 +45,9 @@ describe "Module#attr_reader" do it "raises a TypeError when the given names can't be converted to strings using to_str" do o = mock('o') - lambda { Class.new { attr_reader o } }.should raise_error(TypeError) + -> { Class.new { attr_reader o } }.should raise_error(TypeError) (o = mock('123')).should_receive(:to_str).and_return(123) - lambda { Class.new { attr_reader o } }.should raise_error(TypeError) + -> { Class.new { attr_reader o } }.should raise_error(TypeError) end it "applies current visibility to methods created" do @@ -55,17 +56,18 @@ describe "Module#attr_reader" do attr_reader :foo end - lambda { c.new.foo }.should raise_error(NoMethodError) + -> { c.new.foo }.should raise_error(NoMethodError) end - ruby_version_is ''...'2.5' do - it "is a private method" do - Module.should have_private_instance_method(:attr_reader, false) - end + it "is a public method" do + Module.should have_public_instance_method(:attr_reader, false) end - ruby_version_is '2.5' do - it "is a public method" do - Module.should have_public_instance_method(:attr_reader, false) + + 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 7128c610fe..2f9f4e26dc 100644 --- a/spec/ruby/core/module/attr_spec.rb +++ b/spec/ruby/core/module/attr_spec.rb @@ -1,5 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/attr_added' describe "Module#attr" do before :each do @@ -54,7 +55,7 @@ describe "Module#attr" do o.attr3 = "test3 updated" end - it "creates a getter and setter for the given attribute name if called with and without writeable is true" do + it "creates a getter and setter for the given attribute name if called with and without writable is true" do c = Class.new do attr :attr, true attr :attr @@ -89,8 +90,8 @@ describe "Module#attr" do attr :foo, true end - lambda { c.new.foo }.should raise_error(NoMethodError) - lambda { c.new.foo=1 }.should raise_error(NoMethodError) + -> { c.new.foo }.should raise_error(NoMethodError) + -> { c.new.foo=1 }.should raise_error(NoMethodError) end it "creates a getter but no setter for all given attribute names" do @@ -120,37 +121,39 @@ describe "Module#attr" do attr :foo, :bar end - lambda { c.new.foo }.should raise_error(NoMethodError) - lambda { c.new.bar }.should raise_error(NoMethodError) + -> { c.new.foo }.should raise_error(NoMethodError) + -> { 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 it "raises a TypeError when the given names can't be converted to strings using to_str" do o = mock('o') - lambda { Class.new { attr o } }.should raise_error(TypeError) + -> { Class.new { attr o } }.should raise_error(TypeError) (o = mock('123')).should_receive(:to_str).and_return(123) - lambda { Class.new { attr o } }.should raise_error(TypeError) + -> { Class.new { attr o } }.should raise_error(TypeError) end it "with a boolean argument emits a warning when $VERBOSE is true" do - lambda { - $VERBOSE = true + -> { Class.new { attr :foo, true } - }.should complain(/boolean argument is obsoleted/) + }.should complain(/boolean argument is obsoleted/, verbose: true) end - ruby_version_is ''...'2.5' do - it "is a private method" do - Module.should have_private_instance_method(:attr, false) - end + it "is a public method" do + Module.should have_public_instance_method(:attr, false) end - ruby_version_is '2.5' do - it "is a public method" do - Module.should have_public_instance_method(:attr, false) + + 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 5979e3891b..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 File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +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 - lambda { 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 @@ -44,9 +55,9 @@ describe "Module#attr_writer" do it "raises a TypeError when the given names can't be converted to strings using to_str" do o = mock('test1') - lambda { Class.new { attr_writer o } }.should raise_error(TypeError) + -> { Class.new { attr_writer o } }.should raise_error(TypeError) (o = mock('123')).should_receive(:to_str).and_return(123) - lambda { Class.new { attr_writer o } }.should raise_error(TypeError) + -> { Class.new { attr_writer o } }.should raise_error(TypeError) end it "applies current visibility to methods created" do @@ -55,17 +66,18 @@ describe "Module#attr_writer" do attr_writer :foo end - lambda { c.new.foo=1 }.should raise_error(NoMethodError) + -> { c.new.foo=1 }.should raise_error(NoMethodError) end - ruby_version_is ''...'2.5' do - it "is a private method" do - Module.should have_private_instance_method(:attr_writer, false) - end + it "is a public method" do + Module.should have_public_instance_method(:attr_writer, false) end - ruby_version_is '2.5' do - it "is a public method" do - Module.should have_public_instance_method(:attr_writer, false) + + 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 a72ae7735b..625d945686 100644 --- a/spec/ruby/core/module/autoload_spec.rb +++ b/spec/ruby/core/module/autoload_spec.rb @@ -1,6 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require 'thread' +require_relative '../../spec_helper' +require_relative '../../fixtures/code_loading' +require_relative 'fixtures/classes' describe "Module#autoload?" do it "returns the name of the file that will be autoloaded" do @@ -11,22 +11,41 @@ describe "Module#autoload?" do it "returns nil if no file has been registered for a constant" do ModuleSpecs::Autoload.autoload?(:Manualload).should be_nil end + + it "returns the name of the file that will be autoloaded if an ancestor defined that autoload" do + ModuleSpecs::Autoload::Parent.autoload :AnotherAutoload, "another_autoload.rb" + ModuleSpecs::Autoload::Child.autoload?(:AnotherAutoload).should == "another_autoload.rb" + 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 end describe "Module#autoload" do before :all do @non_existent = fixture __FILE__, "no_autoload.rb" + CodeLoadingSpecs.preload_rubygems end before :each do @loaded_features = $".dup - @frozen_module = Module.new.freeze ScratchPad.clear + @remove = [] end after :each do $".replace @loaded_features + @remove.each { |const| + ModuleSpecs::Autoload.send :remove_const, const + } end it "registers a file to load the first time the named constant is accessed" do @@ -39,16 +58,29 @@ describe "Module#autoload" do ModuleSpecs::Autoload.should have_constant(:B) end + it "can be overridden with a second autoload on the same constant" do + ModuleSpecs::Autoload.autoload :Overridden, @non_existent + @remove << :Overridden + ModuleSpecs::Autoload.autoload?(:Overridden).should == @non_existent + + path = fixture(__FILE__, "autoload_overridden.rb") + ModuleSpecs::Autoload.autoload :Overridden, path + ModuleSpecs::Autoload.autoload?(:Overridden).should == path + + ModuleSpecs::Autoload::Overridden.should == :overridden + end + it "loads the registered constant when it is accessed" do ModuleSpecs::Autoload.should_not have_constant(:X) ModuleSpecs::Autoload.autoload :X, fixture(__FILE__, "autoload_x.rb") + @remove << :X ModuleSpecs::Autoload::X.should == :x - ModuleSpecs::Autoload.send(:remove_const, :X) end it "loads the registered constant into a dynamically created class" do cls = Class.new { autoload :C, fixture(__FILE__, "autoload_c.rb") } ModuleSpecs::Autoload::DynClass = cls + @remove << :DynClass ScratchPad.recorded.should be_nil ModuleSpecs::Autoload::DynClass::C.new.loaded.should == :dynclass_c @@ -58,6 +90,7 @@ describe "Module#autoload" do it "loads the registered constant into a dynamically created module" do mod = Module.new { autoload :D, fixture(__FILE__, "autoload_d.rb") } ModuleSpecs::Autoload::DynModule = mod + @remove << :DynModule ScratchPad.recorded.should be_nil ModuleSpecs::Autoload::DynModule::D.new.loaded.should == :dynmodule_d @@ -95,6 +128,7 @@ describe "Module#autoload" do it "does not load the file when the constant is already set" do ModuleSpecs::Autoload.autoload :I, fixture(__FILE__, "autoload_i.rb") + @remove << :I ModuleSpecs::Autoload.const_set :I, 3 ModuleSpecs::Autoload::I.should == 3 ScratchPad.recorded.should be_nil @@ -105,9 +139,18 @@ describe "Module#autoload" do ModuleSpecs::Autoload::J.should == :autoload_j end + it "calls main.require(path) to load the file" do + ModuleSpecs::Autoload.autoload :ModuleAutoloadCallsRequire, "module_autoload_not_exist.rb" + main = TOPLEVEL_BINDING.eval("self") + main.should_receive(:require).with("module_autoload_not_exist.rb") + # The constant won't be defined since require is mocked to do nothing + -> { ModuleSpecs::Autoload::ModuleAutoloadCallsRequire }.should raise_error(NameError) + end + it "does not load the file if the file is manually required" do filename = fixture(__FILE__, "autoload_k.rb") ModuleSpecs::Autoload.autoload :KHash, filename + @remove << :KHash require filename ScratchPad.recorded.should == :loaded @@ -127,8 +170,8 @@ describe "Module#autoload" do ScratchPad.clear ModuleSpecs::Autoload.autoload :S, filename + @remove << :S ModuleSpecs::Autoload.autoload?(:S).should be_nil - ModuleSpecs::Autoload.send(:remove_const, :S) end it "retains the autoload even if the request to require fails" do @@ -137,7 +180,7 @@ describe "Module#autoload" do ModuleSpecs::Autoload.autoload :NotThere, filename ModuleSpecs::Autoload.autoload?(:NotThere).should == filename - lambda { + -> { require filename }.should raise_error(LoadError) @@ -158,28 +201,322 @@ describe "Module#autoload" do ModuleSpecs::Autoload.use_ex1.should == :good end - it "does not load the file when referring to the constant in defined?" do - module ModuleSpecs::Autoload::Q - autoload :R, fixture(__FILE__, "autoload.rb") - defined?(R).should == "constant" + 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 + autoload :R, fixture(__FILE__, "autoload_exception.rb") + end + + defined?(ModuleSpecs::Autoload::Dog::R).should == "constant" + ScratchPad.recorded.should be_nil + + ModuleSpecs::Autoload::Dog.should have_constant(:R) + end + + it "loads an autoloaded parent when referencing a nested constant" do + module ModuleSpecs::Autoload + autoload :GoodParent, fixture(__FILE__, "autoload_nested.rb") + end + @remove << :GoodParent + + defined?(ModuleSpecs::Autoload::GoodParent::Nested).should == 'constant' + ScratchPad.recorded.should == :loaded + end + + it "returns nil when it fails to load an autoloaded parent when referencing a nested constant" do + module ModuleSpecs::Autoload + autoload :BadParent, fixture(__FILE__, "autoload_exception.rb") + end + + defined?(ModuleSpecs::Autoload::BadParent::Nested).should be_nil + ScratchPad.recorded.should == :exception + end + end + + describe "the autoload is triggered when the same file is required directly" do + before :each do + module ModuleSpecs::Autoload + autoload :RequiredDirectly, fixture(__FILE__, "autoload_required_directly.rb") + end + @remove << :RequiredDirectly + @path = fixture(__FILE__, "autoload_required_directly.rb") + @check = -> { + [ + defined?(ModuleSpecs::Autoload::RequiredDirectly), + ModuleSpecs::Autoload.autoload?(:RequiredDirectly) + ] + } + ScratchPad.record @check + end + + it "with a full path" do + @check.call.should == ["constant", @path] + require @path + ScratchPad.recorded.should == [nil, nil] + @check.call.should == ["constant", nil] + end + + it "with a relative path" do + @check.call.should == ["constant", @path] + $:.push File.dirname(@path) + begin + require "autoload_required_directly.rb" + ensure + $:.pop + end + ScratchPad.recorded.should == [nil, nil] + @check.call.should == ["constant", nil] + end + + it "in a nested require" do + nested = fixture(__FILE__, "autoload_required_directly_nested.rb") + nested_require = -> { + result = nil + ScratchPad.record -> { + result = @check.call + } + require nested + result + } + ScratchPad.record nested_require + + @check.call.should == ["constant", @path] + require @path + ScratchPad.recorded.should == [nil, nil] + @check.call.should == ["constant", nil] + end + + it "does not raise an error if the autoload constant was not defined" do + module ModuleSpecs::Autoload + autoload :RequiredDirectlyNoConstant, fixture(__FILE__, "autoload_required_directly_no_constant.rb") + end + @path = fixture(__FILE__, "autoload_required_directly_no_constant.rb") + @remove << :RequiredDirectlyNoConstant + @check = -> { + [ + defined?(ModuleSpecs::Autoload::RequiredDirectlyNoConstant), + ModuleSpecs::Autoload.constants(false).include?(:RequiredDirectlyNoConstant), + ModuleSpecs::Autoload.const_defined?(:RequiredDirectlyNoConstant), + ModuleSpecs::Autoload.autoload?(:RequiredDirectlyNoConstant) + ] + } + ScratchPad.record @check + @check.call.should == ["constant", true, true, @path] + $:.push File.dirname(@path) + begin + require "autoload_required_directly_no_constant.rb" + ensure + $:.pop + end + ScratchPad.recorded.should == [nil, true, false, nil] + @check.call.should == [nil, true, false, nil] + end + end + + describe "after the autoload is triggered by require" do + before :each do + @path = tmp("autoload.rb") + end + + after :each do + rm_r @path + end + + it "the mapping feature to autoload is removed, and a new autoload with the same path is considered" do + ModuleSpecs::Autoload.autoload :RequireMapping1, @path + touch(@path) { |f| f.puts "ModuleSpecs::Autoload::RequireMapping1 = 1" } + ModuleSpecs::Autoload::RequireMapping1.should == 1 + + $LOADED_FEATURES.delete(@path) + ModuleSpecs::Autoload.autoload :RequireMapping2, @path[0...-3] + @remove << :RequireMapping2 + touch(@path) { |f| f.puts "ModuleSpecs::Autoload::RequireMapping2 = 2" } + ModuleSpecs::Autoload::RequireMapping2.should == 2 + 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") + ModuleSpecs::Autoload.autoload :DuringAutoload, @path + @remove << :DuringAutoload + raise unless ModuleSpecs::Autoload.autoload?(:DuringAutoload) == @path + end + + it "returns nil in autoload thread and 'constant' otherwise for defined?" do + 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(: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(: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(:DuringAutoload) { + ModuleSpecs::Autoload.autoload?(:DuringAutoload) + } + results.should == [@path, nil, @path, nil] end - ModuleSpecs::Autoload::Q.should have_constant(:R) end - it "does not remove the constant from the constant table if load fails" do + 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 + + ModuleSpecs::Autoload.const_defined?(:Fail).should == true ModuleSpecs::Autoload.should have_constant(:Fail) + ModuleSpecs::Autoload.autoload?(:Fail).should == @non_existent + + -> { ModuleSpecs::Autoload::Fail }.should raise_error(LoadError) - lambda { ModuleSpecs::Autoload::Fail }.should raise_error(LoadError) ModuleSpecs::Autoload.should have_constant(:Fail) + ModuleSpecs::Autoload.const_defined?(:Fail).should == true + ModuleSpecs::Autoload.autoload?(:Fail).should == @non_existent + + -> { ModuleSpecs::Autoload::Fail }.should raise_error(LoadError) end - it "does not remove the constant from the constant table if the loaded files does not define it" do - ModuleSpecs::Autoload.autoload :O, fixture(__FILE__, "autoload_o.rb") - ModuleSpecs::Autoload.should have_constant(:O) + it "does not remove the constant from Module#constants if load raises a RuntimeError and keeps it as an autoload" do + path = fixture(__FILE__, "autoload_raise.rb") + ScratchPad.record [] + ModuleSpecs::Autoload.autoload :Raise, path + + ModuleSpecs::Autoload.const_defined?(:Raise).should == true + ModuleSpecs::Autoload.should have_constant(:Raise) + ModuleSpecs::Autoload.autoload?(:Raise).should == path - lambda { ModuleSpecs::Autoload::O }.should raise_error(NameError) + -> { ModuleSpecs::Autoload::Raise }.should raise_error(RuntimeError) + ScratchPad.recorded.should == [:raise] + + ModuleSpecs::Autoload.should have_constant(:Raise) + ModuleSpecs::Autoload.const_defined?(:Raise).should == true + ModuleSpecs::Autoload.autoload?(:Raise).should == path + + -> { ModuleSpecs::Autoload::Raise }.should raise_error(RuntimeError) + ScratchPad.recorded.should == [:raise, :raise] + end + + 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 + + ModuleSpecs::Autoload.const_defined?(:O).should == true ModuleSpecs::Autoload.should have_constant(:O) + ModuleSpecs::Autoload.autoload?(:O).should == path + + -> { ModuleSpecs::Autoload::O }.should raise_error(NameError) + + 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 + + it "does not try to load the file again if the loaded file did not define the constant" do + path = fixture(__FILE__, "autoload_o.rb") + ScratchPad.record [] + ModuleSpecs::Autoload.autoload :NotDefinedByFile, path + + -> { ModuleSpecs::Autoload::NotDefinedByFile }.should raise_error(NameError) + ScratchPad.recorded.should == [:loaded] + -> { ModuleSpecs::Autoload::NotDefinedByFile }.should raise_error(NameError) + ScratchPad.recorded.should == [:loaded] + + Thread.new { + -> { ModuleSpecs::Autoload::NotDefinedByFile }.should raise_error(NameError) + }.join + ScratchPad.recorded.should == [:loaded] end it "returns 'constant' on referring the constant with defined?()" do @@ -201,6 +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 + ModuleSpecs::Autoload.const_defined?("P").should be_true end it "loads the file when opening a module that is the autoloaded constant" do @@ -211,43 +549,188 @@ describe "Module#autoload" do X = get_value end end + @remove << :U ModuleSpecs::Autoload::U::V::X.should == :autoload_uvx end - it "loads the file that defines subclass XX::YY < YY and YY is a top level constant" do - + it "loads the file that defines subclass XX::CS_CONST_AUTOLOAD < CS_CONST_AUTOLOAD and CS_CONST_AUTOLOAD is a top level constant" do module ModuleSpecs::Autoload::XX - autoload :YY, fixture(__FILE__, "autoload_subclass.rb") + autoload :CS_CONST_AUTOLOAD, fixture(__FILE__, "autoload_subclass.rb") end - ModuleSpecs::Autoload::XX::YY.superclass.should == YY + ModuleSpecs::Autoload::XX::CS_CONST_AUTOLOAD.superclass.should == CS_CONST_AUTOLOAD end + describe "after autoloading searches for the constant like the original lookup" do + it "in lexical scopes if both declared and defined in parent" do + module ModuleSpecs::Autoload + ScratchPad.record -> { + DeclaredAndDefinedInParent = :declared_and_defined_in_parent + } + autoload :DeclaredAndDefinedInParent, fixture(__FILE__, "autoload_callback.rb") + class LexicalScope + DeclaredAndDefinedInParent.should == :declared_and_defined_in_parent + + # The constant is really in Autoload, not Autoload::LexicalScope + self.should_not have_constant(:DeclaredAndDefinedInParent) + -> { const_get(:DeclaredAndDefinedInParent) }.should raise_error(NameError) + end + DeclaredAndDefinedInParent.should == :declared_and_defined_in_parent + end + end - it "looks up the constant in the scope where it is referred" do - module ModuleSpecs - module Autoload - autoload :QQ, fixture(__FILE__, "autoload_scope.rb") - class PP - QQ.new.should be_kind_of(ModuleSpecs::Autoload::PP::QQ) + it "in lexical scopes if declared in parent and defined in current" do + module ModuleSpecs::Autoload + ScratchPad.record -> { + class LexicalScope + DeclaredInParentDefinedInCurrent = :declared_in_parent_defined_in_current + end + } + autoload :DeclaredInParentDefinedInCurrent, fixture(__FILE__, "autoload_callback.rb") + + class LexicalScope + DeclaredInParentDefinedInCurrent.should == :declared_in_parent_defined_in_current + LexicalScope::DeclaredInParentDefinedInCurrent.should == :declared_in_parent_defined_in_current + end + + # Basically, the parent autoload constant remains in a "undefined" state + self.autoload?(:DeclaredInParentDefinedInCurrent).should == nil + const_defined?(:DeclaredInParentDefinedInCurrent).should == false + -> { DeclaredInParentDefinedInCurrent }.should raise_error(NameError) + + ModuleSpecs::Autoload::LexicalScope.send(:remove_const, :DeclaredInParentDefinedInCurrent) + end + end + + 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 - end - it "looks up the constant when in a meta class scope" do - module ModuleSpecs - module Autoload - autoload :R, fixture(__FILE__, "autoload_r.rb") + it "looks up in parent scope after failed autoload" do + @remove << :DeclaredInCurrentDefinedInParent + module ModuleSpecs::Autoload + ScratchPad.record -> { + DeclaredInCurrentDefinedInParent = :declared_in_current_defined_in_parent + } + + class LexicalScope + autoload :DeclaredInCurrentDefinedInParent, fixture(__FILE__, "autoload_callback.rb") + -> { 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 + -> { const_get(:DeclaredInCurrentDefinedInParent) }.should raise_error(NameError) + end + + DeclaredInCurrentDefinedInParent.should == :declared_in_current_defined_in_parent + end + end + + it "in the included modules" do + @remove << :DefinedInIncludedModule + module ModuleSpecs::Autoload + ScratchPad.record -> { + module DefinedInIncludedModule + Incl = :defined_in_included_module + end + include DefinedInIncludedModule + } + autoload :Incl, fixture(__FILE__, "autoload_callback.rb") + Incl.should == :defined_in_included_module + end + end + + it "in the included modules of the superclass" do + @remove << :DefinedInSuperclassIncludedModule + module ModuleSpecs::Autoload + class LookupAfterAutoloadSuper + end + class LookupAfterAutoloadChild < LookupAfterAutoloadSuper + end + + ScratchPad.record -> { + module DefinedInSuperclassIncludedModule + InclS = :defined_in_superclass_included_module + end + LookupAfterAutoloadSuper.include DefinedInSuperclassIncludedModule + } + + class LookupAfterAutoloadChild + autoload :InclS, fixture(__FILE__, "autoload_callback.rb") + InclS.should == :defined_in_superclass_included_module + end + end + end + + it "in the prepended modules" do + @remove << :DefinedInPrependedModule + module ModuleSpecs::Autoload + ScratchPad.record -> { + module DefinedInPrependedModule + Prep = :defined_in_prepended_module + end + include DefinedInPrependedModule + } + autoload :Prep, fixture(__FILE__, "autoload_callback.rb") + Prep.should == :defined_in_prepended_module + end + end + + it "in a meta class scope" do + module ModuleSpecs::Autoload + ScratchPad.record -> { + class MetaScope + end + } + autoload :MetaScope, fixture(__FILE__, "autoload_callback.rb") class << self def r - R.new + MetaScope.new end end end + ModuleSpecs::Autoload.r.should be_kind_of(ModuleSpecs::Autoload::MetaScope) end - ModuleSpecs::Autoload.r.should be_kind_of(ModuleSpecs::Autoload::R) + 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] @@ -260,10 +743,45 @@ describe "Module#autoload" do end end end + @remove << :W ModuleSpecs::Autoload::W::Y.should be_kind_of(Class) ScratchPad.recorded.should == :loaded - ModuleSpecs::Autoload::W.send(:remove_const, :Y) + end + + it "does not call #require a second time and does not warn if already loading the same feature with #require" do + main = TOPLEVEL_BINDING.eval("self") + main.should_not_receive(:require) + + module ModuleSpecs::Autoload + autoload :AutoloadDuringRequire, fixture(__FILE__, "autoload_during_require.rb") + end + + -> { + Kernel.require fixture(__FILE__, "autoload_during_require.rb") + }.should_not complain(verbose: true) + ModuleSpecs::Autoload::AutoloadDuringRequire.should be_kind_of(Class) + end + + it "does not call #require a second time and does not warn if feature sets and trigger autoload on itself" do + main = TOPLEVEL_BINDING.eval("self") + main.should_not_receive(:require) + + -> { + Kernel.require fixture(__FILE__, "autoload_self_during_require.rb") + }.should_not complain(verbose: true) + ModuleSpecs::Autoload::AutoloadSelfDuringRequire.should be_kind_of(Class) + end + + it "handles multiple autoloads in the same file" do + $LOAD_PATH.unshift(File.expand_path('../fixtures/multi', __FILE__)) + begin + require 'foo/bar_baz' + ModuleSpecs::Autoload::Foo::Bar.should be_kind_of(Class) + ModuleSpecs::Autoload::Foo::Baz.should be_kind_of(Class) + ensure + $LOAD_PATH.shift + end end it "calls #to_path on non-string filenames" do @@ -273,26 +791,27 @@ describe "Module#autoload" do end it "raises an ArgumentError when an empty filename is given" do - lambda { ModuleSpecs.autoload :A, "" }.should raise_error(ArgumentError) + -> { ModuleSpecs.autoload :A, "" }.should raise_error(ArgumentError) end it "raises a NameError when the constant name starts with a lower case letter" do - lambda { ModuleSpecs.autoload "a", @non_existent }.should raise_error(NameError) + -> { ModuleSpecs.autoload "a", @non_existent }.should raise_error(NameError) end it "raises a NameError when the constant name starts with a number" do - lambda { ModuleSpecs.autoload "1two", @non_existent }.should raise_error(NameError) + -> { ModuleSpecs.autoload "1two", @non_existent }.should raise_error(NameError) end it "raises a NameError when the constant name has a space in it" do - lambda { ModuleSpecs.autoload "a name", @non_existent }.should raise_error(NameError) + -> { ModuleSpecs.autoload "a name", @non_existent }.should raise_error(NameError) end it "shares the autoload request across dup'ed copies of modules" do require fixture(__FILE__, "autoload_s.rb") + @remove << :S filename = fixture(__FILE__, "autoload_t.rb") mod1 = Module.new { autoload :T, filename } - lambda { + -> { ModuleSpecs::Autoload::S = mod1 }.should complain(/already initialized constant/) mod2 = mod1.dup @@ -301,7 +820,7 @@ describe "Module#autoload" do mod2.autoload?(:T).should == filename mod1::T.should == :autoload_t - lambda { mod2::T }.should raise_error(NameError) + -> { mod2::T }.should raise_error(NameError) end it "raises a TypeError if opening a class with a different superclass than the class defined in the autoload file" do @@ -309,29 +828,30 @@ describe "Module#autoload" do class ModuleSpecs::Autoload::ZZ end - lambda do + -> do class ModuleSpecs::Autoload::Z < ModuleSpecs::Autoload::ZZ end end.should raise_error(TypeError) end - it "raises a TypeError if not passed a String or object respodning to #to_path for the filename" do + it "raises a TypeError if not passed a String or object responding to #to_path for the filename" do name = mock("autoload_name.rb") - lambda { ModuleSpecs::Autoload.autoload :Str, name }.should raise_error(TypeError) + -> { ModuleSpecs::Autoload.autoload :Str, name }.should raise_error(TypeError) end it "calls #to_path on non-String filename arguments" do name = mock("autoload_name.rb") name.should_receive(:to_path).and_return("autoload_name.rb") - lambda { ModuleSpecs::Autoload.autoload :Str, name }.should_not raise_error + -> { ModuleSpecs::Autoload.autoload :Str, name }.should_not raise_error end describe "on a frozen module" do - it "raises a RuntimeError before setting the name" do - lambda { @frozen_module.autoload :Foo, @non_existent }.should raise_error(RuntimeError) - @frozen_module.should_not have_constant(:Foo) + it "raises a FrozenError before setting the name" do + frozen_module = Module.new.freeze + -> { frozen_module.autoload :Foo, @non_existent }.should raise_error(FrozenError) + frozen_module.should_not have_constant(:Foo) end end @@ -354,6 +874,7 @@ describe "Module#autoload" do describe "(concurrently)" do it "blocks a second thread while a first is doing the autoload" do ModuleSpecs::Autoload.autoload :Concur, fixture(__FILE__, "autoload_concur.rb") + @remove << :Concur start = false @@ -396,56 +917,53 @@ describe "Module#autoload" do t2_val.should == t1_val t2_exc.should be_nil - - ModuleSpecs::Autoload.send(:remove_const, :Concur) end - ruby_bug "#10892", ""..."2.3" do - it "blocks others threads while doing an autoload" do - file_path = fixture(__FILE__, "repeated_concurrent_autoload.rb") - autoload_path = file_path.sub(/\.rb\Z/, '') - mod_count = 30 - thread_count = 16 + # https://bugs.ruby-lang.org/issues/10892 + it "blocks others threads while doing an autoload" do + file_path = fixture(__FILE__, "repeated_concurrent_autoload.rb") + autoload_path = file_path.sub(/\.rb\Z/, '') + mod_count = 30 + thread_count = 16 + + mod_names = [] + mod_count.times do |i| + mod_name = :"Mod#{i}" + Object.autoload mod_name, autoload_path + mod_names << mod_name + end - mod_names = [] - mod_count.times do |i| - mod_name = :"Mod#{i}" - Object.autoload mod_name, autoload_path - mod_names << mod_name - end + barrier = ModuleSpecs::CyclicBarrier.new thread_count + ScratchPad.record ModuleSpecs::ThreadSafeCounter.new - barrier = ModuleSpecs::CyclicBarrier.new thread_count - ScratchPad.record ModuleSpecs::ThreadSafeCounter.new - - threads = (1..thread_count).map do - Thread.new do - mod_names.each do |mod_name| - break false unless barrier.enabled? - - was_last_one_in = barrier.await # wait for all threads to finish the iteration - # clean up so we can autoload the same file again - $LOADED_FEATURES.delete(file_path) if was_last_one_in && $LOADED_FEATURES.include?(file_path) - barrier.await # get ready for race - - begin - Object.const_get(mod_name).foo - rescue NoMethodError - barrier.disable! - break false - end + threads = (1..thread_count).map do + Thread.new do + mod_names.each do |mod_name| + break false unless barrier.enabled? + + was_last_one_in = barrier.await # wait for all threads to finish the iteration + # clean up so we can autoload the same file again + $LOADED_FEATURES.delete(file_path) if was_last_one_in && $LOADED_FEATURES.include?(file_path) + barrier.await # get ready for race + + begin + Object.const_get(mod_name).foo + rescue NoMethodError + barrier.disable! + break false end end end + end - # check that no thread got a NoMethodError because of partially loaded module - threads.all? {|t| t.value}.should be_true + # check that no thread got a NoMethodError because of partially loaded module + threads.all? {|t| t.value}.should be_true - # check that the autoloaded file was evaled exactly once - ScratchPad.recorded.get.should == mod_count + # check that the autoloaded file was evaled exactly once + ScratchPad.recorded.get.should == mod_count - mod_names.each do |mod_name| - Object.send(:remove_const, mod_name) - end + mod_names.each do |mod_name| + Object.send(:remove_const, mod_name) end end diff --git a/spec/ruby/core/module/case_compare_spec.rb b/spec/ruby/core/module/case_compare_spec.rb index 92f2c2065b..49ac359f6f 100644 --- a/spec/ruby/core/module/case_compare_spec.rb +++ b/spec/ruby/core/module/case_compare_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Module#===" do it "returns true when the given Object is an instance of self or of self's descendants" do diff --git a/spec/ruby/core/module/class_eval_spec.rb b/spec/ruby/core/module/class_eval_spec.rb index 90deef9c2f..c6665d5aff 100644 --- a/spec/ruby/core/module/class_eval_spec.rb +++ b/spec/ruby/core/module/class_eval_spec.rb @@ -1,6 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../shared/class_eval', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/class_eval' describe "Module#class_eval" do it_behaves_like :module_class_eval, :class_eval diff --git a/spec/ruby/core/module/class_exec_spec.rb b/spec/ruby/core/module/class_exec_spec.rb index f9c12cfa48..4acd0169ad 100644 --- a/spec/ruby/core/module/class_exec_spec.rb +++ b/spec/ruby/core/module/class_exec_spec.rb @@ -1,6 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../shared/class_exec', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/class_exec' describe "Module#class_exec" do it_behaves_like :module_class_exec, :class_exec diff --git a/spec/ruby/core/module/class_variable_defined_spec.rb b/spec/ruby/core/module/class_variable_defined_spec.rb index d47329455f..c0f2072a37 100644 --- a/spec/ruby/core/module/class_variable_defined_spec.rb +++ b/spec/ruby/core/module/class_variable_defined_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Module#class_variable_defined?" do it "returns true if a class variable with the given name is defined in self" do @@ -42,16 +42,16 @@ describe "Module#class_variable_defined?" do it "raises a NameError when the given name is not allowed" do c = Class.new - lambda { + -> { c.class_variable_defined?(:invalid_name) }.should raise_error(NameError) - lambda { + -> { c.class_variable_defined?("@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.class_variable_defined?(o).should == true @@ -60,12 +60,12 @@ describe "Module#class_variable_defined?" do it "raises a TypeError when the given names can't be converted to strings using to_str" do c = Class.new { class_variable_set :@@class_var, "test" } o = mock('123') - lambda { + -> { c.class_variable_defined?(o) }.should raise_error(TypeError) o.should_receive(:to_str).and_return(123) - lambda { + -> { c.class_variable_defined?(o) }.should raise_error(TypeError) end diff --git a/spec/ruby/core/module/class_variable_get_spec.rb b/spec/ruby/core/module/class_variable_get_spec.rb index 7068801cc0..e5d06731ec 100644 --- a/spec/ruby/core/module/class_variable_get_spec.rb +++ b/spec/ruby/core/module/class_variable_get_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Module#class_variable_get" do it "returns the value of the class variable with the given name" do @@ -15,14 +15,14 @@ describe "Module#class_variable_get" do it "raises a NameError for a class variable named '@@'" do c = Class.new - lambda { c.send(:class_variable_get, "@@") }.should raise_error(NameError) - lambda { c.send(:class_variable_get, :"@@") }.should raise_error(NameError) + -> { c.send(:class_variable_get, "@@") }.should raise_error(NameError) + -> { c.send(:class_variable_get, :"@@") }.should raise_error(NameError) end it "raises a NameError for a class variables with the given name defined in an extended module" do c = Class.new c.extend ModuleSpecs::MVars - lambda { + -> { c.send(:class_variable_get, "@@mvar") }.should raise_error(NameError) end @@ -49,18 +49,18 @@ describe "Module#class_variable_get" do it "raises a NameError when an uninitialized class variable is accessed" do c = Class.new [:@@no_class_var, "@@no_class_var"].each do |cvar| - lambda { c.send(:class_variable_get, cvar) }.should raise_error(NameError) + -> { c.send(:class_variable_get, cvar) }.should raise_error(NameError) end end it "raises a NameError when the given name is not allowed" do c = Class.new - lambda { c.send(:class_variable_get, :invalid_name) }.should raise_error(NameError) - lambda { c.send(:class_variable_get, "@invalid_name") }.should raise_error(NameError) + -> { c.send(:class_variable_get, :invalid_name) }.should raise_error(NameError) + -> { 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" @@ -69,8 +69,8 @@ describe "Module#class_variable_get" do it "raises a TypeError when the given names can't be converted to strings using to_str" do c = Class.new { class_variable_set :@@class_var, "test" } o = mock('123') - lambda { c.send(:class_variable_get, o) }.should raise_error(TypeError) + -> { c.send(:class_variable_get, o) }.should raise_error(TypeError) o.should_receive(:to_str).and_return(123) - lambda { c.send(:class_variable_get, o) }.should raise_error(TypeError) + -> { c.send(:class_variable_get, o) }.should raise_error(TypeError) end end diff --git a/spec/ruby/core/module/class_variable_set_spec.rb b/spec/ruby/core/module/class_variable_set_spec.rb index 6d36298f5f..63f32f5389 100644 --- a/spec/ruby/core/module/class_variable_set_spec.rb +++ b/spec/ruby/core/module/class_variable_set_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Module#class_variable_set" do it "sets the class variable with the given name to the given value" do @@ -25,27 +25,27 @@ describe "Module#class_variable_set" do c.send(:class_variable_get, "@@mvar").should == :new_mvar end - it "raises a RuntimeError when self is frozen" do - lambda { + it "raises a FrozenError when self is frozen" do + -> { Class.new.freeze.send(:class_variable_set, :@@test, "test") - }.should raise_error(RuntimeError) - lambda { + }.should raise_error(FrozenError) + -> { Module.new.freeze.send(:class_variable_set, :@@test, "test") - }.should raise_error(RuntimeError) + }.should raise_error(FrozenError) end it "raises a NameError when the given name is not allowed" do c = Class.new - lambda { + -> { c.send(:class_variable_set, :invalid_name, "test") }.should raise_error(NameError) - lambda { + -> { c.send(:class_variable_set, "@invalid_name", "test") }.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") @@ -55,8 +55,8 @@ describe "Module#class_variable_set" do it "raises a TypeError when the given names can't be converted to strings using to_str" do c = Class.new { class_variable_set :@@class_var, "test" } o = mock('123') - lambda { c.send(:class_variable_set, o, "test") }.should raise_error(TypeError) + -> { c.send(:class_variable_set, o, "test") }.should raise_error(TypeError) o.should_receive(:to_str).and_return(123) - lambda { c.send(:class_variable_set, o, "test") }.should raise_error(TypeError) + -> { c.send(:class_variable_set, o, "test") }.should raise_error(TypeError) end end diff --git a/spec/ruby/core/module/class_variables_spec.rb b/spec/ruby/core/module/class_variables_spec.rb index 066052cd01..e155f1deac 100644 --- a/spec/ruby/core/module/class_variables_spec.rb +++ b/spec/ruby/core/module/class_variables_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Module#class_variables" do it "returns an Array with the names of class variables of self" do @@ -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/comparison_spec.rb b/spec/ruby/core/module/comparison_spec.rb index e7608d64b9..86ee5db22a 100644 --- a/spec/ruby/core/module/comparison_spec.rb +++ b/spec/ruby/core/module/comparison_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Module#<=>" do it "returns -1 if self is a subclass of or includes the given module" do @@ -16,7 +16,7 @@ describe "Module#<=>" do (ModuleSpecs::Super <=> ModuleSpecs::Super).should == 0 end - it "returns +1 if self is a superclas of or included by the given module" do + it "returns +1 if self is a superclass of or included by the given module" do (ModuleSpecs::Parent <=> ModuleSpecs::Child).should == +1 (ModuleSpecs::Basic <=> ModuleSpecs::Child).should == +1 (ModuleSpecs::Super <=> ModuleSpecs::Child).should == +1 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 0789a03e1c..8b137cd134 100644 --- a/spec/ruby/core/module/const_defined_spec.rb +++ b/spec/ruby/core/module/const_defined_spec.rb @@ -1,8 +1,8 @@ # encoding: utf-8 -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../../../fixtures/constants', __FILE__) -require File.expand_path('../fixtures/constant_unicode', __FILE__) +require_relative '../../spec_helper' +require_relative '../../fixtures/constants' +require_relative 'fixtures/constant_unicode' describe "Module#const_defined?" do it "returns true if the given Symbol names a constant defined in the receiver" do @@ -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 @@ -105,19 +130,19 @@ describe "Module#const_defined?" do end it "raises a NameError if the name does not start with a capital letter" do - lambda { ConstantSpecs.const_defined? "name" }.should raise_error(NameError) + -> { ConstantSpecs.const_defined? "name" }.should raise_error(NameError) end it "raises a NameError if the name starts with '_'" do - lambda { ConstantSpecs.const_defined? "__CONSTX__" }.should raise_error(NameError) + -> { ConstantSpecs.const_defined? "__CONSTX__" }.should raise_error(NameError) end it "raises a NameError if the name starts with '@'" do - lambda { ConstantSpecs.const_defined? "@Name" }.should raise_error(NameError) + -> { ConstantSpecs.const_defined? "@Name" }.should raise_error(NameError) end it "raises a NameError if the name starts with '!'" do - lambda { ConstantSpecs.const_defined? "!Name" }.should raise_error(NameError) + -> { ConstantSpecs.const_defined? "!Name" }.should raise_error(NameError) end it "returns true or false for the nested name" do @@ -130,15 +155,15 @@ describe "Module#const_defined?" do it "raises a NameError if the name contains non-alphabetic characters except '_'" do ConstantSpecs.const_defined?("CS_CONSTX").should == false - lambda { ConstantSpecs.const_defined? "Name=" }.should raise_error(NameError) - lambda { ConstantSpecs.const_defined? "Name?" }.should raise_error(NameError) + -> { ConstantSpecs.const_defined? "Name=" }.should raise_error(NameError) + -> { ConstantSpecs.const_defined? "Name?" }.should raise_error(NameError) end it "raises a TypeError if conversion to a String by calling #to_str fails" do name = mock('123') - lambda { ConstantSpecs.const_defined? name }.should raise_error(TypeError) + -> { ConstantSpecs.const_defined? name }.should raise_error(TypeError) name.should_receive(:to_str).and_return(123) - lambda { ConstantSpecs.const_defined? name }.should raise_error(TypeError) + -> { ConstantSpecs.const_defined? name }.should raise_error(TypeError) end end diff --git a/spec/ruby/core/module/const_get_spec.rb b/spec/ruby/core/module/const_get_spec.rb index 6f8d5d930f..4b53cbe7b3 100644 --- a/spec/ruby/core/module/const_get_spec.rb +++ b/spec/ruby/core/module/const_get_spec.rb @@ -1,5 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../../../fixtures/constants', __FILE__) +require_relative '../../spec_helper' +require_relative '../../fixtures/constants' +require_relative 'fixtures/constants_autoload' describe "Module#const_get" do it "accepts a String or Symbol name" do @@ -8,28 +9,28 @@ describe "Module#const_get" do end it "raises a NameError if no constant is defined in the search path" do - lambda { ConstantSpecs.const_get :CS_CONSTX }.should raise_error(NameError) + -> { ConstantSpecs.const_get :CS_CONSTX }.should raise_error(NameError) end it "raises a NameError with the not found constant symbol" do - error_inspection = lambda { |e| e.name.should == :CS_CONSTX } - lambda { ConstantSpecs.const_get :CS_CONSTX }.should raise_error(NameError, &error_inspection) + error_inspection = -> e { e.name.should == :CS_CONSTX } + -> { ConstantSpecs.const_get :CS_CONSTX }.should raise_error(NameError, &error_inspection) end it "raises a NameError if the name does not start with a capital letter" do - lambda { ConstantSpecs.const_get "name" }.should raise_error(NameError) + -> { ConstantSpecs.const_get "name" }.should raise_error(NameError) end it "raises a NameError if the name starts with a non-alphabetic character" do - lambda { ConstantSpecs.const_get "__CONSTX__" }.should raise_error(NameError) - lambda { ConstantSpecs.const_get "@CS_CONST1" }.should raise_error(NameError) - lambda { ConstantSpecs.const_get "!CS_CONST1" }.should raise_error(NameError) + -> { ConstantSpecs.const_get "__CONSTX__" }.should raise_error(NameError) + -> { ConstantSpecs.const_get "@CS_CONST1" }.should raise_error(NameError) + -> { ConstantSpecs.const_get "!CS_CONST1" }.should raise_error(NameError) end it "raises a NameError if the name contains non-alphabetic characters except '_'" do Object.const_get("CS_CONST1").should == :const1 - lambda { ConstantSpecs.const_get "CS_CONST1=" }.should raise_error(NameError) - lambda { ConstantSpecs.const_get "CS_CONST1?" }.should raise_error(NameError) + -> { ConstantSpecs.const_get "CS_CONST1=" }.should raise_error(NameError) + -> { ConstantSpecs.const_get "CS_CONST1?" }.should raise_error(NameError) end it "calls #to_str to convert the given name to a String" do @@ -40,10 +41,10 @@ describe "Module#const_get" do it "raises a TypeError if conversion to a String by calling #to_str fails" do name = mock('123') - lambda { ConstantSpecs.const_get(name) }.should raise_error(TypeError) + -> { ConstantSpecs.const_get(name) }.should raise_error(TypeError) name.should_receive(:to_str).and_return(123) - lambda { ConstantSpecs.const_get(name) }.should raise_error(TypeError) + -> { ConstantSpecs.const_get(name) }.should raise_error(TypeError) end it "calls #const_missing on the receiver if unable to locate the constant" do @@ -52,21 +53,21 @@ describe "Module#const_get" do end it "does not search the singleton class of a Class or Module" do - lambda do + -> do ConstantSpecs::ContainerA::ChildA.const_get(:CS_CONST14) end.should raise_error(NameError) - lambda { ConstantSpecs.const_get(:CS_CONST14) }.should raise_error(NameError) + -> { ConstantSpecs.const_get(:CS_CONST14) }.should raise_error(NameError) end it "does not search the containing scope" do ConstantSpecs::ContainerA::ChildA.const_get(:CS_CONST20).should == :const20_2 - lambda do + -> do ConstantSpecs::ContainerA::ChildA.const_get(:CS_CONST5) end.should raise_error(NameError) end - it "raises a NameError if the constant is defined in the receiver's supperclass and the inherit flag is false" do - lambda do + it "raises a NameError if the constant is defined in the receiver's superclass and the inherit flag is false" do + -> do ConstantSpecs::ContainerA::ChildA.const_get(:CS_CONST4, false) end.should raise_error(NameError) end @@ -76,39 +77,81 @@ describe "Module#const_get" do end it "raises a NameError when the receiver is a Module, the constant is defined at toplevel and the inherit flag is false" do - lambda do + -> do ConstantSpecs::ModuleA.const_get(:CS_CONST1, false) end.should raise_error(NameError) end it "raises a NameError when the receiver is a Class, the constant is defined at toplevel and the inherit flag is false" do - lambda do + -> do ConstantSpecs::ContainerA::ChildA.const_get(:CS_CONST1, false) 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 + it "raises a NameError if the name includes two successive scope separators" do + -> { ConstantSpecs.const_get("ClassA::::CS_CONST10") }.should raise_error(NameError) + end + it "raises a NameError if only '::' is passed" do - lambda { ConstantSpecs.const_get("::") }.should raise_error(NameError) + -> { ConstantSpecs.const_get("::") }.should raise_error(NameError) end it "raises a NameError if a Symbol has a toplevel scope qualifier" do - lambda { ConstantSpecs.const_get(:'::CS_CONST1') }.should raise_error(NameError) + -> { ConstantSpecs.const_get(:'::CS_CONST1') }.should raise_error(NameError) end it "raises a NameError if a Symbol is a scoped constant name" do - lambda { ConstantSpecs.const_get(:'ClassA::CS_CONST10') }.should raise_error(NameError) + -> { ConstantSpecs.const_get(:'ClassA::CS_CONST10') }.should raise_error(NameError) 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 + Object.const_get('CSAutoloadA').name.should == 'CSAutoloadA' + end + + it 'does autoload a constant with a toplevel scope qualifier' do + Object.const_get('::CSAutoloadB').name.should == 'CSAutoloadB' + end + + it 'does autoload a module and resolve a constant within' do + Object.const_get('CSAutoloadC::CONST').should == 7 + end + + it 'does autoload a non-toplevel module' 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 @@ -159,50 +202,72 @@ 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 ConstantSpecs::ClassB::CS_CONST309 = :const309_1 ConstantSpecs::ClassB.const_get(:CS_CONST309).should == :const309_1 - lambda { + -> { 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_missing_spec.rb b/spec/ruby/core/module/const_missing_spec.rb index 24f2b49edc..742218281c 100644 --- a/spec/ruby/core/module/const_missing_spec.rb +++ b/spec/ruby/core/module/const_missing_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../../../fixtures/constants', __FILE__) +require_relative '../../spec_helper' +require_relative '../../fixtures/constants' describe "Module#const_missing" do it "is called when an undefined constant is referenced via literal form" do @@ -11,7 +11,7 @@ describe "Module#const_missing" do end it "raises NameError and includes the name of the value that wasn't found" do - lambda { + -> { ConstantSpecs.const_missing("HelloMissing") }.should raise_error(NameError, /ConstantSpecs::HelloMissing/) end @@ -24,4 +24,13 @@ describe "Module#const_missing" do end end + it "is called regardless of visibility" do + klass = Class.new do + def self.const_missing(name) + "Found:#{name}" + end + private_class_method :const_missing + end + klass::Hello.should == 'Found:Hello' + end end diff --git a/spec/ruby/core/module/const_set_spec.rb b/spec/ruby/core/module/const_set_spec.rb index 6f4f6f980f..823768b882 100644 --- a/spec/ruby/core/module/const_set_spec.rb +++ b/spec/ruby/core/module/const_set_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../../../fixtures/constants', __FILE__) +require_relative '../../spec_helper' +require_relative '../../fixtures/constants' describe "Module#const_set" do it "sets the constant specified by a String or Symbol to the given value" do @@ -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,23 +45,27 @@ 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 - lambda { ConstantSpecs.const_set "name", 1 }.should raise_error(NameError) + -> { ConstantSpecs.const_set "name", 1 }.should raise_error(NameError) end it "raises a NameError if the name starts with a non-alphabetic character" do - lambda { ConstantSpecs.const_set "__CONSTX__", 1 }.should raise_error(NameError) - lambda { ConstantSpecs.const_set "@Name", 1 }.should raise_error(NameError) - lambda { ConstantSpecs.const_set "!Name", 1 }.should raise_error(NameError) - lambda { ConstantSpecs.const_set "::Name", 1 }.should raise_error(NameError) + -> { ConstantSpecs.const_set "__CONSTX__", 1 }.should raise_error(NameError) + -> { ConstantSpecs.const_set "@Name", 1 }.should raise_error(NameError) + -> { ConstantSpecs.const_set "!Name", 1 }.should raise_error(NameError) + -> { ConstantSpecs.const_set "::Name", 1 }.should raise_error(NameError) end it "raises a NameError if the name contains non-alphabetic characters except '_'" do ConstantSpecs.const_set("CS_CONST404", :const404).should == :const404 - lambda { ConstantSpecs.const_set "Name=", 1 }.should raise_error(NameError) - lambda { ConstantSpecs.const_set "Name?", 1 }.should raise_error(NameError) + -> { 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,14 +73,62 @@ 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 name = mock('123') - lambda { ConstantSpecs.const_set name, 1 }.should raise_error(TypeError) + -> { ConstantSpecs.const_set name, 1 }.should raise_error(TypeError) name.should_receive(:to_str).and_return(123) - lambda { ConstantSpecs.const_set name, 1 }.should raise_error(TypeError) + -> { ConstantSpecs.const_set name, 1 }.should raise_error(TypeError) + end + + describe "when overwriting an existing constant" do + it "warns if the previous value was a normal value" do + mod = Module.new + mod.const_set :Foo, 42 + -> { + mod.const_set :Foo, 1 + }.should complain(/already initialized constant/) + mod.const_get(:Foo).should == 1 + end + + it "does not warn if the previous value was an autoload" do + mod = Module.new + mod.autoload :Foo, "not-existing" + -> { + mod.const_set :Foo, 1 + }.should_not complain + mod.const_get(:Foo).should == 1 + end + + it "does not warn after a failed autoload" do + path = fixture(__FILE__, "autoload_o.rb") + ScratchPad.record [] + mod = Module.new + + mod.autoload :Foo, path + -> { mod::Foo }.should raise_error(NameError) + + mod.const_defined?(:Foo).should == false + mod.autoload?(:Foo).should == nil + + -> { + mod.const_set :Foo, 1 + }.should_not complain + mod.const_get(:Foo).should == 1 + end + + it "does not warn if the new value is an autoload" do + mod = Module.new + mod.const_set :Foo, 42 + -> { + mod.autoload :Foo, "not-existing" + }.should_not complain + mod.const_get(:Foo).should == 42 + end end describe "on a frozen module" do @@ -78,8 +137,8 @@ describe "Module#const_set" do @name = :Foo end - it "raises a RuntimeError before setting the name" do - lambda { @frozen.const_set @name, nil }.should raise_error(RuntimeError) + it "raises a FrozenError before setting the name" do + -> { @frozen.const_set @name, nil }.should raise_error(FrozenError) @frozen.should_not have_constant(@name) end end 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 c331482bfb..330da1cc88 100644 --- a/spec/ruby/core/module/constants_spec.rb +++ b/spec/ruby/core/module/constants_spec.rb @@ -1,6 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../../../fixtures/constants', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative '../../fixtures/constants' +require_relative 'fixtures/classes' describe "Module.constants" do it "returns an array of the names of all toplevel constants" do @@ -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 64ac5306f0..c5dfc53764 100644 --- a/spec/ruby/core/module/define_method_spec.rb +++ b/spec/ruby/core/module/define_method_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' class DefineMethodSpecClass end @@ -12,7 +12,7 @@ describe "passed { |a, b = 1| } creates a method that" do end it "raises an ArgumentError when passed zero arguments" do - lambda { @klass.new.m }.should raise_error(ArgumentError) + -> { @klass.new.m }.should raise_error(ArgumentError) end it "has a default value for b when passed one argument" do @@ -24,7 +24,7 @@ describe "passed { |a, b = 1| } creates a method that" do end it "raises an ArgumentError when passed three arguments" do - lambda { @klass.new.m(1, 2, 3) }.should raise_error(ArgumentError) + -> { @klass.new.m(1, 2, 3) }.should raise_error(ArgumentError) end end @@ -83,11 +83,28 @@ describe "Module#define_method when given an UnboundMethod" do define_method :piggy, instance_method(:ziggy) end - lambda { foo.new.ziggy }.should raise_error(NoMethodError) + -> { foo.new.ziggy }.should raise_error(NoMethodError) foo.new.piggy.should == 'piggy' 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 @@ -194,7 +222,7 @@ describe "Module#define_method" do it "defines a new method with the given name and the given block as body in self" do class DefineMethodSpecClass define_method(:block_test1) { self } - define_method(:block_test2, &lambda { self }) + define_method(:block_test2, &-> { self }) end o = DefineMethodSpecClass.new @@ -202,39 +230,74 @@ 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 - lambda { + -> { Class.new { define_method(:test, "self") } - }.should raise_error(TypeError) + }.should raise_error(TypeError, "wrong argument type String (expected Proc/Method/UnboundMethod)") - lambda { + -> { Class.new { define_method(:test, 1234) } - }.should raise_error(TypeError) + }.should raise_error(TypeError, "wrong argument type Integer (expected Proc/Method/UnboundMethod)") - lambda { + -> { 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 - lambda { + -> { Class.new { define_method(:test) } }.should raise_error(ArgumentError) end - ruby_version_is "2.3" do - it "does not use the caller block when no block is given" do - o = Object.new - def o.define(name) - self.class.class_eval do - define_method(name) - end + it "does not use the caller block when no block is given" do + o = Object.new + def o.define(name) + self.class.class_eval do + define_method(name) end - - lambda { - o.define(:foo) { raise "not used" } - }.should raise_error(ArgumentError) end + + -> { + o.define(:foo) { raise "not used" } + }.should raise_error(ArgumentError) end it "does not change the arity check style of the original proc" do @@ -244,13 +307,13 @@ describe "Module#define_method" do end obj = DefineMethodSpecClass.new - lambda { obj.proc_style_test :arg }.should raise_error(ArgumentError) + -> { obj.proc_style_test :arg }.should raise_error(ArgumentError) end - it "raises a RuntimeError if frozen" do - lambda { + it "raises a FrozenError if frozen" do + -> { Class.new { freeze; define_method(:foo) {} } - }.should raise_error(RuntimeError) + }.should raise_error(FrozenError) end it "accepts a Method (still bound)" do @@ -269,7 +332,7 @@ describe "Module#define_method" do c = klass.new c.data = :bar c.other_inspect.should == "data is bar" - lambda{o.other_inspect}.should raise_error(NoMethodError) + ->{o.other_inspect}.should raise_error(NoMethodError) end it "raises a TypeError when a Method from a singleton class is defined on another class" do @@ -281,9 +344,9 @@ describe "Module#define_method" do end m = c.method(:foo) - lambda { + -> { Class.new { define_method :bar, m } - }.should raise_error(TypeError) + }.should raise_error(TypeError, /can't bind singleton method to a different class/) end it "raises a TypeError when a Method from one class is defined on an unrelated class" do @@ -293,7 +356,7 @@ describe "Module#define_method" do end m = c.new.method(:foo) - lambda { + -> { Class.new { define_method :bar, m } }.should raise_error(TypeError) end @@ -307,7 +370,7 @@ describe "Module#define_method" do o = DefineMethodSpecClass.new DefineMethodSpecClass.send(:undef_method, :accessor_method) - lambda { o.accessor_method }.should raise_error(NoMethodError) + -> { o.accessor_method }.should raise_error(NoMethodError) DefineMethodSpecClass.send(:define_method, :accessor_method, m) @@ -335,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 @@ -357,15 +428,8 @@ describe "Module#define_method" do klass.new.string_test.should == "string_test result" end - ruby_version_is ''...'2.5' do - it "is a private method" do - Module.should have_private_instance_method(:define_method) - end - end - ruby_version_is '2.5' do - it "is a public method" do - Module.should have_public_instance_method(:define_method) - end + it "is a public method" do + Module.should have_public_instance_method(:define_method) end it "returns its symbol" do @@ -398,20 +462,72 @@ describe "Module#define_method" do klass.new.should respond_to(:bar) end + + it "allows an UnboundMethod of a Kernel method retrieved from Object to defined on a BasicObject subclass" do + klass = Class.new(BasicObject) do + define_method :instance_of?, ::Object.instance_method(:instance_of?) + end + klass.new.instance_of?(klass).should == true + end + it "raises a TypeError when an UnboundMethod from a child class is defined on a parent class" do - lambda { + -> { ParentClass = Class.new { define_method(:foo) { :bar } } ChildClass = Class.new(ParentClass) { define_method(:foo) { :baz } } ParentClass.send :define_method, :foo, ChildClass.instance_method(:foo) - }.should raise_error(TypeError) + }.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 - lambda { + -> { DestinationClass = Class.new { define_method :bar, ModuleSpecs::InstanceMeth.instance_method(:foo) } - }.should raise_error(TypeError) + }.should raise_error(TypeError, /bind argument must be a subclass of ModuleSpecs::InstanceMeth/) + end + + it "raises a TypeError when an UnboundMethod from a singleton class is defined on another class" do + c = Class.new do + class << self + def foo + end + end + end + m = c.method(:foo).unbind + + -> { + 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 @@ -428,11 +544,11 @@ describe "Module#define_method" do end it "raises an ArgumentError when passed one argument" do - lambda { @klass.new.m 1 }.should raise_error(ArgumentError) + -> { @klass.new.m 1 }.should raise_error(ArgumentError) end it "raises an ArgumentError when passed two arguments" do - lambda { @klass.new.m 1, 2 }.should raise_error(ArgumentError) + -> { @klass.new.m 1, 2 }.should raise_error(ArgumentError) end end @@ -448,11 +564,11 @@ describe "Module#define_method" do end it "raises an ArgumentError when passed one argument" do - lambda { @klass.new.m 1 }.should raise_error(ArgumentError) + -> { @klass.new.m 1 }.should raise_error(ArgumentError) end it "raises an ArgumentError when passed two arguments" do - lambda { @klass.new.m 1, 2 }.should raise_error(ArgumentError) + -> { @klass.new.m 1, 2 }.should raise_error(ArgumentError) end end @@ -464,21 +580,50 @@ describe "Module#define_method" do end it "raises an ArgumentError when passed zero arguments" do - lambda { @klass.new.m }.should raise_error(ArgumentError) + -> { @klass.new.m }.should raise_error(ArgumentError) end it "raises an ArgumentError when passed zero arguments and a block" do - lambda { @klass.new.m { :computed } }.should raise_error(ArgumentError) + -> { @klass.new.m { :computed } }.should raise_error(ArgumentError) end it "raises an ArgumentError when passed two arguments" do - lambda { @klass.new.m 1, 2 }.should raise_error(ArgumentError) + -> { @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 + 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 @@ -509,7 +654,7 @@ describe "Module#define_method" do end it "raises an ArgumentError when passed zero arguments" do - lambda { @klass.new.m }.should raise_error(ArgumentError) + -> { @klass.new.m }.should raise_error(ArgumentError) end it "returns the value computed by the block when passed one argument" do @@ -537,19 +682,19 @@ describe "Module#define_method" do end it "raises an ArgumentError when passed zero arguments" do - lambda { @klass.new.m }.should raise_error(ArgumentError) + -> { @klass.new.m }.should raise_error(ArgumentError) end it "raises an ArgumentError when passed one argument" do - lambda { @klass.new.m 1 }.should raise_error(ArgumentError) + -> { @klass.new.m 1 }.should raise_error(ArgumentError) end it "raises an ArgumentError when passed one argument and a block" do - lambda { @klass.new.m(1) { } }.should raise_error(ArgumentError) + -> { @klass.new.m(1) { } }.should raise_error(ArgumentError) end it "raises an ArgumentError when passed three arguments" do - lambda { @klass.new.m 1, 2, 3 }.should raise_error(ArgumentError) + -> { @klass.new.m 1, 2, 3 }.should raise_error(ArgumentError) end end @@ -561,15 +706,15 @@ describe "Module#define_method" do end it "raises an ArgumentError when passed zero arguments" do - lambda { @klass.new.m }.should raise_error(ArgumentError) + -> { @klass.new.m }.should raise_error(ArgumentError) end it "raises an ArgumentError when passed one argument" do - lambda { @klass.new.m 1 }.should raise_error(ArgumentError) + -> { @klass.new.m 1 }.should raise_error(ArgumentError) end it "raises an ArgumentError when passed one argument and a block" do - lambda { @klass.new.m(1) { } }.should raise_error(ArgumentError) + -> { @klass.new.m(1) { } }.should raise_error(ArgumentError) end it "receives an empty array as the third argument when passed two arguments" do @@ -582,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) @@ -607,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) @@ -631,3 +776,71 @@ describe "Method#define_method when passed an UnboundMethod object" do @obj.method(:n).parameters.should == @obj.method(:m).parameters end end + +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 + t = Class.new do + prc = -> { + def nested_method_in_proc_for_define_method + 42 + end + } + end + + c = Class.new do + define_method(:test, prc) + end + + o = c.new + o.test + o.should_not have_method :nested_method_in_proc_for_define_method + + t.new.nested_method_in_proc_for_define_method.should == 42 + 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/define_singleton_method_spec.rb b/spec/ruby/core/module/define_singleton_method_spec.rb index 0841d5d7e2..eb5cb89ed1 100644 --- a/spec/ruby/core/module/define_singleton_method_spec.rb +++ b/spec/ruby/core/module/define_singleton_method_spec.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' describe "Module#define_singleton_method" do it "defines the given method as an class method with the given name in self" do @@ -6,12 +6,10 @@ describe "Module#define_singleton_method" do define_singleton_method :a do 42 end - define_singleton_method(:b, lambda {|x| 2*x }) + define_singleton_method(:b, -> x { 2*x }) end klass.a.should == 42 klass.b(10).should == 20 end - - it "needs to be reviewed for spec completeness" end diff --git a/spec/ruby/core/module/deprecate_constant_spec.rb b/spec/ruby/core/module/deprecate_constant_spec.rb index 8c7a170f1c..ec0de6782f 100644 --- a/spec/ruby/core/module/deprecate_constant_spec.rb +++ b/spec/ruby/core/module/deprecate_constant_spec.rb @@ -1,52 +1,70 @@ -require File.expand_path('../../../spec_helper', __FILE__) - -ruby_version_is "2.3" do - describe "Module#deprecate_constant" do - before :each do - @module = Module.new - @value = :value - @module::PUBLIC1 = @value - @module::PUBLIC2 = @value - @module::PRIVATE = @value - @module.private_constant :PRIVATE - @module.deprecate_constant :PRIVATE - @pattern = /deprecated/ +require_relative '../../spec_helper' + +describe "Module#deprecate_constant" do + before :each do + @module = Module.new + @value = :value + @module::PUBLIC1 = @value + @module::PUBLIC2 = @value + @module::PRIVATE = @value + @module.private_constant :PRIVATE + @module.deprecate_constant :PRIVATE + end + + describe "when accessing the deprecated module" do + it "passes the accessing" do + @module.deprecate_constant :PUBLIC1 + + value = nil + -> { + value = @module::PUBLIC1 + }.should complain(/warning: constant .+::PUBLIC1 is deprecated/) + value.should equal(@value) + + -> { @module::PRIVATE }.should raise_error(NameError) end - describe "when accessing the deprecated module" do - it "passes the accessing" do - @module.deprecate_constant :PUBLIC1 + it "warns with a message" do + @module.deprecate_constant :PUBLIC1 + + -> { @module::PUBLIC1 }.should complain(/warning: constant .+::PUBLIC1 is deprecated/) + -> { @module.const_get :PRIVATE }.should complain(/warning: constant .+::PRIVATE is deprecated/) + end - value = nil - lambda { - value = @module::PUBLIC1 - }.should complain(@pattern) - value.should equal(@value) + it "does not warn if Warning[:deprecated] is false" do + @module.deprecate_constant :PUBLIC1 - lambda { @module::PRIVATE }.should raise_error(NameError) + 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 - - lambda { @module::PUBLIC1 }.should complain(@pattern) - lambda { @module.const_get :PRIVATE }.should complain(@pattern) + -> { @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 + it "accepts multiple symbols and strings as constant names" do + @module.deprecate_constant "PUBLIC1", :PUBLIC2 - lambda { @module::PUBLIC1 }.should complain(@pattern) - lambda { @module::PUBLIC2 }.should complain(@pattern) - end + -> { @module::PUBLIC1 }.should complain(/warning: constant .+::PUBLIC1 is deprecated/) + -> { @module::PUBLIC2 }.should complain(/warning: constant .+::PUBLIC2 is deprecated/) + end - it "returns self" do - @module.deprecate_constant(:PUBLIC1).should equal(@module) - end + it "returns self" do + @module.deprecate_constant(:PUBLIC1).should equal(@module) + end - it "raises a NameError when given an undefined name" do - lambda { @module.deprecate_constant :UNDEFINED }.should raise_error(NameError) - end + it "raises a NameError when given an undefined name" do + -> { @module.deprecate_constant :UNDEFINED }.should raise_error(NameError) end end diff --git a/spec/ruby/core/module/eql_spec.rb b/spec/ruby/core/module/eql_spec.rb index f8878d7f4e..76bb271d8d 100644 --- a/spec/ruby/core/module/eql_spec.rb +++ b/spec/ruby/core/module/eql_spec.rb @@ -1,7 +1,7 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../shared/equal_value', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/equal_value' describe "Module#eql?" do - it_behaves_like(:module_equal, :eql?) + it_behaves_like :module_equal, :eql? end diff --git a/spec/ruby/core/module/equal_spec.rb b/spec/ruby/core/module/equal_spec.rb index bc70a6277a..01ab06152d 100644 --- a/spec/ruby/core/module/equal_spec.rb +++ b/spec/ruby/core/module/equal_spec.rb @@ -1,7 +1,7 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../shared/equal_value', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/equal_value' describe "Module#equal?" do - it_behaves_like(:module_equal, :equal?) + it_behaves_like :module_equal, :equal? end diff --git a/spec/ruby/core/module/equal_value_spec.rb b/spec/ruby/core/module/equal_value_spec.rb index 12494477f9..a7191cd755 100644 --- a/spec/ruby/core/module/equal_value_spec.rb +++ b/spec/ruby/core/module/equal_value_spec.rb @@ -1,7 +1,7 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../shared/equal_value', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/equal_value' describe "Module#==" do - it_behaves_like(:module_equal, :==) + it_behaves_like :module_equal, :== end diff --git a/spec/ruby/core/module/extend_object_spec.rb b/spec/ruby/core/module/extend_object_spec.rb index 73c1623dce..1fd1abc0b5 100644 --- a/spec/ruby/core/module/extend_object_spec.rb +++ b/spec/ruby/core/module/extend_object_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Module#extend_object" do before :each do @@ -16,7 +16,7 @@ describe "Module#extend_object" do end it "raises a TypeError if calling after rebinded to Class" do - lambda { + -> { Module.instance_method(:extend_object).bind(Class.new).call Object.new }.should raise_error(TypeError) end @@ -42,18 +42,6 @@ describe "Module#extend_object" do ScratchPad.recorded.should == :extended end - 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 - describe "when given a frozen object" do before :each do @receiver = Module.new @@ -61,7 +49,7 @@ describe "Module#extend_object" do end it "raises a RuntimeError before extending the object" do - lambda { @receiver.send(:extend_object, @object) }.should raise_error(RuntimeError) + -> { @receiver.send(:extend_object, @object) }.should raise_error(RuntimeError) @object.should_not be_kind_of(@receiver) end end diff --git a/spec/ruby/core/module/extended_spec.rb b/spec/ruby/core/module/extended_spec.rb index 0a62dd9850..c6300ffa0b 100644 --- a/spec/ruby/core/module/extended_spec.rb +++ b/spec/ruby/core/module/extended_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Module#extended" do it "is called when an object gets extended with self" do diff --git a/spec/ruby/core/module/fixtures/autoload_callback.rb b/spec/ruby/core/module/fixtures/autoload_callback.rb new file mode 100644 index 0000000000..51d53eb580 --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_callback.rb @@ -0,0 +1,2 @@ +block = ScratchPad.recorded +block.call 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.rb b/spec/ruby/core/module/fixtures/autoload_during_autoload.rb new file mode 100644 index 0000000000..5202bd8b23 --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_during_autoload.rb @@ -0,0 +1,7 @@ +block = ScratchPad.recorded +ScratchPad.record(block.call) + +module ModuleSpecs::Autoload + class DuringAutoload + 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.rb b/spec/ruby/core/module/fixtures/autoload_during_require.rb new file mode 100644 index 0000000000..6fd81592e3 --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_during_require.rb @@ -0,0 +1,4 @@ +module ModuleSpecs::Autoload + class AutoloadDuringRequire + 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_exception.rb b/spec/ruby/core/module/fixtures/autoload_exception.rb new file mode 100644 index 0000000000..09acf9f537 --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_exception.rb @@ -0,0 +1,3 @@ +ScratchPad.record(:exception) + +raise 'intentional error to test failure conditions during autoloading' 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/autoload_nested.rb b/spec/ruby/core/module/fixtures/autoload_nested.rb new file mode 100644 index 0000000000..073cec0dce --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_nested.rb @@ -0,0 +1,8 @@ +module ModuleSpecs::Autoload + module GoodParent + class Nested + end + end +end + +ScratchPad.record(:loaded) diff --git a/spec/ruby/core/module/fixtures/autoload_o.rb b/spec/ruby/core/module/fixtures/autoload_o.rb index 6d54ddaf12..7d88f969b2 100644 --- a/spec/ruby/core/module/fixtures/autoload_o.rb +++ b/spec/ruby/core/module/fixtures/autoload_o.rb @@ -1 +1,2 @@ # does not define ModuleSpecs::Autoload::O +ScratchPad << :loaded diff --git a/spec/ruby/core/module/fixtures/autoload_overridden.rb b/spec/ruby/core/module/fixtures/autoload_overridden.rb new file mode 100644 index 0000000000..7062bcfabc --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_overridden.rb @@ -0,0 +1,3 @@ +module ModuleSpecs::Autoload + Overridden = :overridden +end diff --git a/spec/ruby/core/module/fixtures/autoload_raise.rb b/spec/ruby/core/module/fixtures/autoload_raise.rb new file mode 100644 index 0000000000..f6051e3ba2 --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_raise.rb @@ -0,0 +1,2 @@ +ScratchPad << :raise +raise "exception during autoload" diff --git a/spec/ruby/core/module/fixtures/autoload_required_directly.rb b/spec/ruby/core/module/fixtures/autoload_required_directly.rb new file mode 100644 index 0000000000..bed60a71ec --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_required_directly.rb @@ -0,0 +1,7 @@ +block = ScratchPad.recorded +ScratchPad.record(block.call) + +module ModuleSpecs::Autoload + class RequiredDirectly + end +end diff --git a/spec/ruby/core/module/fixtures/autoload_required_directly_nested.rb b/spec/ruby/core/module/fixtures/autoload_required_directly_nested.rb new file mode 100644 index 0000000000..a9f11c2188 --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_required_directly_nested.rb @@ -0,0 +1 @@ +ScratchPad.recorded.call diff --git a/spec/ruby/core/module/fixtures/autoload_required_directly_no_constant.rb b/spec/ruby/core/module/fixtures/autoload_required_directly_no_constant.rb new file mode 100644 index 0000000000..25e08c1129 --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_required_directly_no_constant.rb @@ -0,0 +1,2 @@ +block = ScratchPad.recorded +ScratchPad.record(block.call) diff --git a/spec/ruby/core/module/fixtures/autoload_scope.rb b/spec/ruby/core/module/fixtures/autoload_scope.rb deleted file mode 100644 index 04193687b5..0000000000 --- a/spec/ruby/core/module/fixtures/autoload_scope.rb +++ /dev/null @@ -1,8 +0,0 @@ -module ModuleSpecs - module Autoload - class PP - class QQ - end - end - end -end diff --git a/spec/ruby/core/module/fixtures/autoload_self_during_require.rb b/spec/ruby/core/module/fixtures/autoload_self_during_require.rb new file mode 100644 index 0000000000..f4a514a807 --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_self_during_require.rb @@ -0,0 +1,5 @@ +module ModuleSpecs::Autoload + autoload :AutoloadSelfDuringRequire, __FILE__ + class AutoloadSelfDuringRequire + end +end diff --git a/spec/ruby/core/module/fixtures/autoload_subclass.rb b/spec/ruby/core/module/fixtures/autoload_subclass.rb index 569972118c..8027fa3fcd 100644 --- a/spec/ruby/core/module/fixtures/autoload_subclass.rb +++ b/spec/ruby/core/module/fixtures/autoload_subclass.rb @@ -1,10 +1,10 @@ -class YY +class CS_CONST_AUTOLOAD end module ModuleSpecs module Autoload module XX - class YY < YY + class CS_CONST_AUTOLOAD < CS_CONST_AUTOLOAD end end end diff --git a/spec/ruby/core/module/fixtures/classes.rb b/spec/ruby/core/module/fixtures/classes.rb index f93c39683e..964f64c593 100644 --- a/spec/ruby/core/module/fixtures/classes.rb +++ b/spec/ruby/core/module/fixtures/classes.rb @@ -1,11 +1,14 @@ 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 CONST = :plain_constant + class NamedClass + end + module PrivConstModule PRIVATE_CONSTANT = 1 private_constant :PRIVATE_CONSTANT @@ -39,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 @@ -349,6 +360,10 @@ module ModuleSpecs end end + class SubCVars < CVars + @@sub = :sub + end + module MVars @@mvar = :mvar end @@ -373,6 +388,7 @@ module ModuleSpecs # empty modules module M1; end module M2; end + module M3; end module Autoload def self.use_ex1 @@ -386,6 +402,12 @@ module ModuleSpecs end end + class Parent + end + + class Child < Parent + end + module FromThread module A autoload :B, fixture(__FILE__, "autoload_empty.rb") @@ -574,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/constants_autoload.rb b/spec/ruby/core/module/fixtures/constants_autoload.rb new file mode 100644 index 0000000000..8e9aa8de0c --- /dev/null +++ b/spec/ruby/core/module/fixtures/constants_autoload.rb @@ -0,0 +1,6 @@ +autoload :CSAutoloadA, fixture(__FILE__, 'constants_autoload_a.rb') +autoload :CSAutoloadB, fixture(__FILE__, 'constants_autoload_b.rb') +autoload :CSAutoloadC, fixture(__FILE__, 'constants_autoload_c.rb') +module CSAutoloadD + autoload :InnerModule, fixture(__FILE__, 'constants_autoload_d.rb') +end diff --git a/spec/ruby/core/module/fixtures/constants_autoload_a.rb b/spec/ruby/core/module/fixtures/constants_autoload_a.rb new file mode 100644 index 0000000000..48d3b63681 --- /dev/null +++ b/spec/ruby/core/module/fixtures/constants_autoload_a.rb @@ -0,0 +1,2 @@ +module CSAutoloadA +end diff --git a/spec/ruby/core/module/fixtures/constants_autoload_b.rb b/spec/ruby/core/module/fixtures/constants_autoload_b.rb new file mode 100644 index 0000000000..29cd742d03 --- /dev/null +++ b/spec/ruby/core/module/fixtures/constants_autoload_b.rb @@ -0,0 +1,2 @@ +module CSAutoloadB +end diff --git a/spec/ruby/core/module/fixtures/constants_autoload_c.rb b/spec/ruby/core/module/fixtures/constants_autoload_c.rb new file mode 100644 index 0000000000..9d6a6bf4d7 --- /dev/null +++ b/spec/ruby/core/module/fixtures/constants_autoload_c.rb @@ -0,0 +1,3 @@ +module CSAutoloadC + CONST = 7 +end diff --git a/spec/ruby/core/module/fixtures/constants_autoload_d.rb b/spec/ruby/core/module/fixtures/constants_autoload_d.rb new file mode 100644 index 0000000000..52d550bab0 --- /dev/null +++ b/spec/ruby/core/module/fixtures/constants_autoload_d.rb @@ -0,0 +1,4 @@ +module CSAutoloadD + module InnerModule + 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/multi/foo.rb b/spec/ruby/core/module/fixtures/multi/foo.rb new file mode 100644 index 0000000000..549996f08f --- /dev/null +++ b/spec/ruby/core/module/fixtures/multi/foo.rb @@ -0,0 +1,6 @@ +module ModuleSpecs::Autoload + module Foo + autoload :Bar, 'foo/bar_baz' + autoload :Baz, 'foo/bar_baz' + end +end diff --git a/spec/ruby/core/module/fixtures/multi/foo/bar_baz.rb b/spec/ruby/core/module/fixtures/multi/foo/bar_baz.rb new file mode 100644 index 0000000000..53d3849e1f --- /dev/null +++ b/spec/ruby/core/module/fixtures/multi/foo/bar_baz.rb @@ -0,0 +1,11 @@ +require 'foo' + +module ModuleSpecs::Autoload + module Foo + class Bar + end + + class Baz + end + 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 46975361dd..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 @@ -10,4 +14,12 @@ module ModuleSpecs module IncludedModule def foo; "foo from included module"; end end + + 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/freeze_spec.rb b/spec/ruby/core/module/freeze_spec.rb index 07a0ee0837..fd76141431 100644 --- a/spec/ruby/core/module/freeze_spec.rb +++ b/spec/ruby/core/module/freeze_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Module#freeze" do it "needs to be reviewed for spec completeness" diff --git a/spec/ruby/core/module/gt_spec.rb b/spec/ruby/core/module/gt_spec.rb index 73a6c80b69..b8a73c9ff9 100644 --- a/spec/ruby/core/module/gt_spec.rb +++ b/spec/ruby/core/module/gt_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Module#>" do it "returns false if self is a subclass of or includes the given module" do @@ -31,6 +31,6 @@ describe "Module#>" do end it "raises a TypeError if the argument is not a class/module" do - lambda { ModuleSpecs::Parent > mock('x') }.should raise_error(TypeError) + -> { ModuleSpecs::Parent > mock('x') }.should raise_error(TypeError) end end diff --git a/spec/ruby/core/module/gte_spec.rb b/spec/ruby/core/module/gte_spec.rb index 84758e2df6..18c60ba586 100644 --- a/spec/ruby/core/module/gte_spec.rb +++ b/spec/ruby/core/module/gte_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Module#>=" do it "returns true if self is a superclass of, the same as or included by given module" do @@ -28,6 +28,6 @@ describe "Module#>=" do end it "raises a TypeError if the argument is not a class/module" do - lambda { ModuleSpecs::Parent >= mock('x') }.should raise_error(TypeError) + -> { ModuleSpecs::Parent >= mock('x') }.should raise_error(TypeError) end end diff --git a/spec/ruby/core/module/include_spec.rb b/spec/ruby/core/module/include_spec.rb index e5d19fc820..210918b2e7 100644 --- a/spec/ruby/core/module/include_spec.rb +++ b/spec/ruby/core/module/include_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Module#include" do it "is a public method" do @@ -40,11 +40,27 @@ describe "Module#include" do end it "raises a TypeError when the argument is not a Module" do - lambda { ModuleSpecs::Basic.include(Class.new) }.should raise_error(TypeError) + -> { ModuleSpecs::Basic.include(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 - lambda { 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 @@ -158,31 +174,19 @@ describe "Module#include" do end it "detects cyclic includes" do - lambda { + -> { module ModuleSpecs::M include ModuleSpecs::M end }.should raise_error(ArgumentError) end - ruby_version_is ''...'2.4' do - it "accepts no-arguments" do - lambda { - Module.new do - include - end - }.should_not raise_error - end - end - - ruby_version_is '2.4' do - it "doesn't accept no-arguments" do - lambda { - Module.new do - include - end - }.should raise_error(ArgumentError) - end + it "doesn't accept no-arguments" do + -> { + Module.new do + include + end + }.should raise_error(ArgumentError) end it "returns the class it's included into" do @@ -246,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 @@ -264,7 +622,7 @@ describe "Module#include?" do end it "raises a TypeError when no module was given" do - lambda { ModuleSpecs::Child.include?("Test") }.should raise_error(TypeError) - lambda { ModuleSpecs::Child.include?(ModuleSpecs::Parent) }.should raise_error(TypeError) + -> { ModuleSpecs::Child.include?("Test") }.should raise_error(TypeError) + -> { ModuleSpecs::Child.include?(ModuleSpecs::Parent) }.should raise_error(TypeError) end end diff --git a/spec/ruby/core/module/included_modules_spec.rb b/spec/ruby/core/module/included_modules_spec.rb index ff2dc434dd..ce94ed1285 100644 --- a/spec/ruby/core/module/included_modules_spec.rb +++ b/spec/ruby/core/module/included_modules_spec.rb @@ -1,12 +1,14 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +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/included_spec.rb b/spec/ruby/core/module/included_spec.rb index 39a08b6ed8..2fdd4325f2 100644 --- a/spec/ruby/core/module/included_spec.rb +++ b/spec/ruby/core/module/included_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Module#included" do it "is invoked when self is included in another module or class" do @@ -39,6 +39,6 @@ describe "Module#included" do it "works with super using a singleton class" do ModuleSpecs::SingletonOnModuleCase::Bar.include ModuleSpecs::SingletonOnModuleCase::Foo - ModuleSpecs::SingletonOnModuleCase::Bar.included_called?.should == true + ModuleSpecs::SingletonOnModuleCase::Bar.should.included_called? end end diff --git a/spec/ruby/core/module/initialize_copy_spec.rb b/spec/ruby/core/module/initialize_copy_spec.rb index 85f1bbeb17..7ae48f85a9 100644 --- a/spec/ruby/core/module/initialize_copy_spec.rb +++ b/spec/ruby/core/module/initialize_copy_spec.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' describe "Module#initialize_copy" do it "should retain singleton methods when duped" do @@ -7,4 +7,12 @@ describe "Module#initialize_copy" do end mod.dup.methods(false).should == [:hello] end + + # jruby/jruby#5245, https://bugs.ruby-lang.org/issues/3461 + it "should produce a duped module with inspectable class methods" do + mod = Module.new + def mod.hello + end + mod.dup.method(:hello).inspect.should =~ /Module.*hello/ + end end diff --git a/spec/ruby/core/module/initialize_spec.rb b/spec/ruby/core/module/initialize_spec.rb index b32e0f5d2a..99e41e4619 100644 --- a/spec/ruby/core/module/initialize_spec.rb +++ b/spec/ruby/core/module/initialize_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Module#initialize" do it "accepts a block" do diff --git a/spec/ruby/core/module/instance_method_spec.rb b/spec/ruby/core/module/instance_method_spec.rb index 82e397b4bc..182cdf5c54 100644 --- a/spec/ruby/core/module/instance_method_spec.rb +++ b/spec/ruby/core/module/instance_method_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Module#instance_method" do before :all do @@ -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 - lambda { Object.instance_method([]) }.should raise_error(TypeError) - lambda { 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 - lambda { Object.instance_method(nil) }.should raise_error(TypeError) - lambda { 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 @@ -66,13 +87,13 @@ describe "Module#instance_method" do child.send :undef_method, :foo um = ModuleSpecs::InstanceMeth.instance_method(:foo) um.should == @parent_um - lambda do + -> do child.instance_method(:foo) end.should raise_error(NameError) end it "raises a NameError if the method does not exist" do - lambda { Object.instance_method(:missing) }.should raise_error(NameError) + -> { Object.instance_method(:missing) }.should raise_error(NameError) end it "sets the NameError#name attribute to the name of the missing method" do diff --git a/spec/ruby/core/module/instance_methods_spec.rb b/spec/ruby/core/module/instance_methods_spec.rb index 289879cdbc..d2d38cdda2 100644 --- a/spec/ruby/core/module/instance_methods_spec.rb +++ b/spec/ruby/core/module/instance_methods_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Module#instance_methods" do it "does not return methods undefined in a superclass" do diff --git a/spec/ruby/core/module/lt_spec.rb b/spec/ruby/core/module/lt_spec.rb index ce0d25b5a2..d7771e07a8 100644 --- a/spec/ruby/core/module/lt_spec.rb +++ b/spec/ruby/core/module/lt_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Module#<" do it "returns true if self is a subclass of or includes the given module" do @@ -31,6 +31,6 @@ describe "Module#<" do end it "raises a TypeError if the argument is not a class/module" do - lambda { ModuleSpecs::Parent < mock('x') }.should raise_error(TypeError) + -> { ModuleSpecs::Parent < mock('x') }.should raise_error(TypeError) end end diff --git a/spec/ruby/core/module/lte_spec.rb b/spec/ruby/core/module/lte_spec.rb index 8a699b4714..7a0e8496b8 100644 --- a/spec/ruby/core/module/lte_spec.rb +++ b/spec/ruby/core/module/lte_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Module#<=" do it "returns true if self is a subclass of, the same as or includes the given module" do @@ -28,6 +28,6 @@ describe "Module#<=" do end it "raises a TypeError if the argument is not a class/module" do - lambda { ModuleSpecs::Parent <= mock('x') }.should raise_error(TypeError) + -> { ModuleSpecs::Parent <= mock('x') }.should raise_error(TypeError) end end diff --git a/spec/ruby/core/module/method_added_spec.rb b/spec/ruby/core/module/method_added_spec.rb index 643291be17..ec92cddc1e 100644 --- a/spec/ruby/core/module/method_added_spec.rb +++ b/spec/ruby/core/module/method_added_spec.rb @@ -1,7 +1,11 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +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 d6206fa57e..bc5b420e11 100644 --- a/spec/ruby/core/module/method_defined_spec.rb +++ b/spec/ruby/core/module/method_defined_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Module#method_defined?" do it "returns true if a public or private method with the given name is defined in self, self's ancestors or one of self's included modules" do @@ -30,14 +30,14 @@ 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') - lambda { c.method_defined?(o) }.should raise_error(TypeError) + -> { c.method_defined?(o) }.should raise_error(TypeError) o.should_receive(:to_str).and_return(123) - lambda { c.method_defined?(o) }.should raise_error(TypeError) + -> { c.method_defined?(o) }.should raise_error(TypeError) end it "converts the given name to a string using to_str" do @@ -46,4 +46,53 @@ describe "Module#method_defined?" do c.method_defined?(o).should == true 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 + end end diff --git a/spec/ruby/core/module/method_removed_spec.rb b/spec/ruby/core/module/method_removed_spec.rb index 4c1443cfaa..9b39eb77a6 100644 --- a/spec/ruby/core/module/method_removed_spec.rb +++ b/spec/ruby/core/module/method_removed_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Module#method_removed" do it "is a private instance method" do diff --git a/spec/ruby/core/module/method_undefined_spec.rb b/spec/ruby/core/module/method_undefined_spec.rb index 65001b9421..a94388fe0a 100644 --- a/spec/ruby/core/module/method_undefined_spec.rb +++ b/spec/ruby/core/module/method_undefined_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Module#method_undefined" do it "is a private instance method" do diff --git a/spec/ruby/core/module/module_eval_spec.rb b/spec/ruby/core/module/module_eval_spec.rb index 2bef0e9abf..e9e9fda28d 100644 --- a/spec/ruby/core/module/module_eval_spec.rb +++ b/spec/ruby/core/module/module_eval_spec.rb @@ -1,6 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../shared/class_eval', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/class_eval' describe "Module#module_eval" do it_behaves_like :module_class_eval, :module_eval diff --git a/spec/ruby/core/module/module_exec_spec.rb b/spec/ruby/core/module/module_exec_spec.rb index 77d74a083b..47cdf7ef52 100644 --- a/spec/ruby/core/module/module_exec_spec.rb +++ b/spec/ruby/core/module/module_exec_spec.rb @@ -1,6 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../shared/class_exec', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/class_exec' describe "Module#module_exec" do it_behaves_like :module_class_exec, :module_exec diff --git a/spec/ruby/core/module/module_function_spec.rb b/spec/ruby/core/module/module_function_spec.rb index 61509c8d03..51f647142e 100644 --- a/spec/ruby/core/module/module_function_spec.rb +++ b/spec/ruby/core/module/module_function_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Module#module_function" do it "is a private method" do @@ -12,11 +12,11 @@ describe "Module#module_function" do end it "raises a TypeError if calling after rebinded to Class" do - lambda { + -> { Module.instance_method(:module_function).bind(Class.new).call }.should raise_error(TypeError) - lambda { + -> { Module.instance_method(:module_function).bind(Class.new).call :foo }.should raise_error(TypeError) end @@ -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 @@ -87,7 +85,7 @@ describe "Module#module_function with specific method names" do o.respond_to?(:test).should == false m.should have_private_instance_method(:test) o.send(:test).should == "hello" - lambda { o.test }.should raise_error(NoMethodError) + -> { o.test }.should raise_error(NoMethodError) end it "makes the new Module methods public" do @@ -116,10 +114,10 @@ describe "Module#module_function with specific method names" do it "raises a TypeError when the given names can't be converted to string using to_str" do o = mock('123') - lambda { Module.new { module_function(o) } }.should raise_error(TypeError) + -> { Module.new { module_function(o) } }.should raise_error(TypeError) o.should_receive(:to_str).and_return(123) - lambda { Module.new { module_function(o) } }.should raise_error(TypeError) + -> { Module.new { module_function(o) } }.should raise_error(TypeError) end it "can make accessible private methods" do # JRUBY-4214 @@ -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 0727278591..d3318e1645 100644 --- a/spec/ruby/core/module/name_spec.rb +++ b/spec/ruby/core/module/name_spec.rb @@ -1,21 +1,69 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/module', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/module' describe "Module#name" do it "is nil for an anonymous module" 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 m = Module.new module m::N; end - m::N.name.should =~ /#<Module:0x[0-9a-f]+>::N/ + m::N.name.should =~ /\A#<Module:0x[0-9a-f]+>::N\z/ + end + + it "returns nil for a singleton class" do + Module.new.singleton_class.name.should be_nil + String.singleton_class.name.should be_nil + Object.new.singleton_class.name.should be_nil + end + + it "changes when the module is reachable through a constant path" do + m = Module.new + module m::N; end + 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 + module ModuleSpecs + module ModuleToRemove + end + + mod = ModuleToRemove + remove_const(:ModuleToRemove) + mod.name.should == "ModuleSpecs::ModuleToRemove" + end + end + + it "is set after it is removed from a constant under an anonymous module" do + m = Module.new + module m::Child; end + child = m::Child + m.send(:remove_const, :Child) + child.name.should =~ /\A#<Module:0x\h+>::Child\z/ end it "is set when opened with the module keyword" do @@ -26,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 @@ -38,6 +96,18 @@ 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 + m = Module.new + module m::M; end + first_name = m::M.name.dup + module m::N; end + m::N::F = m::M + m::M.name.should == first_name end # http://bugs.ruby-lang.org/issues/6067 @@ -59,10 +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 + + # 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 + + # module with name + module TEST1 + end + + # anonymous module + TEST2 = Module.new + RUBY + 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 + + 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/nesting_spec.rb b/spec/ruby/core/module/nesting_spec.rb index 0a86e5b8fc..d0611b3efe 100644 --- a/spec/ruby/core/module/nesting_spec.rb +++ b/spec/ruby/core/module/nesting_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Module::Nesting" do diff --git a/spec/ruby/core/module/new_spec.rb b/spec/ruby/core/module/new_spec.rb index 21d3f954fa..ec521360bd 100644 --- a/spec/ruby/core/module/new_spec.rb +++ b/spec/ruby/core/module/new_spec.rb @@ -1,11 +1,15 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Module.new" do it "creates a new anonymous Module" 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 6f341804d1..09c15c5c15 100644 --- a/spec/ruby/core/module/prepend_features_spec.rb +++ b/spec/ruby/core/module/prepend_features_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Module#prepend_features" do it "is a private method" do @@ -23,23 +23,11 @@ describe "Module#prepend_features" do end it "raises an ArgumentError on a cyclic prepend" do - lambda { + -> { ModuleSpecs::CyclicPrepend.send(:prepend_features, ModuleSpecs::CyclicPrepend) }.should raise_error(ArgumentError) end - 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 - it "clears caches of the given module" do parent = Class.new do def bar; :bar; end @@ -68,7 +56,7 @@ describe "Module#prepend_features" do end it "raises a TypeError if calling after rebinded to Class" do - lambda { + -> { Module.instance_method(:prepend_features).bind(Class.new).call Module.new }.should raise_error(TypeError) end diff --git a/spec/ruby/core/module/prepend_spec.rb b/spec/ruby/core/module/prepend_spec.rb index c0cce616a2..71e82c513e 100644 --- a/spec/ruby/core/module/prepend_spec.rb +++ b/spec/ruby/core/module/prepend_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Module#prepend" do it "is a public method" do @@ -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 - lambda { ModuleSpecs::Basic.prepend(Class.new) }.should raise_error(TypeError) + -> { 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 - lambda { 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 @@ -104,6 +527,18 @@ describe "Module#prepend" do c.new.alias.should == :m end + it "reports the class for the owner of an aliased method on the class" do + m = Module.new + c = Class.new { prepend(m); def meth; :c end; alias_method :alias, :meth } + c.instance_method(:alias).owner.should == c + end + + it "reports the class for the owner of a method aliased from the prepended module" do + m = Module.new { def meth; :m end } + c = Class.new { prepend(m); alias_method :alias, :meth } + c.instance_method(:alias).owner.should == c + end + it "sees an instance of a prepended class as kind of the prepended module" do m = Module.new c = Class.new { prepend(m) } @@ -116,16 +551,16 @@ describe "Module#prepend" do c.dup.new.should be_kind_of(m) end - it "keeps the module in the chain when dupping an intermediate module" 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 == [m2dup,m1,m2] + 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,4].should == [m2dup,m1,m2,c2] + c2.ancestors[0,3].should == [m1,m2dup,c2] c2.new.should be_kind_of(m1) end @@ -192,7 +627,7 @@ describe "Module#prepend" do super << :class end end - lambda { c.new.chain }.should raise_error(NoMethodError) + -> { c.new.chain }.should raise_error(NoMethodError) end it "calls prepended after prepend_features" do @@ -211,32 +646,32 @@ 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 - lambda { + -> { module ModuleSpecs::P prepend ModuleSpecs::P end }.should raise_error(ArgumentError) end - ruby_version_is ''...'2.4' do - it "accepts no-arguments" do - lambda { - Module.new do - prepend - end - }.should_not raise_error - end - end - - ruby_version_is '2.4' do - it "doesn't accept no-arguments" do - lambda { - Module.new do - prepend - end - }.should raise_error(ArgumentError) - end + it "doesn't accept no-arguments" do + -> { + Module.new do + prepend + end + }.should raise_error(ArgumentError) end it "returns the class it's included into" do @@ -343,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/prepended_spec.rb b/spec/ruby/core/module/prepended_spec.rb index ed8e473941..bd95d8fd05 100644 --- a/spec/ruby/core/module/prepended_spec.rb +++ b/spec/ruby/core/module/prepended_spec.rb @@ -1,6 +1,6 @@ # -*- encoding: us-ascii -*- -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' describe "Module#prepended" do before :each do diff --git a/spec/ruby/core/module/private_class_method_spec.rb b/spec/ruby/core/module/private_class_method_spec.rb index ec10bcbf87..f899c71a57 100644 --- a/spec/ruby/core/module/private_class_method_spec.rb +++ b/spec/ruby/core/module/private_class_method_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Module#private_class_method" do before :each do @@ -22,11 +22,11 @@ describe "Module#private_class_method" do it "makes an existing class method private" do ModuleSpecs::Parent.private_method_1.should == nil ModuleSpecs::Parent.private_class_method :private_method_1 - lambda { ModuleSpecs::Parent.private_method_1 }.should raise_error(NoMethodError) + -> { ModuleSpecs::Parent.private_method_1 }.should raise_error(NoMethodError) # Technically above we're testing the Singleton classes, class method(right?). # Try a "real" class method set private. - lambda { ModuleSpecs::Parent.private_method }.should raise_error(NoMethodError) + -> { ModuleSpecs::Parent.private_method }.should raise_error(NoMethodError) end it "makes an existing class method private up the inheritance tree" do @@ -34,8 +34,8 @@ describe "Module#private_class_method" do ModuleSpecs::Child.private_method_1.should == nil ModuleSpecs::Child.private_class_method :private_method_1 - lambda { ModuleSpecs::Child.private_method_1 }.should raise_error(NoMethodError) - lambda { ModuleSpecs::Child.private_method }.should raise_error(NoMethodError) + -> { ModuleSpecs::Child.private_method_1 }.should raise_error(NoMethodError) + -> { ModuleSpecs::Child.private_method }.should raise_error(NoMethodError) end it "accepts more than one method at a time" do @@ -44,12 +44,12 @@ describe "Module#private_class_method" do ModuleSpecs::Child.private_class_method :private_method_1, :private_method_2 - lambda { ModuleSpecs::Child.private_method_1 }.should raise_error(NoMethodError) - lambda { ModuleSpecs::Child.private_method_2 }.should raise_error(NoMethodError) + -> { ModuleSpecs::Child.private_method_1 }.should raise_error(NoMethodError) + -> { ModuleSpecs::Child.private_method_2 }.should raise_error(NoMethodError) end it "raises a NameError if class method doesn't exist" do - lambda do + -> do ModuleSpecs.private_class_method :no_method_here end.should raise_error(NameError) end @@ -59,11 +59,11 @@ describe "Module#private_class_method" do def self.foo() "foo" end private_class_method :foo end - lambda { c.foo }.should raise_error(NoMethodError) + -> { c.foo }.should raise_error(NoMethodError) end it "raises a NameError when the given name is not a method" do - lambda do + -> do Class.new do private_class_method :foo end @@ -71,11 +71,21 @@ describe "Module#private_class_method" do end it "raises a NameError when the given name is an instance method" do - lambda do + -> do Class.new do def foo() "foo" end private_class_method :foo 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_constant_spec.rb b/spec/ruby/core/module/private_constant_spec.rb index af0b1facdd..3a91b3c3cd 100644 --- a/spec/ruby/core/module/private_constant_spec.rb +++ b/spec/ruby/core/module/private_constant_spec.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' describe "Module#private_constant" do it "can only be passed constant names defined in the target (self) module" do @@ -6,7 +6,7 @@ describe "Module#private_constant" do cls1.const_set :Foo, true cls2 = Class.new(cls1) - lambda do + -> do cls2.send :private_constant, :Foo end.should raise_error(NameError) end @@ -16,7 +16,7 @@ describe "Module#private_constant" do cls.const_set :Foo, true cls.send :private_constant, "Foo" - lambda { cls::Foo }.should raise_error(NameError) + -> { cls::Foo }.should raise_error(NameError) end it "accepts multiple names" do @@ -26,7 +26,7 @@ describe "Module#private_constant" do mod.send :private_constant, :Foo, :Bar - lambda {mod::Foo}.should raise_error(NameError) - lambda {mod::Bar}.should raise_error(NameError) + -> {mod::Foo}.should raise_error(NameError) + -> {mod::Bar}.should raise_error(NameError) end end diff --git a/spec/ruby/core/module/private_instance_methods_spec.rb b/spec/ruby/core/module/private_instance_methods_spec.rb index f5c4c3a0f9..cce0f001bf 100644 --- a/spec/ruby/core/module/private_instance_methods_spec.rb +++ b/spec/ruby/core/module/private_instance_methods_spec.rb @@ -1,6 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../../../fixtures/reflection', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative '../../fixtures/reflection' # TODO: rewrite describe "Module#private_instance_methods" do diff --git a/spec/ruby/core/module/private_method_defined_spec.rb b/spec/ruby/core/module/private_method_defined_spec.rb index b3e1814056..01fc8f8fb9 100644 --- a/spec/ruby/core/module/private_method_defined_spec.rb +++ b/spec/ruby/core/module/private_method_defined_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Module#private_method_defined?" do it "returns true if the named private method is defined by module or its ancestors" do @@ -31,26 +31,26 @@ 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 - lambda do + it "raises a TypeError if passed an Integer" do + -> do ModuleSpecs::CountsMixin.private_method_defined?(1) end.should raise_error(TypeError) end it "raises a TypeError if passed nil" do - lambda do + -> do ModuleSpecs::CountsMixin.private_method_defined?(nil) end.should raise_error(TypeError) end it "raises a TypeError if passed false" do - lambda do + -> do ModuleSpecs::CountsMixin.private_method_defined?(false) end.should raise_error(TypeError) end it "raises a TypeError if passed an object that does not defined #to_str" do - lambda do + -> do ModuleSpecs::CountsMixin.private_method_defined?(mock('x')) end.should raise_error(TypeError) end @@ -59,7 +59,7 @@ describe "Module#private_method_defined?" do sym = mock('symbol') def sym.to_sym() :private_3 end - lambda do + -> do ModuleSpecs::CountsMixin.private_method_defined?(sym) end.should raise_error(TypeError) end @@ -69,4 +69,52 @@ describe "Module#private_method_defined?" do def str.to_str() 'private_3' end ModuleSpecs::CountsMixin.private_method_defined?(str).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 + end end diff --git a/spec/ruby/core/module/private_spec.rb b/spec/ruby/core/module/private_spec.rb index 07b8f4c781..9e1a297eea 100644 --- a/spec/ruby/core/module/private_spec.rb +++ b/spec/ruby/core/module/private_spec.rb @@ -1,6 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../shared/set_visibility', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/set_visibility' describe "Module#private" do it_behaves_like :set_visibility, :private @@ -17,7 +17,7 @@ describe "Module#private" do private :foo end - lambda { obj.foo }.should raise_error(NoMethodError) + -> { obj.foo }.should raise_error(NoMethodError) end it "makes a public Object instance method private in a new module" do @@ -38,16 +38,18 @@ 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 it "raises a NameError when given an undefined name" do - lambda do + -> do Module.new.send(:private, :undefined) end.should raise_error(NameError) end @@ -67,7 +69,7 @@ describe "Module#private" do end base.new.wrapped.should == 1 - lambda do + -> do klass.new.wrapped end.should raise_error(NameError) end diff --git a/spec/ruby/core/module/protected_instance_methods_spec.rb b/spec/ruby/core/module/protected_instance_methods_spec.rb index d5c9e5cd33..78ce7e788f 100644 --- a/spec/ruby/core/module/protected_instance_methods_spec.rb +++ b/spec/ruby/core/module/protected_instance_methods_spec.rb @@ -1,6 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../../../fixtures/reflection', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative '../../fixtures/reflection' # TODO: rewrite describe "Module#protected_instance_methods" do diff --git a/spec/ruby/core/module/protected_method_defined_spec.rb b/spec/ruby/core/module/protected_method_defined_spec.rb index af08efae81..31e24a16c1 100644 --- a/spec/ruby/core/module/protected_method_defined_spec.rb +++ b/spec/ruby/core/module/protected_method_defined_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Module#protected_method_defined?" do it "returns true if the named protected method is defined by module or its ancestors" do @@ -31,26 +31,26 @@ 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 - lambda do + it "raises a TypeError if passed an Integer" do + -> do ModuleSpecs::CountsMixin.protected_method_defined?(1) end.should raise_error(TypeError) end it "raises a TypeError if passed nil" do - lambda do + -> do ModuleSpecs::CountsMixin.protected_method_defined?(nil) end.should raise_error(TypeError) end it "raises a TypeError if passed false" do - lambda do + -> do ModuleSpecs::CountsMixin.protected_method_defined?(false) end.should raise_error(TypeError) end it "raises a TypeError if passed an object that does not defined #to_str" do - lambda do + -> do ModuleSpecs::CountsMixin.protected_method_defined?(mock('x')) end.should raise_error(TypeError) end @@ -59,7 +59,7 @@ describe "Module#protected_method_defined?" do sym = mock('symbol') def sym.to_sym() :protected_3 end - lambda do + -> do ModuleSpecs::CountsMixin.protected_method_defined?(sym) end.should raise_error(TypeError) end @@ -69,4 +69,52 @@ describe "Module#protected_method_defined?" do str.should_receive(:to_str).and_return("protected_3") ModuleSpecs::CountsMixin.protected_method_defined?(str).should == true 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 + end end diff --git a/spec/ruby/core/module/protected_spec.rb b/spec/ruby/core/module/protected_spec.rb index 7e77138c12..9e37223e18 100644 --- a/spec/ruby/core/module/protected_spec.rb +++ b/spec/ruby/core/module/protected_spec.rb @@ -1,6 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../shared/set_visibility', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/set_visibility' describe "Module#protected" do before :each do @@ -18,7 +18,7 @@ describe "Module#protected" do protected :protected_method_1 end - lambda { ModuleSpecs::Parent.protected_method_1 }.should raise_error(NoMethodError) + -> { ModuleSpecs::Parent.protected_method_1 }.should raise_error(NoMethodError) end it "makes a public Object instance method protected in a new module" do @@ -39,18 +39,19 @@ 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 it "raises a NameError when given an undefined name" do - lambda do + -> do Module.new.send(:protected, :undefined) end.should raise_error(NameError) 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 2c909b1223..71b20acda5 100644 --- a/spec/ruby/core/module/public_class_method_spec.rb +++ b/spec/ruby/core/module/public_class_method_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Module#public_class_method" do before :each do @@ -18,7 +18,7 @@ describe "Module#public_class_method" do end it "makes an existing class method public" do - lambda { ModuleSpecs::Parent.public_method_1 }.should raise_error(NoMethodError) + -> { ModuleSpecs::Parent.public_method_1 }.should raise_error(NoMethodError) ModuleSpecs::Parent.public_class_method :public_method_1 ModuleSpecs::Parent.public_method_1.should == nil @@ -29,7 +29,7 @@ describe "Module#public_class_method" do it "makes an existing class method public up the inheritance tree" do ModuleSpecs::Child.private_class_method :public_method_1 - lambda { ModuleSpecs::Child.public_method_1 }.should raise_error(NoMethodError) + -> { ModuleSpecs::Child.public_method_1 }.should raise_error(NoMethodError) ModuleSpecs::Child.public_class_method :public_method_1 ModuleSpecs::Child.public_method_1.should == nil @@ -37,8 +37,8 @@ describe "Module#public_class_method" do end it "accepts more than one method at a time" do - lambda { ModuleSpecs::Parent.public_method_1 }.should raise_error(NameError) - lambda { ModuleSpecs::Parent.public_method_2 }.should raise_error(NameError) + -> { ModuleSpecs::Parent.public_method_1 }.should raise_error(NameError) + -> { ModuleSpecs::Parent.public_method_2 }.should raise_error(NameError) ModuleSpecs::Child.public_class_method :public_method_1, :public_method_2 @@ -47,7 +47,7 @@ describe "Module#public_class_method" do end it "raises a NameError if class method doesn't exist" do - lambda do + -> do ModuleSpecs.public_class_method :no_method_here end.should raise_error(NameError) end @@ -62,7 +62,7 @@ describe "Module#public_class_method" do end it "raises a NameError when the given name is not a method" do - lambda do + -> do Class.new do public_class_method :foo end @@ -70,11 +70,25 @@ describe "Module#public_class_method" do end it "raises a NameError when the given name is an instance method" do - lambda do + -> do Class.new do def foo() "foo" end public_class_method :foo 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_constant_spec.rb b/spec/ruby/core/module/public_constant_spec.rb index cbf51a6df6..e624d45fd2 100644 --- a/spec/ruby/core/module/public_constant_spec.rb +++ b/spec/ruby/core/module/public_constant_spec.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' describe "Module#public_constant" do it "can only be passed constant names defined in the target (self) module" do @@ -6,7 +6,7 @@ describe "Module#public_constant" do cls1.const_set :Foo, true cls2 = Class.new(cls1) - lambda do + -> do cls2.send :public_constant, :Foo end.should raise_error(NameError) end diff --git a/spec/ruby/core/module/public_instance_method_spec.rb b/spec/ruby/core/module/public_instance_method_spec.rb index c1e94c5fed..ba19ad0404 100644 --- a/spec/ruby/core/module/public_instance_method_spec.rb +++ b/spec/ruby/core/module/public_instance_method_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Module#public_instance_method" do it "is a public method" do @@ -28,29 +28,29 @@ describe "Module#public_instance_method" do end it "raises a TypeError when given a name is not Symbol or String" do - lambda { Module.new.public_instance_method(nil) }.should raise_error(TypeError) + -> { Module.new.public_instance_method(nil) }.should raise_error(TypeError) end it "raises a NameError when given a protected method name" do - lambda do + -> do ModuleSpecs::Basic.public_instance_method(:protected_module) end.should raise_error(NameError) end it "raises a NameError if the method is private" do - lambda do + -> do ModuleSpecs::Basic.public_instance_method(:private_module) end.should raise_error(NameError) end it "raises a NameError if the method has been undefined" do - lambda do + -> do ModuleSpecs::Parent.public_instance_method(:undefed_method) end.should raise_error(NameError) end it "raises a NameError if the method does not exist" do - lambda do + -> do Module.new.public_instance_method(:missing) end.should raise_error(NameError) end diff --git a/spec/ruby/core/module/public_instance_methods_spec.rb b/spec/ruby/core/module/public_instance_methods_spec.rb index 14453109c3..ae7d9b5ffb 100644 --- a/spec/ruby/core/module/public_instance_methods_spec.rb +++ b/spec/ruby/core/module/public_instance_methods_spec.rb @@ -1,6 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../../../fixtures/reflection', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative '../../fixtures/reflection' # TODO: rewrite diff --git a/spec/ruby/core/module/public_method_defined_spec.rb b/spec/ruby/core/module/public_method_defined_spec.rb index 329778ac15..5c9bdf1ccc 100644 --- a/spec/ruby/core/module/public_method_defined_spec.rb +++ b/spec/ruby/core/module/public_method_defined_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Module#public_method_defined?" do it "returns true if the named public method is defined by module or its ancestors" do @@ -31,26 +31,26 @@ 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 - lambda do + it "raises a TypeError if passed an Integer" do + -> do ModuleSpecs::CountsMixin.public_method_defined?(1) end.should raise_error(TypeError) end it "raises a TypeError if passed nil" do - lambda do + -> do ModuleSpecs::CountsMixin.public_method_defined?(nil) end.should raise_error(TypeError) end it "raises a TypeError if passed false" do - lambda do + -> do ModuleSpecs::CountsMixin.public_method_defined?(false) end.should raise_error(TypeError) end it "raises a TypeError if passed an object that does not defined #to_str" do - lambda do + -> do ModuleSpecs::CountsMixin.public_method_defined?(mock('x')) end.should raise_error(TypeError) end @@ -59,7 +59,7 @@ describe "Module#public_method_defined?" do sym = mock('symbol') def sym.to_sym() :public_3 end - lambda do + -> do ModuleSpecs::CountsMixin.public_method_defined?(sym) end.should raise_error(TypeError) end diff --git a/spec/ruby/core/module/public_spec.rb b/spec/ruby/core/module/public_spec.rb index 8507689752..ce31eb5d0e 100644 --- a/spec/ruby/core/module/public_spec.rb +++ b/spec/ruby/core/module/public_spec.rb @@ -1,6 +1,6 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) -require File.expand_path('../shared/set_visibility', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/set_visibility' describe "Module#public" do it_behaves_like :set_visibility, :public @@ -27,17 +27,18 @@ 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 it "raises a NameError when given an undefined name" do - lambda do + -> do Module.new.send(:public, :undefined) end.should raise_error(NameError) end diff --git a/spec/ruby/core/module/refine_spec.rb b/spec/ruby/core/module/refine_spec.rb index ca7db0c2b6..d219b98825 100644 --- a/spec/ruby/core/module/refine_spec.rb +++ b/spec/ruby/core/module/refine_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/refine', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/refine' describe "Module#refine" do it "runs its block in an anonymous module" do @@ -59,7 +59,7 @@ describe "Module#refine" do end it "raises ArgumentError if not passed an argument" do - lambda do + -> do Module.new do refine {} end @@ -67,42 +67,53 @@ describe "Module#refine" do end it "raises TypeError if not passed a class" do - lambda do + -> 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 - ruby_version_is "" ... "2.4" do - it "raises TypeError if passed a module" do - lambda do - Module.new do - refine(Enumerable) {} + it "accepts a module as argument" do + inner_self = nil + Module.new do + refine(Enumerable) do + def blah end - end.should raise_error(TypeError) + inner_self = self + end end + + inner_self.public_instance_methods.should include(:blah) end - quarantine! do # https://bugs.ruby-lang.org/issues/14070 - ruby_version_is "2.4" do - it "accepts a module as argument" do - inner_self = nil - Module.new do - refine(Enumerable) do - def blah - end - inner_self = self - end + it "applies refinements to the module" do + refinement = Module.new do + refine(Enumerable) do + def foo? + self.any? ? "yes" : "no" end + end + end + + foo = Class.new do + using refinement - inner_self.public_instance_methods.should include(:blah) + def initialize(items) + @items = items + end + + def result + @items.foo? end end + + foo.new([]).result.should == "no" + foo.new([1]).result.should == "yes" end it "raises ArgumentError if not given a block" do - lambda do + -> do Module.new do refine String end @@ -123,7 +134,7 @@ describe "Module#refine" do it "doesn't apply refinements outside the refine block" do Module.new do refine(String) {def foo; "foo"; end} - -> () { + -> { "hello".foo }.should raise_error(NoMethodError) end @@ -134,7 +145,7 @@ describe "Module#refine" do refine(String) {def foo; 'foo'; end} end - lambda {"hello".foo}.should raise_error(NoMethodError) + -> {"hello".foo}.should raise_error(NoMethodError) end # When defining multiple refinements in the same module, @@ -191,7 +202,7 @@ describe "Module#refine" do result = nil - -> () { + -> { Module.new do using refinery_integer using refinery_array @@ -210,8 +221,10 @@ describe "Module#refine" do # * The included modules of C describe "method lookup" do it "looks in the object singleton class first" do + refined_class = ModuleSpecs.build_refined_class + refinement = Module.new do - refine ModuleSpecs::ClassWithFoo do + refine refined_class do def foo; "foo from refinement"; end end end @@ -220,7 +233,7 @@ describe "Module#refine" do Module.new do using refinement - obj = ModuleSpecs::ClassWithFoo.new + obj = refined_class.new class << obj def foo; "foo from singleton class"; end end @@ -230,68 +243,48 @@ describe "Module#refine" do result.should == "foo from singleton class" end - it "looks in prepended modules from the refinement first" do - refinement = Module.new do - refine ModuleSpecs::ClassWithFoo do - include ModuleSpecs::IncludedModule - prepend ModuleSpecs::PrependedModule - - def foo; "foo from refinement"; end + it "looks in later included modules of the refined module first" do + a = Module.new do + def foo + "foo from A" end end - result = nil - Module.new do - using refinement - result = ModuleSpecs::ClassWithFoo.new.foo - end - - result.should == "foo from prepended module" - end - - it "looks in refinement then" do - refinement = Module.new do - refine(ModuleSpecs::ClassWithFoo) do - include ModuleSpecs::IncludedModule - - def foo; "foo from refinement"; end + include_me_later = Module.new do + def foo + "foo from IncludeMeLater" end end - result = nil - Module.new do - using refinement - result = ModuleSpecs::ClassWithFoo.new.foo + c = Class.new do + include a end - result.should == "foo from refinement" - end - - it "looks in included modules from the refinement then" do refinement = Module.new do - refine ModuleSpecs::ClassWithFoo do - include ModuleSpecs::IncludedModule - end - end + refine c do; end + end result = nil Module.new do using refinement - result = ModuleSpecs::ClassWithFoo.new.foo + c.include include_me_later + result = c.new.foo end - result.should == "foo from included module" + result.should == "foo from IncludeMeLater" end it "looks in the class then" do + refined_class = ModuleSpecs.build_refined_class + refinement = Module.new do - refine(ModuleSpecs::ClassWithFoo) { } + refine(refined_class) { } end result = nil Module.new do using refinement - result = ModuleSpecs::ClassWithFoo.new.foo + result = refined_class.new.foo end result.should == "foo" @@ -301,12 +294,14 @@ describe "Module#refine" do # methods in a subclass have priority over refinements in a superclass it "does not override methods in subclasses" do - subclass = Class.new(ModuleSpecs::ClassWithFoo) do + refined_class = ModuleSpecs.build_refined_class + + subclass = Class.new(refined_class) do def foo; "foo from subclass"; end end refinement = Module.new do - refine ModuleSpecs::ClassWithFoo do + refine refined_class do def foo; "foo from refinement"; end end end @@ -321,148 +316,94 @@ describe "Module#refine" do end context "for methods accessed indirectly" do - ruby_version_is "" ... "2.4" do - it "is not honored by Kernel#send" do - refinement = Module.new do - refine ModuleSpecs::ClassWithFoo do - def foo; "foo from refinement"; end - end - end + it "is honored by Kernel#send" do + refined_class = ModuleSpecs.build_refined_class - result = nil - Module.new do - using refinement - result = ModuleSpecs::ClassWithFoo.new.send :foo + refinement = Module.new do + refine refined_class do + def foo; "foo from refinement"; end end - - result.should == "foo" end - it "is not honored by BasicObject#__send__" do - refinement = Module.new do - refine ModuleSpecs::ClassWithFoo do - def foo; "foo from refinement"; end - end - end - - result = nil - Module.new do - using refinement - result = ModuleSpecs::ClassWithFoo.new.__send__ :foo - end - - result.should == "foo" + result = nil + Module.new do + using refinement + result = refined_class.new.send :foo end - it "is not honored by Symbol#to_proc" do - refinement = Module.new do - refine Integer do - def to_s - "(#{super})" - end - end - end - - result = nil - Module.new do - using refinement - result = [1, 2, 3].map(&:to_s) - end - - result.should == ["1", "2", "3"] - end + result.should == "foo from refinement" end - ruby_version_is "2.4" do - it "is honored by Kernel#send" do - refinement = Module.new do - refine ModuleSpecs::ClassWithFoo do - def foo; "foo from refinement"; end - end - end + it "is honored by BasicObject#__send__" do + refined_class = ModuleSpecs.build_refined_class - result = nil - Module.new do - using refinement - result = ModuleSpecs::ClassWithFoo.new.send :foo + refinement = Module.new do + refine refined_class do + def foo; "foo from refinement"; end end - - result.should == "foo from refinement" end - it "is honored by BasicObject#__send__" do - refinement = Module.new do - refine ModuleSpecs::ClassWithFoo do - def foo; "foo from refinement"; end - end - end - - result = nil - Module.new do - using refinement - result = ModuleSpecs::ClassWithFoo.new.__send__ :foo - end - - result.should == "foo from refinement" + result = nil + Module.new do + using refinement + result = refined_class.new.__send__ :foo end - it "is honored by Symbol#to_proc" do - refinement = Module.new do - refine Integer do - def to_s - "(#{super})" - end - end - end + result.should == "foo from refinement" + end - result = nil - Module.new do - using refinement - result = [1, 2, 3].map(&:to_s) + it "is honored by Symbol#to_proc" do + refinement = Module.new do + refine Integer do + def to_s + "(#{super})" + end end + end - result.should == ["(1)", "(2)", "(3)"] + result = nil + Module.new do + using refinement + result = [1, 2, 3].map(&:to_s) end + + result.should == ["(1)", "(2)", "(3)"] end - ruby_version_is "" ... "2.5" do - it "is not honored by string interpolation" do - refinement = Module.new do - refine Integer do - def to_s - "foo" - 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 = "#{1}" + refinement = Module.new do + refine refined_class do + def foo; "foo from refinement"; end end + end - result.should == "1" + result = nil + Module.new do + using refinement + result = refined_class.new.public_send :foo end + + result.should == "foo from refinement" end - ruby_version_is "2.5" do - it "is honored by string interpolation" do - refinement = Module.new do - refine Integer do - def to_s - "foo" - end + it "is honored by string interpolation" do + refinement = Module.new do + refine Integer do + def to_s + "foo" end end + end - result = nil - Module.new do - using refinement - result = "#{1}" - end - - result.should == "foo" + result = nil + Module.new do + using refinement + result = "#{1}" end + + result.should == "foo" end it "is honored by Kernel#binding" do @@ -490,7 +431,7 @@ describe "Module#refine" do result.should == "hello from refinement" end - it "is not honored by Kernel#method" do + it "is honored by Kernel#method" do klass = Class.new refinement = Module.new do refine klass do @@ -498,15 +439,33 @@ describe "Module#refine" do end end - -> { - Module.new do - using refinement - klass.new.method(:foo) + result = nil + Module.new do + using refinement + result = klass.new.method(:foo).class + end + + result.should == Method + end + + it "is honored by Kernel#public_method" do + klass = Class.new + refinement = Module.new do + refine klass do + def foo; end end - }.should raise_error(NameError, /undefined method `foo'/) + end + + result = nil + Module.new do + using refinement + result = klass.new.public_method(:foo).class + end + + result.should == Method end - it "is not honored by Kernel#respond_to?" do + it "is honored by Kernel#instance_method" do klass = Class.new refinement = Module.new do refine klass do @@ -517,21 +476,34 @@ describe "Module#refine" do result = nil Module.new do using refinement - result = klass.new.respond_to?(:foo) + result = klass.instance_method(:foo).class end - result.should == false + result.should == UnboundMethod end - end - context "when super is called in a refinement" do - it "looks in the included to refinery module" do + it "is honored by Kernel#respond_to?" do + klass = Class.new refinement = Module.new do - refine ModuleSpecs::ClassWithFoo do - include ModuleSpecs::IncludedModule + refine klass do + def foo; end + end + end - def foo - super + result = nil + Module.new do + using refinement + result = klass.new.respond_to?(:foo) + end + + result.should == true + end + + it "is honored by &" do + refinement = Module.new do + refine String do + def to_proc(*args) + -> * { 'foo' } end end end @@ -539,15 +511,19 @@ describe "Module#refine" do result = nil Module.new do using refinement - result = ModuleSpecs::ClassWithFoo.new.foo + result = ["hola"].map(&"upcase") end - result.should == "foo from included module" + result.should == ['foo'] end + end + context "when super is called in a refinement" do it "looks in the refined class" do + refined_class = ModuleSpecs.build_refined_class + refinement = Module.new do - refine ModuleSpecs::ClassWithFoo do + refine refined_class do def foo super end @@ -557,7 +533,7 @@ describe "Module#refine" do result = nil Module.new do using refinement - result = ModuleSpecs::ClassWithFoo.new.foo + result = refined_class.new.foo end result.should == "foo" @@ -566,19 +542,21 @@ describe "Module#refine" do # 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 + 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 ModuleSpecs::ClassWithFoo do + refine refined_class do def foo - "foo from refinement" + [:R1] end end end refinement_with_super = Module.new do - refine ModuleSpecs::ClassWithFoo do + refine refined_class do def foo - super + [:R2] + super end end end @@ -587,11 +565,75 @@ describe "Module#refine" do Module.new do using refinement using refinement_with_super - result = ModuleSpecs::ClassWithFoo.new.foo + result = refined_class.new.foo end - result.should == "foo" + result.should == [:R2, :C] end + + 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 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 bar + super + end + end + end + + + Module.new do + using refinement + using refinement_with_super + -> { + refined_class.new.bar + }.should raise_error(NoMethodError) + end + end + end + + it 'and alias aliases a method within a refinement module, but not outside it' do + Module.new do + using Module.new { + refine Array do + alias :orig_count :count + end + } + [1,2].orig_count.should == 2 + end + -> { [1,2].orig_count }.should raise_error(NoMethodError) + end + + it 'and alias_method aliases a method within a refinement module, but not outside it' do + Module.new do + using Module.new { + refine Array do + alias_method :orig_count, :count + end + } + [1,2].orig_count.should == 2 + end + -> { [1,2].orig_count }.should raise_error(NoMethodError) + end + + it "and instance_methods returns a list of methods including those of the refined module" do + methods = Array.instance_methods + methods_2 = [] + Module.new do + refine Array do + methods_2 = instance_methods + end + end + methods.should == methods_2 end # Refinements are inherited by module inclusion. @@ -654,4 +696,19 @@ describe "Module#refine" do result.should == "hello from refinement" end end + + it 'does not list methods defined only in refinement' do + refine_object = Module.new do + refine Object do + def refinement_only_method + end + end + end + spec = self + klass = Class.new { instance_methods.should_not spec.send(:include, :refinement_only_method) } + instance = klass.new + instance.methods.should_not include :refinement_only_method + instance.respond_to?(:refinement_only_method).should == false + -> { instance.method :refinement_only_method }.should raise_error(NameError) + 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_class_variable_spec.rb b/spec/ruby/core/module/remove_class_variable_spec.rb index adf2c1d1f8..ab9514adf6 100644 --- a/spec/ruby/core/module/remove_class_variable_spec.rb +++ b/spec/ruby/core/module/remove_class_variable_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Module#remove_class_variable" do it "removes class variable" do @@ -23,19 +23,19 @@ describe "Module#remove_class_variable" do it "raises a NameError when removing class variable declared in included module" do c = ModuleSpecs::RemoveClassVariable.new { include ModuleSpecs::MVars.dup } - lambda { c.send(:remove_class_variable, :@@mvar) }.should raise_error(NameError) + -> { c.send(:remove_class_variable, :@@mvar) }.should raise_error(NameError) end it "raises a NameError when passed a symbol with one leading @" do - lambda { ModuleSpecs::MVars.send(:remove_class_variable, :@mvar) }.should raise_error(NameError) + -> { ModuleSpecs::MVars.send(:remove_class_variable, :@mvar) }.should raise_error(NameError) end it "raises a NameError when passed a symbol with no leading @" do - lambda { ModuleSpecs::MVars.send(:remove_class_variable, :mvar) }.should raise_error(NameError) + -> { ModuleSpecs::MVars.send(:remove_class_variable, :mvar) }.should raise_error(NameError) end it "raises a NameError when an uninitialized class variable is given" do - lambda { ModuleSpecs::MVars.send(:remove_class_variable, :@@nonexisting_class_variable) }.should raise_error(NameError) + -> { ModuleSpecs::MVars.send(:remove_class_variable, :@@nonexisting_class_variable) }.should raise_error(NameError) end it "is public" do diff --git a/spec/ruby/core/module/remove_const_spec.rb b/spec/ruby/core/module/remove_const_spec.rb index 0c1b87598e..35a9d65105 100644 --- a/spec/ruby/core/module/remove_const_spec.rb +++ b/spec/ruby/core/module/remove_const_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../../../fixtures/constants', __FILE__) +require_relative '../../spec_helper' +require_relative '../../fixtures/constants' describe "Module#remove_const" do it "removes the constant specified by a String or Symbol from the receiver's constant table" do @@ -7,13 +7,13 @@ describe "Module#remove_const" do ConstantSpecs::ModuleM::CS_CONST252.should == :const252 ConstantSpecs::ModuleM.send :remove_const, :CS_CONST252 - lambda { ConstantSpecs::ModuleM::CS_CONST252 }.should raise_error(NameError) + -> { ConstantSpecs::ModuleM::CS_CONST252 }.should raise_error(NameError) ConstantSpecs::ModuleM::CS_CONST253 = :const253 ConstantSpecs::ModuleM::CS_CONST253.should == :const253 ConstantSpecs::ModuleM.send :remove_const, "CS_CONST253" - lambda { ConstantSpecs::ModuleM::CS_CONST253 }.should raise_error(NameError) + -> { ConstantSpecs::ModuleM::CS_CONST253 }.should raise_error(NameError) end it "returns the value of the removed constant" do @@ -23,7 +23,7 @@ describe "Module#remove_const" do it "raises a NameError and does not call #const_missing if the constant is not defined" do ConstantSpecs.should_not_receive(:const_missing) - lambda { ConstantSpecs.send(:remove_const, :Nonexistent) }.should raise_error(NameError) + -> { ConstantSpecs.send(:remove_const, :Nonexistent) }.should raise_error(NameError) end it "raises a NameError and does not call #const_missing if the constant is not defined directly in the module" do @@ -32,7 +32,7 @@ describe "Module#remove_const" do ConstantSpecs::ContainerA::CS_CONST255.should == :const255 ConstantSpecs::ContainerA.should_not_receive(:const_missing) - lambda do + -> do ConstantSpecs::ContainerA.send :remove_const, :CS_CONST255 end.should raise_error(NameError) ensure @@ -41,21 +41,21 @@ describe "Module#remove_const" do end it "raises a NameError if the name does not start with a capital letter" do - lambda { ConstantSpecs.send :remove_const, "name" }.should raise_error(NameError) + -> { ConstantSpecs.send :remove_const, "name" }.should raise_error(NameError) end it "raises a NameError if the name starts with a non-alphabetic character" do - lambda { ConstantSpecs.send :remove_const, "__CONSTX__" }.should raise_error(NameError) - lambda { ConstantSpecs.send :remove_const, "@Name" }.should raise_error(NameError) - lambda { ConstantSpecs.send :remove_const, "!Name" }.should raise_error(NameError) - lambda { ConstantSpecs.send :remove_const, "::Name" }.should raise_error(NameError) + -> { ConstantSpecs.send :remove_const, "__CONSTX__" }.should raise_error(NameError) + -> { ConstantSpecs.send :remove_const, "@Name" }.should raise_error(NameError) + -> { ConstantSpecs.send :remove_const, "!Name" }.should raise_error(NameError) + -> { ConstantSpecs.send :remove_const, "::Name" }.should raise_error(NameError) end it "raises a NameError if the name contains non-alphabetic characters except '_'" do ConstantSpecs::ModuleM::CS_CONST256 = :const256 ConstantSpecs::ModuleM.send :remove_const, "CS_CONST256" - lambda { ConstantSpecs.send :remove_const, "Name=" }.should raise_error(NameError) - lambda { ConstantSpecs.send :remove_const, "Name?" }.should raise_error(NameError) + -> { ConstantSpecs.send :remove_const, "Name=" }.should raise_error(NameError) + -> { ConstantSpecs.send :remove_const, "Name?" }.should raise_error(NameError) end it "calls #to_str to convert the given name to a String" do @@ -67,10 +67,10 @@ describe "Module#remove_const" do it "raises a TypeError if conversion to a String by calling #to_str fails" do name = mock('123') - lambda { ConstantSpecs.send :remove_const, name }.should raise_error(TypeError) + -> { ConstantSpecs.send :remove_const, name }.should raise_error(TypeError) name.should_receive(:to_str).and_return(123) - lambda { ConstantSpecs.send :remove_const, name }.should raise_error(TypeError) + -> { ConstantSpecs.send :remove_const, name }.should raise_error(TypeError) end it "is a private method" do @@ -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 d82e0c65ca..94b255df62 100644 --- a/spec/ruby/core/module/remove_method_spec.rb +++ b/spec/ruby/core/module/remove_method_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' module ModuleSpecs class Parent @@ -20,15 +20,8 @@ describe "Module#remove_method" do @module = Module.new { def method_to_remove; end } end - ruby_version_is ''...'2.5' do - it "is a private method" do - Module.should have_private_instance_method(:remove_method, false) - end - end - ruby_version_is '2.5' do - it "is a public method" do - Module.should have_public_instance_method(:remove_method, false) - end + it "is a public method" do + Module.should have_public_instance_method(:remove_method, false) end it "removes the method from a class" do @@ -50,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 @@ -77,19 +92,19 @@ describe "Module#remove_method" do end it "raises a NameError when attempting to remove method further up the inheritance tree" do - lambda { - class Third < ModuleSpecs::Second + Class.new(ModuleSpecs::Second) do + -> { remove_method :method_to_remove - end - }.should raise_error(NameError) + }.should raise_error(NameError) + end end it "raises a NameError when attempting to remove a missing method" do - lambda { - class Third < ModuleSpecs::Second + Class.new(ModuleSpecs::Second) do + -> { remove_method :blah - end - }.should raise_error(NameError) + }.should raise_error(NameError) + end end describe "on frozen instance" do @@ -97,16 +112,16 @@ describe "Module#remove_method" do @frozen = @module.dup.freeze end - it "raises a RuntimeError when passed a name" do - lambda { @frozen.send :remove_method, :method_to_remove }.should raise_error(RuntimeError) + it "raises a FrozenError when passed a name" do + -> { @frozen.send :remove_method, :method_to_remove }.should raise_error(FrozenError) end - it "raises a RuntimeError when passed a missing name" do - lambda { @frozen.send :remove_method, :not_exist }.should raise_error(RuntimeError) + it "raises a FrozenError when passed a missing name" do + -> { @frozen.send :remove_method, :not_exist }.should raise_error(FrozenError) end it "raises a TypeError when passed a not name" do - lambda { @frozen.send :remove_method, Object.new }.should raise_error(TypeError) + -> { @frozen.send :remove_method, Object.new }.should raise_error(TypeError) end it "does not raise exceptions when no arguments given" do 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 6bb9668fee..b1d5cb3814 100644 --- a/spec/ruby/core/module/shared/class_eval.rb +++ b/spec/ruby/core/module/shared/class_eval.rb @@ -14,7 +14,7 @@ describe :module_class_eval, shared: true do 'foo' end end - lambda {42.foo}.should raise_error(NoMethodError) + -> {42.foo}.should raise_error(NoMethodError) end it "resolves constants in the caller scope" do @@ -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) - lambda { 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') - lambda { 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) - lambda { 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 - lambda { 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 - lambda { + -> { 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 - lambda { + -> { 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 @@ -112,4 +127,48 @@ describe :module_class_eval, shared: true do a.attribute.should == "A" b.attribute.should == "B" end + + it "activates refinements from the eval scope" do + refinery = Module.new do + refine ModuleSpecs::NamedClass do + def foo + "bar" + end + end + end + + mid = @method + result = nil + + Class.new do + using refinery + + result = send(mid, "ModuleSpecs::NamedClass.new.foo") + end + + result.should == "bar" + end + + it "activates refinements from the eval scope with block" do + refinery = Module.new do + refine ModuleSpecs::NamedClass do + def foo + "bar" + end + end + end + + mid = @method + result = nil + + Class.new do + using refinery + + result = send(mid) do + ModuleSpecs::NamedClass.new.foo + end + end + + result.should == "bar" + end end diff --git a/spec/ruby/core/module/shared/class_exec.rb b/spec/ruby/core/module/shared/class_exec.rb index c5c18b0a34..c7a9e5297f 100644 --- a/spec/ruby/core/module/shared/class_exec.rb +++ b/spec/ruby/core/module/shared/class_exec.rb @@ -5,7 +5,7 @@ describe :module_class_exec, shared: true do 'foo' end end - lambda {42.foo}.should raise_error(NoMethodError) + -> {42.foo}.should raise_error(NoMethodError) end it "defines method in the receiver's scope" do @@ -19,7 +19,7 @@ describe :module_class_exec, shared: true do end it "raises a LocalJumpError when no block is given" do - lambda { ModuleSpecs::Subclass.send(@method) }.should raise_error(LocalJumpError) + -> { ModuleSpecs::Subclass.send(@method) }.should raise_error(LocalJumpError) end it "passes arguments to the block" do diff --git a/spec/ruby/core/module/shared/set_visibility.rb b/spec/ruby/core/module/shared/set_visibility.rb index c39d59e05d..a1586dd2bd 100644 --- a/spec/ruby/core/module/shared/set_visibility.rb +++ b/spec/ruby/core/module/shared/set_visibility.rb @@ -5,6 +5,55 @@ describe :set_visibility, shared: true do Module.should have_private_instance_method(@method, false) 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 { + def test_method; end + send(visibility, :test_method) + } + + child = Module.new { + include parent + send(visibility, :test_method) + } + + child.should_not send(:"have_#{visibility}_instance_method", :test_method, false) + end + end + describe "without arguments" do it "sets visibility to following method definitions" do visibility = @method diff --git a/spec/ruby/core/module/singleton_class_spec.rb b/spec/ruby/core/module/singleton_class_spec.rb index 177dfc224f..052755b73b 100644 --- a/spec/ruby/core/module/singleton_class_spec.rb +++ b/spec/ruby/core/module/singleton_class_spec.rb @@ -1,27 +1,27 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' describe "Module#singleton_class?" do it "returns true for singleton classes" do xs = self.singleton_class - xs.singleton_class?.should == true + xs.should.singleton_class? end it "returns false for other classes" do c = Class.new - c.singleton_class?.should == false + c.should_not.singleton_class? end describe "with singleton values" do it "returns false for nil's singleton class" do - NilClass.singleton_class?.should == false + NilClass.should_not.singleton_class? end it "returns false for true's singleton class" do - TrueClass.singleton_class?.should == false + TrueClass.should_not.singleton_class? end it "returns false for false's singleton class" do - FalseClass.singleton_class?.should == false + FalseClass.should_not.singleton_class? end end end diff --git a/spec/ruby/core/module/to_s_spec.rb b/spec/ruby/core/module/to_s_spec.rb index de924a8739..83c0ae0825 100644 --- a/spec/ruby/core/module/to_s_spec.rb +++ b/spec/ruby/core/module/to_s_spec.rb @@ -1,18 +1,70 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' describe "Module#to_s" do + it 'returns the name of the module if it has a name' do + Enumerable.to_s.should == 'Enumerable' + String.to_s.should == 'String' + end + it "returns the full constant path leading to the module" do ModuleSpecs::LookupMod.to_s.should == "ModuleSpecs::LookupMod" end it "works with an anonymous module" do m = Module.new - m.to_s.should =~ /#<Module:0x[0-9a-f]+>/ + m.to_s.should =~ /\A#<Module:0x\h+>\z/ end it "works with an anonymous class" do c = Class.new - c.to_s.should =~ /#<Class:0x[0-9a-f]+>/ + c.to_s.should =~ /\A#<Class:0x\h+>\z/ + end + + it 'for the singleton class of an object of an anonymous class' do + klass = Class.new + obj = klass.new + sclass = obj.singleton_class + sclass.to_s.should == "#<Class:#{obj}>" + sclass.to_s.should =~ /\A#<Class:#<#{klass}:0x\h+>>\z/ + sclass.to_s.should =~ /\A#<Class:#<#<Class:0x\h+>:0x\h+>>\z/ + end + + it 'for a singleton class of a module includes the module name' do + ModuleSpecs.singleton_class.to_s.should == '#<Class:ModuleSpecs>' + end + + it 'for a metaclass includes the class name' do + ModuleSpecs::NamedClass.singleton_class.to_s.should == '#<Class:ModuleSpecs::NamedClass>' + end + + it 'for objects includes class name and object ID' 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 54c3d37c7f..d4efcd51cb 100644 --- a/spec/ruby/core/module/undef_method_spec.rb +++ b/spec/ruby/core/module/undef_method_spec.rb @@ -1,5 +1,5 @@ -require File.expand_path('../../../spec_helper', __FILE__) -require File.expand_path('../fixtures/classes', __FILE__) +require_relative '../../spec_helper' +require_relative 'fixtures/classes' module ModuleSpecs class Parent @@ -18,15 +18,8 @@ describe "Module#undef_method" do @module = Module.new { def method_to_undef; end } end - ruby_version_is ''...'2.5' do - it "is a private method" do - Module.should have_private_instance_method(:undef_method, false) - end - end - ruby_version_is '2.5' do - it "is a public method" do - Module.should have_public_instance_method(:undef_method, false) - end + it "is a public method" do + Module.should have_public_instance_method(:undef_method, false) end it "requires multiple arguments" do @@ -41,8 +34,8 @@ describe "Module#undef_method" do x = klass.new klass.send(:undef_method, :method_to_undef, :another_method_to_undef) - lambda { x.method_to_undef }.should raise_error(NoMethodError) - lambda { x.another_method_to_undef }.should raise_error(NoMethodError) + -> { x.method_to_undef }.should raise_error(NoMethodError) + -> { x.another_method_to_undef }.should raise_error(NoMethodError) end it "does not undef any instance methods when argument not given" do @@ -56,8 +49,37 @@ describe "Module#undef_method" do @module.send(:undef_method, :method_to_undef).should equal(@module) end - it "raises a NameError when passed a missing name" do - lambda { @module.send :undef_method, :not_exist }.should raise_error(NameError) { |e| + 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| + # a NameError and not a NoMethodError + e.class.should == NameError + } + end + + 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| + # a NameError and not a NoMethodError + e.class.should == NameError + } + end + + it "raises a NameError when passed a missing name for a singleton class" do + klass = Class.new + 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 =~ /[`']#<Class:#<#<Class:/ + + # a NameError and not a NoMethodError + e.class.should == NameError + } + end + + 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| # a NameError and not a NoMethodError e.class.should == NameError } @@ -68,16 +90,16 @@ describe "Module#undef_method" do @frozen = @module.dup.freeze end - it "raises a RuntimeError when passed a name" do - lambda { @frozen.send :undef_method, :method_to_undef }.should raise_error(RuntimeError) + it "raises a FrozenError when passed a name" do + -> { @frozen.send :undef_method, :method_to_undef }.should raise_error(FrozenError) end - it "raises a RuntimeError when passed a missing name" do - lambda { @frozen.send :undef_method, :not_exist }.should raise_error(RuntimeError) + it "raises a FrozenError when passed a missing name" do + -> { @frozen.send :undef_method, :not_exist }.should raise_error(FrozenError) end it "raises a TypeError when passed a not name" do - lambda { @frozen.send :undef_method, Object.new }.should raise_error(TypeError) + -> { @frozen.send :undef_method, Object.new }.should raise_error(TypeError) end it "does not raise exceptions when no arguments given" do @@ -98,7 +120,7 @@ describe "Module#undef_method with symbol" do klass.send :undef_method, :method_to_undef - lambda { x.method_to_undef }.should raise_error(NoMethodError) + -> { x.method_to_undef }.should raise_error(NoMethodError) end it "removes a method defined in a super class" do @@ -108,7 +130,7 @@ describe "Module#undef_method with symbol" do child_class.send :undef_method, :method_to_undef - lambda { child.method_to_undef }.should raise_error(NoMethodError) + -> { child.method_to_undef }.should raise_error(NoMethodError) end it "does not remove a method defined in a super class when removed from a subclass" do @@ -134,7 +156,7 @@ describe "Module#undef_method with string" do klass.send :undef_method, 'another_method_to_undef' - lambda { x.another_method_to_undef }.should raise_error(NoMethodError) + -> { x.another_method_to_undef }.should raise_error(NoMethodError) end it "removes a method defined in a super class" do @@ -144,7 +166,7 @@ describe "Module#undef_method with string" do child_class.send :undef_method, 'another_method_to_undef' - lambda { child.another_method_to_undef }.should raise_error(NoMethodError) + -> { child.another_method_to_undef }.should raise_error(NoMethodError) end it "does not remove a method defined in a super class when removed from a subclass" do 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 2e7e15b748..a908363c96 100644 --- a/spec/ruby/core/module/using_spec.rb +++ b/spec/ruby/core/module/using_spec.rb @@ -1,4 +1,4 @@ -require File.expand_path('../../../spec_helper', __FILE__) +require_relative '../../spec_helper' describe "Module#using" do it "imports class refinements from module into the current class/module" do @@ -24,7 +24,7 @@ describe "Module#using" do end end - -> () { + -> { Module.new do using refinement end @@ -34,7 +34,7 @@ describe "Module#using" do it "accepts module without refinements" do mod = Module.new - -> () { + -> { Module.new do using mod end @@ -44,7 +44,7 @@ describe "Module#using" do it "does not accept class" do klass = Class.new - -> () { + -> { Module.new do using klass end @@ -52,7 +52,7 @@ describe "Module#using" do end it "raises TypeError if passed something other than module" do - -> () { + -> { Module.new do using "foo" end @@ -93,7 +93,7 @@ describe "Module#using" do end end - -> () { + -> { mod.foo }.should raise_error(RuntimeError, /Module#using is not permitted in methods/) end @@ -243,6 +243,96 @@ describe "Module#using" do mod.call_foo(c).should == "foo from refinement" end + it "is active for module defined via Module.new {}" do + refinement = Module.new do + refine Integer do + def foo; "foo from refinement"; end + end + end + + result = nil + + Module.new do + using refinement + + Module.new do + result = 1.foo + end + end + + result.should == "foo from refinement" + end + + it "is active for class defined via Class.new {}" do + refinement = Module.new do + refine Integer do + def foo; "foo from refinement"; end + end + end + + result = nil + + Module.new do + using refinement + + Class.new do + result = 1.foo + end + end + + result.should == "foo from refinement" + end + + it "is active for block called via instance_exec" do + refinement = Module.new do + refine Integer do + def foo; "foo from refinement"; end + end + end + + c = Class.new do + using refinement + + def abc + block = -> { + 1.foo + } + + self.instance_exec(&block) + end + end + + c.new.abc.should == "foo from refinement" + end + + it "is active for block called via instance_eval" do + refinement = Module.new do + refine String do + def foo; "foo from refinement"; end + end + end + + c = Class.new do + using refinement + + def initialize + @a = +"1703" + + @a.instance_eval do + def abc + "#{self}: #{self.foo}" + end + end + end + + def abc + @a.abc + end + end + + c.new.abc.should == "1703: foo from refinement" + end + it "is not active if `using` call is not evaluated" do result = nil |
