diff options
Diffstat (limited to 'spec/ruby/core/module')
146 files changed, 11900 insertions, 0 deletions
diff --git a/spec/ruby/core/module/alias_method_spec.rb b/spec/ruby/core/module/alias_method_spec.rb new file mode 100644 index 0000000000..c36dedd2d8 --- /dev/null +++ b/spec/ruby/core/module/alias_method_spec.rb @@ -0,0 +1,171 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Module#alias_method" do + before :each do + @class = Class.new(ModuleSpecs::Aliasing) + @object = @class.new + end + + it "makes a copy of the method" do + @class.make_alias :uno, :public_one + @class.make_alias :double, :public_two + @object.uno.should == @object.public_one + @object.double(12).should == @object.public_two(12) + end + + it "creates methods that are == to each other" do + @class.make_alias :uno, :public_one + @object.method(:uno).should == @object.method(:public_one) + end + + it "preserves the arguments information of the original methods" do + @class.make_alias :uno, :public_one + @class.make_alias :double, :public_two + @class.instance_method(:uno).parameters.should == @class.instance_method(:public_one).parameters + @class.instance_method(:double).parameters.should == @class.instance_method(:public_two).parameters + end + + it "retains method visibility" do + @class.make_alias :private_ichi, :private_one + -> { @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 + -> { @object.protected_ichi }.should raise_error(NameError) + end + + it "handles aliasing a stub that changes visibility" do + @class.__send__ :public, :private_one + @class.make_alias :was_private_one, :private_one + @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 + -> { @class.make_alias :ni, :san }.should raise_error(NameError) { |e| + # a NameError and not a NoMethodError + e.class.should == NameError + } + end + + it "raises FrozenError if frozen" do + @class.freeze + -> { @class.make_alias :uno, :public_one }.should raise_error(FrozenError) + end + + it "converts the names using #to_str" do + @class.make_alias "un", "public_one" + @class.make_alias :deux, "public_one" + @class.make_alias "trois", :public_one + @class.make_alias :quatre, :public_one + name = mock('cinq') + name.should_receive(:to_str).any_number_of_times.and_return("cinq") + @class.make_alias name, "public_one" + @class.make_alias "cinq", name + end + + it "raises a TypeError when the given name can't be converted using to_str" do + -> { @class.make_alias mock('x'), :public_one }.should raise_error(TypeError) + end + + it "raises a NoMethodError if the given name raises a NoMethodError during type coercion using to_str" do + obj = mock("mock-name") + obj.should_receive(:to_str).and_raise(NoMethodError) + -> { @class.make_alias obj, :public_one }.should raise_error(NoMethodError) + end + + it "is a public method" do + Module.should have_public_instance_method(:alias_method, false) + end + + 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 + ModuleSpecs::Allonym.new.publish.should == :report + end + + it "works on private module methods in a module that has been reopened" do + ModuleSpecs::ReopeningModule.foo.should == true + -> { ModuleSpecs::ReopeningModule.foo2 }.should_not raise_error(NoMethodError) + end + + it "accesses a method defined on Object from Kernel" do + Kernel.should_not have_public_instance_method(:module_specs_public_method_on_object) + + Kernel.should have_public_instance_method(:module_specs_alias_on_kernel) + Object.should have_public_instance_method(:module_specs_alias_on_kernel) + end + + it "can call a method with super aliased twice" do + ModuleSpecs::AliasingSuper::Target.new.super_call(1).should == 1 + end + + it "preserves original super call after alias redefine" do + ModuleSpecs::AliasingSuper::RedefineAfterAlias.new.alias_super_call(1).should == 1 + end + + describe "aliasing special methods" do + before :all do + @class = ModuleSpecs::Aliasing + @subclass = ModuleSpecs::AliasingSubclass + end + + it "keeps initialize private when aliasing" do + @class.make_alias(:initialize, :public_one) + @class.private_instance_methods.include?(:initialize).should be_true + + @subclass.make_alias(:initialize, :public_one) + @subclass.private_instance_methods.include?(:initialize).should be_true + end + + it "keeps initialize_copy private when aliasing" do + @class.make_alias(:initialize_copy, :public_one) + @class.private_instance_methods.include?(:initialize_copy).should be_true + + @subclass.make_alias(:initialize_copy, :public_one) + @subclass.private_instance_methods.include?(:initialize_copy).should be_true + end + + it "keeps initialize_clone private when aliasing" do + @class.make_alias(:initialize_clone, :public_one) + @class.private_instance_methods.include?(:initialize_clone).should be_true + + @subclass.make_alias(:initialize_clone, :public_one) + @subclass.private_instance_methods.include?(:initialize_clone).should be_true + end + + it "keeps initialize_dup private when aliasing" do + @class.make_alias(:initialize_dup, :public_one) + @class.private_instance_methods.include?(:initialize_dup).should be_true + + @subclass.make_alias(:initialize_dup, :public_one) + @subclass.private_instance_methods.include?(:initialize_dup).should be_true + end + + it "keeps respond_to_missing? private when aliasing" do + @class.make_alias(:respond_to_missing?, :public_one) + @class.private_instance_methods.include?(:respond_to_missing?).should be_true + + @subclass.make_alias(:respond_to_missing?, :public_one) + @subclass.private_instance_methods.include?(:respond_to_missing?).should be_true + end + end +end diff --git a/spec/ruby/core/module/ancestors_spec.rb b/spec/ruby/core/module/ancestors_spec.rb new file mode 100644 index 0000000000..90c26941d1 --- /dev/null +++ b/spec/ruby/core/module/ancestors_spec.rb @@ -0,0 +1,88 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Module#ancestors" do + it "returns a list of modules included in self (including self)" do + BasicObject.ancestors.should == [BasicObject] + ModuleSpecs.ancestors.should == [ModuleSpecs] + ModuleSpecs::Basic.ancestors.should == [ModuleSpecs::Basic] + ModuleSpecs::Super.ancestors.should == [ModuleSpecs::Super, ModuleSpecs::Basic] + 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 + class << ModuleSpecs::Child; self; end.ancestors.should include(ModuleSpecs::Internal, Class, Module, Object, Kernel) + end + + it "has 1 entry per module or class" 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 + child = Class.new(parent) + schild = child.singleton_class + + schild.ancestors.should include(schild, + parent.singleton_class, + Object.singleton_class, + BasicObject.singleton_class, + Class, + Module, + Object, + Kernel, + BasicObject) + + end + + describe 'for a standalone module' do + it 'does not include Class' do + s_mod = ModuleSpecs.singleton_class + s_mod.ancestors.should_not include(Class) + end + + it 'does not include other singleton classes' do + s_standalone_mod = ModuleSpecs.singleton_class + s_module = Module.singleton_class + s_object = Object.singleton_class + s_basic_object = BasicObject.singleton_class + + s_standalone_mod.ancestors.should_not include(s_module, s_object, s_basic_object) + end + + it 'includes its own singleton class' do + s_mod = ModuleSpecs.singleton_class + + s_mod.ancestors.should include(s_mod) + end + + it 'includes standard chain' do + s_mod = ModuleSpecs.singleton_class + + s_mod.ancestors.should include(Module, Object, Kernel, BasicObject) + end + end + end +end diff --git a/spec/ruby/core/module/append_features_spec.rb b/spec/ruby/core/module/append_features_spec.rb new file mode 100644 index 0000000000..1724cde5d6 --- /dev/null +++ b/spec/ruby/core/module/append_features_spec.rb @@ -0,0 +1,61 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Module#append_features" do + it "is a private method" do + Module.should have_private_instance_method(:append_features) + end + + describe "on Class" do + it "is undefined" do + Class.should_not have_private_instance_method(:append_features, true) + end + + it "raises a TypeError if calling after rebinded to Class" do + -> { + Module.instance_method(:append_features).bind(Class.new).call Module.new + }.should raise_error(TypeError) + end + end + + it "gets called when self is included in another module/class" do + begin + m = Module.new do + def self.append_features(mod) + $appended_to = mod + end + end + + c = Class.new do + include m + end + + $appended_to.should == c + ensure + $appended_to = nil + end + end + + it "raises an ArgumentError on a cyclic include" do + -> { + ModuleSpecs::CyclicAppendA.send(:append_features, ModuleSpecs::CyclicAppendA) + }.should raise_error(ArgumentError) + + -> { + ModuleSpecs::CyclicAppendB.send(:append_features, ModuleSpecs::CyclicAppendA) + }.should raise_error(ArgumentError) + + end + + describe "when other is frozen" do + before :each do + @receiver = Module.new + @other = Module.new.freeze + end + + 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 +end diff --git a/spec/ruby/core/module/attr_accessor_spec.rb b/spec/ruby/core/module/attr_accessor_spec.rb new file mode 100644 index 0000000000..503dccc61e --- /dev/null +++ b/spec/ruby/core/module/attr_accessor_spec.rb @@ -0,0 +1,112 @@ +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 + c = Class.new do + attr_accessor :a, "b" + end + + o = c.new + + ['a','b'].each do |x| + o.respond_to?(x).should == true + o.respond_to?("#{x}=").should == true + end + + o.a = "a" + o.a.should == "a" + + o.b = "b" + o.b.should == "b" + o.a = o.b = nil + + o.send(:a=,"a") + o.send(:a).should == "a" + + o.send(:b=, "b") + o.send(:b).should == "b" + end + + it "not allows creating an attr_accessor on an immediate class" do + class TrueClass + attr_accessor :spec_attr_accessor + end + + -> { true.spec_attr_accessor = "a" }.should raise_error(FrozenError) + end + + 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 + end + + c.new.respond_to?("test").should == true + c.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') + -> { Class.new { attr_accessor o } }.should raise_error(TypeError) + (o = mock('123')).should_receive(:to_str).and_return(123) + -> { Class.new { attr_accessor o } }.should raise_error(TypeError) + end + + it "applies current visibility to methods created" do + c = Class.new do + protected + attr_accessor :foo + end + + -> { c.new.foo }.should raise_error(NoMethodError) + -> { c.new.foo=1 }.should raise_error(NoMethodError) + end + + it "is a public method" do + Module.should have_public_instance_method(:attr_accessor, false) + end + + it "returns an array of defined method names as symbols" do + Class.new do + (attr_accessor :foo, 'bar').should == [:foo, :foo=, :bar, :bar=] + end + end + + describe "on immediates" do + before :each do + class Integer + attr_accessor :foobar + end + end + + after :each do + if Integer.method_defined?(:foobar) + Integer.send(:remove_method, :foobar) + end + if Integer.method_defined?(:foobar=) + Integer.send(:remove_method, :foobar=) + end + end + + it "can read through the 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 new file mode 100644 index 0000000000..37fd537ff5 --- /dev/null +++ b/spec/ruby/core/module/attr_reader_spec.rb @@ -0,0 +1,73 @@ +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 + c = Class.new do + attr_reader :a, "b" + + def initialize + @a = "test" + @b = "test2" + end + end + + o = c.new + %w{a b}.each do |x| + o.respond_to?(x).should == true + o.respond_to?("#{x}=").should == false + end + + o.a.should == "test" + o.b.should == "test2" + o.send(:a).should == "test" + o.send(:b).should == "test2" + end + + it "not allows for adding an attr_reader to an immediate" do + class TrueClass + attr_reader :spec_attr_reader + end + + -> { true.instance_variable_set("@spec_attr_reader", "a") }.should raise_error(RuntimeError) + 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_reader o + end + + c.new.respond_to?("test").should == true + c.new.respond_to?("test=").should == false + end + + it "raises a TypeError when the given names can't be converted to strings using to_str" do + o = mock('o') + -> { Class.new { attr_reader o } }.should raise_error(TypeError) + (o = mock('123')).should_receive(:to_str).and_return(123) + -> { Class.new { attr_reader o } }.should raise_error(TypeError) + end + + it "applies current visibility to methods created" do + c = Class.new do + protected + attr_reader :foo + end + + -> { c.new.foo }.should raise_error(NoMethodError) + end + + it "is a public method" do + Module.should have_public_instance_method(:attr_reader, false) + end + + it "returns an array of defined method names as symbols" do + Class.new do + (attr_reader :foo, 'bar').should == [:foo, :bar] + end + end + + it_behaves_like :module_attr_added, :attr_reader +end diff --git a/spec/ruby/core/module/attr_spec.rb b/spec/ruby/core/module/attr_spec.rb new file mode 100644 index 0000000000..2f9f4e26dc --- /dev/null +++ b/spec/ruby/core/module/attr_spec.rb @@ -0,0 +1,159 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/attr_added' + +describe "Module#attr" do + before :each do + $VERBOSE, @verbose = false, $VERBOSE + end + + after :each do + $VERBOSE = @verbose + end + + it "creates a getter for the given attribute name" do + c = Class.new do + attr :attr + attr "attr3" + + def initialize + @attr, @attr2, @attr3 = "test", "test2", "test3" + end + end + + o = c.new + + %w{attr attr3}.each do |a| + o.respond_to?(a).should == true + o.respond_to?("#{a}=").should == false + end + + o.attr.should == "test" + o.attr3.should == "test3" + o.send(:attr).should == "test" + o.send(:attr3).should == "test3" + end + + it "creates a setter for the given attribute name if writable is true" do + c = Class.new do + attr :attr, true + attr "attr3", true + + def initialize + @attr, @attr2, @attr3 = "test", "test2", "test3" + end + end + + o = c.new + + %w{attr attr3}.each do |a| + o.respond_to?(a).should == true + o.respond_to?("#{a}=").should == true + end + + o.attr = "test updated" + o.attr3 = "test3 updated" + end + + 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 + + attr "attr3", true + attr "attr3" + + def initialize + @attr, @attr2, @attr3 = "test", "test2", "test3" + end + end + + o = c.new + + %w{attr attr3}.each do |a| + o.respond_to?(a).should == true + o.respond_to?("#{a}=").should == true + end + + o.attr.should == "test" + o.attr = "test updated" + o.attr.should == "test updated" + + o.attr3.should == "test3" + o.attr3 = "test3 updated" + o.attr3.should == "test3 updated" + end + + it "applies current visibility to methods created" do + c = Class.new do + protected + attr :foo, true + end + + -> { 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 + c = Class.new do + attr :attr, "attr2", "attr3" + + def initialize + @attr, @attr2, @attr3 = "test", "test2", "test3" + end + end + + o = c.new + + %w{attr attr2 attr3}.each do |a| + o.respond_to?(a).should == true + o.respond_to?("#{a}=").should == false + end + + o.attr.should == "test" + o.attr2.should == "test2" + o.attr3.should == "test3" + end + + it "applies current visibility to methods created" do + c = Class.new do + protected + attr :foo, :bar + end + + -> { c.new.foo }.should raise_error(NoMethodError) + -> { c.new.bar }.should raise_error(NoMethodError) + 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") + 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') + -> { Class.new { attr o } }.should raise_error(TypeError) + (o = mock('123')).should_receive(:to_str).and_return(123) + -> { Class.new { attr o } }.should raise_error(TypeError) + end + + it "with a boolean argument emits a warning when $VERBOSE is true" do + -> { + Class.new { attr :foo, true } + }.should complain(/boolean argument is obsoleted/, verbose: true) + end + + it "is a public method" do + Module.should have_public_instance_method(:attr, false) + end + + it "returns an array of defined method names as symbols" do + Class.new do + (attr :foo, 'bar').should == [:foo, :bar] + (attr :baz, false).should == [:baz] + (attr :qux, true).should == [:qux, :qux=] + end + end + + it_behaves_like :module_attr_added, :attr +end diff --git a/spec/ruby/core/module/attr_writer_spec.rb b/spec/ruby/core/module/attr_writer_spec.rb new file mode 100644 index 0000000000..5b863ef88c --- /dev/null +++ b/spec/ruby/core/module/attr_writer_spec.rb @@ -0,0 +1,83 @@ +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 + c = Class.new do + attr_writer :test1, "test2" + end + o = c.new + + o.respond_to?("test1").should == false + o.respond_to?("test2").should == false + + o.respond_to?("test1=").should == true + o.test1 = "test_1" + o.instance_variable_get(:@test1).should == "test_1" + + o.respond_to?("test2=").should == true + o.test2 = "test_2" + o.instance_variable_get(:@test2).should == "test_2" + o.send(:test1=,"test_1 updated") + o.instance_variable_get(:@test1).should == "test_1 updated" + o.send(:test2=,"test_2 updated") + o.instance_variable_get(:@test2).should == "test_2 updated" + end + + it "not allows for adding an attr_writer to an immediate" do + class TrueClass + attr_writer :spec_attr_writer + end + + -> { true.spec_attr_writer = "a" }.should raise_error(FrozenError) + end + + 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 + end + + c.new.respond_to?("test").should == false + c.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('test1') + -> { Class.new { attr_writer o } }.should raise_error(TypeError) + (o = mock('123')).should_receive(:to_str).and_return(123) + -> { Class.new { attr_writer o } }.should raise_error(TypeError) + end + + it "applies current visibility to methods created" do + c = Class.new do + protected + attr_writer :foo + end + + -> { c.new.foo=1 }.should raise_error(NoMethodError) + end + + it "is a public method" do + Module.should have_public_instance_method(:attr_writer, false) + end + + it "returns an array of defined method names as symbols" do + Class.new do + (attr_writer :foo, 'bar').should == [:foo=, :bar=] + end + end + + it_behaves_like :module_attr_added, :attr_writer +end diff --git a/spec/ruby/core/module/autoload_spec.rb b/spec/ruby/core/module/autoload_spec.rb new file mode 100644 index 0000000000..625d945686 --- /dev/null +++ b/spec/ruby/core/module/autoload_spec.rb @@ -0,0 +1,1026 @@ +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 + ModuleSpecs::Autoload.autoload :Autoload, "autoload.rb" + ModuleSpecs::Autoload.autoload?(:Autoload).should == "autoload.rb" + end + + 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 + + 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 + ModuleSpecs::Autoload.autoload :A, @non_existent + ModuleSpecs::Autoload.autoload?(:A).should == @non_existent + end + + it "sets the autoload constant in the constants table" do + ModuleSpecs::Autoload.autoload :B, @non_existent + 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 + 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 + ScratchPad.recorded.should == :loaded + end + + 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 + ScratchPad.recorded.should == :loaded + end + + it "loads the registered constant when it is opened as a class" do + ModuleSpecs::Autoload.autoload :E, fixture(__FILE__, "autoload_e.rb") + class ModuleSpecs::Autoload::E + end + ModuleSpecs::Autoload::E.new.loaded.should == :autoload_e + end + + it "loads the registered constant when it is opened as a module" do + ModuleSpecs::Autoload.autoload :F, fixture(__FILE__, "autoload_f.rb") + module ModuleSpecs::Autoload::F + end + ModuleSpecs::Autoload::F.loaded.should == :autoload_f + end + + it "loads the registered constant when it is inherited from" do + ModuleSpecs::Autoload.autoload :G, fixture(__FILE__, "autoload_g.rb") + class ModuleSpecs::Autoload::Gsub < ModuleSpecs::Autoload::G + end + ModuleSpecs::Autoload::Gsub.new.loaded.should == :autoload_g + end + + it "loads the registered constant when it is included" do + ModuleSpecs::Autoload.autoload :H, fixture(__FILE__, "autoload_h.rb") + class ModuleSpecs::Autoload::HClass + include ModuleSpecs::Autoload::H + end + ModuleSpecs::Autoload::HClass.new.loaded.should == :autoload_h + end + + 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 + end + + it "loads a file with .rb extension when passed the name without the extension" do + ModuleSpecs::Autoload.autoload :J, fixture(__FILE__, "autoload_j") + 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 + ScratchPad.clear + + ModuleSpecs::Autoload::KHash.should be_kind_of(Class) + ModuleSpecs::Autoload::KHash::K.should == :autoload_k + ScratchPad.recorded.should be_nil + end + + it "ignores the autoload request if the file is already loaded" do + filename = fixture(__FILE__, "autoload_s.rb") + + require filename + + ScratchPad.recorded.should == :loaded + ScratchPad.clear + + ModuleSpecs::Autoload.autoload :S, filename + @remove << :S + ModuleSpecs::Autoload.autoload?(:S).should be_nil + end + + it "retains the autoload even if the request to require fails" do + filename = fixture(__FILE__, "a_path_that_should_not_exist.rb") + + ModuleSpecs::Autoload.autoload :NotThere, filename + ModuleSpecs::Autoload.autoload?(:NotThere).should == filename + + -> { + require filename + }.should raise_error(LoadError) + + ModuleSpecs::Autoload.autoload?(:NotThere).should == filename + end + + it "allows multiple autoload constants for a single file" do + filename = fixture(__FILE__, "autoload_lm.rb") + ModuleSpecs::Autoload.autoload :L, filename + ModuleSpecs::Autoload.autoload :M, filename + ModuleSpecs::Autoload::L.should == :autoload_l + ModuleSpecs::Autoload::M.should == :autoload_m + end + + it "runs for an exception condition class and doesn't trample the exception" do + filename = fixture(__FILE__, "autoload_ex1.rb") + ModuleSpecs::Autoload.autoload :EX1, filename + ModuleSpecs::Autoload.use_ex1.should == :good + end + + it "considers an autoload constant as loaded when autoload is called for/from the current file" do + filename = fixture(__FILE__, "autoload_during_require_current_file.rb") + require filename + + ScratchPad.recorded.should be_nil + end + + describe "interacting with defined?" do + it "does not load the file when referring to the constant in defined?" do + module ModuleSpecs::Autoload::Dog + 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 + end + + describe "during the autoload after the constant is assigned" do + before :each do + @path = fixture(__FILE__, "autoload_during_autoload_after_define.rb") + ModuleSpecs::Autoload.autoload :DuringAutoloadAfterDefine, @path + @autoload_location = [__FILE__, __LINE__ - 1] + @const_location = [@path, 2] + @remove << :DuringAutoloadAfterDefine + raise unless ModuleSpecs::Autoload.autoload?(:DuringAutoloadAfterDefine) == @path + end + + it "returns 'constant' in both threads" do + results = check_before_during_thread_after(:DuringAutoloadAfterDefine) { + defined?(ModuleSpecs::Autoload::DuringAutoloadAfterDefine) + } + results.should == ['constant', 'constant', 'constant', 'constant'] + end + + it "Module#constants include the autoloaded in both threads" do + results = check_before_during_thread_after(:DuringAutoloadAfterDefine) { + ModuleSpecs::Autoload.constants(false).include?(:DuringAutoloadAfterDefine) + } + results.should == [true, true, true, true] + end + + it "Module#const_defined? returns true in both threads" do + results = check_before_during_thread_after(:DuringAutoloadAfterDefine) { + ModuleSpecs::Autoload.const_defined?(:DuringAutoloadAfterDefine, false) + } + results.should == [true, true, true, true] + end + + it "returns nil in autoload thread and returns the path in other threads for Module#autoload?" do + results = check_before_during_thread_after(:DuringAutoloadAfterDefine) { + ModuleSpecs::Autoload.autoload?(:DuringAutoloadAfterDefine) + } + results.should == [@path, nil, @path, nil] + end + + ruby_bug("#20188", ""..."3.4") do + it "returns the real constant location in autoload thread and returns the autoload location in other threads for Module#const_source_location" do + results = check_before_during_thread_after(:DuringAutoloadAfterDefine) { + ModuleSpecs::Autoload.const_source_location(:DuringAutoloadAfterDefine) + } + results.should == [@autoload_location, @const_location, @autoload_location, @const_location] + end + end + end + + it "does not remove the constant from Module#constants if load fails and keeps it as an autoload" do + ModuleSpecs::Autoload.autoload :Fail, @non_existent + + 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) + + 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 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 + + -> { 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 + module ModuleSpecs::Autoload::Q + autoload :R, fixture(__FILE__, "autoload.rb") + defined?(R).should == 'constant' + end + ModuleSpecs::Autoload::Q.should have_constant(:R) + end + + it "does not load the file when removing an autoload constant" do + module ModuleSpecs::Autoload::Q + autoload :R, fixture(__FILE__, "autoload.rb") + remove_const :R + end + ModuleSpecs::Autoload::Q.should_not have_constant(:R) + end + + 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 + module ModuleSpecs::Autoload::U + autoload :V, fixture(__FILE__, "autoload_v.rb") + + class V + X = get_value + end + end + @remove << :U + + ModuleSpecs::Autoload::U::V::X.should == :autoload_uvx + end + + 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 :CS_CONST_AUTOLOAD, fixture(__FILE__, "autoload_subclass.rb") + end + + 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 "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 + + 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 + MetaScope.new + end + end + end + ModuleSpecs::Autoload.r.should be_kind_of(ModuleSpecs::Autoload::MetaScope) + end + end + + it "should trigger the autoload when using `private_constant`" do + @remove << :DynClass + module ModuleSpecs::Autoload + autoload :DynClass, fixture(__FILE__, "autoload_c.rb") + private_constant :DynClass + + ScratchPad.recorded.should be_nil + + DynClass::C.new.loaded.should == :dynclass_c + ScratchPad.recorded.should == :loaded + end + + -> { ModuleSpecs::Autoload::DynClass }.should raise_error(NameError, /private constant/) + end + + # [ruby-core:19127] [ruby-core:29941] + it "does NOT raise a NameError when the autoload file did not define the constant and a module is opened with the same name" do + module ModuleSpecs::Autoload + class W + autoload :Y, fixture(__FILE__, "autoload_w.rb") + + class Y + end + end + end + @remove << :W + + ModuleSpecs::Autoload::W::Y.should be_kind_of(Class) + ScratchPad.recorded.should == :loaded + 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 + p = mock('path') + p.should_receive(:to_path).and_return @non_existent + ModuleSpecs.autoload :A, p + end + + it "raises an ArgumentError when an empty filename is given" do + -> { ModuleSpecs.autoload :A, "" }.should raise_error(ArgumentError) + end + + it "raises a NameError when the constant name starts with a lower case letter" do + -> { ModuleSpecs.autoload "a", @non_existent }.should raise_error(NameError) + end + + it "raises a NameError when the constant name starts with a number" do + -> { ModuleSpecs.autoload "1two", @non_existent }.should raise_error(NameError) + end + + it "raises a NameError when the constant name has a space in it" do + -> { 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 } + -> { + ModuleSpecs::Autoload::S = mod1 + }.should complain(/already initialized constant/) + mod2 = mod1.dup + + mod1.autoload?(:T).should == filename + mod2.autoload?(:T).should == filename + + mod1::T.should == :autoload_t + -> { 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 + ModuleSpecs::Autoload.autoload :Z, fixture(__FILE__, "autoload_z.rb") + class ModuleSpecs::Autoload::ZZ + end + + -> 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 responding to #to_path for the filename" do + name = mock("autoload_name.rb") + + -> { 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") + + -> { ModuleSpecs::Autoload.autoload :Str, name }.should_not raise_error + end + + describe "on a frozen module" do + 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 + + describe "when changing $LOAD_PATH" do + before do + $LOAD_PATH.unshift(File.expand_path('../fixtures/path1', __FILE__)) + end + + after do + $LOAD_PATH.shift + $LOAD_PATH.shift + end + + it "does not reload a file due to a different load path" do + ModuleSpecs::Autoload.autoload :LoadPath, "load_path" + ModuleSpecs::Autoload::LoadPath.loaded.should == :autoload_load_path + end + end + + 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 + + ScratchPad.record [] + + t1_val = nil + t2_val = nil + + fin = false + + t1 = Thread.new do + Thread.pass until start + t1_val = ModuleSpecs::Autoload::Concur + ScratchPad.recorded << :t1_post + fin = true + end + + t2_exc = nil + + t2 = Thread.new do + Thread.pass until t1 and t1[:in_autoload_rb] + begin + t2_val = ModuleSpecs::Autoload::Concur + rescue Exception => e + t2_exc = e + else + Thread.pass until fin + ScratchPad.recorded << :t2_post + end + end + + start = true + + t1.join + t2.join + + ScratchPad.recorded.should == [:con_pre, :con_post, :t1_post, :t2_post] + + t1_val.should == 1 + t2_val.should == t1_val + + t2_exc.should be_nil + end + + # 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 + + 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 + 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 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 + end + + it "raises a NameError in each thread if the constant is not set" do + file = fixture(__FILE__, "autoload_never_set.rb") + start = false + + threads = Array.new(10) do + Thread.new do + Thread.pass until start + begin + ModuleSpecs::Autoload.autoload :NeverSetConstant, file + Thread.pass + ModuleSpecs::Autoload::NeverSetConstant + rescue NameError => e + e + ensure + Thread.pass + end + end + end + + start = true + threads.each { |t| + t.value.should be_an_instance_of(NameError) + } + end + + it "raises a LoadError in each thread if the file does not exist" do + file = fixture(__FILE__, "autoload_does_not_exist.rb") + start = false + + threads = Array.new(10) do + Thread.new do + Thread.pass until start + begin + ModuleSpecs::Autoload.autoload :FileDoesNotExist, file + Thread.pass + ModuleSpecs::Autoload::FileDoesNotExist + rescue LoadError => e + e + ensure + Thread.pass + end + end + end + + start = true + threads.each { |t| + t.value.should be_an_instance_of(LoadError) + } + end + end + + it "loads the registered constant even if the constant was already loaded by another thread" do + Thread.new { + ModuleSpecs::Autoload::FromThread::D.foo + }.value.should == :foo + end +end diff --git a/spec/ruby/core/module/case_compare_spec.rb b/spec/ruby/core/module/case_compare_spec.rb new file mode 100644 index 0000000000..49ac359f6f --- /dev/null +++ b/spec/ruby/core/module/case_compare_spec.rb @@ -0,0 +1,31 @@ +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 + (ModuleSpecs::Child === ModuleSpecs::Child.new).should == true + (ModuleSpecs::Parent === ModuleSpecs::Parent.new).should == true + + (ModuleSpecs::Parent === ModuleSpecs::Child.new).should == true + (Object === ModuleSpecs::Child.new).should == true + + (ModuleSpecs::Child === String.new).should == false + (ModuleSpecs::Child === mock('x')).should == false + end + + it "returns true when the given Object's class includes self or when the given Object is extended by self" do + (ModuleSpecs::Basic === ModuleSpecs::Child.new).should == true + (ModuleSpecs::Super === ModuleSpecs::Child.new).should == true + (ModuleSpecs::Basic === mock('x').extend(ModuleSpecs::Super)).should == true + (ModuleSpecs::Super === mock('y').extend(ModuleSpecs::Super)).should == true + + (ModuleSpecs::Basic === ModuleSpecs::Parent.new).should == false + (ModuleSpecs::Super === ModuleSpecs::Parent.new).should == false + (ModuleSpecs::Basic === mock('z')).should == false + (ModuleSpecs::Super === mock('a')).should == false + end + + it "does not let a module singleton class interfere when its on the RHS" do + (Class === ModuleSpecs::CaseCompareOnSingleton).should == false + end +end diff --git a/spec/ruby/core/module/class_eval_spec.rb b/spec/ruby/core/module/class_eval_spec.rb new file mode 100644 index 0000000000..c6665d5aff --- /dev/null +++ b/spec/ruby/core/module/class_eval_spec.rb @@ -0,0 +1,7 @@ +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 +end diff --git a/spec/ruby/core/module/class_exec_spec.rb b/spec/ruby/core/module/class_exec_spec.rb new file mode 100644 index 0000000000..4acd0169ad --- /dev/null +++ b/spec/ruby/core/module/class_exec_spec.rb @@ -0,0 +1,7 @@ +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 +end diff --git a/spec/ruby/core/module/class_variable_defined_spec.rb b/spec/ruby/core/module/class_variable_defined_spec.rb new file mode 100644 index 0000000000..c0f2072a37 --- /dev/null +++ b/spec/ruby/core/module/class_variable_defined_spec.rb @@ -0,0 +1,72 @@ +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 + c = Class.new { class_variable_set :@@class_var, "test" } + c.class_variable_defined?(:@@class_var).should == true + c.class_variable_defined?("@@class_var").should == true + c.class_variable_defined?(:@@no_class_var).should == false + c.class_variable_defined?("@@no_class_var").should == false + ModuleSpecs::CVars.class_variable_defined?("@@cls").should == true + end + + it "returns true if a class variable with the given name is defined in the metaclass" do + ModuleSpecs::CVars.class_variable_defined?("@@meta").should == true + end + + it "returns true if the class variable is defined in a metaclass" do + obj = mock("metaclass class variable") + meta = obj.singleton_class + meta.send :class_variable_set, :@@var, 1 + meta.send(:class_variable_defined?, :@@var).should be_true + end + + it "returns false if the class variable is not defined in a metaclass" do + obj = mock("metaclass class variable") + meta = obj.singleton_class + meta.class_variable_defined?(:@@var).should be_false + end + + it "returns true if a class variables with the given name is defined in an included module" do + c = Class.new { include ModuleSpecs::MVars } + c.class_variable_defined?("@@mvar").should == true + end + + it "returns false if a class variables with the given name is defined in an extended module" do + c = Class.new + c.extend ModuleSpecs::MVars + c.class_variable_defined?("@@mvar").should == false + end + + it "raises a NameError when the given name is not allowed" do + c = Class.new + + -> { + c.class_variable_defined?(:invalid_name) + }.should raise_error(NameError) + + -> { + c.class_variable_defined?("@invalid_name") + }.should raise_error(NameError) + end + + 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 + end + + 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') + -> { + c.class_variable_defined?(o) + }.should raise_error(TypeError) + + o.should_receive(:to_str).and_return(123) + -> { + c.class_variable_defined?(o) + }.should raise_error(TypeError) + end +end diff --git a/spec/ruby/core/module/class_variable_get_spec.rb b/spec/ruby/core/module/class_variable_get_spec.rb new file mode 100644 index 0000000000..e5d06731ec --- /dev/null +++ b/spec/ruby/core/module/class_variable_get_spec.rb @@ -0,0 +1,76 @@ +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 + c = Class.new { class_variable_set :@@class_var, "test" } + c.send(:class_variable_get, :@@class_var).should == "test" + c.send(:class_variable_get, "@@class_var").should == "test" + end + + it "returns the value of a class variable with the given name defined in an included module" do + c = Class.new { include ModuleSpecs::MVars } + c.send(:class_variable_get, "@@mvar").should == :mvar + end + + it "raises a NameError for a class variable named '@@'" do + c = Class.new + -> { 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 + -> { + c.send(:class_variable_get, "@@mvar") + }.should raise_error(NameError) + end + + it "returns class variables defined in the class body and accessed in the metaclass" do + ModuleSpecs::CVars.cls.should == :class + end + + it "returns class variables defined in the metaclass and accessed by class methods" do + ModuleSpecs::CVars.meta.should == :metainfo + end + + it "returns class variables defined in the metaclass and accessed by instance methods" do + ModuleSpecs::CVars.new.meta.should == :metainfo + end + + it "returns a class variable defined in a metaclass" do + obj = mock("metaclass class variable") + meta = obj.singleton_class + meta.send :class_variable_set, :@@var, :cvar_value + meta.send(:class_variable_get, :@@var).should == :cvar_value + end + + it "raises a NameError when an uninitialized class variable is accessed" do + c = Class.new + [:@@no_class_var, "@@no_class_var"].each do |cvar| + -> { 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 + + -> { 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 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" + end + + 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') + -> { c.send(:class_variable_get, o) }.should raise_error(TypeError) + o.should_receive(:to_str).and_return(123) + -> { 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 new file mode 100644 index 0000000000..63f32f5389 --- /dev/null +++ b/spec/ruby/core/module/class_variable_set_spec.rb @@ -0,0 +1,62 @@ +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 + c = Class.new + + c.send(:class_variable_set, :@@test, "test") + c.send(:class_variable_set, "@@test3", "test3") + + c.send(:class_variable_get, :@@test).should == "test" + c.send(:class_variable_get, :@@test3).should == "test3" + end + + it "sets a class variable on a metaclass" do + obj = mock("metaclass class variable") + meta = obj.singleton_class + meta.send(:class_variable_set, :@@var, :cvar_value).should == :cvar_value + meta.send(:class_variable_get, :@@var).should == :cvar_value + end + + it "sets the value of a class variable with the given name defined in an included module" do + c = Class.new { include ModuleSpecs::MVars.dup } + c.send(:class_variable_set, "@@mvar", :new_mvar).should == :new_mvar + c.send(:class_variable_get, "@@mvar").should == :new_mvar + end + + it "raises a FrozenError when self is frozen" do + -> { + Class.new.freeze.send(:class_variable_set, :@@test, "test") + }.should raise_error(FrozenError) + -> { + Module.new.freeze.send(:class_variable_set, :@@test, "test") + }.should raise_error(FrozenError) + end + + it "raises a NameError when the given name is not allowed" do + c = Class.new + + -> { + c.send(:class_variable_set, :invalid_name, "test") + }.should raise_error(NameError) + -> { + c.send(:class_variable_set, "@invalid_name", "test") + }.should raise_error(NameError) + end + + 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") + c.send(:class_variable_get, :@@class_var).should == "test" + end + + 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') + -> { c.send(:class_variable_set, o, "test") }.should raise_error(TypeError) + o.should_receive(:to_str).and_return(123) + -> { 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 new file mode 100644 index 0000000000..e155f1deac --- /dev/null +++ b/spec/ruby/core/module/class_variables_spec.rb @@ -0,0 +1,34 @@ +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 + ModuleSpecs::ClassVars::A.class_variables.should include(:@@a_cvar) + ModuleSpecs::ClassVars::M.class_variables.should include(:@@m_cvar) + end + + it "returns an Array of Symbols of class variable names defined in a metaclass" do + obj = mock("metaclass class variable") + meta = obj.singleton_class + meta.send :class_variable_set, :@@var, :cvar_value + meta.class_variables.should == [:@@var] + end + + it "returns an Array with names of class variables defined in metaclasses" do + ModuleSpecs::CVars.class_variables.should include(:@@cls, :@@meta) + end + + it "does not return class variables defined in extended modules" do + c = Class.new + 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 new file mode 100644 index 0000000000..86ee5db22a --- /dev/null +++ b/spec/ruby/core/module/comparison_spec.rb @@ -0,0 +1,36 @@ +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 + (ModuleSpecs::Child <=> ModuleSpecs::Parent).should == -1 + (ModuleSpecs::Child <=> ModuleSpecs::Basic).should == -1 + (ModuleSpecs::Child <=> ModuleSpecs::Super).should == -1 + (ModuleSpecs::Super <=> ModuleSpecs::Basic).should == -1 + end + + it "returns 0 if self is the same as the given module" do + (ModuleSpecs::Child <=> ModuleSpecs::Child).should == 0 + (ModuleSpecs::Parent <=> ModuleSpecs::Parent).should == 0 + (ModuleSpecs::Basic <=> ModuleSpecs::Basic).should == 0 + (ModuleSpecs::Super <=> ModuleSpecs::Super).should == 0 + end + + 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 + (ModuleSpecs::Basic <=> ModuleSpecs::Super).should == +1 + end + + it "returns nil if self and the given module are not related" do + (ModuleSpecs::Parent <=> ModuleSpecs::Basic).should == nil + (ModuleSpecs::Parent <=> ModuleSpecs::Super).should == nil + (ModuleSpecs::Basic <=> ModuleSpecs::Parent).should == nil + (ModuleSpecs::Super <=> ModuleSpecs::Parent).should == nil + end + + it "returns nil if the argument is not a class/module" do + (ModuleSpecs::Parent <=> mock('x')).should == nil + end +end diff --git a/spec/ruby/core/module/const_added_spec.rb b/spec/ruby/core/module/const_added_spec.rb new file mode 100644 index 0000000000..90cd36551a --- /dev/null +++ b/spec/ruby/core/module/const_added_spec.rb @@ -0,0 +1,238 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'fixtures/const_added' + +describe "Module#const_added" do + it "is a private instance method" do + Module.should have_private_instance_method(:const_added) + end + + it "returns nil in the default implementation" do + Module.new do + const_added(:TEST).should == nil + end + end + + it "for a class defined with the `class` keyword, const_added runs before inherited" do + ScratchPad.record [] + + mod = Module.new do + def self.const_added(_) + ScratchPad << :const_added + end + end + + parent = Class.new do + def self.inherited(_) + ScratchPad << :inherited + end + end + + class mod::C < parent; end + + ScratchPad.recorded.should == [:const_added, :inherited] + end + + it "the superclass of a class assigned to a constant is set before const_added is called" do + ScratchPad.record [] + + parent = Class.new do + def self.const_added(name) + ScratchPad << name + ScratchPad << const_get(name).superclass + end + end + + class parent::C < parent; end + + ScratchPad.recorded.should == [:C, parent] + end + + it "is called when a new constant is assigned on self" do + ScratchPad.record [] + + mod = Module.new do + def self.const_added(name) + ScratchPad << name + end + end + + mod.module_eval(<<-RUBY, __FILE__, __LINE__ + 1) + TEST = 1 + RUBY + + ScratchPad.recorded.should == [:TEST] + end + + it "is called when a new constant is assigned on self through const_set" do + ScratchPad.record [] + + mod = Module.new do + def self.const_added(name) + ScratchPad << name + end + end + + mod.const_set(:TEST, 1) + + ScratchPad.recorded.should == [:TEST] + end + + it "is called when a new module is defined under self" do + ScratchPad.record [] + + mod = Module.new do + def self.const_added(name) + ScratchPad << name + end + end + + mod.module_eval(<<-RUBY, __FILE__, __LINE__ + 1) + module SubModule + end + + module SubModule + end + RUBY + + ScratchPad.recorded.should == [:SubModule] + end + + it "is called when a new module is defined under a named module (assigned to a constant)" do + ScratchPad.record [] + + ModuleSpecs::ConstAddedSpecs::NamedModule = Module.new do + def self.const_added(name) + ScratchPad << name + end + + module self::A + def self.const_added(name) + ScratchPad << name + end + + module self::B + end + end + end + + ScratchPad.recorded.should == [:A, :B] + ModuleSpecs::ConstAddedSpecs.send :remove_const, :NamedModule + end + + it "is called when a new class is defined under self" do + ScratchPad.record [] + + mod = Module.new do + def self.const_added(name) + ScratchPad << name + end + end + + mod.module_eval(<<-RUBY, __FILE__, __LINE__ + 1) + class SubClass + end + + class SubClass + end + RUBY + + ScratchPad.recorded.should == [:SubClass] + end + + it "is called when a new class is defined under a named module (assigned to a constant)" do + ScratchPad.record [] + + ModuleSpecs::ConstAddedSpecs::NamedModuleB = Module.new do + def self.const_added(name) + ScratchPad << name + end + + class self::A + def self.const_added(name) + ScratchPad << name + end + + class self::B + end + end + end + + ScratchPad.recorded.should == [:A, :B] + ModuleSpecs::ConstAddedSpecs.send :remove_const, :NamedModuleB + end + + it "is called when an autoload is defined" do + ScratchPad.record [] + + mod = Module.new do + def self.const_added(name) + ScratchPad << name + end + end + + mod.autoload :Autoload, "foo" + ScratchPad.recorded.should == [:Autoload] + end + + it "is called with a precise caller location with the line of definition" do + ScratchPad.record [] + + mod = Module.new do + def self.const_added(name) + location = caller_locations(1, 1)[0] + ScratchPad << location.lineno + end + end + + line = __LINE__ + mod.module_eval(<<-RUBY, __FILE__, __LINE__ + 1) + TEST = 1 + + module SubModule + end + + class SubClass + end + RUBY + + mod.const_set(:CONST_SET, 1) + + ScratchPad.recorded.should == [line + 2, line + 4, line + 7, line + 11] + end + + it "is called when the constant is already assigned a value" do + ScratchPad.record [] + + mod = Module.new do + def self.const_added(name) + ScratchPad.record const_get(name) + end + end + + mod.module_eval(<<-RUBY, __FILE__, __LINE__ + 1) + TEST = 123 + RUBY + + ScratchPad.recorded.should == 123 + end + + it "records re-definition of existing constants" do + ScratchPad.record [] + + mod = Module.new do + def self.const_added(name) + ScratchPad << const_get(name) + end + end + + -> { + mod.module_eval(<<-RUBY, __FILE__, __LINE__ + 1) + TEST = 123 + TEST = 456 + RUBY + }.should complain(/warning: already initialized constant .+::TEST/) + + ScratchPad.recorded.should == [123, 456] + end +end diff --git a/spec/ruby/core/module/const_defined_spec.rb b/spec/ruby/core/module/const_defined_spec.rb new file mode 100644 index 0000000000..8b137cd134 --- /dev/null +++ b/spec/ruby/core/module/const_defined_spec.rb @@ -0,0 +1,169 @@ +# encoding: utf-8 + +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 + ConstantSpecs.const_defined?(:CS_CONST2).should == true + ConstantSpecs.const_defined?(:ModuleA).should == true + ConstantSpecs.const_defined?(:ClassA).should == true + ConstantSpecs::ContainerA.const_defined?(:ChildA).should == true + end + + it "returns true if the constant is defined in the receiver's superclass" do + # CS_CONST4 is defined in the superclass of ChildA + 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'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 + end + + it "returns true if the constant is defined in Object and the receiver is a class that has Object among its ancestors" do + # CS_CONST1 is defined in Object + ConstantSpecs::ContainerA::ChildA.const_defined?(:CS_CONST1).should be_true + end + + it "returns false if the constant is defined in the receiver's superclass and the inherit flag is false" do + ConstantSpecs::ContainerA::ChildA.const_defined?(:CS_CONST4, false).should be_false + end + + it "returns true if the constant is defined in the receiver's superclass and the inherit flag is true" 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 + ConstantSpecs.const_defined?("ClassA").should == true + ConstantSpecs::ContainerA.const_defined?("ChildA").should == true + end + + it "returns true when passed a constant name with unicode characters" do + ConstantUnicodeSpecs.const_defined?("CS_CONSTλ").should be_true + end + + it "returns true when passed a constant name with EUC-JP characters" 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 + # The following constant isn't defined at all. + ConstantSpecs::ContainerA::ChildA.const_defined?(:CS_CONST4726).should be_false + # DETACHED_CONSTANT is defined in ConstantSpecs::Detached, which isn't + # included by or inherited from ParentA + ConstantSpecs::ParentA.const_defined?(:DETACHED_CONSTANT).should be_false + end + + it "does not call #const_missing if the constant is not defined in the receiver" do + ConstantSpecs::ClassA.should_not_receive(:const_missing) + ConstantSpecs::ClassA.const_defined?(:CS_CONSTX).should == false + end + + 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 + Object.const_defined?(:CS_CONST10).should be_true + end + + it "returns true for toplevel constant when the name begins with '::'" do + ConstantSpecs.const_defined?("::Array").should be_true + end + + it "returns true when passed a scoped constant name" do + ConstantSpecs.const_defined?("ClassC::CS_CONST1").should be_true + end + + it "returns true when passed a scoped constant name for a constant in the inheritance hierarchy and the inherited flag is default" do + ConstantSpecs::ClassD.const_defined?("ClassE::CS_CONST2").should be_true + end + + it "returns true when passed a scoped constant name for a constant in the inheritance hierarchy and the inherited flag is true" do + ConstantSpecs::ClassD.const_defined?("ClassE::CS_CONST2", true).should be_true + end + + it "returns false when passed a scoped constant name for a constant in the inheritance hierarchy and the inherited flag is false" do + ConstantSpecs::ClassD.const_defined?("ClassE::CS_CONST2", false).should be_false + end + + it "returns false when the name begins with '::' and the toplevel constant does not exist" do + ConstantSpecs.const_defined?("::Name").should be_false + end + + it "raises a NameError if the name does not start with a capital letter" do + -> { ConstantSpecs.const_defined? "name" }.should raise_error(NameError) + end + + it "raises a NameError if the name starts with '_'" do + -> { ConstantSpecs.const_defined? "__CONSTX__" }.should raise_error(NameError) + end + + it "raises a NameError if the name starts with '@'" do + -> { ConstantSpecs.const_defined? "@Name" }.should raise_error(NameError) + end + + it "raises a NameError if the name starts with '!'" do + -> { ConstantSpecs.const_defined? "!Name" }.should raise_error(NameError) + end + + it "returns true or false for the nested name" do + ConstantSpecs.const_defined?("NotExist::Name").should == false + ConstantSpecs.const_defined?("::Name").should == false + ConstantSpecs.const_defined?("::Object").should == true + ConstantSpecs.const_defined?("ClassA::CS_CONST10").should == true + ConstantSpecs.const_defined?("ClassA::CS_CONST10_").should == false + end + + it "raises a NameError if the name contains non-alphabetic characters except '_'" do + ConstantSpecs.const_defined?("CS_CONSTX").should == false + -> { 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') + -> { ConstantSpecs.const_defined? name }.should raise_error(TypeError) + + name.should_receive(:to_str).and_return(123) + -> { 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 new file mode 100644 index 0000000000..4b53cbe7b3 --- /dev/null +++ b/spec/ruby/core/module/const_get_spec.rb @@ -0,0 +1,273 @@ +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 + Object.const_get(:CS_CONST1).should == :const1 + Object.const_get("CS_CONST1").should == :const1 + end + + it "raises a NameError if no constant is defined in the search path" do + -> { ConstantSpecs.const_get :CS_CONSTX }.should raise_error(NameError) + end + + it "raises a NameError with the not found constant symbol" do + 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 + -> { ConstantSpecs.const_get "name" }.should raise_error(NameError) + end + + it "raises a NameError if the name starts with a non-alphabetic character" do + -> { 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 + -> { 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 + name = mock("ClassA") + name.should_receive(:to_str).and_return("ClassA") + ConstantSpecs.const_get(name).should == ConstantSpecs::ClassA + end + + it "raises a TypeError if conversion to a String by calling #to_str fails" do + name = mock('123') + -> { ConstantSpecs.const_get(name) }.should raise_error(TypeError) + + name.should_receive(:to_str).and_return(123) + -> { ConstantSpecs.const_get(name) }.should raise_error(TypeError) + end + + it "calls #const_missing on the receiver if unable to locate the constant" do + ConstantSpecs::ContainerA.should_receive(:const_missing).with(:CS_CONSTX) + ConstantSpecs::ContainerA.const_get(:CS_CONSTX) + end + + it "does not search the singleton class of a Class or Module" do + -> do + ConstantSpecs::ContainerA::ChildA.const_get(:CS_CONST14) + end.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 + -> 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 superclass and the inherit flag is false" do + -> do + ConstantSpecs::ContainerA::ChildA.const_get(:CS_CONST4, false) + end.should raise_error(NameError) + end + + it "searches into the receiver superclasses if the inherit flag is true" do + ConstantSpecs::ContainerA::ChildA.const_get(:CS_CONST4, true).should == :const4 + end + + it "raises a NameError when the receiver is a Module, the constant is defined at toplevel and the inherit flag is false" 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 + -> 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 + -> { ConstantSpecs.const_get("::") }.should raise_error(NameError) + end + + it "raises a NameError if a Symbol has a toplevel scope qualifier" do + -> { ConstantSpecs.const_get(:'::CS_CONST1') }.should raise_error(NameError) + end + + it "raises a NameError if a Symbol is a scoped constant name" do + -> { 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 + 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 + it "searches the immediate class or module first" do + ConstantSpecs::ClassA.const_get(:CS_CONST10).should == :const10_10 + ConstantSpecs::ModuleA.const_get(:CS_CONST10).should == :const10_1 + ConstantSpecs::ParentA.const_get(:CS_CONST10).should == :const10_5 + ConstantSpecs::ContainerA.const_get(:CS_CONST10).should == :const10_2 + ConstantSpecs::ContainerA::ChildA.const_get(:CS_CONST10).should == :const10_3 + end + + it "searches a module included in the immediate class before the superclass" do + ConstantSpecs::ContainerA::ChildA.const_get(:CS_CONST15).should == :const15_1 + end + + it "searches the superclass before a module included in the superclass" do + ConstantSpecs::ContainerA::ChildA.const_get(:CS_CONST11).should == :const11_1 + end + + it "searches a module included in the superclass" do + ConstantSpecs::ContainerA::ChildA.const_get(:CS_CONST12).should == :const12_1 + end + + it "searches the superclass chain" do + ConstantSpecs::ContainerA::ChildA.const_get(:CS_CONST13).should == :const13 + end + + it "returns a toplevel constant when the receiver is a Class" do + ConstantSpecs::ContainerA::ChildA.const_get(:CS_CONST1).should == :const1 + end + + it "returns a toplevel constant when the receiver is a Module" do + ConstantSpecs.const_get(:CS_CONST1).should == :const1 + ConstantSpecs::ModuleA.const_get(:CS_CONST1).should == :const1 + end + end + + describe "with dynamically assigned constants" do + it "searches the immediate class or module first" do + ConstantSpecs::ClassA::CS_CONST301 = :const301_1 + ConstantSpecs::ClassA.const_get(:CS_CONST301).should == :const301_1 + + ConstantSpecs::ModuleA::CS_CONST301 = :const301_2 + ConstantSpecs::ModuleA.const_get(:CS_CONST301).should == :const301_2 + + ConstantSpecs::ParentA::CS_CONST301 = :const301_3 + ConstantSpecs::ParentA.const_get(:CS_CONST301).should == :const301_3 + + 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 + + -> { + 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 new file mode 100644 index 0000000000..742218281c --- /dev/null +++ b/spec/ruby/core/module/const_missing_spec.rb @@ -0,0 +1,36 @@ +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 + ConstantSpecs::ClassA::CS_CONSTX.should == :CS_CONSTX + end + + it "is called when an undefined constant is referenced via #const_get" do + ConstantSpecs::ClassA.const_get(:CS_CONSTX).should == :CS_CONSTX + end + + it "raises NameError and includes the name of the value that wasn't found" do + -> { + ConstantSpecs.const_missing("HelloMissing") + }.should raise_error(NameError, /ConstantSpecs::HelloMissing/) + end + + it "raises NameError and does not include toplevel Object" do + begin + Object.const_missing("HelloMissing") + rescue NameError => e + e.message.should_not =~ / Object::/ + 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 new file mode 100644 index 0000000000..823768b882 --- /dev/null +++ b/spec/ruby/core/module/const_set_spec.rb @@ -0,0 +1,145 @@ +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 + ConstantSpecs.const_set :CS_CONST401, :const401 + ConstantSpecs::CS_CONST401.should == :const401 + + 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 "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.end_with? '::B' + end + + it "sets the name of contained modules when assigning a toplevel anonymous module" do + a, b, c, d = Module.new, Module.new, Module.new, Module.new + a::B = b + a::B::C = c + a::B::C::E = c + a::D = d + + Object.const_set :ModuleSpecs_CS3, a + a.name.should == "ModuleSpecs_CS3" + 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 + -> { ConstantSpecs.const_set "name", 1 }.should raise_error(NameError) + end + + it "raises a NameError if the name starts with a non-alphabetic character" do + -> { 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 + -> { 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 + name = mock("CS_CONST405") + 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') + -> { ConstantSpecs.const_set name, 1 }.should raise_error(TypeError) + + name.should_receive(:to_str).and_return(123) + -> { 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 + before :each do + @frozen = Module.new.freeze + @name = :Foo + end + + 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 +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 new file mode 100644 index 0000000000..330da1cc88 --- /dev/null +++ b/spec/ruby/core/module/constants_spec.rb @@ -0,0 +1,97 @@ +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 + count = Module.constants.size + module ConstantSpecsAdded + end + Module.constants.size.should == count + 1 + Object.send(:remove_const, :ConstantSpecsAdded) + end + + it "returns an array of Symbol names" do + # This in NOT an exhaustive list + Module.constants.should include(:Array, :Class, :Comparable, :Dir, + :Enumerable, :ENV, :Exception, :FalseClass, + :File, :Float, :Hash, :Integer, :IO, + :Kernel, :Math, :Method, :Module, :NilClass, + :Numeric, :Object, :Range, :Regexp, :String, + :Symbol, :Thread, :Time, :TrueClass) + end + + it "returns Module's constants when given a parameter" do + direct = Module.constants(false) + indirect = Module.constants(true) + module ConstantSpecsIncludedModule + MODULE_CONSTANTS_SPECS_INDIRECT = :foo + end + + class Module + MODULE_CONSTANTS_SPECS_DIRECT = :bar + include ConstantSpecsIncludedModule + end + (Module.constants(false) - direct).should == [:MODULE_CONSTANTS_SPECS_DIRECT] + (Module.constants(true) - indirect).sort.should == [:MODULE_CONSTANTS_SPECS_DIRECT, :MODULE_CONSTANTS_SPECS_INDIRECT] + + Module.send(:remove_const, :MODULE_CONSTANTS_SPECS_DIRECT) + ConstantSpecsIncludedModule.send(:remove_const, :MODULE_CONSTANTS_SPECS_INDIRECT) + end +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_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_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_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_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_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 + before :each do + ConstantSpecs::ModuleM::CS_CONST251 = :const251 + end + + after :each do + ConstantSpecs::ModuleM.send(:remove_const, :CS_CONST251) + end + + it "includes names of constants defined after a module is included" do + ConstantSpecs::ContainerA.constants.should include(:CS_CONST251) + end +end diff --git a/spec/ruby/core/module/define_method_spec.rb b/spec/ruby/core/module/define_method_spec.rb new file mode 100644 index 0000000000..c5dfc53764 --- /dev/null +++ b/spec/ruby/core/module/define_method_spec.rb @@ -0,0 +1,846 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +class DefineMethodSpecClass +end + +describe "passed { |a, b = 1| } creates a method that" do + before :each do + @klass = Class.new do + define_method(:m) { |a, b = 1| return a, b } + end + end + + it "raises an ArgumentError when passed zero arguments" do + -> { @klass.new.m }.should raise_error(ArgumentError) + end + + it "has a default value for b when passed one argument" do + @klass.new.m(1).should == [1, 1] + end + + it "overrides the default argument when passed two arguments" do + @klass.new.m(1, 2).should == [1, 2] + end + + it "raises an ArgumentError when passed three arguments" do + -> { @klass.new.m(1, 2, 3) }.should raise_error(ArgumentError) + end +end + +describe "Module#define_method when given an UnboundMethod" do + it "passes the given arguments to the new method" do + klass = Class.new do + def test_method(arg1, arg2) + [arg1, arg2] + end + define_method(:another_test_method, instance_method(:test_method)) + end + + klass.new.another_test_method(1, 2).should == [1, 2] + end + + it "adds the new method to the methods list" do + klass = Class.new do + def test_method(arg1, arg2) + [arg1, arg2] + end + define_method(:another_test_method, instance_method(:test_method)) + end + klass.new.should have_method(:another_test_method) + end + + describe "defining a method on a singleton class" do + before do + klass = Class.new + class << klass + def test_method + :foo + end + end + child = Class.new(klass) + sc = class << child; self; end + sc.send :define_method, :another_test_method, klass.method(:test_method).unbind + + @class = child + end + + it "doesn't raise TypeError when calling the method" do + @class.another_test_method.should == :foo + end + end + + it "sets the new method's visibility to the current frame's visibility" do + foo = Class.new do + def ziggy + 'piggy' + end + private :ziggy + + # make sure frame visibility is public + public + + define_method :piggy, instance_method(:ziggy) + end + + -> { 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 + it "sets the visibility of the method to the current visibility" do + klass = Class.new do + define_method(:bar, ModuleSpecs::EmptyFooMethod) + private + define_method(:baz, ModuleSpecs::EmptyFooMethod) + end + + klass.should have_public_instance_method(:bar) + klass.should have_private_instance_method(:baz) + end + end + + describe "and called from another module" do + it "sets the visibility of the method to public" do + klass = Class.new + Class.new do + klass.send(:define_method, :bar, ModuleSpecs::EmptyFooMethod) + private + klass.send(:define_method, :baz, ModuleSpecs::EmptyFooMethod) + end + + klass.should have_public_instance_method(:bar) + 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 + describe "and called from the target module" do + it "sets the visibility of the method to the current visibility" do + klass = Class.new do + define_method(:bar) {} + private + define_method(:baz) {} + end + + klass.should have_public_instance_method(:bar) + klass.should have_private_instance_method(:baz) + end + end + + describe "and called from another module" do + it "sets the visibility of the method to public" do + klass = Class.new + Class.new do + klass.send(:define_method, :bar) {} + private + klass.send(:define_method, :baz) {} + end + + klass.should have_public_instance_method(:bar) + klass.should have_public_instance_method(:baz) + end + end + end +end + +describe "Module#define_method when name is :initialize" do + describe "passed a block" do + it "sets visibility to private when method name is :initialize" do + klass = Class.new do + define_method(:initialize) { } + end + klass.should have_private_instance_method(:initialize) + end + end + + describe "given an UnboundMethod" do + it "sets the visibility to private when method is named :initialize" do + klass = Class.new do + def test_method + end + define_method(:initialize, instance_method(:test_method)) + end + klass.should have_private_instance_method(:initialize) + end + end +end + +describe "Module#define_method" do + it "defines the given method as an instance method with the given name in self" do + class DefineMethodSpecClass + def test1 + "test" + end + define_method(:another_test, instance_method(:test1)) + end + + o = DefineMethodSpecClass.new + o.test1.should == o.another_test + end + + it "calls #method_added after the method is added to the Module" do + DefineMethodSpecClass.should_receive(:method_added).with(:test_ma) + + class DefineMethodSpecClass + define_method(:test_ma) { true } + end + end + + 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, &-> { self }) + end + + o = DefineMethodSpecClass.new + o.block_test1.should == o + o.block_test2.should == o + end + + it "raises TypeError if name cannot converted to String" do + -> { + Class.new { define_method(1001, -> {}) } + }.should raise_error(TypeError, /is not a symbol nor a string/) + + -> { + Class.new { define_method([], -> {}) } + }.should raise_error(TypeError, /is not a symbol nor a string/) + end + + it "converts non-String name to String with #to_str" do + obj = Object.new + def obj.to_str() "foo" end + + new_class = Class.new { define_method(obj, -> { :called }) } + new_class.new.foo.should == :called + end + + it "raises TypeError when #to_str called on non-String name returns non-String value" do + obj = Object.new + def obj.to_str() [] end + + -> { + Class.new { define_method(obj, -> {}) } + }.should raise_error(TypeError, /can't convert Object to String/) + end + + it "raises a TypeError when the given method is no Method/Proc" do + -> { + Class.new { define_method(:test, "self") } + }.should raise_error(TypeError, "wrong argument type String (expected Proc/Method/UnboundMethod)") + + -> { + Class.new { define_method(:test, 1234) } + }.should raise_error(TypeError, "wrong argument type Integer (expected Proc/Method/UnboundMethod)") + + -> { + Class.new { define_method(:test, nil) } + }.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 + -> { + Class.new { define_method(:test) } + }.should raise_error(ArgumentError) + 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 + 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 + class DefineMethodSpecClass + prc = Proc.new { || true } + define_method("proc_style_test", &prc) + end + + obj = DefineMethodSpecClass.new + -> { obj.proc_style_test :arg }.should raise_error(ArgumentError) + end + + it "raises a FrozenError if frozen" do + -> { + Class.new { freeze; define_method(:foo) {} } + }.should raise_error(FrozenError) + end + + it "accepts a Method (still bound)" do + class DefineMethodSpecClass + attr_accessor :data + def inspect_data + "data is #{@data}" + end + end + o = DefineMethodSpecClass.new + o.data = :foo + m = o.method(:inspect_data) + m.should be_an_instance_of(Method) + klass = Class.new(DefineMethodSpecClass) + klass.send(:define_method,:other_inspect, m) + c = klass.new + c.data = :bar + c.other_inspect.should == "data is bar" + ->{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 + c = Class.new do + class << self + def foo + end + end + end + m = c.method(:foo) + + -> { + Class.new { define_method :bar, m } + }.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 + c = Class.new do + def foo + end + end + m = c.new.method(:foo) + + -> { + Class.new { define_method :bar, m } + }.should raise_error(TypeError) + end + + it "accepts an UnboundMethod from an attr_accessor method" do + class DefineMethodSpecClass + attr_accessor :accessor_method + end + + m = DefineMethodSpecClass.instance_method(:accessor_method) + o = DefineMethodSpecClass.new + + DefineMethodSpecClass.send(:undef_method, :accessor_method) + -> { o.accessor_method }.should raise_error(NoMethodError) + + DefineMethodSpecClass.send(:define_method, :accessor_method, m) + + o.accessor_method = :abc + o.accessor_method.should == :abc + end + + it "accepts a proc from a method" do + class ProcFromMethod + attr_accessor :data + def cool_method + "data is #{@data}" + end + end + + object1 = ProcFromMethod.new + object1.data = :foo + + method_proc = object1.method(:cool_method).to_proc + klass = Class.new(ProcFromMethod) + klass.send(:define_method, :other_cool_method, &method_proc) + + object2 = klass.new + object2.data = :bar + 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 + method_proc = proc { in_scope } + + define_method(:proc_test, &method_proc) + end + + o = DefineMethodByProcClass.new + o.proc_test.should be_true + end + + it "accepts a String method name" do + klass = Class.new do + define_method("string_test") do + "string_test result" + end + end + + klass.new.string_test.should == "string_test result" + end + + it "is a public method" do + Module.should have_public_instance_method(:define_method) + end + + it "returns its symbol" do + class DefineMethodSpecClass + method = define_method("return_test") { true } + method.should == :return_test + end + end + + it "allows an UnboundMethod from a module to be defined on a class" do + klass = Class.new { + define_method :bar, ModuleSpecs::UnboundMethodTest.instance_method(:foo) + } + klass.new.should respond_to(:bar) + end + + it "allows an UnboundMethod from a parent class to be defined on a child class" do + parent = Class.new { define_method(:foo) { :bar } } + child = Class.new(parent) { + define_method :baz, parent.instance_method(:foo) + } + child.new.should respond_to(:baz) + end + + it "allows an UnboundMethod from a module to be defined on another unrelated module" do + mod = Module.new { + define_method :bar, ModuleSpecs::UnboundMethodTest.instance_method(:foo) + } + klass = Class.new { include mod } + 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 + -> { + 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, /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 + -> { + DestinationClass = Class.new { + define_method :bar, ModuleSpecs::InstanceMeth.instance_method(:foo) + } + }.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 + +describe "Module#define_method" do + describe "passed { } creates a method that" do + before :each do + @klass = Class.new do + define_method(:m) { :called } + end + end + + it "returns the value computed by the block when passed zero arguments" do + @klass.new.m().should == :called + end + + it "raises an ArgumentError when passed one argument" do + -> { @klass.new.m 1 }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError when passed two arguments" do + -> { @klass.new.m 1, 2 }.should raise_error(ArgumentError) + end + end + + describe "passed { || } creates a method that" do + before :each do + @klass = Class.new do + define_method(:m) { || :called } + end + end + + it "returns the value computed by the block when passed zero arguments" do + @klass.new.m().should == :called + end + + it "raises an ArgumentError when passed one argument" do + -> { @klass.new.m 1 }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError when passed two arguments" do + -> { @klass.new.m 1, 2 }.should raise_error(ArgumentError) + 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 + 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 + before :each do + @klass = Class.new do + define_method(:m) { |*a| a } + end + end + + it "receives an empty array as the argument when passed zero arguments" do + @klass.new.m().should == [] + end + + it "receives the value in an array when passed one argument" do + @klass.new.m(1).should == [1] + end + + it "receives the values in an array when passed two arguments" do + @klass.new.m(1, 2).should == [1, 2] + end + end + + describe "passed { |a, *b| } creates a method that" do + before :each do + @klass = Class.new do + define_method(:m) { |a, *b| return a, b } + end + end + + it "raises an ArgumentError when passed zero arguments" do + -> { @klass.new.m }.should raise_error(ArgumentError) + end + + it "returns the value computed by the block when passed one argument" do + @klass.new.m(1).should == [1, []] + end + + it "returns the value computed by the block when passed two arguments" do + @klass.new.m(1, 2).should == [1, [2]] + end + + it "returns the value computed by the block when passed three arguments" do + @klass.new.m(1, 2, 3).should == [1, [2, 3]] + end + end + + describe "passed { |a, b| } creates a method that" do + before :each do + @klass = Class.new do + define_method(:m) { |a, b| return a, b } + end + end + + it "returns the value computed by the block when passed two arguments" do + @klass.new.m(1, 2).should == [1, 2] + end + + it "raises an ArgumentError when passed zero arguments" do + -> { @klass.new.m }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError when passed one argument" do + -> { @klass.new.m 1 }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError when passed one argument and a block" do + -> { @klass.new.m(1) { } }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError when passed three arguments" do + -> { @klass.new.m 1, 2, 3 }.should raise_error(ArgumentError) + end + end + + describe "passed { |a, b, *c| } creates a method that" do + before :each do + @klass = Class.new do + define_method(:m) { |a, b, *c| return a, b, c } + 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 one argument" do + -> { @klass.new.m 1 }.should raise_error(ArgumentError) + end + + it "raises an ArgumentError when passed one argument and a block" do + -> { @klass.new.m(1) { } }.should raise_error(ArgumentError) + end + + it "receives an empty array as the third argument when passed two arguments" do + @klass.new.m(1, 2).should == [1, 2, []] + end + + it "receives the third argument in an array when passed three arguments" do + @klass.new.m(1, 2, 3).should == [1, 2, [3]] + end + end +end + +describe "Module#define_method when passed a Method object" do + before :each do + @klass = Class.new do + def m(a, b, *c) + :m + end + end + + @obj = @klass.new + m = @obj.method :m + + @klass.class_exec do + define_method :n, m + end + end + + it "defines a method with the same #arity as the original" do + @obj.method(:n).arity.should == @obj.method(:m).arity + end + + it "defines a method with the same #parameters as the original" do + @obj.method(:n).parameters.should == @obj.method(:m).parameters + end +end + +describe "Module#define_method when passed an UnboundMethod object" do + before :each do + @klass = Class.new do + def m(a, b, *c) + :m + end + end + + @obj = @klass.new + m = @klass.instance_method :m + + @klass.class_exec do + define_method :n, m + end + end + + it "defines a method with the same #arity as the original" do + @obj.method(:n).arity.should == @obj.method(:m).arity + end + + it "defines a method with the same #parameters as the original" 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 new file mode 100644 index 0000000000..eb5cb89ed1 --- /dev/null +++ b/spec/ruby/core/module/define_singleton_method_spec.rb @@ -0,0 +1,15 @@ +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 + klass = Module.new do + define_singleton_method :a do + 42 + end + define_singleton_method(:b, -> x { 2*x }) + end + + klass.a.should == 42 + klass.b(10).should == 20 + end +end diff --git a/spec/ruby/core/module/deprecate_constant_spec.rb b/spec/ruby/core/module/deprecate_constant_spec.rb new file mode 100644 index 0000000000..ec0de6782f --- /dev/null +++ b/spec/ruby/core/module/deprecate_constant_spec.rb @@ -0,0 +1,70 @@ +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 + + 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 + + it "does not warn if Warning[:deprecated] is false" do + @module.deprecate_constant :PUBLIC1 + + deprecated = Warning[:deprecated] + begin + Warning[:deprecated] = false + -> { @module::PUBLIC1 }.should_not complain + ensure + Warning[:deprecated] = deprecated + end + end + end + + ruby_bug '#20900', ''...'3.4' do + describe "when removing the deprecated module" do + it "warns with a message" do + @module.deprecate_constant :PUBLIC1 + -> { @module.module_eval {remove_const :PUBLIC1} }.should complain(/warning: constant .+::PUBLIC1 is deprecated/) + end + end + end + + it "accepts multiple symbols and strings as constant names" do + @module.deprecate_constant "PUBLIC1", :PUBLIC2 + + -> { @module::PUBLIC1 }.should complain(/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 "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 new file mode 100644 index 0000000000..76bb271d8d --- /dev/null +++ b/spec/ruby/core/module/eql_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/equal_value' + +describe "Module#eql?" do + 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 new file mode 100644 index 0000000000..01ab06152d --- /dev/null +++ b/spec/ruby/core/module/equal_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/equal_value' + +describe "Module#equal?" do + 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 new file mode 100644 index 0000000000..a7191cd755 --- /dev/null +++ b/spec/ruby/core/module/equal_value_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/equal_value' + +describe "Module#==" do + 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 new file mode 100644 index 0000000000..1fd1abc0b5 --- /dev/null +++ b/spec/ruby/core/module/extend_object_spec.rb @@ -0,0 +1,56 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Module#extend_object" do + before :each do + ScratchPad.clear + end + + it "is a private method" do + Module.should have_private_instance_method(:extend_object) + end + + describe "on Class" do + it "is undefined" do + Class.should_not have_private_instance_method(:extend_object, true) + end + + it "raises a TypeError if calling after rebinded to Class" do + -> { + Module.instance_method(:extend_object).bind(Class.new).call Object.new + }.should raise_error(TypeError) + end + end + + it "is called when #extend is called on an object" do + ModuleSpecs::ExtendObject.should_receive(:extend_object) + obj = mock("extended object") + obj.extend ModuleSpecs::ExtendObject + end + + it "extends the given object with its constants and methods by default" do + obj = mock("extended direct") + ModuleSpecs::ExtendObject.send :extend_object, obj + + obj.test_method.should == "hello test" + obj.singleton_class.const_get(:C).should == :test + end + + it "is called even when private" do + obj = mock("extended private") + obj.extend ModuleSpecs::ExtendObjectPrivate + ScratchPad.recorded.should == :extended + end + + describe "when given a frozen object" do + before :each do + @receiver = Module.new + @object = Object.new.freeze + end + + it "raises a RuntimeError before extending the object" do + -> { @receiver.send(:extend_object, @object) }.should raise_error(RuntimeError) + @object.should_not be_kind_of(@receiver) + end + end +end diff --git a/spec/ruby/core/module/extended_spec.rb b/spec/ruby/core/module/extended_spec.rb new file mode 100644 index 0000000000..c6300ffa0b --- /dev/null +++ b/spec/ruby/core/module/extended_spec.rb @@ -0,0 +1,44 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Module#extended" do + it "is called when an object gets extended with self" do + begin + m = Module.new do + def self.extended(o) + $extended_object = o + end + end + + (o = mock('x')).extend(m) + + $extended_object.should == o + ensure + $extended_object = nil + end + end + + it "is called after Module#extend_object" do + begin + m = Module.new do + def self.extend_object(o) + $extended_object = nil + end + + def self.extended(o) + $extended_object = o + end + end + + (o = mock('x')).extend(m) + + $extended_object.should == o + ensure + $extended_object = nil + end + end + + it "is private in its default implementation" do + Module.new.private_methods.should include(:extended) + end +end diff --git a/spec/ruby/core/module/fixtures/autoload.rb b/spec/ruby/core/module/fixtures/autoload.rb new file mode 100644 index 0000000000..5a77a2d9d4 --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload.rb @@ -0,0 +1 @@ +$m.const_set(:AAA, "test") unless $m.nil? diff --git a/spec/ruby/core/module/fixtures/autoload_abc.rb b/spec/ruby/core/module/fixtures/autoload_abc.rb new file mode 100644 index 0000000000..ffaec91cfe --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_abc.rb @@ -0,0 +1,11 @@ +module ModuleSpecs::Autoload::FromThread + module A + class B + class C + def self.foo + :foo + end + end + end + end +end diff --git a/spec/ruby/core/module/fixtures/autoload_c.rb b/spec/ruby/core/module/fixtures/autoload_c.rb new file mode 100644 index 0000000000..ff2bcc548c --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_c.rb @@ -0,0 +1,11 @@ +module ModuleSpecs::Autoload + class DynClass + class C + def loaded + :dynclass_c + end + end + end +end + +ScratchPad.record :loaded 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_concur.rb b/spec/ruby/core/module/fixtures/autoload_concur.rb new file mode 100644 index 0000000000..0585e36880 --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_concur.rb @@ -0,0 +1,9 @@ +ScratchPad.recorded << :con_pre +Thread.current[:in_autoload_rb] = true +sleep 0.1 + +module ModuleSpecs::Autoload + Concur = 1 +end + +ScratchPad.recorded << :con_post 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_d.rb b/spec/ruby/core/module/fixtures/autoload_d.rb new file mode 100644 index 0000000000..6f5eee741c --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_d.rb @@ -0,0 +1,11 @@ +module ModuleSpecs::Autoload + module DynModule + class D + def loaded + :dynmodule_d + end + end + end +end + +ScratchPad.record :loaded 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_e.rb b/spec/ruby/core/module/fixtures/autoload_e.rb new file mode 100644 index 0000000000..fb78c6cbd4 --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_e.rb @@ -0,0 +1,7 @@ +module ModuleSpecs::Autoload + class E + def loaded + :autoload_e + end + end +end diff --git a/spec/ruby/core/module/fixtures/autoload_empty.rb b/spec/ruby/core/module/fixtures/autoload_empty.rb new file mode 100644 index 0000000000..d7116f3049 --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_empty.rb @@ -0,0 +1 @@ +# This file is left empty on purpose diff --git a/spec/ruby/core/module/fixtures/autoload_ex1.rb b/spec/ruby/core/module/fixtures/autoload_ex1.rb new file mode 100644 index 0000000000..a90092389c --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_ex1.rb @@ -0,0 +1,16 @@ + +class ModuleSpecs::Autoload::EX1 < Exception + def self.trample1 + 1.times { return } + end + + def self.trample2 + begin + raise "hello" + rescue + end + end + + trample1 + trample2 +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_f.rb b/spec/ruby/core/module/fixtures/autoload_f.rb new file mode 100644 index 0000000000..54c2b05b7b --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_f.rb @@ -0,0 +1,7 @@ +module ModuleSpecs::Autoload + module F + def self.loaded + :autoload_f + end + end +end diff --git a/spec/ruby/core/module/fixtures/autoload_g.rb b/spec/ruby/core/module/fixtures/autoload_g.rb new file mode 100644 index 0000000000..a1c4e429d9 --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_g.rb @@ -0,0 +1,7 @@ +module ModuleSpecs::Autoload + class G + def loaded + :autoload_g + end + end +end diff --git a/spec/ruby/core/module/fixtures/autoload_h.rb b/spec/ruby/core/module/fixtures/autoload_h.rb new file mode 100644 index 0000000000..53988c5382 --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_h.rb @@ -0,0 +1,7 @@ +module ModuleSpecs::Autoload + module H + def loaded + :autoload_h + end + end +end diff --git a/spec/ruby/core/module/fixtures/autoload_i.rb b/spec/ruby/core/module/fixtures/autoload_i.rb new file mode 100644 index 0000000000..f7f720516e --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_i.rb @@ -0,0 +1,5 @@ +module ModuleSpecs::Autoload + I = :autoloaded +end + +ScratchPad.record :loaded diff --git a/spec/ruby/core/module/fixtures/autoload_j.rb b/spec/ruby/core/module/fixtures/autoload_j.rb new file mode 100644 index 0000000000..da6d35d43d --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_j.rb @@ -0,0 +1,3 @@ +module ModuleSpecs::Autoload + J = :autoload_j +end diff --git a/spec/ruby/core/module/fixtures/autoload_k.rb b/spec/ruby/core/module/fixtures/autoload_k.rb new file mode 100644 index 0000000000..431602bf80 --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_k.rb @@ -0,0 +1,7 @@ +module ModuleSpecs::Autoload + class KHash < Hash + K = :autoload_k + end +end + +ScratchPad.record :loaded diff --git a/spec/ruby/core/module/fixtures/autoload_lm.rb b/spec/ruby/core/module/fixtures/autoload_lm.rb new file mode 100644 index 0000000000..d93d783cd0 --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_lm.rb @@ -0,0 +1,4 @@ +module ModuleSpecs::Autoload + L = :autoload_l + M = :autoload_m +end diff --git a/spec/ruby/core/module/fixtures/autoload_location.rb b/spec/ruby/core/module/fixtures/autoload_location.rb new file mode 100644 index 0000000000..318851b2df --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_location.rb @@ -0,0 +1,3 @@ +module ConstantSpecs + CONST_LOCATION = __LINE__ +end diff --git a/spec/ruby/core/module/fixtures/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_never_set.rb b/spec/ruby/core/module/fixtures/autoload_never_set.rb new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_never_set.rb @@ -0,0 +1 @@ + diff --git a/spec/ruby/core/module/fixtures/autoload_o.rb b/spec/ruby/core/module/fixtures/autoload_o.rb new file mode 100644 index 0000000000..7d88f969b2 --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_o.rb @@ -0,0 +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_r.rb b/spec/ruby/core/module/fixtures/autoload_r.rb new file mode 100644 index 0000000000..34209d20c3 --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_r.rb @@ -0,0 +1,4 @@ +module ModuleSpecs::Autoload + class R + end +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_s.rb b/spec/ruby/core/module/fixtures/autoload_s.rb new file mode 100644 index 0000000000..f5d412ff18 --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_s.rb @@ -0,0 +1,5 @@ +module ModuleSpecs::Autoload + S = :autoload_s +end + +ScratchPad.record :loaded 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 new file mode 100644 index 0000000000..8027fa3fcd --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_subclass.rb @@ -0,0 +1,11 @@ +class CS_CONST_AUTOLOAD +end + +module ModuleSpecs + module Autoload + module XX + class CS_CONST_AUTOLOAD < CS_CONST_AUTOLOAD + end + end + end +end diff --git a/spec/ruby/core/module/fixtures/autoload_t.rb b/spec/ruby/core/module/fixtures/autoload_t.rb new file mode 100644 index 0000000000..4962c97f4c --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_t.rb @@ -0,0 +1,3 @@ +module ModuleSpecs::Autoload::S + T = :autoload_t +end diff --git a/spec/ruby/core/module/fixtures/autoload_v.rb b/spec/ruby/core/module/fixtures/autoload_v.rb new file mode 100644 index 0000000000..2aa8c44169 --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_v.rb @@ -0,0 +1,7 @@ +module ModuleSpecs::Autoload::U + class V + def self.get_value + :autoload_uvx + end + end +end diff --git a/spec/ruby/core/module/fixtures/autoload_w.rb b/spec/ruby/core/module/fixtures/autoload_w.rb new file mode 100644 index 0000000000..997273812e --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_w.rb @@ -0,0 +1,2 @@ +# Fails to define ModuleSpecs::Autoload::W::Y +ScratchPad.record :loaded diff --git a/spec/ruby/core/module/fixtures/autoload_w2.rb b/spec/ruby/core/module/fixtures/autoload_w2.rb new file mode 100644 index 0000000000..a8dbebf322 --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_w2.rb @@ -0,0 +1 @@ +ScratchPad.record :loaded diff --git a/spec/ruby/core/module/fixtures/autoload_x.rb b/spec/ruby/core/module/fixtures/autoload_x.rb new file mode 100644 index 0000000000..a6c5842609 --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_x.rb @@ -0,0 +1,3 @@ +module ModuleSpecs::Autoload + X = :x +end diff --git a/spec/ruby/core/module/fixtures/autoload_z.rb b/spec/ruby/core/module/fixtures/autoload_z.rb new file mode 100644 index 0000000000..cce1c13f37 --- /dev/null +++ b/spec/ruby/core/module/fixtures/autoload_z.rb @@ -0,0 +1,5 @@ +class ModuleSpecs::Autoload::YY +end + +class ModuleSpecs::Autoload::Z < ModuleSpecs::Autoload::YY +end diff --git a/spec/ruby/core/module/fixtures/classes.rb b/spec/ruby/core/module/fixtures/classes.rb new file mode 100644 index 0000000000..964f64c593 --- /dev/null +++ b/spec/ruby/core/module/fixtures/classes.rb @@ -0,0 +1,653 @@ +module ModuleSpecs + def self.without_test_modules(modules) + 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 + PUBLIC_CONSTANT = 2 + end + + class Subclass < Module + end + + class SubclassSpec + end + + class RemoveClassVariable + end + + module LookupModInMod + INCS = :ethereal + end + + module LookupMod + include LookupModInMod + + MODS = :rockers + end + + class Lookup + include LookupMod + LOOKIE = :lookie + end + + 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 + private_class_method :private_method + + def undefed_method() end + undef_method :undefed_method + + def parent_method; end + def another_parent_method; end + + # For public_class_method spec + private + def self.public_method; end + public_class_method :public_method + + public + def public_parent() end + + protected + def protected_parent() end + + private + def private_parent() end + end + + module Basic + def public_module() end + + protected + def protected_module() end + + private + def private_module() end + end + + module Super + include Basic + + def public_super_module() end + + protected + def protected_super_module() end + + private + def private_super_module() end + + def super_included_method; end + + class SuperChild + end + end + + module Internal + end + + class Child < Parent + include Super + + class << self + include Internal + end + attr_accessor :accessor_method + + def public_child() end + + undef_method :parent_method + undef_method :another_parent_method + + protected + def protected_child() end + + private + def private_child() end + end + + class Grandchild < Child + undef_method :super_included_method + end + + class Child2 < Parent + attr_reader :foo + end + + # Be careful touching the Counts* classes as there used for testing + # private_instance_methods, public_instance_methods, etc. So adding, removing + # a method will break those tests. + module CountsMixin + def public_3; end + public :public_3 + + def private_3; end + private :private_3 + + def protected_3; end + protected :protected_3 + end + + class CountsParent + include CountsMixin + + def public_2; end + + private + def private_2; end + + protected + def protected_2; end + end + + class CountsChild < CountsParent + def public_1; end + + private + def private_1; end + + protected + def protected_1; end + end + + module AddConstant + end + + module A + CONSTANT_A = :a + OVERRIDE = :a + def ma(); :a; end + def self.cma(); :a; end + end + + module B + CONSTANT_B = :b + OVERRIDE = :b + include A + def mb(); :b; end + def self.cmb(); :b; end + end + + class C + OVERRIDE = :c + include B + end + + module Z + MODULE_SPEC_TOPLEVEL_CONSTANT = 1 + end + + module Alias + def report() :report end + alias publish report + end + + class Allonym + include ModuleSpecs::Alias + end + + class Aliasing + def self.make_alias(*a) + alias_method(*a) + end + + def public_one; 1; end + + def public_two(n); n * 2; end + + private + def private_one; 1; end + + protected + def protected_one; 1; end + end + + class AliasingSubclass < Aliasing + end + + module AliasingSuper + + module Parent + def super_call(arg) + arg + end + end + + module Child + include Parent + def super_call(arg) + super(arg) + end + end + + class Target + include Child + alias_method :alias_super_call, :super_call + alias_method :super_call, :alias_super_call + end + + class RedefineAfterAlias + include Parent + + def super_call(arg) + super(arg) + end + + alias_method :alias_super_call, :super_call + + def super_call(arg) + :wrong + end + end + end + + + module ReopeningModule + def foo; true; end + module_function :foo + private :foo + end + + # Yes, we want to re-open the module + module ReopeningModule + alias :foo2 :foo + module_function :foo2 + end + + module Nesting + @tests = {} + def self.[](name); @tests[name]; end + def self.[]=(name, val); @tests[name] = val; end + def self.meta; class << self; self; end; end + + Nesting[:basic] = Module.nesting + + module ::ModuleSpecs + Nesting[:open_first_level] = Module.nesting + end + + class << self + Nesting[:open_meta] = Module.nesting + end + + def self.called_from_module_method + Module.nesting + end + + class NestedClass + Nesting[:nest_class] = Module.nesting + + def self.called_from_class_method + Module.nesting + end + + def called_from_inst_method + Module.nesting + end + end + + end + + Nesting[:first_level] = Module.nesting + + module InstanceMethMod + def bar(); :bar; end + end + + class InstanceMeth + include InstanceMethMod + def foo(); :foo; end + end + + class InstanceMethChild < InstanceMeth + end + + module ClassVars + class A + @@a_cvar = :a_cvar + end + + module M + @@m_cvar = :m_cvar + end + + class B < A + include M + + @@b_cvar = :b_cvar + end + end + + class CVars + @@cls = :class + + # Singleton class lexical scopes are ignored for class variables + class << self + def cls + # This looks in the parent lexical scope, class CVars + @@cls + end + # This actually adds it to the parent lexical scope, class CVars + @@meta = :metainfo + end + + def self.meta + @@meta + end + + def meta + @@meta + end + end + + class SubCVars < CVars + @@sub = :sub + end + + module MVars + @@mvar = :mvar + end + + class SubModule < Module + attr_reader :special + def initialize + @special = 10 + end + end + + module MA; end + module MB + include MA + end + module MC; end + + class MultipleIncludes + include MB + end + + # empty modules + module M1; end + module M2; end + module M3; end + + module Autoload + def self.use_ex1 + begin + begin + raise "test exception" + rescue ModuleSpecs::Autoload::EX1 + end + rescue RuntimeError + return :good + end + end + + class Parent + end + + class Child < Parent + end + + module FromThread + module A + autoload :B, fixture(__FILE__, "autoload_empty.rb") + + class B + autoload :C, fixture(__FILE__, "autoload_abc.rb") + + def self.foo + C.foo + end + end + end + + class D < A::B; end + end + end + + # This class isn't inherited from or included in anywhere. + # It exists to test the constant scoping rules. + class Detached + DETACHED_CONSTANT = :d + end + + class ParentPrivateMethodRedef + private + def private_method_redefined + :before_redefinition + end + end + + class ChildPrivateMethodMadePublic < ParentPrivateMethodRedef + public :private_method_redefined + end + + class ParentPrivateMethodRedef + def private_method_redefined + :after_redefinition + end + end + + module CyclicAppendA + end + + module CyclicAppendB + include CyclicAppendA + end + + module CyclicPrepend + end + + module ExtendObject + C = :test + def test_method + "hello test" + end + end + + module ExtendObjectPrivate + class << self + def extend_object(obj) + ScratchPad.record :extended + end + private :extend_object + end + end + + class CyclicBarrier + def initialize(count = 1) + @count = count + @state = 0 + @mutex = Mutex.new + @cond = ConditionVariable.new + end + + def await + @mutex.synchronize do + @state += 1 + if @state >= @count + @state = 0 + @cond.broadcast + true + else + @cond.wait @mutex + false + end + end + end + + def enabled? + @mutex.synchronize { @count != -1 } + end + + def disable! + @mutex.synchronize do + @count = -1 + @cond.broadcast + end + end + end + + class ThreadSafeCounter + def initialize(value = 0) + @value = 0 + @mutex = Mutex.new + end + + def get + @mutex.synchronize { @value } + end + + def increment_and_get + @mutex.synchronize do + prev_value = @value + @value += 1 + prev_value + end + end + end + + module ShadowingOuter + module M + SHADOW = 123 + end + + module N + SHADOW = 456 + end + end + + module UnboundMethodTest + def foo + 'bar' + end + end + + module ClassEvalTest + def self.get_constant_from_scope + module_eval("Lookup") + end + + def self.get_constant_from_scope_with_send(method) + send(method, "Lookup") + end + end + + class RecordIncludedModules + def self.inherited(base) + ScratchPad.record base + end + end + + module SingletonOnModuleCase + module Foo + class << Foo + def included(base) + base.included_called + super + end + end + end + + class Bar + @included_called = false + + class << self + def included_called + @included_called = true + end + + def included_called? + @included_called + end + end + end + end + + module CaseCompareOnSingleton + def self.===(*) + raise 'method contents are irrelevant to test' + end + end + + m = Module.new do + def foo + end + 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 + def module_specs_public_method_on_object; end + + def module_specs_private_method_on_object; end + private :module_specs_private_method_on_object + + def module_specs_protected_method_on_object; end + protected :module_specs_private_method_on_object + + def module_specs_private_method_on_object_for_kernel_public; end + private :module_specs_private_method_on_object_for_kernel_public + + def module_specs_public_method_on_object_for_kernel_protected; end + def module_specs_public_method_on_object_for_kernel_private; end +end + +module Kernel + def module_specs_public_method_on_kernel; end + + alias_method :module_specs_alias_on_kernel, :module_specs_public_method_on_object + + public :module_specs_private_method_on_object_for_kernel_public + protected :module_specs_public_method_on_object_for_kernel_protected + private :module_specs_public_method_on_object_for_kernel_private +end + +ModuleSpecs::Nesting[:root_level] = Module.nesting 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/constant_unicode.rb b/spec/ruby/core/module/fixtures/constant_unicode.rb new file mode 100644 index 0000000000..415911576d --- /dev/null +++ b/spec/ruby/core/module/fixtures/constant_unicode.rb @@ -0,0 +1,5 @@ +# encoding: utf-8 + +module ConstantUnicodeSpecs + CS_CONSTλ = :const_unicode +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 new file mode 100644 index 0000000000..34543ca2b4 --- /dev/null +++ b/spec/ruby/core/module/fixtures/module.rb @@ -0,0 +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 new file mode 100644 index 0000000000..25c74d3944 --- /dev/null +++ b/spec/ruby/core/module/fixtures/name.rb @@ -0,0 +1,13 @@ +# -*- encoding: utf-8 -*- +module ModuleSpecs + class NameEncoding + class Cß + end + def name + Cß.name + end + end + + module NameSpecs + end +end diff --git a/spec/ruby/core/module/fixtures/path1/load_path.rb b/spec/ruby/core/module/fixtures/path1/load_path.rb new file mode 100644 index 0000000000..d4c45463dc --- /dev/null +++ b/spec/ruby/core/module/fixtures/path1/load_path.rb @@ -0,0 +1,9 @@ +$LOAD_PATH.unshift(File.expand_path('../../path2', __FILE__)) + +module ModuleSpecs::Autoload + module LoadPath + def self.loaded + :autoload_load_path + end + end +end diff --git a/spec/ruby/core/module/fixtures/path2/load_path.rb b/spec/ruby/core/module/fixtures/path2/load_path.rb new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/spec/ruby/core/module/fixtures/path2/load_path.rb @@ -0,0 +1 @@ + diff --git a/spec/ruby/core/module/fixtures/refine.rb b/spec/ruby/core/module/fixtures/refine.rb new file mode 100644 index 0000000000..e8215aa640 --- /dev/null +++ b/spec/ruby/core/module/fixtures/refine.rb @@ -0,0 +1,25 @@ +module ModuleSpecs + class ClassWithFoo + def foo; "foo" end + end + + class ClassWithSuperFoo + def foo; [:C] end + end + + module PrependedModule + def foo; "foo from prepended module"; end + end + + 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/repeated_concurrent_autoload.rb b/spec/ruby/core/module/fixtures/repeated_concurrent_autoload.rb new file mode 100644 index 0000000000..32b770e6cf --- /dev/null +++ b/spec/ruby/core/module/fixtures/repeated_concurrent_autoload.rb @@ -0,0 +1,8 @@ +prev_value = ScratchPad.recorded.increment_and_get +eval <<-RUBY_EVAL + module Mod#{prev_value} + sleep(0.05) + def self.foo + end + end +RUBY_EVAL 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 new file mode 100644 index 0000000000..fd76141431 --- /dev/null +++ b/spec/ruby/core/module/freeze_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Module#freeze" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/module/gt_spec.rb b/spec/ruby/core/module/gt_spec.rb new file mode 100644 index 0000000000..b8a73c9ff9 --- /dev/null +++ b/spec/ruby/core/module/gt_spec.rb @@ -0,0 +1,36 @@ +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 + (ModuleSpecs::Child > ModuleSpecs::Parent).should be_false + (ModuleSpecs::Child > ModuleSpecs::Basic).should be_false + (ModuleSpecs::Child > ModuleSpecs::Super).should be_false + (ModuleSpecs::Super > ModuleSpecs::Basic).should be_false + end + + it "returns true if self is a superclass of or included by the given module" do + (ModuleSpecs::Parent > ModuleSpecs::Child).should == true + (ModuleSpecs::Basic > ModuleSpecs::Child).should == true + (ModuleSpecs::Super > ModuleSpecs::Child).should == true + (ModuleSpecs::Basic > ModuleSpecs::Super).should == true + end + + it "returns false if self is the same as the given module" do + (ModuleSpecs::Child > ModuleSpecs::Child).should == false + (ModuleSpecs::Parent > ModuleSpecs::Parent).should == false + (ModuleSpecs::Basic > ModuleSpecs::Basic).should == false + (ModuleSpecs::Super > ModuleSpecs::Super).should == false + end + + it "returns nil if self is not related to the given module" do + (ModuleSpecs::Parent > ModuleSpecs::Basic).should == nil + (ModuleSpecs::Parent > ModuleSpecs::Super).should == nil + (ModuleSpecs::Basic > ModuleSpecs::Parent).should == nil + (ModuleSpecs::Super > ModuleSpecs::Parent).should == nil + end + + it "raises a TypeError if the argument is not a class/module" do + -> { 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 new file mode 100644 index 0000000000..18c60ba586 --- /dev/null +++ b/spec/ruby/core/module/gte_spec.rb @@ -0,0 +1,33 @@ +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 + (ModuleSpecs::Parent >= ModuleSpecs::Child).should == true + (ModuleSpecs::Basic >= ModuleSpecs::Child).should == true + (ModuleSpecs::Super >= ModuleSpecs::Child).should == true + (ModuleSpecs::Basic >= ModuleSpecs::Super).should == true + (ModuleSpecs::Child >= ModuleSpecs::Child).should == true + (ModuleSpecs::Parent >= ModuleSpecs::Parent).should == true + (ModuleSpecs::Basic >= ModuleSpecs::Basic).should == true + (ModuleSpecs::Super >= ModuleSpecs::Super).should == true + end + + it "returns nil if self is not related to the given module" do + (ModuleSpecs::Parent >= ModuleSpecs::Basic).should == nil + (ModuleSpecs::Parent >= ModuleSpecs::Super).should == nil + (ModuleSpecs::Basic >= ModuleSpecs::Parent).should == nil + (ModuleSpecs::Super >= ModuleSpecs::Parent).should == nil + end + + it "returns false if self is a subclass of or includes the given module" do + (ModuleSpecs::Child >= ModuleSpecs::Parent).should == false + (ModuleSpecs::Child >= ModuleSpecs::Basic).should == false + (ModuleSpecs::Child >= ModuleSpecs::Super).should == false + (ModuleSpecs::Super >= ModuleSpecs::Basic).should == false + end + + it "raises a TypeError if the argument is not a class/module" do + -> { 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 new file mode 100644 index 0000000000..210918b2e7 --- /dev/null +++ b/spec/ruby/core/module/include_spec.rb @@ -0,0 +1,628 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Module#include" do + it "is a public method" do + Module.should have_public_instance_method(:include, false) + end + + it "calls #append_features(self) in reversed order on each module" do + $appended_modules = [] + + m = Module.new do + def self.append_features(mod) + $appended_modules << [ self, mod ] + end + end + + m2 = Module.new do + def self.append_features(mod) + $appended_modules << [ self, mod ] + end + end + + m3 = Module.new do + def self.append_features(mod) + $appended_modules << [ self, mod ] + end + end + + c = Class.new { include(m, m2, m3) } + + $appended_modules.should == [ [ m3, c], [ m2, c ], [ m, c ] ] + end + + it "adds all ancestor modules when a previously included module is included again" do + ModuleSpecs::MultipleIncludes.ancestors.should include(ModuleSpecs::MA, ModuleSpecs::MB) + ModuleSpecs::MB.include(ModuleSpecs::MC) + ModuleSpecs::MultipleIncludes.include(ModuleSpecs::MB) + ModuleSpecs::MultipleIncludes.ancestors.should include(ModuleSpecs::MA, ModuleSpecs::MB, ModuleSpecs::MC) + end + + it "raises a TypeError when the argument is not a Module" do + -> { 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 + 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 + ModuleSpecs::A.constants.should include(:CONSTANT_A) + ModuleSpecs::B.constants.should include(:CONSTANT_A, :CONSTANT_B) + ModuleSpecs::C.constants.should include(:CONSTANT_A, :CONSTANT_B) + end + + it "shadows constants from ancestors" do + klass = Class.new + klass.include ModuleSpecs::ShadowingOuter::M + klass::SHADOW.should == 123 + klass.include ModuleSpecs::ShadowingOuter::N + klass::SHADOW.should == 456 + end + + it "does not override existing constants in modules and classes" do + ModuleSpecs::A::OVERRIDE.should == :a + ModuleSpecs::B::OVERRIDE.should == :b + ModuleSpecs::C::OVERRIDE.should == :c + end + + it "imports instance methods to modules and classes" do + ModuleSpecs::A.instance_methods.should include(:ma) + ModuleSpecs::B.instance_methods.should include(:ma,:mb) + ModuleSpecs::C.instance_methods.should include(:ma,:mb) + end + + it "does not import methods to modules and classes" do + ModuleSpecs::A.methods.include?(:cma).should == true + ModuleSpecs::B.methods.include?(:cma).should == false + ModuleSpecs::B.methods.include?(:cmb).should == true + ModuleSpecs::C.methods.include?(:cma).should == false + ModuleSpecs::C.methods.include?(:cmb).should == false + end + + it "attaches the module as the caller's immediate ancestor" do + module IncludeSpecsTop + def value; 5; end + end + + module IncludeSpecsMiddle + include IncludeSpecsTop + def value; 6; end + end + + class IncludeSpecsClass + include IncludeSpecsMiddle + end + + IncludeSpecsClass.new.value.should == 6 + end + + it "doesn't include module if it is included in a super class" do + module ModuleSpecs::M1 + module M; end + class A; include M; end + class B < A; include M; end + + all = [A, B, M] + + (B.ancestors.filter { |a| all.include?(a) }).should == [B, A, M] + end + end + + it "recursively includes new mixins" do + module ModuleSpecs::M1 + module U; end + module V; end + module W; end + module X; end + module Y; end + class A; include X; end; + class B < A; include U, V, W; end; + + # update V + module V; include X, U, Y; end + + # This code used to use Array#& and then compare 2 arrays, but + # the ordering from Array#& is undefined, as it uses Hash internally. + # + # Loop is more verbose, but more explicit in what we're testing. + + anc = B.ancestors + [B, U, V, W, A, X].each do |i| + anc.include?(i).should be_true + end + + class B; include V; end + + # the only new module is Y, it is added after U since it follows U in V mixin list: + anc = B.ancestors + [B, U, Y, V, W, A, X].each do |i| + anc.include?(i).should be_true + end + end + end + + it "preserves ancestor order" do + module ModuleSpecs::M2 + module M1; end + module M2; end + module M3; include M2; end + + module M2; include M1; end + module M3; include M2; end + + M3.ancestors.should == [M3, M2, M1] + + end + end + + it "detects cyclic includes" do + -> { + module ModuleSpecs::M + include ModuleSpecs::M + 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 + m = Module.new + r = nil + c = Class.new { r = include m } + r.should == c + end + + it "ignores modules it has already included via module mutual inclusion" do + module ModuleSpecs::AlreadyInc + module M0 + end + + module M + include M0 + end + + class K + include M + include M + end + + K.ancestors[0].should == K + K.ancestors[1].should == M + K.ancestors[2].should == M0 + end + end + + it "clears any caches" do + module ModuleSpecs::M3 + module M1 + def foo + :m1 + end + end + + module M2 + def foo + :m2 + end + end + + class C + include M1 + + def get + foo + end + end + + c = C.new + c.get.should == :m1 + + class C + include M2 + end + + c.get.should == :m2 + + 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 + it "returns true if the given module is included by self or one of it's ancestors" do + ModuleSpecs::Super.include?(ModuleSpecs::Basic).should == true + ModuleSpecs::Child.include?(ModuleSpecs::Basic).should == true + ModuleSpecs::Child.include?(ModuleSpecs::Super).should == true + ModuleSpecs::Child.include?(Kernel).should == true + + ModuleSpecs::Parent.include?(ModuleSpecs::Basic).should == false + ModuleSpecs::Basic.include?(ModuleSpecs::Super).should == false + end + + it "returns false if given module is equal to self" do + ModuleSpecs.include?(ModuleSpecs).should == false + end + + it "raises a TypeError when no module was given" do + -> { 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 new file mode 100644 index 0000000000..ce94ed1285 --- /dev/null +++ b/spec/ruby/core/module/included_modules_spec.rb @@ -0,0 +1,14 @@ +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 new file mode 100644 index 0000000000..2fdd4325f2 --- /dev/null +++ b/spec/ruby/core/module/included_spec.rb @@ -0,0 +1,44 @@ +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 + begin + m = Module.new do + def self.included(o) + $included_by = o + end + end + + c = Class.new { include m } + + $included_by.should == c + ensure + $included_by = nil + end + end + + it "allows extending self with the object into which it is being included" do + m = Module.new do + def self.included(o) + o.extend(self) + end + + def test + :passed + end + end + + c = Class.new{ include(m) } + c.test.should == :passed + end + + it "is private in its default implementation" do + Module.should have_private_instance_method(:included) + end + + it "works with super using a singleton class" do + ModuleSpecs::SingletonOnModuleCase::Bar.include ModuleSpecs::SingletonOnModuleCase::Foo + 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 new file mode 100644 index 0000000000..7ae48f85a9 --- /dev/null +++ b/spec/ruby/core/module/initialize_copy_spec.rb @@ -0,0 +1,18 @@ +require_relative '../../spec_helper' + +describe "Module#initialize_copy" do + it "should retain singleton methods when duped" do + mod = Module.new + def mod.hello + 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 new file mode 100644 index 0000000000..99e41e4619 --- /dev/null +++ b/spec/ruby/core/module/initialize_spec.rb @@ -0,0 +1,18 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Module#initialize" do + it "accepts a block" do + m = Module.new do + const_set :A, "A" + end + m.const_get("A").should == "A" + end + + it "is called on subclasses" do + m = ModuleSpecs::SubModule.new + m.special.should == 10 + m.methods.should_not == nil + m.constants.should_not == nil + end +end diff --git a/spec/ruby/core/module/instance_method_spec.rb b/spec/ruby/core/module/instance_method_spec.rb new file mode 100644 index 0000000000..182cdf5c54 --- /dev/null +++ b/spec/ruby/core/module/instance_method_spec.rb @@ -0,0 +1,106 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Module#instance_method" do + before :all do + @parent_um = ModuleSpecs::InstanceMeth.instance_method(:foo) + @child_um = ModuleSpecs::InstanceMethChild.instance_method(:foo) + @mod_um = ModuleSpecs::InstanceMethChild.instance_method(:bar) + end + + it "is a public method" do + Module.should have_public_instance_method(:instance_method, false) + end + + it "requires an argument" do + Module.new.method(:instance_method).arity.should == 1 + end + + it "returns an UnboundMethod corresponding to the given name" do + @parent_um.should be_kind_of(UnboundMethod) + @parent_um.bind(ModuleSpecs::InstanceMeth.new).call.should == :foo + end + + it "returns an UnboundMethod corresponding to the given name from a superclass" do + @child_um.should be_kind_of(UnboundMethod) + @child_um.bind(ModuleSpecs::InstanceMethChild.new).call.should == :foo + end + + it "returns an UnboundMethod corresponding to the given name from an included Module" do + @mod_um.should be_kind_of(UnboundMethod) + @mod_um.bind(ModuleSpecs::InstanceMethChild.new).call.should == :bar + end + + it "returns an UnboundMethod when given a protected method name" do + ModuleSpecs::Basic.instance_method(:protected_module).should be_an_instance_of(UnboundMethod) + end + + it "returns an UnboundMethod when given a private method name" do + ModuleSpecs::Basic.instance_method(:private_module).should be_an_instance_of(UnboundMethod) + end + + it "gives UnboundMethod method name, Module defined in and Module extracted from" do + @parent_um.inspect.should =~ /\bfoo\b/ + @parent_um.inspect.should =~ /\bModuleSpecs::InstanceMeth\b/ + @parent_um.inspect.should =~ /\bModuleSpecs::InstanceMeth\b/ + @child_um.inspect.should =~ /\bfoo\b/ + @child_um.inspect.should =~ /\bModuleSpecs::InstanceMeth\b/ + + @mod_um.inspect.should =~ /\bbar\b/ + @mod_um.inspect.should =~ /\bModuleSpecs::InstanceMethMod\b/ + end + + 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 "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 + child = Class.new(ModuleSpecs::InstanceMeth) + child.send :undef_method, :foo + um = ModuleSpecs::InstanceMeth.instance_method(:foo) + um.should == @parent_um + -> do + child.instance_method(:foo) + end.should raise_error(NameError) + end + + it "raises a NameError if the method does not exist" do + -> { Object.instance_method(:missing) }.should raise_error(NameError) + end + + it "sets the NameError#name attribute to the name of the missing method" do + begin + Object.instance_method(:missing) + rescue NameError => e + e.name.should == :missing + end + end +end diff --git a/spec/ruby/core/module/instance_methods_spec.rb b/spec/ruby/core/module/instance_methods_spec.rb new file mode 100644 index 0000000000..d2d38cdda2 --- /dev/null +++ b/spec/ruby/core/module/instance_methods_spec.rb @@ -0,0 +1,61 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Module#instance_methods" do + it "does not return methods undefined in a superclass" do + methods = ModuleSpecs::Parent.instance_methods(false) + methods.should_not include(:undefed_method) + end + + it "only includes module methods on an included module" do + methods = ModuleSpecs::Basic.instance_methods(false) + methods.should include(:public_module) + # Child is an including class + methods = ModuleSpecs::Child.instance_methods(false) + methods.should include(:public_child) + methods.should_not include(:public_module) + end + + it "does not return methods undefined in a subclass" do + methods = ModuleSpecs::Grandchild.instance_methods + methods.should_not include(:parent_method, :another_parent_method) + end + + it "does not return methods undefined in the current class" do + class ModuleSpecs::Child + def undefed_child + end + end + ModuleSpecs::Child.send(:undef_method, :undefed_child) + methods = ModuleSpecs::Child.instance_methods + methods.should_not include(:undefed_method, :undefed_child) + end + + it "does not return methods from an included module that are undefined in the class" do + ModuleSpecs::Grandchild.instance_methods.should_not include(:super_included_method) + end + + it "returns the public and protected methods of self if include_super is false" do + methods = ModuleSpecs::Parent.instance_methods(false) + methods.should include(:protected_parent, :public_parent) + + methods = ModuleSpecs::Child.instance_methods(false) + methods.should include(:protected_child, :public_child) + end + + it "returns the public and protected methods of self and it's ancestors" do + methods = ModuleSpecs::Basic.instance_methods + methods.should include(:protected_module, :public_module) + + methods = ModuleSpecs::Super.instance_methods + methods.should include(:protected_module, :protected_super_module, + :public_module, :public_super_module) + end + + it "makes a private Object instance method public in Kernel" do + methods = Kernel.instance_methods + methods.should include(:module_specs_private_method_on_object_for_kernel_public) + methods = Object.instance_methods + methods.should_not include(:module_specs_private_method_on_object_for_kernel_public) + end +end diff --git a/spec/ruby/core/module/lt_spec.rb b/spec/ruby/core/module/lt_spec.rb new file mode 100644 index 0000000000..d7771e07a8 --- /dev/null +++ b/spec/ruby/core/module/lt_spec.rb @@ -0,0 +1,36 @@ +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 + (ModuleSpecs::Child < ModuleSpecs::Parent).should == true + (ModuleSpecs::Child < ModuleSpecs::Basic).should == true + (ModuleSpecs::Child < ModuleSpecs::Super).should == true + (ModuleSpecs::Super < ModuleSpecs::Basic).should == true + end + + it "returns false if self is a superclass of or included by the given module" do + (ModuleSpecs::Parent < ModuleSpecs::Child).should be_false + (ModuleSpecs::Basic < ModuleSpecs::Child).should be_false + (ModuleSpecs::Super < ModuleSpecs::Child).should be_false + (ModuleSpecs::Basic < ModuleSpecs::Super).should be_false + end + + it "returns false if self is the same as the given module" do + (ModuleSpecs::Child < ModuleSpecs::Child).should == false + (ModuleSpecs::Parent < ModuleSpecs::Parent).should == false + (ModuleSpecs::Basic < ModuleSpecs::Basic).should == false + (ModuleSpecs::Super < ModuleSpecs::Super).should == false + end + + it "returns nil if self is not related to the given module" do + (ModuleSpecs::Parent < ModuleSpecs::Basic).should == nil + (ModuleSpecs::Parent < ModuleSpecs::Super).should == nil + (ModuleSpecs::Basic < ModuleSpecs::Parent).should == nil + (ModuleSpecs::Super < ModuleSpecs::Parent).should == nil + end + + it "raises a TypeError if the argument is not a class/module" do + -> { 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 new file mode 100644 index 0000000000..7a0e8496b8 --- /dev/null +++ b/spec/ruby/core/module/lte_spec.rb @@ -0,0 +1,33 @@ +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 + (ModuleSpecs::Child <= ModuleSpecs::Parent).should == true + (ModuleSpecs::Child <= ModuleSpecs::Basic).should == true + (ModuleSpecs::Child <= ModuleSpecs::Super).should == true + (ModuleSpecs::Super <= ModuleSpecs::Basic).should == true + (ModuleSpecs::Child <= ModuleSpecs::Child).should == true + (ModuleSpecs::Parent <= ModuleSpecs::Parent).should == true + (ModuleSpecs::Basic <= ModuleSpecs::Basic).should == true + (ModuleSpecs::Super <= ModuleSpecs::Super).should == true + end + + it "returns nil if self is not related to the given module" do + (ModuleSpecs::Parent <= ModuleSpecs::Basic).should == nil + (ModuleSpecs::Parent <= ModuleSpecs::Super).should == nil + (ModuleSpecs::Basic <= ModuleSpecs::Parent).should == nil + (ModuleSpecs::Super <= ModuleSpecs::Parent).should == nil + end + + it "returns false if self is a superclass of or is included by the given module" do + (ModuleSpecs::Parent <= ModuleSpecs::Child).should == false + (ModuleSpecs::Basic <= ModuleSpecs::Child).should == false + (ModuleSpecs::Super <= ModuleSpecs::Child).should == false + (ModuleSpecs::Basic <= ModuleSpecs::Super).should == false + end + + it "raises a TypeError if the argument is not a class/module" do + -> { 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 new file mode 100644 index 0000000000..ec92cddc1e --- /dev/null +++ b/spec/ruby/core/module/method_added_spec.rb @@ -0,0 +1,146 @@ +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 + + it "returns nil in the default implementation" do + Module.new do + method_added(:test).should == nil + end + end + + it "is called when a new instance method is defined in self" do + Module.new do + def self.method_added(name) + ScratchPad << name + end + + def test() end + def test2() end + def test() end + alias_method :aliased_test, :test + alias aliased_test2 test + end + + ScratchPad.recorded.should == [:test, :test2, :test, :aliased_test, :aliased_test2] + end + + it "is not called when a singleton method is added" do + # obj.singleton_method_added is called instead + klass = Class.new + def klass.method_added(name) + ScratchPad << name + end + + obj = klass.new + def obj.new_singleton_method + end + + ScratchPad.recorded.should == [] + end + + it "is not called when a method is undefined in self" do + m = Module.new do + def method_to_undef + end + + def self.method_added(name) + fail("method_added called by undef_method") + end + + undef_method :method_to_undef + 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 new file mode 100644 index 0000000000..bc5b420e11 --- /dev/null +++ b/spec/ruby/core/module/method_defined_spec.rb @@ -0,0 +1,98 @@ +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 + # Defined in Child + ModuleSpecs::Child.method_defined?(:public_child).should == true + ModuleSpecs::Child.method_defined?("private_child").should == false + ModuleSpecs::Child.method_defined?(:accessor_method).should == true + + # Defined in Parent + ModuleSpecs::Child.method_defined?("public_parent").should == true + ModuleSpecs::Child.method_defined?(:private_parent).should == false + + # Defined in Module + ModuleSpecs::Child.method_defined?(:public_module).should == true + ModuleSpecs::Child.method_defined?(:protected_module).should == true + ModuleSpecs::Child.method_defined?(:private_module).should == false + + # Defined in SuperModule + ModuleSpecs::Child.method_defined?(:public_super_module).should == true + ModuleSpecs::Child.method_defined?(:protected_super_module).should == true + ModuleSpecs::Child.method_defined?(:private_super_module).should == false + end + + # unlike alias_method, module_function, public, and friends, + it "does not search Object or Kernel when called on a module" do + m = Module.new + + 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" do + c = Class.new + o = mock('123') + + -> { c.method_defined?(o) }.should raise_error(TypeError) + + o.should_receive(:to_str).and_return(123) + -> { c.method_defined?(o) }.should raise_error(TypeError) + end + + it "converts the given name to a string using to_str" do + c = Class.new { def test(); end } + (o = mock('test')).should_receive(:to_str).and_return("test") + + 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 new file mode 100644 index 0000000000..9b39eb77a6 --- /dev/null +++ b/spec/ruby/core/module/method_removed_spec.rb @@ -0,0 +1,33 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Module#method_removed" do + it "is a private instance method" do + Module.should have_private_instance_method(:method_removed) + end + + it "returns nil in the default implementation" do + Module.new do + method_removed(:test).should == nil + end + end + + it "is called when a method is removed from self" do + begin + Module.new do + def self.method_removed(name) + $method_removed = name + end + + def test + "test" + end + remove_method :test + end + + $method_removed.should == :test + ensure + $method_removed = nil + end + end +end diff --git a/spec/ruby/core/module/method_undefined_spec.rb b/spec/ruby/core/module/method_undefined_spec.rb new file mode 100644 index 0000000000..a94388fe0a --- /dev/null +++ b/spec/ruby/core/module/method_undefined_spec.rb @@ -0,0 +1,33 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Module#method_undefined" do + it "is a private instance method" do + Module.should have_private_instance_method(:method_undefined) + end + + it "returns nil in the default implementation" do + Module.new do + method_undefined(:test).should == nil + end + end + + it "is called when a method is undefined from self" do + begin + Module.new do + def self.method_undefined(name) + $method_undefined = name + end + + def test + "test" + end + undef_method :test + end + + $method_undefined.should == :test + ensure + $method_undefined = nil + end + end +end diff --git a/spec/ruby/core/module/module_eval_spec.rb b/spec/ruby/core/module/module_eval_spec.rb new file mode 100644 index 0000000000..e9e9fda28d --- /dev/null +++ b/spec/ruby/core/module/module_eval_spec.rb @@ -0,0 +1,7 @@ +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 +end diff --git a/spec/ruby/core/module/module_exec_spec.rb b/spec/ruby/core/module/module_exec_spec.rb new file mode 100644 index 0000000000..47cdf7ef52 --- /dev/null +++ b/spec/ruby/core/module/module_exec_spec.rb @@ -0,0 +1,7 @@ +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 +end diff --git a/spec/ruby/core/module/module_function_spec.rb b/spec/ruby/core/module/module_function_spec.rb new file mode 100644 index 0000000000..51f647142e --- /dev/null +++ b/spec/ruby/core/module/module_function_spec.rb @@ -0,0 +1,358 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Module#module_function" do + it "is a private method" do + Module.should have_private_instance_method(:module_function) + end + + describe "on Class" do + it "is undefined" do + Class.should_not have_private_instance_method(:module_function, true) + end + + it "raises a TypeError if calling after rebinded to Class" do + -> { + Module.instance_method(:module_function).bind(Class.new).call + }.should raise_error(TypeError) + + -> { + Module.instance_method(:module_function).bind(Class.new).call :foo + }.should raise_error(TypeError) + end + end +end + +describe "Module#module_function with specific method names" do + it "creates duplicates of the given instance methods on the Module object" do + m = Module.new do + def test() end + def test2() end + def test3() end + + module_function :test, :test2 + end + + m.respond_to?(:test).should == true + m.respond_to?(:test2).should == true + m.respond_to?(:test3).should == false + end + + 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 + end + + it "creates an independent copy of the method, not a redirect" do + module Mixin + def test + "hello" + end + module_function :test + end + + class BaseClass + include Mixin + def call_test + test + end + end + + Mixin.test.should == "hello" + c = BaseClass.new + c.call_test.should == "hello" + + module Mixin + def test + "goodbye" + end + end + + Mixin.test.should == "hello" + c.call_test.should == "goodbye" + end + + it "makes the instance methods private" do + m = Module.new do + def test() "hello" end + module_function :test + end + + (o = mock('x')).extend(m) + o.respond_to?(:test).should == false + m.should have_private_instance_method(:test) + o.send(:test).should == "hello" + -> { o.test }.should raise_error(NoMethodError) + end + + it "makes the new Module methods public" do + m = Module.new do + def test() "hello" end + module_function :test + end + + m.public_methods.map {|me| me.to_s }.include?('test').should == true + end + + it "tries to convert the given names to strings using to_str" do + (o = mock('test')).should_receive(:to_str).any_number_of_times.and_return("test") + (o2 = mock('test2')).should_receive(:to_str).any_number_of_times.and_return("test2") + + m = Module.new do + def test() end + def test2() end + module_function o, o2 + end + + m.respond_to?(:test).should == true + m.respond_to?(:test2).should == true + end + + it "raises a TypeError when the given names can't be converted to string using to_str" do + o = mock('123') + + -> { Module.new { module_function(o) } }.should raise_error(TypeError) + + o.should_receive(:to_str).and_return(123) + -> { Module.new { module_function(o) } }.should raise_error(TypeError) + end + + it "can make accessible private methods" do # JRUBY-4214 + m = Module.new do + module_function :require + end + m.respond_to?(:require).should be_true + end + + it "creates Module methods that super up the singleton class of the module" do + super_m = Module.new do + def foo + "super_m" + end + end + + m = Module.new do + extend super_m + module_function + def foo + ["m", super] + end + end + + 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 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 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 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 + m.respond_to?(:test3).should == false + end + + it "does not stop creating module functions if the body encounters " \ + "public/protected/private WITH arguments" do + 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 + m.respond_to?(:test3).should == true + end + + it "does not affect module_evaled method definitions also if outside the eval itself" do + 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 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 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 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 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 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 new file mode 100644 index 0000000000..d3318e1645 --- /dev/null +++ b/spec/ruby/core/module/name_spec.rb @@ -0,0 +1,205 @@ +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 not nil when assigned to a constant in an anonymous module" do + m = Module.new + m::N = Module.new + 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 =~ /\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 + ModuleSpecs.name.should == "ModuleSpecs" + end + + it "is set when a nested module is opened with the module keyword" do + ModuleSpecs::Anonymous.name.should == "ModuleSpecs::Anonymous" + end + + 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 + m = Module.new + ModuleSpecs::Anonymous::B = m + 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 + it "is set with a conditional assignment to a nested constant" do + eval("ModuleSpecs::Anonymous::F ||= Module.new") + ModuleSpecs::Anonymous::F.name.should == "ModuleSpecs::Anonymous::F" + end + + it "is set with a conditional assignment to a constant" do + module ModuleSpecs::Anonymous + D ||= Module.new + end + ModuleSpecs::Anonymous::D.name.should == "ModuleSpecs::Anonymous::D" + end + + # http://redmine.ruby-lang.org/issues/show/1833 + it "preserves the encoding in which the class was defined" do + require fixture(__FILE__, "name") + ModuleSpecs::NameEncoding.new.name.encoding.should == Encoding::UTF_8 + end + + 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 new file mode 100644 index 0000000000..d0611b3efe --- /dev/null +++ b/spec/ruby/core/module/nesting_spec.rb @@ -0,0 +1,31 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Module::Nesting" do + + it "returns the list of Modules nested at the point of call" do + ModuleSpecs::Nesting[:root_level].should == [] + ModuleSpecs::Nesting[:first_level].should == [ModuleSpecs] + ModuleSpecs::Nesting[:basic].should == [ModuleSpecs::Nesting, ModuleSpecs] + ModuleSpecs::Nesting[:open_first_level].should == + [ModuleSpecs, ModuleSpecs::Nesting, ModuleSpecs] + ModuleSpecs::Nesting[:open_meta].should == + [ModuleSpecs::Nesting.meta, ModuleSpecs::Nesting, ModuleSpecs] + ModuleSpecs::Nesting[:nest_class].should == + [ModuleSpecs::Nesting::NestedClass, ModuleSpecs::Nesting, ModuleSpecs] + end + + it "returns the nesting for module/class declaring the called method" do + ModuleSpecs::Nesting.called_from_module_method.should == + [ModuleSpecs::Nesting, ModuleSpecs] + ModuleSpecs::Nesting::NestedClass.called_from_class_method.should == + [ModuleSpecs::Nesting::NestedClass, ModuleSpecs::Nesting, ModuleSpecs] + ModuleSpecs::Nesting::NestedClass.new.called_from_inst_method.should == + [ModuleSpecs::Nesting::NestedClass, ModuleSpecs::Nesting, ModuleSpecs] + end + +end + +describe "Module.nesting" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/module/new_spec.rb b/spec/ruby/core/module/new_spec.rb new file mode 100644 index 0000000000..ec521360bd --- /dev/null +++ b/spec/ruby/core/module/new_spec.rb @@ -0,0 +1,35 @@ +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| + mod.should_not == nil + self.should == mod + test_mod = mod + mod.is_a?(Module).should == true + Object.new # trying to return something + end + test_mod.should == m + end + + it "evaluates a passed block in the context of the module" do + fred = Module.new do + def hello() "hello" end + def bye() "bye" end + end + + (o = mock('x')).extend(fred) + o.hello.should == "hello" + o.bye.should == "bye" + end +end diff --git a/spec/ruby/core/module/prepend_features_spec.rb b/spec/ruby/core/module/prepend_features_spec.rb new file mode 100644 index 0000000000..09c15c5c15 --- /dev/null +++ b/spec/ruby/core/module/prepend_features_spec.rb @@ -0,0 +1,64 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Module#prepend_features" do + it "is a private method" do + Module.should have_private_instance_method(:prepend_features, true) + end + + it "gets called when self is included in another module/class" do + ScratchPad.record [] + + m = Module.new do + def self.prepend_features(mod) + ScratchPad << mod + end + end + + c = Class.new do + prepend m + end + + ScratchPad.recorded.should == [c] + end + + it "raises an ArgumentError on a cyclic prepend" do + -> { + ModuleSpecs::CyclicPrepend.send(:prepend_features, ModuleSpecs::CyclicPrepend) + }.should raise_error(ArgumentError) + end + + it "clears caches of the given module" do + parent = Class.new do + def bar; :bar; end + end + + child = Class.new(parent) do + def foo; :foo; end + def bar; super; end + end + + mod = Module.new do + def foo; :fooo; end + end + + child.new.foo + child.new.bar + + child.prepend(mod) + + child.new.bar.should == :bar + end + + describe "on Class" do + it "is undefined" do + Class.should_not have_private_instance_method(:prepend_features, true) + end + + it "raises a TypeError if calling after rebinded to Class" do + -> { + Module.instance_method(:prepend_features).bind(Class.new).call Module.new + }.should raise_error(TypeError) + end + end +end diff --git a/spec/ruby/core/module/prepend_spec.rb b/spec/ruby/core/module/prepend_spec.rb new file mode 100644 index 0000000000..71e82c513e --- /dev/null +++ b/spec/ruby/core/module/prepend_spec.rb @@ -0,0 +1,823 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Module#prepend" do + it "is a public method" do + Module.should have_public_instance_method(:prepend, false) + end + + it "does not affect the superclass" do + Class.new { prepend Module.new }.superclass.should == Object + end + + it "calls #prepend_features(self) in reversed order on each module" do + ScratchPad.record [] + + m = Module.new do + def self.prepend_features(mod) + ScratchPad << [ self, mod ] + end + end + + m2 = Module.new do + def self.prepend_features(mod) + ScratchPad << [ self, mod ] + end + end + + m3 = Module.new do + def self.prepend_features(mod) + ScratchPad << [ self, mod ] + end + end + + c = Class.new { prepend(m, m2, m3) } + + ScratchPad.recorded.should == [ [ m3, c], [ m2, c ], [ m, c ] ] + end + + it "updates the method when a module is prepended" do + m_module = Module.new do + def foo + "m" + end + end + a_class = Class.new do + def foo + 'a' + end + end + a = a_class.new + foo = -> { a.foo } + foo.call.should == 'a' + a_class.class_eval do + prepend m_module + end + foo.call.should == 'm' + end + + it "updates the method when a prepended module is updated" do + m_module = Module.new + a_class = Class.new do + prepend m_module + def foo + 'a' + end + end + a = a_class.new + foo = -> { a.foo } + foo.call.should == 'a' + m_module.module_eval do + def foo + "m" + end + end + foo.call.should == 'm' + end + + it "updates the optimized method when a prepended module is updated" do + out = ruby_exe(<<~RUBY) + module M; end + class Integer + prepend M + end + l = -> { 1 + 2 } + p l.call + M.module_eval do + def +(o) + $called = true + super(o) + end + end + p l.call + p $called + RUBY + out.should == "3\n3\ntrue\n" + end + + it "updates the method when there is a base included method and the prepended module overrides it" do + base_module = Module.new do + def foo + 'a' + end + end + a_class = Class.new do + include base_module + end + a = a_class.new + foo = -> { a.foo } + foo.call.should == 'a' + + m_module = Module.new do + def foo + "m" + end + end + a_class.prepend m_module + foo.call.should == 'm' + end + + it "updates the method when there is a base included method and the prepended module is later updated" do + base_module = Module.new do + def foo + 'a' + end + end + a_class = Class.new do + include base_module + end + a = a_class.new + foo = -> { a.foo } + foo.call.should == 'a' + + m_module = Module.new + a_class.prepend m_module + foo.call.should == 'a' + + m_module.module_eval do + def foo + "m" + end + end + foo.call.should == 'm' + end + + it "updates the method when a module prepended after a call is later updated" do + m_module = Module.new + a_class = Class.new do + def foo + 'a' + end + end + a = a_class.new + foo = -> { a.foo } + foo.call.should == 'a' + + a_class.prepend m_module + foo.call.should == 'a' + + m_module.module_eval do + def foo + "m" + end + end + foo.call.should == 'm' + end + + it "updates the method when a module is prepended after another and the method is defined later on that module" do + m_module = Module.new do + def foo + 'a' + end + end + a_class = Class.new + a_class.prepend m_module + a = a_class.new + foo = -> { a.foo } + foo.call.should == 'a' + + n_module = Module.new + a_class.prepend n_module + foo.call.should == 'a' + + n_module.module_eval do + def foo + "n" + end + end + foo.call.should == 'n' + end + + it "updates the method when a module is included in a prepended module and the method is defined later" do + a_class = Class.new + base_module = Module.new do + def foo + 'a' + end + end + a_class.prepend base_module + a = a_class.new + foo = -> { a.foo } + foo.call.should == 'a' + + m_module = Module.new + n_module = Module.new + m_module.include n_module + a_class.prepend m_module + + n_module.module_eval do + def foo + "n" + end + end + foo.call.should == 'n' + end + + it "updates the method when a new module with an included module is prepended" do + a_class = Class.new do + def foo + 'a' + end + end + + n_module = Module.new do + def foo + 'n' + end + end + + m_module = Module.new do + include n_module + end + + a = a_class.new + foo = -> { a.foo } + + foo.call.should == 'a' + + a_class.class_eval do + prepend m_module + end + + foo.call.should == 'n' + end + + it "updates the constant when a module is prepended" do + module ModuleSpecs::ConstUpdatePrepended + module M + FOO = 'm' + end + module A + FOO = 'a' + end + module B + include A + def self.foo + FOO + end + end + + B.foo.should == 'a' + B.prepend M + B.foo.should == 'm' + end + ensure + ModuleSpecs.send(:remove_const, :ConstUpdatePrepended) + end + + it "updates the constant when a prepended module is updated" do + module ModuleSpecs::ConstPrependedUpdated + module M + end + module A + FOO = 'a' + end + module B + include A + prepend M + def self.foo + FOO + end + end + B.foo.should == 'a' + M.const_set(:FOO, 'm') + B.foo.should == 'm' + end + ensure + ModuleSpecs.send(:remove_const, :ConstPrependedUpdated) + end + + it "updates the constant when there is a base included constant and the prepended module overrides it" do + module ModuleSpecs::ConstIncludedPrependedOverride + module Base + FOO = 'a' + end + module A + include Base + def self.foo + FOO + end + end + A.foo.should == 'a' + + module M + FOO = 'm' + end + A.prepend M + A.foo.should == 'm' + end + ensure + ModuleSpecs.send(:remove_const, :ConstIncludedPrependedOverride) + end + + it "updates the constant when there is a base included constant and the prepended module is later updated" do + module ModuleSpecs::ConstIncludedPrependedLaterUpdated + module Base + FOO = 'a' + end + module A + include Base + def self.foo + FOO + end + end + A.foo.should == 'a' + + module M + end + A.prepend M + A.foo.should == 'a' + + M.const_set(:FOO, 'm') + A.foo.should == 'm' + end + ensure + ModuleSpecs.send(:remove_const, :ConstIncludedPrependedLaterUpdated) + end + + it "updates the constant when a module prepended after a constant is later updated" do + module ModuleSpecs::ConstUpdatedPrependedAfterLaterUpdated + module M + end + module A + FOO = 'a' + end + module B + include A + def self.foo + FOO + end + end + B.foo.should == 'a' + + B.prepend M + B.foo.should == 'a' + + M.const_set(:FOO, 'm') + B.foo.should == 'm' + end + ensure + ModuleSpecs.send(:remove_const, :ConstUpdatedPrependedAfterLaterUpdated) + end + + it "updates the constant when a module is prepended after another and the constant is defined later on that module" do + module ModuleSpecs::ConstUpdatedPrependedAfterConstDefined + module M + FOO = 'm' + end + module A + prepend M + def self.foo + FOO + end + end + + A.foo.should == 'm' + + module N + end + A.prepend N + A.foo.should == 'm' + + N.const_set(:FOO, 'n') + A.foo.should == 'n' + end + ensure + ModuleSpecs.send(:remove_const, :ConstUpdatedPrependedAfterConstDefined) + end + + it "updates the constant when a module is included in a prepended module and the constant is defined later" do + module ModuleSpecs::ConstUpdatedIncludedInPrependedConstDefinedLater + module A + def self.foo + FOO + end + end + module Base + FOO = 'a' + end + + A.prepend Base + A.foo.should == 'a' + + module N + end + module M + include N + end + + A.prepend M + + N.const_set(:FOO, 'n') + A.foo.should == 'n' + end + ensure + ModuleSpecs.send(:remove_const, :ConstUpdatedIncludedInPrependedConstDefinedLater) + end + + it "updates the constant when a new module with an included module is prepended" do + module ModuleSpecs::ConstUpdatedNewModuleIncludedPrepended + module A + FOO = 'a' + end + module B + include A + def self.foo + FOO + end + end + module N + FOO = 'n' + end + + module M + include N + end + + B.foo.should == 'a' + + B.prepend M + B.foo.should == 'n' + end + ensure + ModuleSpecs.send(:remove_const, :ConstUpdatedNewModuleIncludedPrepended) + end + + it "raises a TypeError when the argument is not a Module" do + -> { ModuleSpecs::Basic.prepend(Class.new) }.should raise_error(TypeError) + end + + it "does not raise a TypeError when the argument is an instance of a subclass of Module" do + 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 + m1 = Module.new + m1::MY_CONSTANT = 1 + m2 = Module.new { prepend(m1) } + m2.constants.should include(:MY_CONSTANT) + end + + it "imports instance methods" do + Module.new { prepend ModuleSpecs::A }.instance_methods.should include(:ma) + end + + it "does not import methods to modules and classes" do + Module.new { prepend ModuleSpecs::A }.methods.should_not include(:ma) + end + + it "allows wrapping methods" do + m = Module.new { def calc(x) super + 3 end } + c = Class.new { def calc(x) x*2 end } + c.prepend(m) + c.new.calc(1).should == 5 + end + + it "also prepends included modules" do + a = Module.new { def calc(x) x end } + b = Module.new { include a } + c = Class.new { prepend b } + c.new.calc(1).should == 1 + end + + it "prepends multiple modules in the right order" do + m1 = Module.new { def chain; super << :m1; end } + m2 = Module.new { def chain; super << :m2; end; prepend(m1) } + c = Class.new { def chain; [:c]; end; prepend(m2) } + c.new.chain.should == [:c, :m2, :m1] + end + + it "includes prepended modules in ancestors" do + m = Module.new + Class.new { prepend(m) }.ancestors.should include(m) + end + + it "reports the prepended module as the method owner" do + m = Module.new { def meth; end } + c = Class.new { def meth; end; prepend(m) } + c.new.method(:meth).owner.should == m + end + + it "reports the prepended module as the unbound method owner" do + m = Module.new { def meth; end } + c = Class.new { def meth; end; prepend(m) } + c.instance_method(:meth).owner.should == m + c.public_instance_method(:meth).owner.should == m + end + + it "causes the prepended module's method to be aliased by alias_method" do + m = Module.new { def meth; :m end } + c = Class.new { def meth; :c end; prepend(m); alias_method :alias, :meth } + 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) } + c.new.should be_kind_of(m) + end + + it "keeps the module in the chain when dupping the class" do + m = Module.new + c = Class.new { prepend(m) } + c.dup.new.should be_kind_of(m) + end + + it "uses only new module when dupping the module" do + m1 = Module.new { def calc(x) x end } + m2 = Module.new { prepend(m1) } + c1 = Class.new { prepend(m2) } + m2dup = m2.dup + m2dup.ancestors.should == [m1,m2dup] + c2 = Class.new { prepend(m2dup) } + c1.ancestors[0,3].should == [m1,m2,c1] + c1.new.should be_kind_of(m1) + c2.ancestors[0,3].should == [m1,m2dup,c2] + c2.new.should be_kind_of(m1) + end + + it "depends on prepend_features to add the module" do + m = Module.new { def self.prepend_features(mod) end } + Class.new { prepend(m) }.ancestors.should_not include(m) + end + + it "adds the module in the subclass chains" do + parent = Class.new { def chain; [:parent]; end } + child = Class.new(parent) { def chain; super << :child; end } + mod = Module.new { def chain; super << :mod; end } + parent.prepend(mod) + parent.ancestors[0,2].should == [mod, parent] + child.ancestors[0,3].should == [child, mod, parent] + + parent.new.chain.should == [:parent, :mod] + child.new.chain.should == [:parent, :mod, :child] + end + + it "inserts a later prepended module into the chain" do + m1 = Module.new { def chain; super << :m1; end } + m2 = Module.new { def chain; super << :m2; end } + c1 = Class.new { def chain; [:c1]; end; prepend m1 } + c2 = Class.new(c1) { def chain; super << :c2; end } + c2.new.chain.should == [:c1, :m1, :c2] + c1.prepend(m2) + c2.new.chain.should == [:c1, :m1, :m2, :c2] + end + + it "works with subclasses" do + m = Module.new do + def chain + super << :module + end + end + + c = Class.new do + prepend m + def chain + [:class] + end + end + + s = Class.new(c) do + def chain + super << :subclass + end + end + + s.new.chain.should == [:class, :module, :subclass] + end + + it "throws a NoMethodError when there is no more superclass" do + m = Module.new do + def chain + super << :module + end + end + + c = Class.new do + prepend m + def chain + super << :class + end + end + -> { c.new.chain }.should raise_error(NoMethodError) + end + + it "calls prepended after prepend_features" do + ScratchPad.record [] + + m = Module.new do + def self.prepend_features(klass) + ScratchPad << [:prepend_features, klass] + end + def self.prepended(klass) + ScratchPad << [:prepended, klass] + end + end + + c = Class.new { prepend(m) } + ScratchPad.recorded.should == [[:prepend_features, c], [:prepended, c]] + end + + it "prepends a module if it is included in a super class" do + module ModuleSpecs::M3 + module M; end + class A; include M; end + class B < A; prepend M; end + + all = [A, B, M] + + (B.ancestors.filter { |a| all.include?(a) }).should == [M, B, A, M] + end + end + + it "detects cyclic prepends" do + -> { + module ModuleSpecs::P + prepend ModuleSpecs::P + 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 + m = Module.new + r = nil + c = Class.new { r = prepend m } + r.should == c + end + + it "clears any caches" do + module ModuleSpecs::M3 + module PM1 + def foo + :m1 + end + end + + module PM2 + def foo + :m2 + end + end + + klass = Class.new do + prepend PM1 + + def get + foo + end + end + + o = klass.new + o.get.should == :m1 + + klass.class_eval do + prepend PM2 + end + + o.get.should == :m2 + end + end + + it "supports super when the module is prepended into a singleton class" do + ScratchPad.record [] + + mod = Module.new do + def self.inherited(base) + super + end + end + + module_with_singleton_class_prepend = Module.new do + singleton_class.prepend(mod) + end + + klass = Class.new(ModuleSpecs::RecordIncludedModules) do + include module_with_singleton_class_prepend + end + + ScratchPad.recorded.should == klass + end + + it "supports super when the module is prepended into a singleton class with a class super" do + ScratchPad.record [] + + base_class = Class.new(ModuleSpecs::RecordIncludedModules) do + def self.inherited(base) + super + end + end + + prepended_module = Module.new + base_class.singleton_class.prepend(prepended_module) + + child_class = Class.new(base_class) + ScratchPad.recorded.should == child_class + end + + it "does not interfere with a define_method super in the original class" do + base_class = Class.new do + def foo(ary) + ary << 1 + end + end + + child_class = Class.new(base_class) do + define_method :foo do |ary| + ary << 2 + super(ary) + end + end + + prep_mod = Module.new do + def foo(ary) + ary << 3 + super(ary) + end + end + + child_class.prepend(prep_mod) + + ary = [] + child_class.new.foo(ary) + 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 + mod = Module.new do + def foo; end + end + cls = Class.new do + include mod + end + pre = Module.new + mod.prepend pre + + cls.instance_methods.should include(:foo) + end + end +end diff --git a/spec/ruby/core/module/prepended_spec.rb b/spec/ruby/core/module/prepended_spec.rb new file mode 100644 index 0000000000..bd95d8fd05 --- /dev/null +++ b/spec/ruby/core/module/prepended_spec.rb @@ -0,0 +1,25 @@ +# -*- encoding: us-ascii -*- + +require_relative '../../spec_helper' + +describe "Module#prepended" do + before :each do + ScratchPad.clear + end + + it "is a private method" do + Module.should have_private_instance_method(:prepended, true) + end + + it "is invoked when self is prepended to another module or class" do + m = Module.new do + def self.prepended(o) + ScratchPad.record o + end + end + + c = Class.new { prepend m } + + ScratchPad.recorded.should == c + end +end diff --git a/spec/ruby/core/module/private_class_method_spec.rb b/spec/ruby/core/module/private_class_method_spec.rb new file mode 100644 index 0000000000..f899c71a57 --- /dev/null +++ b/spec/ruby/core/module/private_class_method_spec.rb @@ -0,0 +1,91 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Module#private_class_method" do + before :each do + # This is not in classes.rb because after marking a class method private it + # will stay private. + class << ModuleSpecs::Parent + public + def private_method_1; end + def private_method_2; end + end + end + + after :each do + class << ModuleSpecs::Parent + remove_method :private_method_1 + remove_method :private_method_2 + end + end + + it "makes an existing class method private" do + ModuleSpecs::Parent.private_method_1.should == nil + ModuleSpecs::Parent.private_class_method :private_method_1 + -> { 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. + -> { ModuleSpecs::Parent.private_method }.should raise_error(NoMethodError) + end + + it "makes an existing class method private up the inheritance tree" do + ModuleSpecs::Child.public_class_method :private_method_1 + ModuleSpecs::Child.private_method_1.should == nil + ModuleSpecs::Child.private_class_method :private_method_1 + + -> { 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 + ModuleSpecs::Parent.private_method_1.should == nil + ModuleSpecs::Parent.private_method_2.should == nil + + ModuleSpecs::Child.private_class_method :private_method_1, :private_method_2 + + -> { 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 + -> do + ModuleSpecs.private_class_method :no_method_here + end.should raise_error(NameError) + end + + it "makes a class method private" do + c = Class.new do + def self.foo() "foo" end + private_class_method :foo + end + -> { c.foo }.should raise_error(NoMethodError) + end + + it "raises a NameError when the given name is not a method" do + -> do + Class.new do + private_class_method :foo + end + end.should raise_error(NameError) + end + + it "raises a NameError when the given name is an instance method" 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 new file mode 100644 index 0000000000..3a91b3c3cd --- /dev/null +++ b/spec/ruby/core/module/private_constant_spec.rb @@ -0,0 +1,32 @@ +require_relative '../../spec_helper' + +describe "Module#private_constant" do + it "can only be passed constant names defined in the target (self) module" do + cls1 = Class.new + cls1.const_set :Foo, true + cls2 = Class.new(cls1) + + -> do + cls2.send :private_constant, :Foo + end.should raise_error(NameError) + end + + it "accepts strings as constant names" do + cls = Class.new + cls.const_set :Foo, true + cls.send :private_constant, "Foo" + + -> { cls::Foo }.should raise_error(NameError) + end + + it "accepts multiple names" do + mod = Module.new + mod.const_set :Foo, true + mod.const_set :Bar, true + + mod.send :private_constant, :Foo, :Bar + + -> {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 new file mode 100644 index 0000000000..cce0f001bf --- /dev/null +++ b/spec/ruby/core/module/private_instance_methods_spec.rb @@ -0,0 +1,54 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative '../../fixtures/reflection' + +# TODO: rewrite +describe "Module#private_instance_methods" do + it "returns a list of private methods in module and its ancestors" do + ModuleSpecs::CountsMixin.should have_private_instance_method(:private_3) + + ModuleSpecs::CountsParent.should have_private_instance_method(:private_2) + ModuleSpecs::CountsParent.should have_private_instance_method(:private_3) + + ModuleSpecs::CountsChild.should have_private_instance_method(:private_1) + ModuleSpecs::CountsChild.should have_private_instance_method(:private_2) + ModuleSpecs::CountsChild.should have_private_instance_method(:private_3) + end + + it "when passed false as a parameter, should return only methods defined in that module" do + ModuleSpecs::CountsMixin.should have_private_instance_method(:private_3, false) + ModuleSpecs::CountsParent.should have_private_instance_method(:private_2, false) + ModuleSpecs::CountsChild.should have_private_instance_method(:private_1, false) + end + + it "default list should be the same as passing true as an argument" do + ModuleSpecs::CountsMixin.private_instance_methods(true).should == + ModuleSpecs::CountsMixin.private_instance_methods + ModuleSpecs::CountsParent.private_instance_methods(true).should == + ModuleSpecs::CountsParent.private_instance_methods + ModuleSpecs::CountsChild.private_instance_methods(true).should == + ModuleSpecs::CountsChild.private_instance_methods + end +end + +describe :module_private_instance_methods_supers, shared: true do + it "returns a unique list for a class including a module" do + m = ReflectSpecs::D.private_instance_methods(*@object) + m.select { |x| x == :pri }.sort.should == [:pri] + end + + it "returns a unique list for a subclass" do + m = ReflectSpecs::E.private_instance_methods(*@object) + m.select { |x| x == :pri }.sort.should == [:pri] + end +end + +describe "Module#private_instance_methods" do + describe "when not passed an argument" do + it_behaves_like :module_private_instance_methods_supers, nil, [] + end + + describe "when passed true" do + it_behaves_like :module_private_instance_methods_supers, nil, true + end +end diff --git a/spec/ruby/core/module/private_method_defined_spec.rb b/spec/ruby/core/module/private_method_defined_spec.rb new file mode 100644 index 0000000000..01fc8f8fb9 --- /dev/null +++ b/spec/ruby/core/module/private_method_defined_spec.rb @@ -0,0 +1,120 @@ +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 + ModuleSpecs::CountsMixin.private_method_defined?("private_3").should == true + + ModuleSpecs::CountsParent.private_method_defined?("private_3").should == true + ModuleSpecs::CountsParent.private_method_defined?("private_2").should == true + + ModuleSpecs::CountsChild.private_method_defined?("private_3").should == true + ModuleSpecs::CountsChild.private_method_defined?("private_2").should == true + ModuleSpecs::CountsChild.private_method_defined?("private_1").should == true + end + + it "returns false if method is not a private method" do + ModuleSpecs::CountsChild.private_method_defined?("public_3").should == false + ModuleSpecs::CountsChild.private_method_defined?("public_2").should == false + ModuleSpecs::CountsChild.private_method_defined?("public_1").should == false + + ModuleSpecs::CountsChild.private_method_defined?("protected_3").should == false + ModuleSpecs::CountsChild.private_method_defined?("protected_2").should == false + ModuleSpecs::CountsChild.private_method_defined?("protected_1").should == false + end + + it "returns false if the named method is not defined by the module or its ancestors" do + ModuleSpecs::CountsMixin.private_method_defined?(:private_10).should == false + end + + it "accepts symbols for the method name" do + ModuleSpecs::CountsMixin.private_method_defined?(:private_3).should == true + end + + 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 + -> do + ModuleSpecs::CountsMixin.private_method_defined?(nil) + end.should raise_error(TypeError) + end + + it "raises a TypeError if passed false" 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 + -> do + ModuleSpecs::CountsMixin.private_method_defined?(mock('x')) + end.should raise_error(TypeError) + end + + it "raises a TypeError if passed an object that defines #to_sym" do + sym = mock('symbol') + def sym.to_sym() :private_3 end + + -> do + ModuleSpecs::CountsMixin.private_method_defined?(sym) + end.should raise_error(TypeError) + end + + it "calls #to_str to convert an Object" do + str = mock('string') + 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 new file mode 100644 index 0000000000..9e1a297eea --- /dev/null +++ b/spec/ruby/core/module/private_spec.rb @@ -0,0 +1,95 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/set_visibility' + +describe "Module#private" do + it_behaves_like :set_visibility, :private + + it "makes the target method uncallable from other types" do + obj = Object.new + class << obj + def foo; true; end + end + + obj.foo.should == true + + class << obj + private :foo + end + + -> { obj.foo }.should raise_error(NoMethodError) + end + + it "makes a public Object instance method private in a new module" do + m = Module.new do + private :module_specs_public_method_on_object + end + + m.should have_private_instance_method(:module_specs_public_method_on_object) + + # Ensure we did not change Object's method + Object.should_not have_private_instance_method(:module_specs_public_method_on_object) + end + + it "makes a public Object instance method private in Kernel" do + Kernel.should have_private_instance_method( + :module_specs_public_method_on_object_for_kernel_private) + Object.should_not have_private_instance_method( + :module_specs_public_method_on_object_for_kernel_private) + end + + it "returns argument or arguments if given" do + (class << Object.new; self; end).class_eval do + def foo; end + 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 + -> do + Module.new.send(:private, :undefined) + end.should raise_error(NameError) + end + + it "only makes the method private in the class it is called on" do + base = Class.new do + def wrapped + 1 + end + end + + klass = Class.new(base) do + def wrapped + super + 1 + end + private :wrapped + end + + base.new.wrapped.should == 1 + -> do + klass.new.wrapped + end.should raise_error(NameError) + end + + it "continues to allow a prepended module method to call super" do + wrapper = Module.new do + def wrapped + super + 1 + end + end + + klass = Class.new do + prepend wrapper + + def wrapped + 1 + end + private :wrapped + end + + klass.new.wrapped.should == 2 + end +end diff --git a/spec/ruby/core/module/protected_instance_methods_spec.rb b/spec/ruby/core/module/protected_instance_methods_spec.rb new file mode 100644 index 0000000000..78ce7e788f --- /dev/null +++ b/spec/ruby/core/module/protected_instance_methods_spec.rb @@ -0,0 +1,57 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative '../../fixtures/reflection' + +# TODO: rewrite +describe "Module#protected_instance_methods" do + it "returns a list of protected methods in module and its ancestors" do + methods = ModuleSpecs::CountsMixin.protected_instance_methods + methods.should include(:protected_3) + + methods = ModuleSpecs::CountsParent.protected_instance_methods + methods.should include(:protected_3) + methods.should include(:protected_2) + + methods = ModuleSpecs::CountsChild.protected_instance_methods + methods.should include(:protected_3) + methods.should include(:protected_2) + methods.should include(:protected_1) + end + + it "when passed false as a parameter, should return only methods defined in that module" do + ModuleSpecs::CountsMixin.protected_instance_methods(false).should == [:protected_3] + ModuleSpecs::CountsParent.protected_instance_methods(false).should == [:protected_2] + ModuleSpecs::CountsChild.protected_instance_methods(false).should == [:protected_1] + end + + it "default list should be the same as passing true as an argument" do + ModuleSpecs::CountsMixin.protected_instance_methods(true).should == + ModuleSpecs::CountsMixin.protected_instance_methods + ModuleSpecs::CountsParent.protected_instance_methods(true).should == + ModuleSpecs::CountsParent.protected_instance_methods + ModuleSpecs::CountsChild.protected_instance_methods(true).should == + ModuleSpecs::CountsChild.protected_instance_methods + end +end + +describe :module_protected_instance_methods_supers, shared: true do + it "returns a unique list for a class including a module" do + m = ReflectSpecs::D.protected_instance_methods(*@object) + m.select { |x| x == :pro }.sort.should == [:pro] + end + + it "returns a unique list for a subclass" do + m = ReflectSpecs::E.protected_instance_methods(*@object) + m.select { |x| x == :pro }.sort.should == [:pro] + end +end + +describe "Module#protected_instance_methods" do + describe "when not passed an argument" do + it_behaves_like :module_protected_instance_methods_supers, nil, [] + end + + describe "when passed true" do + it_behaves_like :module_protected_instance_methods_supers, nil, true + end +end diff --git a/spec/ruby/core/module/protected_method_defined_spec.rb b/spec/ruby/core/module/protected_method_defined_spec.rb new file mode 100644 index 0000000000..31e24a16c1 --- /dev/null +++ b/spec/ruby/core/module/protected_method_defined_spec.rb @@ -0,0 +1,120 @@ +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 + ModuleSpecs::CountsMixin.protected_method_defined?("protected_3").should == true + + ModuleSpecs::CountsParent.protected_method_defined?("protected_3").should == true + ModuleSpecs::CountsParent.protected_method_defined?("protected_2").should == true + + ModuleSpecs::CountsChild.protected_method_defined?("protected_3").should == true + ModuleSpecs::CountsChild.protected_method_defined?("protected_2").should == true + ModuleSpecs::CountsChild.protected_method_defined?("protected_1").should == true + end + + it "returns false if method is not a protected method" do + ModuleSpecs::CountsChild.protected_method_defined?("public_3").should == false + ModuleSpecs::CountsChild.protected_method_defined?("public_2").should == false + ModuleSpecs::CountsChild.protected_method_defined?("public_1").should == false + + ModuleSpecs::CountsChild.protected_method_defined?("private_3").should == false + ModuleSpecs::CountsChild.protected_method_defined?("private_2").should == false + ModuleSpecs::CountsChild.protected_method_defined?("private_1").should == false + end + + it "returns false if the named method is not defined by the module or its ancestors" do + ModuleSpecs::CountsMixin.protected_method_defined?(:protected_10).should == false + end + + it "accepts symbols for the method name" do + ModuleSpecs::CountsMixin.protected_method_defined?(:protected_3).should == true + end + + 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 + -> do + ModuleSpecs::CountsMixin.protected_method_defined?(nil) + end.should raise_error(TypeError) + end + + it "raises a TypeError if passed false" 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 + -> do + ModuleSpecs::CountsMixin.protected_method_defined?(mock('x')) + end.should raise_error(TypeError) + end + + it "raises a TypeError if passed an object that defines #to_sym" do + sym = mock('symbol') + def sym.to_sym() :protected_3 end + + -> do + ModuleSpecs::CountsMixin.protected_method_defined?(sym) + end.should raise_error(TypeError) + end + + it "calls #to_str to convert an Object" do + str = mock('protected_3') + 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 new file mode 100644 index 0000000000..9e37223e18 --- /dev/null +++ b/spec/ruby/core/module/protected_spec.rb @@ -0,0 +1,57 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/set_visibility' + +describe "Module#protected" do + before :each do + class << ModuleSpecs::Parent + def protected_method_1; 5; end + end + end + + it_behaves_like :set_visibility, :protected + + it "makes an existing class method protected" do + ModuleSpecs::Parent.protected_method_1.should == 5 + + class << ModuleSpecs::Parent + protected :protected_method_1 + end + + -> { ModuleSpecs::Parent.protected_method_1 }.should raise_error(NoMethodError) + end + + it "makes a public Object instance method protected in a new module" do + m = Module.new do + protected :module_specs_public_method_on_object + end + + m.should have_protected_instance_method(:module_specs_public_method_on_object) + + # Ensure we did not change Object's method + Object.should_not have_protected_instance_method(:module_specs_public_method_on_object) + end + + it "makes a public Object instance method protected in Kernel" do + Kernel.should have_protected_instance_method( + :module_specs_public_method_on_object_for_kernel_protected) + Object.should_not have_protected_instance_method( + :module_specs_public_method_on_object_for_kernel_protected) + end + + it "returns argument or arguments if given" do + (class << Object.new; self; end).class_eval do + def foo; end + 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 + -> 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 new file mode 100644 index 0000000000..71b20acda5 --- /dev/null +++ b/spec/ruby/core/module/public_class_method_spec.rb @@ -0,0 +1,94 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Module#public_class_method" do + before :each do + class << ModuleSpecs::Parent + private + def public_method_1; end + def public_method_2; end + end + end + + after :each do + class << ModuleSpecs::Parent + remove_method :public_method_1 + remove_method :public_method_2 + end + end + + it "makes an existing class method public" do + -> { ModuleSpecs::Parent.public_method_1 }.should raise_error(NoMethodError) + ModuleSpecs::Parent.public_class_method :public_method_1 + ModuleSpecs::Parent.public_method_1.should == nil + + # Technically above we're testing the Singleton classes, class method(right?). + # Try a "real" class method set public. + ModuleSpecs::Parent.public_method.should == nil + end + + it "makes an existing class method public up the inheritance tree" do + ModuleSpecs::Child.private_class_method :public_method_1 + -> { ModuleSpecs::Child.public_method_1 }.should raise_error(NoMethodError) + ModuleSpecs::Child.public_class_method :public_method_1 + + ModuleSpecs::Child.public_method_1.should == nil + ModuleSpecs::Child.public_method.should == nil + end + + it "accepts more than one method at a time" do + -> { 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 + + ModuleSpecs::Child.public_method_1.should == nil + ModuleSpecs::Child.public_method_2.should == nil + end + + it "raises a NameError if class method doesn't exist" do + -> do + ModuleSpecs.public_class_method :no_method_here + end.should raise_error(NameError) + end + + it "makes a class method public" do + c = Class.new do + def self.foo() "foo" end + public_class_method :foo + end + + c.foo.should == "foo" + end + + it "raises a NameError when the given name is not a method" do + -> do + Class.new do + public_class_method :foo + end + end.should raise_error(NameError) + end + + it "raises a NameError when the given name is an instance method" 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 new file mode 100644 index 0000000000..e624d45fd2 --- /dev/null +++ b/spec/ruby/core/module/public_constant_spec.rb @@ -0,0 +1,38 @@ +require_relative '../../spec_helper' + +describe "Module#public_constant" do + it "can only be passed constant names defined in the target (self) module" do + cls1 = Class.new + cls1.const_set :Foo, true + cls2 = Class.new(cls1) + + -> do + cls2.send :public_constant, :Foo + end.should raise_error(NameError) + end + + it "accepts strings as constant names" do + cls = Class.new + cls.const_set :Foo, true + + cls.send :private_constant, :Foo + cls.send :public_constant, "Foo" + + cls::Foo.should == true + end + + # [ruby-list:48558] + it "accepts multiple names" do + mod = Module.new + mod.const_set :Foo, true + mod.const_set :Bar, true + + mod.send :private_constant, :Foo + mod.send :private_constant, :Bar + + mod.send :public_constant, :Foo, :Bar + + mod::Foo.should == true + mod::Bar.should == true + end +end diff --git a/spec/ruby/core/module/public_instance_method_spec.rb b/spec/ruby/core/module/public_instance_method_spec.rb new file mode 100644 index 0000000000..ba19ad0404 --- /dev/null +++ b/spec/ruby/core/module/public_instance_method_spec.rb @@ -0,0 +1,65 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Module#public_instance_method" do + it "is a public method" do + Module.should have_public_instance_method(:public_instance_method, false) + end + + it "requires an argument" do + Module.new.method(:public_instance_method).arity.should == 1 + end + + describe "when given a public method name" do + it "returns an UnboundMethod corresponding to the defined Module" do + ret = ModuleSpecs::Super.public_instance_method(:public_module) + ret.should be_an_instance_of(UnboundMethod) + ret.owner.should equal(ModuleSpecs::Basic) + + ret = ModuleSpecs::Super.public_instance_method(:public_super_module) + ret.should be_an_instance_of(UnboundMethod) + ret.owner.should equal(ModuleSpecs::Super) + end + + it "accepts if the name is a Symbol or String" do + ret = ModuleSpecs::Basic.public_instance_method(:public_module) + ModuleSpecs::Basic.public_instance_method("public_module").should == ret + end + end + + it "raises a TypeError when given a name is not Symbol or String" do + -> { Module.new.public_instance_method(nil) }.should raise_error(TypeError) + end + + it "raises a NameError when given a protected method name" 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 + -> 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 + -> 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 + -> do + Module.new.public_instance_method(:missing) + end.should raise_error(NameError) + end + + it "sets the NameError#name attribute to the name of the missing method" do + begin + Module.new.public_instance_method(:missing) + rescue NameError => e + e.name.should == :missing + end + end +end diff --git a/spec/ruby/core/module/public_instance_methods_spec.rb b/spec/ruby/core/module/public_instance_methods_spec.rb new file mode 100644 index 0000000000..ae7d9b5ffb --- /dev/null +++ b/spec/ruby/core/module/public_instance_methods_spec.rb @@ -0,0 +1,61 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative '../../fixtures/reflection' + +# TODO: rewrite + +describe "Module#public_instance_methods" do + it "returns a list of public methods in module and its ancestors" do + methods = ModuleSpecs::CountsMixin.public_instance_methods + methods.should include(:public_3) + + methods = ModuleSpecs::CountsParent.public_instance_methods + methods.should include(:public_3) + methods.should include(:public_2) + + methods = ModuleSpecs::CountsChild.public_instance_methods + methods.should include(:public_3) + methods.should include(:public_2) + methods.should include(:public_1) + + methods = ModuleSpecs::Child2.public_instance_methods + methods.should include(:foo) + end + + it "when passed false as a parameter, should return only methods defined in that module" do + ModuleSpecs::CountsMixin.public_instance_methods(false).should == [:public_3] + ModuleSpecs::CountsParent.public_instance_methods(false).should == [:public_2] + ModuleSpecs::CountsChild.public_instance_methods(false).should == [:public_1] + end + + it "default list should be the same as passing true as an argument" do + ModuleSpecs::CountsMixin.public_instance_methods(true).should == + ModuleSpecs::CountsMixin.public_instance_methods + ModuleSpecs::CountsParent.public_instance_methods(true).should == + ModuleSpecs::CountsParent.public_instance_methods + ModuleSpecs::CountsChild.public_instance_methods(true).should == + ModuleSpecs::CountsChild.public_instance_methods + end +end + +describe :module_public_instance_methods_supers, shared: true do + it "returns a unique list for a class including a module" do + m = ReflectSpecs::D.public_instance_methods(*@object) + m.select { |x| x == :pub }.sort.should == [:pub] + end + + it "returns a unique list for a subclass" do + m = ReflectSpecs::E.public_instance_methods(*@object) + m.select { |x| x == :pub }.sort.should == [:pub] + end +end + +describe "Module#public_instance_methods" do + describe "when not passed an argument" do + it_behaves_like :module_public_instance_methods_supers, nil, [] + end + + describe "when passed true" do + it_behaves_like :module_public_instance_methods_supers, nil, true + end +end diff --git a/spec/ruby/core/module/public_method_defined_spec.rb b/spec/ruby/core/module/public_method_defined_spec.rb new file mode 100644 index 0000000000..5c9bdf1ccc --- /dev/null +++ b/spec/ruby/core/module/public_method_defined_spec.rb @@ -0,0 +1,72 @@ +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 + ModuleSpecs::CountsMixin.public_method_defined?("public_3").should == true + + ModuleSpecs::CountsParent.public_method_defined?("public_3").should == true + ModuleSpecs::CountsParent.public_method_defined?("public_2").should == true + + ModuleSpecs::CountsChild.public_method_defined?("public_3").should == true + ModuleSpecs::CountsChild.public_method_defined?("public_2").should == true + ModuleSpecs::CountsChild.public_method_defined?("public_1").should == true + end + + it "returns false if method is not a public method" do + ModuleSpecs::CountsChild.public_method_defined?("private_3").should == false + ModuleSpecs::CountsChild.public_method_defined?("private_2").should == false + ModuleSpecs::CountsChild.public_method_defined?("private_1").should == false + + ModuleSpecs::CountsChild.public_method_defined?("protected_3").should == false + ModuleSpecs::CountsChild.public_method_defined?("protected_2").should == false + ModuleSpecs::CountsChild.public_method_defined?("protected_1").should == false + end + + it "returns false if the named method is not defined by the module or its ancestors" do + ModuleSpecs::CountsMixin.public_method_defined?(:public_10).should == false + end + + it "accepts symbols for the method name" do + ModuleSpecs::CountsMixin.public_method_defined?(:public_3).should == true + end + + 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 + -> do + ModuleSpecs::CountsMixin.public_method_defined?(nil) + end.should raise_error(TypeError) + end + + it "raises a TypeError if passed false" 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 + -> do + ModuleSpecs::CountsMixin.public_method_defined?(mock('x')) + end.should raise_error(TypeError) + end + + it "raises a TypeError if passed an object that defines #to_sym" do + sym = mock('symbol') + def sym.to_sym() :public_3 end + + -> do + ModuleSpecs::CountsMixin.public_method_defined?(sym) + end.should raise_error(TypeError) + end + + it "calls #to_str to convert an Object" do + str = mock('public_3') + def str.to_str() 'public_3' end + ModuleSpecs::CountsMixin.public_method_defined?(str).should == true + end +end diff --git a/spec/ruby/core/module/public_spec.rb b/spec/ruby/core/module/public_spec.rb new file mode 100644 index 0000000000..ce31eb5d0e --- /dev/null +++ b/spec/ruby/core/module/public_spec.rb @@ -0,0 +1,45 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/set_visibility' + +describe "Module#public" do + it_behaves_like :set_visibility, :public + + it "on a superclass method calls the redefined method" do + ModuleSpecs::ChildPrivateMethodMadePublic.new.private_method_redefined.should == :after_redefinition + end + + it "makes a private Object instance method public in a new module" do + m = Module.new do + public :module_specs_private_method_on_object + end + + m.should have_public_instance_method(:module_specs_private_method_on_object) + + # Ensure we did not change Object's method + Object.should_not have_public_instance_method(:module_specs_private_method_on_object) + end + + it "makes a private Object instance method public in Kernel" do + Kernel.should have_public_instance_method( + :module_specs_private_method_on_object_for_kernel_public) + Object.should_not have_public_instance_method( + :module_specs_private_method_on_object_for_kernel_public) + end + + it "returns argument or arguments if given" do + (class << Object.new; self; end).class_eval do + def foo; end + 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 + -> do + Module.new.send(:public, :undefined) + end.should raise_error(NameError) + end +end diff --git a/spec/ruby/core/module/refine_spec.rb b/spec/ruby/core/module/refine_spec.rb new file mode 100644 index 0000000000..d219b98825 --- /dev/null +++ b/spec/ruby/core/module/refine_spec.rb @@ -0,0 +1,714 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/refine' + +describe "Module#refine" do + it "runs its block in an anonymous module" do + inner_self = nil + mod = Module.new do + refine String do + inner_self = self + end + end + + mod.should_not == inner_self + inner_self.should be_kind_of(Module) + inner_self.name.should == nil + end + + it "uses the same anonymous module for future refines of the same class" do + selves = [] + mod = Module.new do + refine String do + selves << self + end + end + + mod.module_eval do + refine String do + selves << self + end + end + + selves[0].should == selves[1] + end + + it "adds methods defined in its block to the anonymous module's public instance methods" do + inner_self = nil + mod = Module.new do + refine String do + def blah + "blah" + end + inner_self = self + end + end + + inner_self.public_instance_methods.should include(:blah) + end + + it "returns created anonymous module" do + inner_self = nil + result = nil + mod = Module.new do + result = refine String do + inner_self = self + end + end + + result.should == inner_self + end + + it "raises ArgumentError if not passed an argument" do + -> do + Module.new do + refine {} + end + end.should raise_error(ArgumentError) + end + + it "raises TypeError if not passed a class" do + -> do + Module.new do + refine("foo") {} + end + end.should raise_error(TypeError, "wrong argument type String (expected Class or Module)") + end + + it "accepts a module as argument" do + inner_self = nil + Module.new do + refine(Enumerable) do + def blah + end + inner_self = self + end + end + + inner_self.public_instance_methods.should include(:blah) + 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 + + 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 + -> do + Module.new do + refine String + end + end.should raise_error(ArgumentError) + end + + it "applies refinements to calls in the refine block" do + result = nil + Module.new do + refine(String) do + def foo; "foo"; end + result = "hello".foo + end + end + result.should == "foo" + end + + 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 + end + + it "does not apply refinements to external scopes not using the module" do + Module.new do + refine(String) {def foo; 'foo'; end} + end + + -> {"hello".foo}.should raise_error(NoMethodError) + end + + # When defining multiple refinements in the same module, + # inside a refine block all refinements from the same + # module are active when a refined method is called + it "makes available all refinements from the same module" do + refinement = Module.new do + refine Integer do + def to_json_format + to_s + end + end + + refine Array do + def to_json_format + "[" + map { |i| i.to_json_format }.join(", ") + "]" + end + end + + refine Hash do + def to_json_format + "{" + map { |k, v| k.to_s.dump + ": " + v.to_json_format }.join(", ") + "}" + end + end + end + + result = nil + + Module.new do + using refinement + + result = [{1 => 2}, {3 => 4}].to_json_format + end + + result.should == '[{"1": 2}, {"3": 4}]' + end + + it "does not make available methods from another refinement module" do + refinery_integer = Module.new do + refine Integer do + def to_json_format + to_s + end + end + end + + refinery_array = Module.new do + refine Array do + def to_json_format + "[" + map { |i| i.to_json_format }.join(",") + "]" + end + end + end + + result = nil + + -> { + Module.new do + using refinery_integer + using refinery_array + + [1, 2].to_json_format + end + }.should raise_error(NoMethodError) + end + + # method lookup: + # * The prepended modules from the refinement for C + # * The refinement for C + # * The included modules from the refinement for C + # * The prepended modules of C + # * C + # * 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 refined_class do + def foo; "foo from refinement"; end + end + end + + result = nil + Module.new do + using refinement + + obj = refined_class.new + class << obj + def foo; "foo from singleton class"; end + end + result = obj.foo + end + + result.should == "foo from singleton class" + end + + it "looks in later included modules of the refined module first" do + a = Module.new do + def foo + "foo from A" + end + end + + include_me_later = Module.new do + def foo + "foo from IncludeMeLater" + end + end + + c = Class.new do + include a + end + + refinement = Module.new do + refine c do; end + end + + result = nil + Module.new do + using refinement + c.include include_me_later + result = c.new.foo + end + + result.should == "foo from IncludeMeLater" + end + + it "looks in the class then" do + refined_class = ModuleSpecs.build_refined_class + + refinement = Module.new do + refine(refined_class) { } + end + + result = nil + Module.new do + using refinement + result = refined_class.new.foo + end + + result.should == "foo" + end + end + + + # methods in a subclass have priority over refinements in a superclass + it "does not override methods in subclasses" 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 refined_class do + def foo; "foo from refinement"; end + end + end + + result = nil + Module.new do + using refinement + result = subclass.new.foo + end + + result.should == "foo from subclass" + end + + context "for methods accessed indirectly" do + it "is honored by Kernel#send" do + refined_class = ModuleSpecs.build_refined_class + + refinement = Module.new do + refine refined_class do + def foo; "foo from refinement"; end + end + end + + result = nil + Module.new do + using refinement + result = refined_class.new.send :foo + end + + result.should == "foo from refinement" + end + + it "is honored by BasicObject#__send__" do + refined_class = ModuleSpecs.build_refined_class + + refinement = Module.new do + refine refined_class do + def foo; "foo from refinement"; end + end + end + + result = nil + Module.new do + using refinement + result = refined_class.new.__send__ :foo + end + + result.should == "foo from refinement" + end + + it "is 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 + + it "is honored by Kernel#public_send" do + refined_class = ModuleSpecs.build_refined_class + + refinement = Module.new do + refine refined_class do + def foo; "foo from refinement"; end + end + end + + result = nil + Module.new do + using refinement + result = refined_class.new.public_send :foo + end + + result.should == "foo from refinement" + 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" + end + + it "is honored by Kernel#binding" do + refinement = Module.new do + refine String do + def to_s + "hello from refinement" + end + end + end + + klass = Class.new do + using refinement + + def foo + "foo".to_s + end + + def get_binding + binding + end + end + + result = Kernel.eval("self.foo()", klass.new.get_binding) + result.should == "hello from refinement" + end + + it "is honored by Kernel#method" do + klass = Class.new + refinement = Module.new do + refine klass do + def foo; end + end + end + + result = nil + Module.new do + using refinement + result = klass.new.method(:foo).class + end + + result.should == Method + end + + it "is honored by Kernel#public_method" do + klass = Class.new + refinement = Module.new do + refine klass do + def foo; end + end + end + + result = nil + Module.new do + using refinement + result = klass.new.public_method(:foo).class + end + + result.should == Method + end + + it "is honored by Kernel#instance_method" do + klass = Class.new + refinement = Module.new do + refine klass do + def foo; end + end + end + + result = nil + Module.new do + using refinement + result = klass.instance_method(:foo).class + end + + result.should == UnboundMethod + end + + it "is honored by Kernel#respond_to?" do + klass = Class.new + refinement = Module.new do + refine klass do + def foo; end + end + end + + result = nil + Module.new do + using refinement + result = klass.new.respond_to?(:foo) + end + + result.should == true + end + + it "is honored by &" do + refinement = Module.new do + refine String do + def to_proc(*args) + -> * { 'foo' } + end + end + end + + result = nil + Module.new do + using refinement + result = ["hola"].map(&"upcase") + end + + result.should == ['foo'] + 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 refined_class do + def foo + super + end + end + end + + result = nil + Module.new do + using refinement + result = refined_class.new.foo + end + + result.should == "foo" + end + + # super in a method of a refinement invokes the method in the refined + # class even if there is another refinement which has been activated + # in the same context. + it "looks in the refined class first if called from refined method" do + refined_class = ModuleSpecs.build_refined_class(for_super: true) + + refinement = Module.new do + refine refined_class do + def foo + [:R1] + end + end + end + + refinement_with_super = Module.new do + refine refined_class do + def foo + [:R2] + super + end + end + end + + result = nil + Module.new do + using refinement + using refinement_with_super + result = refined_class.new.foo + end + + 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. + # That is, using activates all refinements in the ancestors of the specified module. + # Refinements in a descendant have priority over refinements in an ancestor. + context "module inclusion" do + it "activates all refinements from all ancestors" do + refinement_included = Module.new do + refine Integer do + def to_json_format + to_s + end + end + end + + refinement = Module.new do + include refinement_included + + refine Array do + def to_json_format + "[" + map { |i| i.to_s }.join(", ") + "]" + end + end + end + + result = nil + Module.new do + using refinement + result = [5.to_json_format, [1, 2, 3].to_json_format] + end + + result.should == ["5", "[1, 2, 3]"] + end + + it "overrides methods of ancestors by methods in descendants" do + refinement_included = Module.new do + refine Integer do + def to_json_format + to_s + end + end + end + + refinement = Module.new do + include refinement_included + + refine Integer do + def to_json_format + "hello from refinement" + end + end + end + + result = nil + Module.new do + using refinement + result = 5.to_json_format + end + + 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 new file mode 100644 index 0000000000..ab9514adf6 --- /dev/null +++ b/spec/ruby/core/module/remove_class_variable_spec.rb @@ -0,0 +1,44 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Module#remove_class_variable" do + it "removes class variable" do + m = ModuleSpecs::MVars.dup + m.send(:remove_class_variable, :@@mvar) + m.class_variable_defined?(:@@mvar).should == false + end + + it "returns the value of removing class variable" do + m = ModuleSpecs::MVars.dup + m.send(:remove_class_variable, :@@mvar).should == :mvar + end + + it "removes a class variable defined in a metaclass" do + obj = mock("metaclass class variable") + meta = obj.singleton_class + meta.send :class_variable_set, :@@var, 1 + meta.send(:remove_class_variable, :@@var).should == 1 + meta.class_variable_defined?(:@@var).should be_false + end + + it "raises a NameError when removing class variable declared in included module" do + c = ModuleSpecs::RemoveClassVariable.new { include ModuleSpecs::MVars.dup } + -> { c.send(:remove_class_variable, :@@mvar) }.should raise_error(NameError) + end + + it "raises a NameError when passed a symbol with one leading @" do + -> { ModuleSpecs::MVars.send(:remove_class_variable, :@mvar) }.should raise_error(NameError) + end + + it "raises a NameError when passed a symbol with no leading @" do + -> { ModuleSpecs::MVars.send(:remove_class_variable, :mvar) }.should raise_error(NameError) + end + + it "raises a NameError when an uninitialized class variable is given" do + -> { ModuleSpecs::MVars.send(:remove_class_variable, :@@nonexisting_class_variable) }.should raise_error(NameError) + end + + it "is public" do + Module.should_not have_private_instance_method(:remove_class_variable) + end +end diff --git a/spec/ruby/core/module/remove_const_spec.rb b/spec/ruby/core/module/remove_const_spec.rb new file mode 100644 index 0000000000..35a9d65105 --- /dev/null +++ b/spec/ruby/core/module/remove_const_spec.rb @@ -0,0 +1,107 @@ +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 + ConstantSpecs::ModuleM::CS_CONST252 = :const252 + ConstantSpecs::ModuleM::CS_CONST252.should == :const252 + + ConstantSpecs::ModuleM.send :remove_const, :CS_CONST252 + -> { 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" + -> { ConstantSpecs::ModuleM::CS_CONST253 }.should raise_error(NameError) + end + + it "returns the value of the removed constant" do + ConstantSpecs::ModuleM::CS_CONST254 = :const254 + ConstantSpecs::ModuleM.send(:remove_const, :CS_CONST254).should == :const254 + end + + it "raises a NameError and does not call #const_missing if the constant is not defined" do + ConstantSpecs.should_not_receive(:const_missing) + -> { 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 + begin + ConstantSpecs::ModuleM::CS_CONST255 = :const255 + ConstantSpecs::ContainerA::CS_CONST255.should == :const255 + ConstantSpecs::ContainerA.should_not_receive(:const_missing) + + -> do + ConstantSpecs::ContainerA.send :remove_const, :CS_CONST255 + end.should raise_error(NameError) + ensure + ConstantSpecs::ModuleM.send :remove_const, "CS_CONST255" + end + end + + it "raises a NameError if the name does not start with a capital letter" do + -> { ConstantSpecs.send :remove_const, "name" }.should raise_error(NameError) + end + + it "raises a NameError if the name starts with a non-alphabetic character" do + -> { 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" + -> { 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 + ConstantSpecs::CS_CONST257 = :const257 + name = mock("CS_CONST257") + name.should_receive(:to_str).and_return("CS_CONST257") + ConstantSpecs.send(:remove_const, name).should == :const257 + end + + it "raises a TypeError if conversion to a String by calling #to_str fails" do + name = mock('123') + -> { ConstantSpecs.send :remove_const, name }.should raise_error(TypeError) + + name.should_receive(:to_str).and_return(123) + -> { ConstantSpecs.send :remove_const, name }.should raise_error(TypeError) + end + + it "is a private method" do + Module.private_methods.should include(:remove_const) + end + + it "returns nil when removing autoloaded constant" 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 new file mode 100644 index 0000000000..94b255df62 --- /dev/null +++ b/spec/ruby/core/module/remove_method_spec.rb @@ -0,0 +1,131 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +module ModuleSpecs + class Parent + def method_to_remove; 1; end + end + + class First + def method_to_remove; 1; end + end + + class Second < First + def method_to_remove; 2; end + end +end + +describe "Module#remove_method" do + before :each do + @module = Module.new { def method_to_remove; end } + 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 + klass = Class.new do + def method_to_remove; 1; end + end + x = klass.new + klass.send(:remove_method, :method_to_remove) + x.respond_to?(:method_to_remove).should == false + end + + it "removes method from subclass, but not parent" do + child = Class.new(ModuleSpecs::Parent) do + def method_to_remove; 2; end + remove_method :method_to_remove + end + x = child.new + x.respond_to?(:method_to_remove).should == true + 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 + def method_to_remove_2; 2; end + remove_method :method_to_remove_1, :method_to_remove_2 + end + x = klass.new + x.respond_to?(:method_to_remove_1).should == false + x.respond_to?(:method_to_remove_2).should == false + end + + it "accepts multiple arguments" do + Module.instance_method(:remove_method).arity.should < 0 + end + + it "does not remove any instance methods when argument not given" do + before = @module.instance_methods(true) + @module.private_instance_methods(true) + @module.send :remove_method + after = @module.instance_methods(true) + @module.private_instance_methods(true) + before.sort.should == after.sort + end + + it "returns self" do + @module.send(:remove_method, :method_to_remove).should equal(@module) + end + + it "raises a NameError when attempting to remove method further up the inheritance tree" do + Class.new(ModuleSpecs::Second) do + -> { + remove_method :method_to_remove + }.should raise_error(NameError) + end + end + + it "raises a NameError when attempting to remove a missing method" do + Class.new(ModuleSpecs::Second) do + -> { + remove_method :blah + }.should raise_error(NameError) + end + end + + describe "on frozen instance" do + before :each do + @frozen = @module.dup.freeze + end + + it "raises a FrozenError when passed a name" do + -> { @frozen.send :remove_method, :method_to_remove }.should raise_error(FrozenError) + end + + 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 + -> { @frozen.send :remove_method, Object.new }.should raise_error(TypeError) + end + + it "does not raise exceptions when no arguments given" do + @frozen.send(:remove_method).should equal(@frozen) + end + end +end diff --git a/spec/ruby/core/module/ruby2_keywords_spec.rb b/spec/ruby/core/module/ruby2_keywords_spec.rb new file mode 100644 index 0000000000..652f9f7083 --- /dev/null +++ b/spec/ruby/core/module/ruby2_keywords_spec.rb @@ -0,0 +1,248 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Module#ruby2_keywords" do + class << self + ruby2_keywords def mark(*args) + args + end + end + + it "marks the final hash argument as keyword hash" do + last = mark(1, 2, a: "a").last + Hash.ruby2_keywords_hash?(last).should == true + end + + it "makes a copy of the hash and only marks the copy as keyword hash" do + obj = Object.new + obj.singleton_class.class_exec do + def regular(*args) + args.last + end + end + + h = {a: 1} + + last = mark(**h).last + Hash.ruby2_keywords_hash?(last).should == true + Hash.ruby2_keywords_hash?(h).should == false + + last2 = mark(**last).last # last is already marked + Hash.ruby2_keywords_hash?(last2).should == true + Hash.ruby2_keywords_hash?(last).should == true + last2.should_not.equal?(last) + Hash.ruby2_keywords_hash?(h).should == false + end + + it "makes a copy and unmark the Hash when calling a method taking (arg)" do + obj = Object.new + obj.singleton_class.class_exec do + def single(arg) + arg + end + end + + h = { a: 1 } + args = mark(**h) + marked = args.last + Hash.ruby2_keywords_hash?(marked).should == true + + after_usage = obj.single(*args) + after_usage.should == h + after_usage.should_not.equal?(h) + after_usage.should_not.equal?(marked) + Hash.ruby2_keywords_hash?(after_usage).should == false + Hash.ruby2_keywords_hash?(marked).should == true + end + + it "makes a copy and unmark the Hash when calling a method taking (**kw)" do + obj = Object.new + obj.singleton_class.class_exec do + def kwargs(**kw) + kw + end + end + + h = { a: 1 } + args = mark(**h) + marked = args.last + Hash.ruby2_keywords_hash?(marked).should == true + + after_usage = obj.kwargs(*args) + after_usage.should == h + after_usage.should_not.equal?(h) + after_usage.should_not.equal?(marked) + Hash.ruby2_keywords_hash?(after_usage).should == false + Hash.ruby2_keywords_hash?(marked).should == true + end + + it "makes a copy and unmark the Hash when calling a method taking (*args)" do + obj = Object.new + obj.singleton_class.class_exec do + def splat(*args) + args.last + end + + def splat1(arg, *args) + args.last + end + + def proc_call(*args) + -> *a { a.last }.call(*args) + end + end + + h = { a: 1 } + args = mark(**h) + marked = args.last + Hash.ruby2_keywords_hash?(marked).should == true + + after_usage = obj.splat(*args) + after_usage.should == h + after_usage.should_not.equal?(h) + after_usage.should_not.equal?(marked) + Hash.ruby2_keywords_hash?(after_usage).should == false + Hash.ruby2_keywords_hash?(marked).should == true + + args = mark(1, **h) + marked = args.last + after_usage = obj.splat1(*args) + after_usage.should == h + after_usage.should_not.equal?(h) + after_usage.should_not.equal?(marked) + Hash.ruby2_keywords_hash?(after_usage).should == false + Hash.ruby2_keywords_hash?(marked).should == true + + args = mark(**h) + marked = args.last + after_usage = obj.proc_call(*args) + after_usage.should == h + after_usage.should_not.equal?(h) + after_usage.should_not.equal?(marked) + Hash.ruby2_keywords_hash?(after_usage).should == false + Hash.ruby2_keywords_hash?(marked).should == true + + args = mark(**h) + marked = args.last + after_usage = obj.send(:splat, *args) + after_usage.should == h + after_usage.should_not.equal?(h) + after_usage.should_not.equal?(marked) + Hash.ruby2_keywords_hash?(after_usage).should == false + Hash.ruby2_keywords_hash?(marked).should == true + end + + it "applies to the underlying method and applies across aliasing" do + obj = Object.new + + obj.singleton_class.class_exec do + def foo(*a) a.last end + alias_method :bar, :foo + ruby2_keywords :foo + + def baz(*a) a.last end + ruby2_keywords :baz + alias_method :bob, :baz + end + + last = obj.foo(1, 2, a: "a") + Hash.ruby2_keywords_hash?(last).should == true + + last = obj.bar(1, 2, a: "a") + Hash.ruby2_keywords_hash?(last).should == true + + last = obj.baz(1, 2, a: "a") + Hash.ruby2_keywords_hash?(last).should == true + + last = obj.bob(1, 2, a: "a") + Hash.ruby2_keywords_hash?(last).should == true + end + + it "returns nil" do + obj = Object.new + + obj.singleton_class.class_exec do + def foo(*a) end + + ruby2_keywords(:foo).should == nil + end + end + + it "raises NameError when passed not existing method name" do + obj = Object.new + + -> { + obj.singleton_class.class_exec do + ruby2_keywords :not_existing + end + }.should raise_error(NameError, /undefined method [`']not_existing'/) + end + + it "accepts String as well" do + obj = Object.new + + obj.singleton_class.class_exec do + def foo(*a) a.last end + ruby2_keywords "foo" + end + + last = obj.foo(1, 2, a: "a") + Hash.ruby2_keywords_hash?(last).should == true + end + + it "raises TypeError when passed not Symbol or String" do + obj = Object.new + + -> { + obj.singleton_class.class_exec do + ruby2_keywords Object.new + end + }.should raise_error(TypeError, /is not a symbol nor a string/) + end + + it "prints warning when a method does not accept argument splat" do + obj = Object.new + def obj.foo(a, b, c) end + + -> { + obj.singleton_class.class_exec do + ruby2_keywords :foo + end + }.should complain(/Skipping set of ruby2_keywords flag for/) + end + + it "prints warning when a method accepts keywords" do + obj = Object.new + def obj.foo(*a, b:) end + + -> { + obj.singleton_class.class_exec do + ruby2_keywords :foo + end + }.should complain(/Skipping set of ruby2_keywords flag for/) + end + + it "prints warning when a method accepts keyword splat" do + obj = Object.new + def obj.foo(*a, **b) end + + -> { + obj.singleton_class.class_exec do + ruby2_keywords :foo + end + }.should complain(/Skipping set of ruby2_keywords flag for/) + end + + ruby_version_is "4.0" do + it "prints warning when a method accepts post arguments" do + obj = Object.new + def obj.foo(*a, b) end + + -> { + obj.singleton_class.class_exec do + ruby2_keywords :foo + end + }.should complain(/Skipping set of ruby2_keywords flag for/) + end + end +end diff --git a/spec/ruby/core/module/set_temporary_name_spec.rb b/spec/ruby/core/module/set_temporary_name_spec.rb new file mode 100644 index 0000000000..46605ed675 --- /dev/null +++ b/spec/ruby/core/module/set_temporary_name_spec.rb @@ -0,0 +1,147 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/set_temporary_name' + +ruby_version_is "3.3" do + describe "Module#set_temporary_name" do + it "can assign a temporary name" do + m = Module.new + m.name.should be_nil + + m.set_temporary_name("fake_name") + m.name.should == "fake_name" + + m.set_temporary_name(nil) + m.name.should be_nil + end + + it "returns self" do + m = Module.new + m.set_temporary_name("fake_name").should.equal? m + end + + it "can assign a temporary name which is not a valid constant path" do + m = Module.new + + m.set_temporary_name("name") + m.name.should == "name" + + m.set_temporary_name("Template['foo.rb']") + m.name.should == "Template['foo.rb']" + + m.set_temporary_name("a::B") + m.name.should == "a::B" + + m.set_temporary_name("A::b") + m.name.should == "A::b" + + m.set_temporary_name("A::B::") + m.name.should == "A::B::" + + m.set_temporary_name("A::::B") + m.name.should == "A::::B" + + m.set_temporary_name("A=") + m.name.should == "A=" + end + + it "can't assign empty string as name" do + m = Module.new + -> { m.set_temporary_name("") }.should raise_error(ArgumentError, "empty class/module name") + end + + it "can't assign a constant name as a temporary name" do + m = Module.new + -> { m.set_temporary_name("Object") }.should raise_error(ArgumentError, "the temporary name must not be a constant path to avoid confusion") + end + + it "can't assign a constant path as a temporary name" do + m = Module.new + -> { m.set_temporary_name("A::B") }.should raise_error(ArgumentError, "the temporary name must not be a constant path to avoid confusion") + -> { m.set_temporary_name("::A") }.should raise_error(ArgumentError, "the temporary name must not be a constant path to avoid confusion") + -> { m.set_temporary_name("::A::B") }.should raise_error(ArgumentError, "the temporary name must not be a constant path to avoid confusion") + end + + it "can't assign name to permanent module" do + -> { Object.set_temporary_name("fake_name") }.should raise_error(RuntimeError, "can't change permanent name") + end + + it "can assign a temporary name to a module nested into an anonymous module" do + m = Module.new + module m::N; end + m::N.name.should =~ /\A#<Module:0x\h+>::N\z/ + + m::N.set_temporary_name("fake_name") + m::N.name.should == "fake_name" + + m::N.set_temporary_name(nil) + m::N.name.should be_nil + end + + it "discards a temporary name when an outer anonymous module gets a permanent name" do + m = Module.new + module m::N; end + + m::N.set_temporary_name("fake_name") + m::N.name.should == "fake_name" + + ModuleSpecs::SetTemporaryNameSpec::M = m + m::N.name.should == "ModuleSpecs::SetTemporaryNameSpec::M::N" + ModuleSpecs::SetTemporaryNameSpec.send :remove_const, :M + end + + it "can update the name when assigned to a constant" do + m = Module.new + m::N = Module.new + m::N.name.should =~ /\A#<Module:0x\h+>::N\z/ + m::N.set_temporary_name(nil) + + m::M = m::N + m::M.name.should =~ /\A#<Module:0x\h+>::M\z/m + end + + it "can reassign a temporary name repeatedly" do + m = Module.new + + m.set_temporary_name("fake_name") + m.name.should == "fake_name" + + m.set_temporary_name("fake_name_2") + m.name.should == "fake_name_2" + end + + ruby_bug "#21094", ""..."4.0" do + it "also updates a name of a nested module" do + m = Module.new + m::N = Module.new + m::N.name.should =~ /\A#<Module:0x\h+>::N\z/ + + m.set_temporary_name "m" + m::N.name.should == "m::N" + + m.set_temporary_name nil + m::N.name.should == nil + end + end + + it "keeps temporary name when assigned in an anonymous module" do + outer = Module.new + m = Module.new + m.set_temporary_name "m" + m.name.should == "m" + outer::M = m + m.name.should == "m" + m.inspect.should == "m" + end + + it "keeps temporary name when assigned in an anonymous module and nested before" do + outer = Module.new + m = Module.new + outer::A = m + m.set_temporary_name "m" + m.name.should == "m" + outer::M = m + m.name.should == "m" + m.inspect.should == "m" + end + end +end diff --git a/spec/ruby/core/module/shared/attr_added.rb b/spec/ruby/core/module/shared/attr_added.rb new file mode 100644 index 0000000000..ce832cdcff --- /dev/null +++ b/spec/ruby/core/module/shared/attr_added.rb @@ -0,0 +1,34 @@ +describe :module_attr_added, shared: true do + it "calls method_added for normal classes" do + ScratchPad.record [] + + cls = Class.new do + class << self + def method_added(name) + ScratchPad.recorded << name + end + end + end + + cls.send(@method, :foo) + + ScratchPad.recorded.each {|name| name.to_s.should =~ /foo[=]?/} + end + + it "calls singleton_method_added for singleton classes" do + ScratchPad.record [] + cls = Class.new do + class << self + def singleton_method_added(name) + # called for this def so ignore it + return if name == :singleton_method_added + ScratchPad.recorded << name + end + end + end + + cls.singleton_class.send(@method, :foo) + + ScratchPad.recorded.each {|name| name.to_s.should =~ /foo[=]?/} + end +end diff --git a/spec/ruby/core/module/shared/class_eval.rb b/spec/ruby/core/module/shared/class_eval.rb new file mode 100644 index 0000000000..b1d5cb3814 --- /dev/null +++ b/spec/ruby/core/module/shared/class_eval.rb @@ -0,0 +1,174 @@ +describe :module_class_eval, shared: true do + # TODO: This should probably be replaced with a "should behave like" that uses + # the many scoping/binding specs from kernel/eval_spec, since most of those + # behaviors are the same for instance_eval. See also module_eval/class_eval. + + it "evaluates a given string in the context of self" do + ModuleSpecs.send(@method, "self").should == ModuleSpecs + ModuleSpecs.send(@method, "1 + 1").should == 2 + end + + it "does not add defined methods to other classes" do + FalseClass.send(@method) do + def foo + 'foo' + end + end + -> {42.foo}.should raise_error(NoMethodError) + end + + it "resolves constants in the caller scope" do + ModuleSpecs::ClassEvalTest.get_constant_from_scope.should == ModuleSpecs::Lookup + end + + it "resolves constants in the caller scope ignoring send" do + ModuleSpecs::ClassEvalTest.get_constant_from_scope_with_send(@method).should == ModuleSpecs::Lookup + end + + it "resolves constants in the receiver's scope" do + ModuleSpecs.send(@method, "Lookup").should == ModuleSpecs::Lookup + ModuleSpecs.send(@method, "Lookup::LOOKIE").should == ModuleSpecs::Lookup::LOOKIE + end + + it "defines constants in the receiver's scope" do + ModuleSpecs.send(@method, "module NewEvaluatedModule;end") + ModuleSpecs.const_defined?(:NewEvaluatedModule, false).should == true + end + + it "evaluates a given block in the context of self" do + ModuleSpecs.send(@method) { self }.should == ModuleSpecs + ModuleSpecs.send(@method) { 1 + 1 }.should == 2 + end + + it "passes the module as the first argument of the block" do + given = nil + ModuleSpecs.send(@method) do |block_parameter| + given = block_parameter + end + given.should equal ModuleSpecs + end + + it "uses the optional filename and lineno parameters for error messages" do + ModuleSpecs.send(@method, "[__FILE__, __LINE__]", "test", 102).should == ["test", 102] + end + + ruby_version_is "3.3" do + it "uses the caller location as default filename" do + ModuleSpecs.send(@method, "[__FILE__, __LINE__]").should == ["(eval at #{__FILE__}:#{__LINE__})", 1] + end + end + + it "converts a non-string filename to a string using to_str" do + (file = mock(__FILE__)).should_receive(:to_str).and_return(__FILE__) + ModuleSpecs.send(@method, "1+1", file) + + (file = mock(__FILE__)).should_receive(:to_str).and_return(__FILE__) + ModuleSpecs.send(@method, "1+1", file, 15) + end + + it "raises a TypeError when the given filename can't be converted to string using to_str" do + (file = mock('123')).should_receive(:to_str).and_return(123) + -> { ModuleSpecs.send(@method, "1+1", file) }.should raise_error(TypeError, /can't convert MockObject to String/) + end + + it "converts non string eval-string to string using to_str" do + (o = mock('1 + 1')).should_receive(:to_str).and_return("1 + 1") + ModuleSpecs.send(@method, o).should == 2 + + (o = mock('1 + 1')).should_receive(:to_str).and_return("1 + 1") + ModuleSpecs.send(@method, o, "file.rb").should == 2 + + (o = mock('1 + 1')).should_receive(:to_str).and_return("1 + 1") + ModuleSpecs.send(@method, o, "file.rb", 15).should == 2 + end + + it "raises a TypeError when the given eval-string can't be converted to string using to_str" do + o = mock('x') + -> { ModuleSpecs.send(@method, o) }.should raise_error(TypeError, "no implicit conversion of MockObject into String") + + (o = mock('123')).should_receive(:to_str).and_return(123) + -> { ModuleSpecs.send(@method, o) }.should raise_error(TypeError, /can't convert MockObject to String/) + end + + it "raises an ArgumentError when no arguments and no block are given" do + -> { ModuleSpecs.send(@method) }.should raise_error(ArgumentError, "wrong number of arguments (given 0, expected 1..3)") + end + + it "raises an ArgumentError when more than 3 arguments are given" do + -> { + ModuleSpecs.send(@method, "1 + 1", "some file", 0, "bogus") + }.should raise_error(ArgumentError, "wrong number of arguments (given 4, expected 1..3)") + end + + it "raises an ArgumentError when a block and normal arguments are given" do + -> { + ModuleSpecs.send(@method, "1 + 1") { 1 + 1 } + }.should raise_error(ArgumentError, "wrong number of arguments (given 1, expected 0)") + end + + # This case was found because Rubinius was caching the compiled + # version of the string and not duping the methods within the + # eval, causing the method addition to change the static scope + # of the shared CompiledCode. + it "adds methods respecting the lexical constant scope" do + code = "def self.attribute; C; end" + + a = Class.new do + self::C = "A" + end + + b = Class.new do + self::C = "B" + end + + a.send @method, code + b.send @method, code + + 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 new file mode 100644 index 0000000000..c7a9e5297f --- /dev/null +++ b/spec/ruby/core/module/shared/class_exec.rb @@ -0,0 +1,29 @@ +describe :module_class_exec, shared: true do + it "does not add defined methods to other classes" do + FalseClass.send(@method) do + def foo + 'foo' + end + end + -> {42.foo}.should raise_error(NoMethodError) + end + + it "defines method in the receiver's scope" do + ModuleSpecs::Subclass.send(@method) { def foo; end } + ModuleSpecs::Subclass.new.respond_to?(:foo).should == true + end + + it "evaluates a given block in the context of self" do + ModuleSpecs::Subclass.send(@method) { self }.should == ModuleSpecs::Subclass + ModuleSpecs::Subclass.new.send(@method) { 1 + 1 }.should == 2 + end + + it "raises a LocalJumpError when no block is given" do + -> { ModuleSpecs::Subclass.send(@method) }.should raise_error(LocalJumpError) + end + + it "passes arguments to the block" do + a = ModuleSpecs::Subclass + a.send(@method, 1) { |b| b }.should equal(1) + end +end diff --git a/spec/ruby/core/module/shared/equal_value.rb b/spec/ruby/core/module/shared/equal_value.rb new file mode 100644 index 0000000000..f1227d873c --- /dev/null +++ b/spec/ruby/core/module/shared/equal_value.rb @@ -0,0 +1,14 @@ +describe :module_equal, shared: true do + it "returns true if self and the given module are the same" do + ModuleSpecs.send(@method, ModuleSpecs).should == true + ModuleSpecs::Child.send(@method, ModuleSpecs::Child).should == true + ModuleSpecs::Parent.send(@method, ModuleSpecs::Parent).should == true + ModuleSpecs::Basic.send(@method, ModuleSpecs::Basic).should == true + ModuleSpecs::Super.send(@method, ModuleSpecs::Super).should == true + + ModuleSpecs::Child.send(@method, ModuleSpecs).should == false + ModuleSpecs::Child.send(@method, ModuleSpecs::Parent).should == false + ModuleSpecs::Child.send(@method, ModuleSpecs::Basic).should == false + ModuleSpecs::Child.send(@method, ModuleSpecs::Super).should == false + end +end diff --git a/spec/ruby/core/module/shared/set_visibility.rb b/spec/ruby/core/module/shared/set_visibility.rb new file mode 100644 index 0000000000..a1586dd2bd --- /dev/null +++ b/spec/ruby/core/module/shared/set_visibility.rb @@ -0,0 +1,184 @@ +# -*- encoding: us-ascii -*- + +describe :set_visibility, shared: true do + it "is a private method" 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 + mod = Module.new { + send visibility + + def test1() end + def test2() end + } + + mod.should send(:"have_#{@method}_instance_method", :test1, false) + mod.should send(:"have_#{@method}_instance_method", :test2, false) + end + + it "stops setting visibility if the body encounters other visibility setters without arguments" do + visibility = @method + new_visibility = nil + mod = Module.new { + send visibility + new_visibility = [:protected, :private].find {|vis| vis != visibility } + send new_visibility + def test1() end + } + + mod.should send(:"have_#{new_visibility}_instance_method", :test1, false) + end + + it "continues setting visibility if the body encounters other visibility setters with arguments" do + visibility = @method + mod = Module.new { + send visibility + def test1() end + send([:protected, :private].find {|vis| vis != visibility }, :test1) + def test2() end + } + + mod.should send(:"have_#{@method}_instance_method", :test2, false) + end + + it "does not affect module_evaled method definitions when itself is outside the eval" do + visibility = @method + mod = Module.new { + send visibility + + module_eval { def test1() end } + module_eval " def test2() end " + } + + mod.should have_public_instance_method(:test1, false) + mod.should have_public_instance_method(:test2, false) + end + + it "does not affect outside method definitions when itself is inside a module_eval" do + visibility = @method + mod = Module.new { + module_eval { send visibility } + + def test1() end + } + + mod.should have_public_instance_method(:test1, false) + end + + it "affects normally if itself and method definitions are inside a module_eval" do + visibility = @method + mod = Module.new { + module_eval { + send visibility + + def test1() end + } + } + + mod.should send(:"have_#{@method}_instance_method", :test1, false) + end + + it "does not affect method definitions when itself is inside an eval and method definitions are outside" do + visibility = @method + initialized_visibility = [:public, :protected, :private].find {|sym| sym != visibility } + mod = Module.new { + send initialized_visibility + eval visibility.to_s + + def test1() end + } + + mod.should send(:"have_#{initialized_visibility}_instance_method", :test1, false) + end + + it "affects evaled method definitions when itself is outside the eval" do + visibility = @method + mod = Module.new { + send visibility + + eval "def test1() end" + } + + mod.should send(:"have_#{@method}_instance_method", :test1, false) + end + + it "affects normally if itself and following method definitions are inside a eval" do + visibility = @method + mod = Module.new { + eval <<-CODE + #{visibility} + + def test1() end + CODE + } + + mod.should send(:"have_#{@method}_instance_method", :test1, false) + end + + describe "within a closure" do + it "sets the visibility outside the closure" do + visibility = @method + mod = Module.new { + 1.times { + send visibility + } + def test1() end + } + + mod.should send(:"have_#{@method}_instance_method", :test1, false) + end + end + end +end diff --git a/spec/ruby/core/module/singleton_class_spec.rb b/spec/ruby/core/module/singleton_class_spec.rb new file mode 100644 index 0000000000..052755b73b --- /dev/null +++ b/spec/ruby/core/module/singleton_class_spec.rb @@ -0,0 +1,27 @@ +require_relative '../../spec_helper' + +describe "Module#singleton_class?" do + it "returns true for singleton classes" do + xs = self.singleton_class + xs.should.singleton_class? + end + + it "returns false for other classes" do + c = Class.new + c.should_not.singleton_class? + end + + describe "with singleton values" do + it "returns false for nil's singleton class" do + NilClass.should_not.singleton_class? + end + + it "returns false for true's singleton class" do + TrueClass.should_not.singleton_class? + end + + it "returns false for false's singleton class" do + 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 new file mode 100644 index 0000000000..83c0ae0825 --- /dev/null +++ b/spec/ruby/core/module/to_s_spec.rb @@ -0,0 +1,70 @@ +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 =~ /\A#<Module:0x\h+>\z/ + end + + it "works with an anonymous class" do + c = Class.new + 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 new file mode 100644 index 0000000000..d4efcd51cb --- /dev/null +++ b/spec/ruby/core/module/undef_method_spec.rb @@ -0,0 +1,181 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +module ModuleSpecs + class Parent + def method_to_undef() 1 end + def another_method_to_undef() 1 end + end + + class Ancestor + def method_to_undef() 1 end + def another_method_to_undef() 1 end + end +end + +describe "Module#undef_method" do + before :each do + @module = Module.new { def method_to_undef; end } + end + + it "is a public method" do + Module.should have_public_instance_method(:undef_method, false) + end + + it "requires multiple arguments" do + Module.instance_method(:undef_method).arity.should < 0 + end + + it "allows multiple methods to be removed at once" do + klass = Class.new do + def method_to_undef() 1 end + def another_method_to_undef() 1 end + end + x = klass.new + klass.send(:undef_method, :method_to_undef, :another_method_to_undef) + + -> { 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 + before = @module.instance_methods(true) + @module.private_instance_methods(true) + @module.send :undef_method + after = @module.instance_methods(true) + @module.private_instance_methods(true) + before.sort.should == after.sort + end + + it "returns self" do + @module.send(:undef_method, :method_to_undef).should equal(@module) + end + + it "raises a NameError when passed a missing name for a module" do + -> { @module.send :undef_method, :not_exist }.should raise_error(NameError, /undefined method [`']not_exist' for module [`']#{@module}'/) { |e| + # 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 + } + end + + describe "on frozen instance" do + before :each do + @frozen = @module.dup.freeze + end + + it "raises a FrozenError when passed a name" do + -> { @frozen.send :undef_method, :method_to_undef }.should raise_error(FrozenError) + end + + 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 + -> { @frozen.send :undef_method, Object.new }.should raise_error(TypeError) + end + + it "does not raise exceptions when no arguments given" do + @frozen.send(:undef_method).should equal(@frozen) + end + end +end + +describe "Module#undef_method with symbol" do + it "removes a method defined in a class" do + klass = Class.new do + def method_to_undef() 1 end + def another_method_to_undef() 1 end + end + x = klass.new + + x.method_to_undef.should == 1 + + klass.send :undef_method, :method_to_undef + + -> { x.method_to_undef }.should raise_error(NoMethodError) + end + + it "removes a method defined in a super class" do + child_class = Class.new(ModuleSpecs::Parent) + child = child_class.new + child.method_to_undef.should == 1 + + child_class.send :undef_method, :method_to_undef + + -> { 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 + descendant = Class.new(ModuleSpecs::Ancestor) + ancestor = ModuleSpecs::Ancestor.new + ancestor.method_to_undef.should == 1 + + descendant.send :undef_method, :method_to_undef + + ancestor.method_to_undef.should == 1 + end +end + +describe "Module#undef_method with string" do + it "removes a method defined in a class" do + klass = Class.new do + def method_to_undef() 1 end + def another_method_to_undef() 1 end + end + x = klass.new + + x.another_method_to_undef.should == 1 + + klass.send :undef_method, 'another_method_to_undef' + + -> { x.another_method_to_undef }.should raise_error(NoMethodError) + end + + it "removes a method defined in a super class" do + child_class = Class.new(ModuleSpecs::Parent) + child = child_class.new + child.another_method_to_undef.should == 1 + + child_class.send :undef_method, 'another_method_to_undef' + + -> { 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 + descendant = Class.new(ModuleSpecs::Ancestor) + ancestor = ModuleSpecs::Ancestor.new + ancestor.another_method_to_undef.should == 1 + + descendant.send :undef_method, 'another_method_to_undef' + + ancestor.another_method_to_undef.should == 1 + end +end 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 new file mode 100644 index 0000000000..a908363c96 --- /dev/null +++ b/spec/ruby/core/module/using_spec.rb @@ -0,0 +1,377 @@ +require_relative '../../spec_helper' + +describe "Module#using" do + it "imports class refinements from module into the current class/module" do + refinement = Module.new do + refine Integer do + def foo; "foo"; end + end + end + + result = nil + Module.new do + using refinement + result = 1.foo + end + + result.should == "foo" + end + + it "accepts module as argument" do + refinement = Module.new do + refine Integer do + def foo; "foo"; end + end + end + + -> { + Module.new do + using refinement + end + }.should_not raise_error + end + + it "accepts module without refinements" do + mod = Module.new + + -> { + Module.new do + using mod + end + }.should_not raise_error + end + + it "does not accept class" do + klass = Class.new + + -> { + Module.new do + using klass + end + }.should raise_error(TypeError) + end + + it "raises TypeError if passed something other than module" do + -> { + Module.new do + using "foo" + end + }.should raise_error(TypeError) + end + + it "returns self" do + refinement = Module.new + + result = nil + mod = Module.new do + result = using refinement + end + + result.should equal(mod) + end + + it "works in classes too" do + refinement = Module.new do + refine Integer do + def foo; "foo"; end + end + end + + result = nil + Class.new do + using refinement + result = 1.foo + end + + result.should == "foo" + end + + it "raises error in method scope" do + mod = Module.new do + def self.foo + using Module.new {} + end + end + + -> { + mod.foo + }.should raise_error(RuntimeError, /Module#using is not permitted in methods/) + end + + it "activates refinement even for existed objects" do + result = nil + + Module.new do + klass = Class.new do + def foo; "foo"; end + end + + refinement = Module.new do + refine klass do + def foo; "foo from refinement"; end + end + end + + obj = klass.new + using refinement + result = obj.foo + end + + result.should == "foo from refinement" + end + + it "activates updates when refinement reopens later" do + result = nil + + Module.new do + klass = Class.new do + def foo; "foo"; end + end + + refinement = Module.new do + refine klass do + def foo; "foo from refinement"; end + end + end + + using refinement + + refinement.class_eval do + refine klass do + def foo; "foo from reopened refinement"; end + end + end + + obj = klass.new + result = obj.foo + end + + result.should == "foo from reopened refinement" + end + + describe "scope of refinement" do + it "is active until the end of current class/module" do + ScratchPad.record [] + + Module.new do + Class.new do + using Module.new { + refine String do + def to_s; "hello from refinement"; end + end + } + ScratchPad << "1".to_s + end + + ScratchPad << "1".to_s + end + + ScratchPad.recorded.should == ["hello from refinement", "1"] + end + + # Refinements are lexical in scope. + # Refinements are only active within a scope after the call to using. + # Any code before the using statement will not have the refinement activated. + it "is not active before the `using` call" do + ScratchPad.record [] + + Module.new do + Class.new do + ScratchPad << "1".to_s + using Module.new { + refine String do + def to_s; "hello from refinement"; end + end + } + ScratchPad << "1".to_s + end + end + + ScratchPad.recorded.should == ["1", "hello from refinement"] + end + + # If you call a method that is defined outside the current scope + # the refinement will be deactivated + it "is not active for code defined outside the current scope" do + result = nil + + Module.new do + klass = Class.new do + def foo; "foo"; end + end + + refinement = Module.new do + refine klass do + def foo; "foo from refinement"; end + end + end + + def self.call_foo(c) + c.foo + end + + using refinement + + result = call_foo(klass.new) + end + + result.should == "foo" + end + + # If a method is defined in a scope where a refinement is active + # the refinement will be active when the method is called. + it "is active for method defined in a scope wherever it's called" do + klass = Class.new do + def foo; "foo"; end + end + + mod = Module.new do + refinement = Module.new do + refine klass do + def foo; "foo from refinement"; end + end + end + + using refinement + + def self.call_foo(c) + c.foo + end + end + + c = klass.new + 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 + + Module.new do + if false + using Module.new { + refine String do + def to_s; "hello from refinement"; end + end + } + end + result = "1".to_s + end + + result.should == "1" + end + + # The refinements in module are not activated automatically + # if the class is reopened later + it "is not active when class/module reopens" do + refinement = Module.new do + refine String do + def to_s + "hello from refinement" + end + end + end + + result = [] + klass = Class.new do + using refinement + result << "1".to_s + end + + klass.class_eval do + result << "1".to_s + end + + result.should == ["hello from refinement", "1"] + end + end +end |
