diff options
Diffstat (limited to 'spec/ruby/core/unboundmethod')
22 files changed, 1006 insertions, 0 deletions
diff --git a/spec/ruby/core/unboundmethod/arity_spec.rb b/spec/ruby/core/unboundmethod/arity_spec.rb new file mode 100644 index 0000000000..cd700b9f9b --- /dev/null +++ b/spec/ruby/core/unboundmethod/arity_spec.rb @@ -0,0 +1,207 @@ +require_relative '../../spec_helper' + +describe "UnboundMethod#arity" do + SpecEvaluate.desc = "for method definition" + + context "returns zero" do + evaluate <<-ruby do + def m() end + ruby + + method(:m).unbind.arity.should == 0 + end + + evaluate <<-ruby do + def n(&b) end + ruby + + method(:n).unbind.arity.should == 0 + end + end + + context "returns positive values" do + evaluate <<-ruby do + def m(a) end + def n(a, b) end + def o(a, b, c) end + def p(a, b, c, d) end + ruby + + method(:m).unbind.arity.should == 1 + method(:n).unbind.arity.should == 2 + method(:o).unbind.arity.should == 3 + method(:p).unbind.arity.should == 4 + end + + evaluate <<-ruby do + def m(a:) end + def n(a:, b:) end + def o(a: 1, b:, c:, d: 2) end + ruby + + method(:m).unbind.arity.should == 1 + method(:n).unbind.arity.should == 1 + method(:o).unbind.arity.should == 1 + end + + evaluate <<-ruby do + def m(a, b:) end + def n(a, b:, &l) end + ruby + + method(:m).unbind.arity.should == 2 + method(:n).unbind.arity.should == 2 + end + + evaluate <<-ruby do + def m(a, b, c:, d: 1) end + def n(a, b, c:, d: 1, **k, &l) end + ruby + + method(:m).unbind.arity.should == 3 + method(:n).unbind.arity.should == 3 + end + end + + context "returns negative values" do + evaluate <<-ruby do + def m(a=1) end + def n(a=1, b=2) end + ruby + + method(:m).unbind.arity.should == -1 + method(:n).unbind.arity.should == -1 + end + + evaluate <<-ruby do + def m(a, b=1) end + def n(a, b, c=1, d=2) end + ruby + + method(:m).unbind.arity.should == -2 + method(:n).unbind.arity.should == -3 + end + + evaluate <<-ruby do + def m(a=1, *b) end + def n(a=1, b=2, *c) end + ruby + + method(:m).unbind.arity.should == -1 + method(:n).unbind.arity.should == -1 + end + + evaluate <<-ruby do + def m(*) end + def n(*a) end + ruby + + method(:m).unbind.arity.should == -1 + method(:n).unbind.arity.should == -1 + end + + evaluate <<-ruby do + def m(a, *) end + def n(a, *b) end + def o(a, b, *c) end + def p(a, b, c, *d) end + ruby + + method(:m).unbind.arity.should == -2 + method(:n).unbind.arity.should == -2 + method(:o).unbind.arity.should == -3 + method(:p).unbind.arity.should == -4 + end + + evaluate <<-ruby do + def m(*a, b) end + def n(*a, b, c) end + def o(*a, b, c, d) end + ruby + + method(:m).unbind.arity.should == -2 + method(:n).unbind.arity.should == -3 + method(:o).unbind.arity.should == -4 + end + + evaluate <<-ruby do + def m(a, *b, c) end + def n(a, b, *c, d, e) end + ruby + + method(:m).unbind.arity.should == -3 + method(:n).unbind.arity.should == -5 + end + + evaluate <<-ruby do + def m(a, b=1, c=2, *d, e, f) end + def n(a, b, c=1, *d, e, f, g) end + ruby + + method(:m).unbind.arity.should == -4 + method(:n).unbind.arity.should == -6 + end + + evaluate <<-ruby do + def m(a: 1) end + def n(a: 1, b: 2) end + ruby + + method(:m).unbind.arity.should == -1 + method(:n).unbind.arity.should == -1 + end + + evaluate <<-ruby do + def m(a=1, b: 2) end + def n(*a, b: 1) end + def o(a=1, b: 2) end + def p(a=1, *b, c: 2, &l) end + ruby + + method(:m).unbind.arity.should == -1 + method(:n).unbind.arity.should == -1 + method(:o).unbind.arity.should == -1 + method(:p).unbind.arity.should == -1 + end + + evaluate <<-ruby do + def m(**k, &l) end + def n(*a, **k) end + def o(a: 1, b: 2, **k) end + ruby + + method(:m).unbind.arity.should == -1 + method(:n).unbind.arity.should == -1 + method(:o).unbind.arity.should == -1 + end + + evaluate <<-ruby do + def m(a=1, *b, c:, d: 2, **k, &l) end + ruby + + method(:m).unbind.arity.should == -2 + end + + evaluate <<-ruby do + def m(a, b=1, *c, d, e:, f: 2, **k, &l) end + def n(a, b=1, *c, d:, e:, f: 2, **k, &l) end + def o(a=0, b=1, *c, d, e:, f: 2, **k, &l) end + def p(a=0, b=1, *c, d:, e:, f: 2, **k, &l) end + ruby + + method(:m).unbind.arity.should == -4 + method(:n).unbind.arity.should == -3 + method(:o).unbind.arity.should == -3 + method(:p).unbind.arity.should == -2 + end + end + + context "for a Method generated by respond_to_missing?" do + it "returns -1" do + obj = mock("method arity respond_to_missing") + obj.should_receive(:respond_to_missing?).and_return(true) + + obj.method(:m).unbind.arity.should == -1 + end + end +end diff --git a/spec/ruby/core/unboundmethod/bind_call_spec.rb b/spec/ruby/core/unboundmethod/bind_call_spec.rb new file mode 100644 index 0000000000..ee1dad9c9e --- /dev/null +++ b/spec/ruby/core/unboundmethod/bind_call_spec.rb @@ -0,0 +1,58 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "UnboundMethod#bind_call" do + before :each do + @normal_um = UnboundMethodSpecs::Methods.new.method(:foo).unbind + @parent_um = UnboundMethodSpecs::Parent.new.method(:foo).unbind + @child1_um = UnboundMethodSpecs::Child1.new.method(:foo).unbind + @child2_um = UnboundMethodSpecs::Child2.new.method(:foo).unbind + @normal_um_super = UnboundMethodSpecs::Mod.instance_method(:foo_super) + @parent_um_super = UnboundMethodSpecs::Parent.new.method(:foo_super).unbind + end + + it "raises TypeError if object is not kind_of? the Module the method defined in" do + -> { @normal_um.bind_call(UnboundMethodSpecs::B.new) }.should.raise(TypeError) + end + + it "binds and calls the method if object is kind_of the Module the method defined in" do + @normal_um.bind_call(UnboundMethodSpecs::Methods.new).should == true + end + + it "binds and calls the method on any object when UnboundMethod is unbound from a module" do + UnboundMethodSpecs::Mod.instance_method(:from_mod).bind_call(Object.new).should == nil + end + + it "binds and calls the method for any object kind_of? the Module the method is defined in" do + @parent_um.bind_call(UnboundMethodSpecs::Child1.new).should == nil + @child1_um.bind_call(UnboundMethodSpecs::Parent.new).should == nil + @child2_um.bind_call(UnboundMethodSpecs::Child1.new).should == nil + end + + it "binds and calls a Kernel method retrieved from Object on BasicObject" do + Object.instance_method(:instance_of?).bind_call(BasicObject.new, BasicObject).should == true + end + + it "binds and calls a Parent's class method to any Child's class methods" do + um = UnboundMethodSpecs::Parent.method(:class_method).unbind + um.bind_call(UnboundMethodSpecs::Child1).should == "I am UnboundMethodSpecs::Child1" + end + + it "will raise when binding a an object singleton's method to another object" do + other = UnboundMethodSpecs::Parent.new + p = UnboundMethodSpecs::Parent.new + class << p + def singleton_method + :single + end + end + um = p.method(:singleton_method).unbind + ->{ um.bind_call(other) }.should.raise(TypeError) + end + + it "allows calling super for module methods bound to hierarchies that do not already have that module" do + p = UnboundMethodSpecs::Parent.new + + @normal_um_super.bind_call(p).should == true + end +end diff --git a/spec/ruby/core/unboundmethod/bind_spec.rb b/spec/ruby/core/unboundmethod/bind_spec.rb new file mode 100644 index 0000000000..087994ff57 --- /dev/null +++ b/spec/ruby/core/unboundmethod/bind_spec.rb @@ -0,0 +1,69 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "UnboundMethod#bind" do + before :each do + @normal_um = UnboundMethodSpecs::Methods.new.method(:foo).unbind + @parent_um = UnboundMethodSpecs::Parent.new.method(:foo).unbind + @child1_um = UnboundMethodSpecs::Child1.new.method(:foo).unbind + @child2_um = UnboundMethodSpecs::Child2.new.method(:foo).unbind + @normal_um_super = UnboundMethodSpecs::Mod.instance_method(:foo_super) + @parent_um_super = UnboundMethodSpecs::Parent.new.method(:foo_super).unbind + end + + it "raises TypeError if object is not kind_of? the Module the method defined in" do + -> { @normal_um.bind(UnboundMethodSpecs::B.new) }.should.raise(TypeError) + end + + it "returns Method for any object that is kind_of? the Module method was extracted from" do + @normal_um.bind(UnboundMethodSpecs::Methods.new).should.is_a?(Method) + end + + it "returns Method on any object when UnboundMethod is unbound from a module" do + UnboundMethodSpecs::Mod.instance_method(:from_mod).bind(Object.new).should.is_a?(Method) + end + + it "the returned Method is equal to the one directly returned by obj.method" do + obj = UnboundMethodSpecs::Methods.new + @normal_um.bind(obj).should == obj.method(:foo) + end + + it "returns Method for any object kind_of? the Module the method is defined in" do + @parent_um.bind(UnboundMethodSpecs::Child1.new).should.is_a?(Method) + @child1_um.bind(UnboundMethodSpecs::Parent.new).should.is_a?(Method) + @child2_um.bind(UnboundMethodSpecs::Child1.new).should.is_a?(Method) + end + + it "allows binding a Kernel method retrieved from Object on BasicObject" do + Object.instance_method(:instance_of?).bind(BasicObject.new).call(BasicObject).should == true + end + + it "returns a callable method" do + obj = UnboundMethodSpecs::Methods.new + @normal_um.bind(obj).call.should == obj.foo + end + + it "binds a Parent's class method to any Child's class methods" do + m = UnboundMethodSpecs::Parent.method(:class_method).unbind.bind(UnboundMethodSpecs::Child1) + m.should.instance_of?(Method) + m.call.should == "I am UnboundMethodSpecs::Child1" + end + + it "will raise when binding a an object singleton's method to another object" do + other = UnboundMethodSpecs::Parent.new + p = UnboundMethodSpecs::Parent.new + class << p + def singleton_method + :single + end + end + um = p.method(:singleton_method).unbind + ->{ um.bind(other) }.should.raise(TypeError) + end + + it "allows calling super for module methods bound to hierarchies that do not already have that module" do + p = UnboundMethodSpecs::Parent.new + + @normal_um_super.bind(p).call.should == true + end +end diff --git a/spec/ruby/core/unboundmethod/clone_spec.rb b/spec/ruby/core/unboundmethod/clone_spec.rb new file mode 100644 index 0000000000..1e7fb18744 --- /dev/null +++ b/spec/ruby/core/unboundmethod/clone_spec.rb @@ -0,0 +1,13 @@ +require_relative '../../spec_helper' +require_relative 'shared/dup' + +describe "UnboundMethod#clone" do + it_behaves_like :unboundmethod_dup, :clone + + it "preserves frozen status" do + method = Class.instance_method(:instance_method) + method.freeze + method.frozen?.should == true + method.clone.frozen?.should == true + end +end diff --git a/spec/ruby/core/unboundmethod/dup_spec.rb b/spec/ruby/core/unboundmethod/dup_spec.rb new file mode 100644 index 0000000000..5a78dd8e36 --- /dev/null +++ b/spec/ruby/core/unboundmethod/dup_spec.rb @@ -0,0 +1,15 @@ +require_relative '../../spec_helper' +require_relative 'shared/dup' + +describe "UnboundMethod#dup" do + ruby_version_is "3.4" do + it_behaves_like :unboundmethod_dup, :dup + + it "resets frozen status" do + method = Class.instance_method(:instance_method) + method.freeze + method.frozen?.should == true + method.dup.frozen?.should == false + end + end +end diff --git a/spec/ruby/core/unboundmethod/eql_spec.rb b/spec/ruby/core/unboundmethod/eql_spec.rb new file mode 100644 index 0000000000..cf2c6b5aae --- /dev/null +++ b/spec/ruby/core/unboundmethod/eql_spec.rb @@ -0,0 +1,5 @@ +require_relative '../../spec_helper' + +describe "UnboundMethod#eql?" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/unboundmethod/equal_value_spec.rb b/spec/ruby/core/unboundmethod/equal_value_spec.rb new file mode 100644 index 0000000000..24d5233299 --- /dev/null +++ b/spec/ruby/core/unboundmethod/equal_value_spec.rb @@ -0,0 +1,165 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +context "Creating UnboundMethods" do + specify "there is no difference between Method#unbind and Module#instance_method" do + UnboundMethodSpecs::Methods.instance_method(:foo).should.is_a?(UnboundMethod) + UnboundMethodSpecs::Methods.new.method(:foo).unbind.should.is_a?(UnboundMethod) + end +end + +describe "UnboundMethod#==" do + before :all do + @from_module = UnboundMethodSpecs::Methods.instance_method(:foo) + @from_unbind = UnboundMethodSpecs::Methods.new.method(:foo).unbind + + @with_block = UnboundMethodSpecs::Methods.instance_method(:with_block) + + @includee = UnboundMethodSpecs::Mod.instance_method(:from_mod) + @includer = UnboundMethodSpecs::Methods.instance_method(:from_mod) + + @alias_1 = UnboundMethodSpecs::Methods.instance_method(:alias_1) + @alias_2 = UnboundMethodSpecs::Methods.instance_method(:alias_2) + + @original_body = UnboundMethodSpecs::Methods.instance_method(:original_body) + @identical_body = UnboundMethodSpecs::Methods.instance_method(:identical_body) + + @parent = UnboundMethodSpecs::Parent.instance_method(:foo) + @child1 = UnboundMethodSpecs::Child1.instance_method(:foo) + @child2 = UnboundMethodSpecs::Child2.instance_method(:foo) + + @child1_alt = UnboundMethodSpecs::Child1.instance_method(:foo) + + @discard_1 = UnboundMethodSpecs::Methods.instance_method(:discard_1) + @discard_2 = UnboundMethodSpecs::Methods.instance_method(:discard_2) + + @method_one = UnboundMethodSpecs::Methods.instance_method(:one) + @method_two = UnboundMethodSpecs::Methods.instance_method(:two) + + @mixin = UnboundMethodSpecs::Mixin.instance_method(:mixin_method) + @includer_base = UnboundMethodSpecs::IncluderBase.new.method(:mixin_method).unbind + @includer_child = UnboundMethodSpecs::IncluderChild.new.method(:mixin_method).unbind + @extender_base = UnboundMethodSpecs::ExtenderBase.method(:mixin_method).unbind + @extender_child = UnboundMethodSpecs::ExtenderChild.method(:mixin_method).unbind + end + + it "returns true if objects refer to the same method" do + (@from_module == @from_module).should == true + (@from_unbind == @from_unbind).should == true + (@from_module == @from_unbind).should == true + (@from_unbind == @from_module).should == true + (@with_block == @with_block).should == true + end + + it "returns true if either is an alias for the other" do + (@from_module == @alias_1).should == true + (@alias_1 == @from_module).should == true + end + + it "returns true if both are aliases for a third method" do + (@from_module == @alias_1).should == true + (@alias_1 == @from_module).should == true + + (@from_module == @alias_2).should == true + (@alias_2 == @from_module).should == true + + (@alias_1 == @alias_2).should == true + (@alias_2 == @alias_1).should == true + end + + it "returns true if same method is extracted from the same subclass" do + (@child1 == @child1_alt).should == true + (@child1_alt == @child1).should == true + end + + it "returns false if UnboundMethods are different methods" do + (@method_one == @method_two).should == false + (@method_two == @method_one).should == false + end + + it "returns false if both have identical body but are not the same" do + (@original_body == @identical_body).should == false + (@identical_body == @original_body).should == false + end + + it "returns true if same method but one extracted from a subclass" do + (@parent == @child1).should == true + (@child1 == @parent).should == true + end + + it "returns true if same method but extracted from two different subclasses" do + (@child2 == @child1).should == true + (@child1 == @child2).should == true + end + + it "returns true if methods are the same but added from an included Module" do + (@includee == @includer).should == true + (@includer == @includee).should == true + end + + ruby_bug "#21873", ""..."4.1" do + it "returns true if same method is present in an object through module inclusion" do + (@mixin == @includer_base).should == true + (@includer_base == @mixin).should == true + + (@mixin == @includer_child).should == true + (@includer_child == @mixin).should == true + + (@includer_base == @includer_child).should == true + (@includer_child == @includer_base).should == true + end + + it "returns true if same method is present in an object through module extension" do + (@mixin == @extender_base).should == true + (@extender_base == @mixin).should == true + + (@mixin == @extender_child).should == true + (@extender_child == @mixin).should == true + + (@extender_base == @extender_child).should == true + (@extender_child == @extender_base).should == true + end + end + + it "returns false if both have same Module, same name, identical body but not the same" do + class UnboundMethodSpecs::Methods + def discard_1; :discard; end + end + + (@discard_1 == UnboundMethodSpecs::Methods.instance_method(:discard_1)).should == false + end + + it "considers methods through aliasing equal" do + c = Class.new do + class << self + alias_method :n, :new + end + end + + c.method(:new).should == c.method(:n) + c.method(:n).should == Class.instance_method(:new).bind(c) + end + + it "considers methods through visibility change equal" do + c = Class.new do + class << self + private :new + end + end + + c.method(:new).should == Class.instance_method(:new).bind(c) + end + + it "considers methods through aliasing and visibility change equal" do + c = Class.new do + class << self + alias_method :n, :new + private :new + end + end + + c.method(:new).should == c.method(:n) + c.method(:n).should == Class.instance_method(:new).bind(c) + c.method(:new).should == Class.instance_method(:new).bind(c) + end +end diff --git a/spec/ruby/core/unboundmethod/fixtures/classes.rb b/spec/ruby/core/unboundmethod/fixtures/classes.rb new file mode 100644 index 0000000000..58120c2f88 --- /dev/null +++ b/spec/ruby/core/unboundmethod/fixtures/classes.rb @@ -0,0 +1,124 @@ +module UnboundMethodSpecs + + + class SourceLocation + def self.location # This needs to be on this line + :location # for the spec to pass + end + + def self.redefined + :first + end + + def self.redefined + :last + end + + def original + end + + alias :aka :original + end + + module Mod + def from_mod; end + def foo_super; super; end + end + + class Methods + include Mod + + def foo + true + end + + def with_block(&block); end + + alias bar foo + alias baz bar + alias qux baz + alias alias_1 foo + alias alias_2 foo + + def original_body(); :this; end + def identical_body(); :this; end + + def one; end + def two(a); end + def three(a, b); end + def four(a, b, &c); end + + def neg_one(*a); end + def neg_two(a, *b); end + def neg_three(a, b, *c); end + def neg_four(a, b, *c, &d); end + + def discard_1(); :discard; end + def discard_2(); :discard; end + + def my_public_method; end + def my_protected_method; end + def my_private_method; end + protected :my_protected_method + private :my_private_method + end + + class Parent + def foo; end + def foo_super + true + end + def self.class_method + "I am #{name}" + end + end + + class Child1 < Parent; end + class Child2 < Parent; end + class Child3 < Parent + class << self + alias_method :another_class_method, :class_method + end + end + + module Mixin + def mixin_method; end + end + + class IncluderBase + include Mixin + end + + class IncluderChild < IncluderBase; end + + class ExtenderBase + extend Mixin + end + + class ExtenderChild < ExtenderBase; end + + class A + def baz(a, b) + return [__FILE__, self.class] + end + def overridden; end + end + + class B < A + def overridden; end + end + + class C < B + def overridden; end + end + + module HashSpecs + class SuperClass + def foo + end + end + + class SubClass < SuperClass + end + end +end diff --git a/spec/ruby/core/unboundmethod/hash_spec.rb b/spec/ruby/core/unboundmethod/hash_spec.rb new file mode 100644 index 0000000000..6888675bc1 --- /dev/null +++ b/spec/ruby/core/unboundmethod/hash_spec.rb @@ -0,0 +1,22 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "UnboundMethod#hash" do + it "returns the same value for user methods that are eql?" do + foo, bar = UnboundMethodSpecs::Methods.instance_method(:foo), UnboundMethodSpecs::Methods.instance_method(:bar) + foo.hash.should == bar.hash + end + + # See also redmine #6048 + it "returns the same value for builtin methods that are eql?" do + to_s, inspect = Array.instance_method(:to_s), Array.instance_method(:inspect) + to_s.hash.should == inspect.hash + end + + it "equals a hash of the same method in the superclass" do + foo_in_superclass = UnboundMethodSpecs::HashSpecs::SuperClass.instance_method(:foo) + foo = UnboundMethodSpecs::HashSpecs::SubClass.instance_method(:foo) + + foo.hash.should == foo_in_superclass.hash + end +end diff --git a/spec/ruby/core/unboundmethod/inspect_spec.rb b/spec/ruby/core/unboundmethod/inspect_spec.rb new file mode 100644 index 0000000000..3abed94f7f --- /dev/null +++ b/spec/ruby/core/unboundmethod/inspect_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/to_s' +require_relative '../method/shared/aliased_inspect' + +describe "UnboundMethod#inspect" do + it_behaves_like :unboundmethod_to_s, :inspect + it_behaves_like :method_to_s_aliased, :inspect, -> meth { meth.unbind } +end diff --git a/spec/ruby/core/unboundmethod/name_spec.rb b/spec/ruby/core/unboundmethod/name_spec.rb new file mode 100644 index 0000000000..4d0fb34fc8 --- /dev/null +++ b/spec/ruby/core/unboundmethod/name_spec.rb @@ -0,0 +1,15 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "UnboundMethod#name" do + it "returns the name of the method" do + String.instance_method(:upcase).name.should == :upcase + end + + it "returns the name even when aliased" do + obj = UnboundMethodSpecs::Methods.new + obj.method(:foo).unbind.name.should == :foo + obj.method(:bar).unbind.name.should == :bar + UnboundMethodSpecs::Methods.instance_method(:bar).name.should == :bar + end +end diff --git a/spec/ruby/core/unboundmethod/original_name_spec.rb b/spec/ruby/core/unboundmethod/original_name_spec.rb new file mode 100644 index 0000000000..cd5f55805d --- /dev/null +++ b/spec/ruby/core/unboundmethod/original_name_spec.rb @@ -0,0 +1,59 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "UnboundMethod#original_name" do + it "returns the name of the method" do + String.instance_method(:upcase).original_name.should == :upcase + end + + it "returns the original name" do + obj = UnboundMethodSpecs::Methods.new + obj.method(:foo).unbind.original_name.should == :foo + obj.method(:bar).unbind.original_name.should == :foo + UnboundMethodSpecs::Methods.instance_method(:bar).original_name.should == :foo + end + + it "returns the original name even when aliased twice" do + obj = UnboundMethodSpecs::Methods.new + obj.method(:foo).unbind.original_name.should == :foo + obj.method(:baz).unbind.original_name.should == :foo + UnboundMethodSpecs::Methods.instance_method(:baz).original_name.should == :foo + end + + it "returns the original name even when aliased thrice" do + obj = UnboundMethodSpecs::Methods.new + obj.method(:qux).unbind.original_name.should == :foo + UnboundMethodSpecs::Methods.instance_method(:qux).original_name.should == :foo + end + + it "returns the source UnboundMethod's name (not the name given to define_method)" do + klass = Class.new { define_method(:my_inspect, ::Kernel.instance_method(:inspect)) } + klass.instance_method(:my_inspect).original_name.should == :inspect + end + + it "preserves the source method's name through define_method and alias" do + source = Class.new { def my_method; end } + klass = Class.new(source) do + define_method(:renamed, source.instance_method(:my_method)) + alias aliased renamed + end + klass.instance_method(:renamed).original_name.should == :my_method + klass.instance_method(:aliased).original_name.should == :my_method + end + + it "returns the source UnboundMethod's name for Kernel#is_a? and Kernel#kind_of?" do + klass = Class.new { define_method(:my_is_a?, ::Kernel.instance_method(:is_a?)) } + klass.instance_method(:my_is_a?).original_name.should == :is_a? + + klass = Class.new { define_method(:my_kind_of?, ::Kernel.instance_method(:kind_of?)) } + klass.instance_method(:my_kind_of?).original_name.should == :kind_of? + end + + it "preserves the source name when aliasing a define_method'd Kernel method" do + klass = Class.new do + define_method(:my_is_a?, ::Kernel.instance_method(:is_a?)) + alias_method :renamed_is_a?, :my_is_a? + end + klass.instance_method(:renamed_is_a?).original_name.should == :is_a? + end +end diff --git a/spec/ruby/core/unboundmethod/owner_spec.rb b/spec/ruby/core/unboundmethod/owner_spec.rb new file mode 100644 index 0000000000..b099c56de1 --- /dev/null +++ b/spec/ruby/core/unboundmethod/owner_spec.rb @@ -0,0 +1,31 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative '../method/fixtures/classes' + +describe "UnboundMethod#owner" do + it "returns the owner of the method" do + String.instance_method(:upcase).owner.should == String + end + + it "returns the same owner when aliased in the same classes" do + UnboundMethodSpecs::Methods.instance_method(:foo).owner.should == UnboundMethodSpecs::Methods + UnboundMethodSpecs::Methods.instance_method(:bar).owner.should == UnboundMethodSpecs::Methods + end + + it "returns the class/module it was defined in" do + UnboundMethodSpecs::C.instance_method(:baz).owner.should == UnboundMethodSpecs::A + UnboundMethodSpecs::Methods.instance_method(:from_mod).owner.should == UnboundMethodSpecs::Mod + end + + it "returns the new owner for aliased methods on singleton classes" do + parent_singleton_class = UnboundMethodSpecs::Parent.singleton_class + child_singleton_class = UnboundMethodSpecs::Child3.singleton_class + + child_singleton_class.instance_method(:class_method).owner.should == parent_singleton_class + child_singleton_class.instance_method(:another_class_method).owner.should == child_singleton_class + end + + it "returns the class on which public was called for a private method in ancestor" do + MethodSpecs::InheritedMethods::C.instance_method(:derp).owner.should == MethodSpecs::InheritedMethods::C + end +end diff --git a/spec/ruby/core/unboundmethod/parameters_spec.rb b/spec/ruby/core/unboundmethod/parameters_spec.rb new file mode 100644 index 0000000000..2a3cb18196 --- /dev/null +++ b/spec/ruby/core/unboundmethod/parameters_spec.rb @@ -0,0 +1,5 @@ +require_relative '../../spec_helper' + +describe "UnboundMethod#parameters" do + it "needs to be reviewed for spec completeness" +end diff --git a/spec/ruby/core/unboundmethod/private_spec.rb b/spec/ruby/core/unboundmethod/private_spec.rb new file mode 100644 index 0000000000..5a563939d1 --- /dev/null +++ b/spec/ruby/core/unboundmethod/private_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "UnboundMethod#private?" do + it "has been removed" do + obj = UnboundMethodSpecs::Methods.new + obj.method(:my_private_method).unbind.should_not.respond_to?(:private?) + end +end diff --git a/spec/ruby/core/unboundmethod/protected_spec.rb b/spec/ruby/core/unboundmethod/protected_spec.rb new file mode 100644 index 0000000000..70622d658d --- /dev/null +++ b/spec/ruby/core/unboundmethod/protected_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "UnboundMethod#protected?" do + it "has been removed" do + obj = UnboundMethodSpecs::Methods.new + obj.method(:my_protected_method).unbind.should_not.respond_to?(:protected?) + end +end diff --git a/spec/ruby/core/unboundmethod/public_spec.rb b/spec/ruby/core/unboundmethod/public_spec.rb new file mode 100644 index 0000000000..ae75e2601c --- /dev/null +++ b/spec/ruby/core/unboundmethod/public_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "UnboundMethod#public?" do + it "has been removed" do + obj = UnboundMethodSpecs::Methods.new + obj.method(:my_public_method).unbind.should_not.respond_to?(:public?) + end +end diff --git a/spec/ruby/core/unboundmethod/shared/dup.rb b/spec/ruby/core/unboundmethod/shared/dup.rb new file mode 100644 index 0000000000..fd30f75c5b --- /dev/null +++ b/spec/ruby/core/unboundmethod/shared/dup.rb @@ -0,0 +1,32 @@ +describe :unboundmethod_dup, shared: true do + it "returns a copy of self" do + a = Class.instance_method(:instance_method) + b = a.send(@method) + + a.should == b + a.should_not.equal?(b) + end + + ruby_version_is "3.4" do + it "copies instance variables" do + method = Class.instance_method(:instance_method) + method.instance_variable_set(:@ivar, 1) + cl = method.send(@method) + cl.instance_variables.should == [:@ivar] + end + + it "copies the finalizer" do + code = <<-'RUBY' + obj = Class.instance_method(:instance_method) + + ObjectSpace.define_finalizer(obj, Proc.new { STDOUT.write "finalized\n" }) + + obj.clone + + exit 0 + RUBY + + ruby_exe(code).lines.sort.should == ["finalized\n", "finalized\n"] + end + end +end diff --git a/spec/ruby/core/unboundmethod/shared/to_s.rb b/spec/ruby/core/unboundmethod/shared/to_s.rb new file mode 100644 index 0000000000..848c1eba2e --- /dev/null +++ b/spec/ruby/core/unboundmethod/shared/to_s.rb @@ -0,0 +1,33 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' + +describe :unboundmethod_to_s, shared: true do + before :each do + @from_module = UnboundMethodSpecs::Methods.instance_method(:from_mod) + @from_method = UnboundMethodSpecs::Methods.new.method(:from_mod).unbind + end + + it "returns a String" do + @from_module.send(@method).should.is_a?(String) + @from_method.send(@method).should.is_a?(String) + end + + it "the String reflects that this is an UnboundMethod object" do + @from_module.send(@method).should =~ /\bUnboundMethod\b/ + @from_method.send(@method).should =~ /\bUnboundMethod\b/ + end + + it "the String shows the method name, Module defined in and Module extracted from" do + @from_module.send(@method).should =~ /\bfrom_mod\b/ + @from_module.send(@method).should =~ /\bUnboundMethodSpecs::Mod\b/ + end + + it "returns a String including all details" do + @from_module.send(@method).should.start_with? "#<UnboundMethod: UnboundMethodSpecs::Mod#from_mod" + @from_method.send(@method).should.start_with? "#<UnboundMethod: UnboundMethodSpecs::Mod#from_mod" + end + + it "does not show the defining module if it is the same as the origin" do + UnboundMethodSpecs::A.instance_method(:baz).send(@method).should.start_with? "#<UnboundMethod: UnboundMethodSpecs::A#baz" + end +end diff --git a/spec/ruby/core/unboundmethod/source_location_spec.rb b/spec/ruby/core/unboundmethod/source_location_spec.rb new file mode 100644 index 0000000000..927600bfcb --- /dev/null +++ b/spec/ruby/core/unboundmethod/source_location_spec.rb @@ -0,0 +1,59 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "UnboundMethod#source_location" do + before :each do + @method = UnboundMethodSpecs::SourceLocation.method(:location).unbind + end + + it "sets the first value to the path of the file in which the method was defined" do + file = @method.source_location.first + file.should.instance_of?(String) + file.should == File.realpath('fixtures/classes.rb', __dir__) + end + + it "sets the last value to an Integer representing the line on which the method was defined" do + line = @method.source_location.last + line.should.instance_of?(Integer) + line.should == 5 + end + + it "returns the last place the method was defined" do + UnboundMethodSpecs::SourceLocation.method(:redefined).unbind.source_location.last.should == 13 + end + + it "returns the location of the original method even if it was aliased" do + UnboundMethodSpecs::SourceLocation.instance_method(:aka).source_location.last.should == 17 + end + + it "works for define_method methods" do + line = nil + cls = Class.new do + line = __LINE__ + 1 + define_method(:foo) { } + end + + method = cls.instance_method(:foo) + method.source_location[0].should =~ /#{__FILE__}/ + method.source_location[1].should == line + end + + it "works for define_singleton_method methods" do + line = nil + cls = Class.new do + line = __LINE__ + 1 + define_singleton_method(:foo) { } + end + + method = cls.method(:foo) + method.source_location[0].should =~ /#{__FILE__}/ + method.source_location[1].should == line + end + + it "works for eval with a given line" do + c = Class.new do + eval('def m; end', nil, "foo", 100) + end + c.instance_method(:m).source_location.should == ["foo", 100] + end +end diff --git a/spec/ruby/core/unboundmethod/super_method_spec.rb b/spec/ruby/core/unboundmethod/super_method_spec.rb new file mode 100644 index 0000000000..aa7c129377 --- /dev/null +++ b/spec/ruby/core/unboundmethod/super_method_spec.rb @@ -0,0 +1,49 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative '../method/fixtures/classes' + +describe "UnboundMethod#super_method" do + it "returns the method that would be called by super in the method" do + meth = UnboundMethodSpecs::C.instance_method(:overridden) + meth = meth.super_method + meth.should == UnboundMethodSpecs::B.instance_method(:overridden) + meth = meth.super_method + meth.should == UnboundMethodSpecs::A.instance_method(:overridden) + end + + it "returns nil when there's no super method in the parent" do + method = Kernel.instance_method(:method) + method.super_method.should == nil + end + + it "returns nil when the parent's method is removed" do + parent = Class.new { def foo; end } + child = Class.new(parent) { def foo; end } + + method = child.instance_method(:foo) + + parent.send(:undef_method, :foo) + + method.super_method.should == nil + end + + # https://github.com/jruby/jruby/issues/7240 + context "after changing an inherited methods visibility" do + it "calls the proper super method" do + method = MethodSpecs::InheritedMethods::C.instance_method(:derp) + method.bind(MethodSpecs::InheritedMethods::C.new).call.should == 'BA' + end + + it "returns the expected super_method" do + method = MethodSpecs::InheritedMethods::C.instance_method(:derp) + method.super_method.owner.should == MethodSpecs::InheritedMethods::A + end + end + + context "after aliasing an inherited method" do + it "returns the expected super_method" do + method = MethodSpecs::InheritedMethods::C.instance_method(:meow) + method.super_method.owner.should == MethodSpecs::InheritedMethods::A + end + end +end diff --git a/spec/ruby/core/unboundmethod/to_s_spec.rb b/spec/ruby/core/unboundmethod/to_s_spec.rb new file mode 100644 index 0000000000..615d88675b --- /dev/null +++ b/spec/ruby/core/unboundmethod/to_s_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/to_s' +require_relative '../method/shared/aliased_inspect' + +describe "UnboundMethod#to_s" do + it_behaves_like :unboundmethod_to_s, :to_s + it_behaves_like :method_to_s_aliased, :to_s, -> meth { meth.unbind } +end |
