diff options
Diffstat (limited to 'spec/ruby/core/method')
30 files changed, 1716 insertions, 0 deletions
diff --git a/spec/ruby/core/method/arity_spec.rb b/spec/ruby/core/method/arity_spec.rb new file mode 100644 index 0000000000..4bb821735a --- /dev/null +++ b/spec/ruby/core/method/arity_spec.rb @@ -0,0 +1,222 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Method#arity" do + SpecEvaluate.desc = "for method definition" + + context "returns zero" do + evaluate <<-ruby do + def m() end + ruby + + method(:m).arity.should == 0 + end + + evaluate <<-ruby do + def n(&b) end + ruby + + method(:n).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).arity.should == 1 + method(:n).arity.should == 2 + method(:o).arity.should == 3 + method(:p).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).arity.should == 1 + method(:n).arity.should == 1 + method(:o).arity.should == 1 + end + + evaluate <<-ruby do + def m(a, b:) end + def n(a, b:, &l) end + ruby + + method(:m).arity.should == 2 + method(:n).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).arity.should == 3 + method(:n).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).arity.should == -1 + method(:n).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).arity.should == -2 + method(:n).arity.should == -3 + end + + evaluate <<-ruby do + def m(a=1, *b) end + def n(a=1, b=2, *c) end + ruby + + method(:m).arity.should == -1 + method(:n).arity.should == -1 + end + + evaluate <<-ruby do + def m(*) end + def n(*a) end + ruby + + method(:m).arity.should == -1 + method(:n).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).arity.should == -2 + method(:n).arity.should == -2 + method(:o).arity.should == -3 + method(:p).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).arity.should == -2 + method(:n).arity.should == -3 + method(:o).arity.should == -4 + end + + evaluate <<-ruby do + def m(a, *b, c) end + def n(a, b, *c, d, e) end + ruby + + method(:m).arity.should == -3 + method(:n).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).arity.should == -4 + method(:n).arity.should == -6 + end + + evaluate <<-ruby do + def m(a: 1) end + def n(a: 1, b: 2) end + ruby + + method(:m).arity.should == -1 + method(:n).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).arity.should == -1 + method(:n).arity.should == -1 + method(:o).arity.should == -1 + method(:p).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).arity.should == -1 + method(:n).arity.should == -1 + method(:o).arity.should == -1 + end + + evaluate <<-ruby do + def m(a=1, *b, c:, d: 2, **k, &l) end + ruby + + method(:m).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).arity.should == -4 + method(:n).arity.should == -3 + method(:o).arity.should == -3 + method(:p).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).arity.should == -1 + end + end + + context "for a Method generated by attr_reader" do + it "return 0" do + obj = MethodSpecs::Methods.new + obj.method(:reader).arity.should == 0 + end + end + + context "for a Method generated by attr_writer" do + it "returns 1" do + obj = MethodSpecs::Methods.new + obj.method(:writer=).arity.should == 1 + end + end +end diff --git a/spec/ruby/core/method/call_spec.rb b/spec/ruby/core/method/call_spec.rb new file mode 100644 index 0000000000..6d997325fa --- /dev/null +++ b/spec/ruby/core/method/call_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/call' + +describe "Method#call" do + it_behaves_like :method_call, :call +end diff --git a/spec/ruby/core/method/case_compare_spec.rb b/spec/ruby/core/method/case_compare_spec.rb new file mode 100644 index 0000000000..a78953e8ad --- /dev/null +++ b/spec/ruby/core/method/case_compare_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/call' + +describe "Method#===" do + it_behaves_like :method_call, :=== +end diff --git a/spec/ruby/core/method/clone_spec.rb b/spec/ruby/core/method/clone_spec.rb new file mode 100644 index 0000000000..b0eb5751a9 --- /dev/null +++ b/spec/ruby/core/method/clone_spec.rb @@ -0,0 +1,13 @@ +require_relative '../../spec_helper' +require_relative 'shared/dup' + +describe "Method#clone" do + it_behaves_like :method_dup, :clone + + it "preserves frozen status" do + method = Object.new.method(:method) + method.freeze + method.frozen?.should == true + method.clone.frozen?.should == true + end +end diff --git a/spec/ruby/core/method/compose_spec.rb b/spec/ruby/core/method/compose_spec.rb new file mode 100644 index 0000000000..7506e33ea8 --- /dev/null +++ b/spec/ruby/core/method/compose_spec.rb @@ -0,0 +1,99 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative '../proc/shared/compose' + +describe "Method#<<" do + it "returns a Proc that is the composition of self and the passed Proc" do + succ = MethodSpecs::Composition.new.method(:succ) + upcase = proc { |s| s.upcase } + + (succ << upcase).call('Ruby').should == "RUBZ" + end + + it "calls passed Proc with arguments and then calls self with result" do + pow_2_proc = proc { |x| x * x } + double_proc = proc { |x| x + x } + + pow_2_method = MethodSpecs::Composition.new.method(:pow_2) + double_method = MethodSpecs::Composition.new.method(:double) + + (pow_2_method << double_proc).call(2).should == 16 + (double_method << pow_2_proc).call(2).should == 8 + end + + it "accepts any callable object" do + inc = MethodSpecs::Composition.new.method(:inc) + + double = Object.new + def double.call(n); n * 2; end + + (inc << double).call(3).should == 7 + end + + it_behaves_like :proc_compose, :<<, -> { MethodSpecs::Composition.new.method(:upcase) } + + describe "composition" do + it "is a lambda" do + pow_2 = MethodSpecs::Composition.new.method(:pow_2) + double = proc { |x| x + x } + + (pow_2 << double).is_a?(Proc).should == true + (pow_2 << double).should_not.lambda? + end + + it "may accept multiple arguments" do + inc = MethodSpecs::Composition.new.method(:inc) + mul = proc { |n, m| n * m } + + (inc << mul).call(2, 3).should == 7 + end + end +end + +describe "Method#>>" do + it "returns a Proc that is the composition of self and the passed Proc" do + upcase = proc { |s| s.upcase } + succ = MethodSpecs::Composition.new.method(:succ) + + (succ >> upcase).call('Ruby').should == "RUBZ" + end + + it "calls passed Proc with arguments and then calls self with result" do + pow_2_proc = proc { |x| x * x } + double_proc = proc { |x| x + x } + + pow_2_method = MethodSpecs::Composition.new.method(:pow_2) + double_method = MethodSpecs::Composition.new.method(:double) + + (pow_2_method >> double_proc).call(2).should == 8 + (double_method >> pow_2_proc).call(2).should == 16 + end + + it "accepts any callable object" do + inc = MethodSpecs::Composition.new.method(:inc) + + double = Object.new + def double.call(n); n * 2; end + + (inc >> double).call(3).should == 8 + end + + it_behaves_like :proc_compose, :>>, -> { MethodSpecs::Composition.new.method(:upcase) } + + describe "composition" do + it "is a lambda" do + pow_2 = MethodSpecs::Composition.new.method(:pow_2) + double = proc { |x| x + x } + + (pow_2 >> double).is_a?(Proc).should == true + (pow_2 >> double).should.lambda? + end + + it "may accept multiple arguments" do + mul = MethodSpecs::Composition.new.method(:mul) + inc = proc { |n| n + 1 } + + (mul >> inc).call(2, 3).should == 7 + end + end +end diff --git a/spec/ruby/core/method/curry_spec.rb b/spec/ruby/core/method/curry_spec.rb new file mode 100644 index 0000000000..219de0f6b5 --- /dev/null +++ b/spec/ruby/core/method/curry_spec.rb @@ -0,0 +1,36 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Method#curry" do + it "returns a curried proc" do + x = Object.new + def x.foo(a,b,c); [a,b,c]; end + + c = x.method(:foo).curry + c.should be_kind_of(Proc) + c.call(1).call(2, 3).should == [1,2,3] + end + + describe "with optional arity argument" do + before(:each) do + @obj = MethodSpecs::Methods.new + end + + it "returns a curried proc when given correct arity" do + @obj.method(:one_req).curry(1).should be_kind_of(Proc) + @obj.method(:zero_with_splat).curry(100).should be_kind_of(Proc) + @obj.method(:two_req_with_splat).curry(2).should be_kind_of(Proc) + end + + it "raises ArgumentError when the method requires less arguments than the given arity" do + -> { @obj.method(:zero).curry(1) }.should raise_error(ArgumentError) + -> { @obj.method(:one_req_one_opt).curry(3) }.should raise_error(ArgumentError) + -> { @obj.method(:two_req_one_opt_with_block).curry(4) }.should raise_error(ArgumentError) + end + + it "raises ArgumentError when the method requires more arguments than the given arity" do + -> { @obj.method(:two_req_with_splat).curry(1) }.should raise_error(ArgumentError) + -> { @obj.method(:one_req).curry(0) }.should raise_error(ArgumentError) + end + end +end diff --git a/spec/ruby/core/method/dup_spec.rb b/spec/ruby/core/method/dup_spec.rb new file mode 100644 index 0000000000..e3e29d8a68 --- /dev/null +++ b/spec/ruby/core/method/dup_spec.rb @@ -0,0 +1,15 @@ +require_relative '../../spec_helper' +require_relative 'shared/dup' + +describe "Method#dup" do + ruby_version_is "3.4" do + it_behaves_like :method_dup, :dup + + it "resets frozen status" do + method = Object.new.method(:method) + method.freeze + method.frozen?.should == true + method.dup.frozen?.should == false + end + end +end diff --git a/spec/ruby/core/method/element_reference_spec.rb b/spec/ruby/core/method/element_reference_spec.rb new file mode 100644 index 0000000000..aa6c54d1cb --- /dev/null +++ b/spec/ruby/core/method/element_reference_spec.rb @@ -0,0 +1,7 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' +require_relative 'shared/call' + +describe "Method#[]" do + it_behaves_like :method_call, :[] +end diff --git a/spec/ruby/core/method/eql_spec.rb b/spec/ruby/core/method/eql_spec.rb new file mode 100644 index 0000000000..b97c9e4db0 --- /dev/null +++ b/spec/ruby/core/method/eql_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/eql' + +describe "Method#eql?" do + it_behaves_like :method_equal, :eql? +end diff --git a/spec/ruby/core/method/equal_value_spec.rb b/spec/ruby/core/method/equal_value_spec.rb new file mode 100644 index 0000000000..0431d0c5f6 --- /dev/null +++ b/spec/ruby/core/method/equal_value_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/eql' + +describe "Method#==" do + it_behaves_like :method_equal, :== +end diff --git a/spec/ruby/core/method/fixtures/classes.rb b/spec/ruby/core/method/fixtures/classes.rb new file mode 100644 index 0000000000..464a519aea --- /dev/null +++ b/spec/ruby/core/method/fixtures/classes.rb @@ -0,0 +1,246 @@ +module MethodSpecs + + + 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 + + class Methods + def foo + true + end + + alias bar foo + alias baz bar + + def same_as_foo + true + end + + def respond_to_missing? method, bool + [:handled_via_method_missing, :also_handled].include? method + end + + def method_missing(method, *arguments) + if [:handled_via_method_missing, :also_handled].include? method + arguments + else + super + end + end + + attr_accessor :attr + + def zero; end + def one_req(a); end + def two_req(a, b); end + + def one_req_named(a:); end + + def zero_with_block(&blk); end + def one_req_with_block(a, &blk); end + def two_req_with_block(a, b, &blk); end + + def one_opt(a=nil); end + def one_req_one_opt(a, b=nil); end + def one_req_two_opt(a, b=nil, c=nil); end + def two_req_one_opt(a, b, c=nil); end + + def one_opt_named(a: nil); end + + def one_opt_with_block(a=nil, &blk); end + def one_req_one_opt_with_block(a, b=nil, &blk); end + def one_req_two_opt_with_block(a, b=nil, c=nil, &blk); end + def two_req_one_opt_with_block(a, b, c=nil, &blk); end + + def zero_with_splat(*a); end + def one_req_with_splat(a, *b); end + def two_req_with_splat(a, b, *c); end + def one_req_one_opt_with_splat(a, b=nil, *c); end + def two_req_one_opt_with_splat(a, b, c=nil, *d); end + def one_req_two_opt_with_splat(a, b=nil, c=nil, *d); end + + def zero_with_double_splat(**a); end + + def zero_with_splat_and_block(*a, &blk); end + def one_req_with_splat_and_block(a, *b, &blk); end + def two_req_with_splat_and_block(a, b, *c, &blk); end + def one_req_one_opt_with_splat_and_block(a, b=nil, *c, &blk); end + def two_req_one_opt_with_splat_and_block(a, b, c=nil, *d, &blk); end + def one_req_two_opt_with_splat_and_block(a, b=nil, c=nil, *d, &blk); end + + def my_public_method; end + def my_protected_method; end + def my_private_method; end + protected :my_protected_method + private :my_private_method + + define_method(:zero_defined_method, Proc.new {||}) + define_method(:zero_with_splat_defined_method, Proc.new {|*x|}) + define_method(:one_req_defined_method, Proc.new {|x|}) + define_method(:two_req_defined_method, Proc.new {|x, y|}) + define_method(:no_args_defined_method) {} + define_method(:two_grouped_defined_method) {|(_x1,_x2)|} + + attr_reader :reader + attr_writer :writer + end + + module MyMod + def bar; :bar; end + end + + class MySuper + include MyMod + end + + class MySub < MySuper; end + + class A + def baz(a, b) + self.class + end + def overridden; end + end + + class B < A + def overridden; end + end + + module BetweenBAndC + def overridden; end + end + + class C < B + include BetweenBAndC + def overridden; end + end + + module OverrideAgain + def overridden; end + end + + class D + def bar() 'done' end + end + + class Eql + + def same_body + 1 + 1 + end + + alias :same_body_alias :same_body + + def same_body_with_args(arg) + 1 + 1 + end + + def different_body + 1 + 2 + end + + def same_body_two + 1 + 1 + end + + private + def same_body_private + 1 + 1 + end + end + + class Eql2 + + def same_body + 1 + 1 + end + + end + + class ToProc + def method_called(a, b) + ScratchPad << [a, b] + end + + def to_proc + method(:method_called).to_proc + end + end + + class ToProcBeta + def method_called(a) + ScratchPad << a + a + end + + def to_proc + method(:method_called).to_proc + end + end + + class Composition + def upcase(s) + s.upcase + end + + def succ(s) + s.succ + end + + def pow_2(n) + n * n + end + + def double(n) + n + n + end + + def inc(n) + n + 1 + end + + def mul(n, m) + n * m + end + end + + module InheritedMethods + module A + private + def derp(message) + 'A' + end + end + + module B + private + def derp + 'B' + super('superclass') + end + end + + class C + include A + include B + + public :derp + alias_method :meow, :derp + end + end +end diff --git a/spec/ruby/core/method/hash_spec.rb b/spec/ruby/core/method/hash_spec.rb new file mode 100644 index 0000000000..d6c8440acc --- /dev/null +++ b/spec/ruby/core/method/hash_spec.rb @@ -0,0 +1,15 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Method#hash" do + it "returns the same value for user methods that are eql?" do + obj = MethodSpecs::Methods.new + obj.method(:foo).hash.should == obj.method(:bar).hash + end + + # See also redmine #6048 + it "returns the same value for builtin methods that are eql?" do + obj = [42] + obj.method(:to_s).hash.should == obj.method(:inspect).hash + end +end diff --git a/spec/ruby/core/method/inspect_spec.rb b/spec/ruby/core/method/inspect_spec.rb new file mode 100644 index 0000000000..e0fe1afdd0 --- /dev/null +++ b/spec/ruby/core/method/inspect_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/to_s' + +describe "Method#inspect" do + it_behaves_like :method_to_s, :inspect +end diff --git a/spec/ruby/core/method/name_spec.rb b/spec/ruby/core/method/name_spec.rb new file mode 100644 index 0000000000..de390c6f52 --- /dev/null +++ b/spec/ruby/core/method/name_spec.rb @@ -0,0 +1,22 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Method#name" do + it "returns the name of the method" do + "abc".method(:upcase).name.should == :upcase + end + + it "returns the name even when aliased" do + obj = MethodSpecs::Methods.new + obj.method(:foo).name.should == :foo + obj.method(:bar).name.should == :bar + obj.method(:bar).unbind.bind(obj).name.should == :bar + end + + describe "for a Method generated by respond_to_missing?" do + it "returns the name passed to respond_to_missing?" do + @m = MethodSpecs::Methods.new + @m.method(:handled_via_method_missing).name.should == :handled_via_method_missing + end + end +end diff --git a/spec/ruby/core/method/original_name_spec.rb b/spec/ruby/core/method/original_name_spec.rb new file mode 100644 index 0000000000..676fdaedb4 --- /dev/null +++ b/spec/ruby/core/method/original_name_spec.rb @@ -0,0 +1,22 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Method#original_name" do + it "returns the name of the method" do + "abc".method(:upcase).original_name.should == :upcase + end + + it "returns the original name when aliased" do + obj = MethodSpecs::Methods.new + obj.method(:foo).original_name.should == :foo + obj.method(:bar).original_name.should == :foo + obj.method(:bar).unbind.bind(obj).original_name.should == :foo + end + + it "returns the original name even when aliased twice" do + obj = MethodSpecs::Methods.new + obj.method(:foo).original_name.should == :foo + obj.method(:baz).original_name.should == :foo + obj.method(:baz).unbind.bind(obj).original_name.should == :foo + end +end diff --git a/spec/ruby/core/method/owner_spec.rb b/spec/ruby/core/method/owner_spec.rb new file mode 100644 index 0000000000..1cdc4edfa7 --- /dev/null +++ b/spec/ruby/core/method/owner_spec.rb @@ -0,0 +1,30 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Method#owner" do + it "returns the owner of the method" do + "abc".method(:upcase).owner.should == String + end + + it "returns the same owner when aliased in the same classes" do + obj = MethodSpecs::Methods.new + obj.method(:foo).owner.should == MethodSpecs::Methods + obj.method(:bar).owner.should == MethodSpecs::Methods + end + + it "returns the class/module it was defined in" do + MethodSpecs::C.new.method(:baz).owner.should == MethodSpecs::A + MethodSpecs::MySuper.new.method(:bar).owner.should == MethodSpecs::MyMod + end + + describe "for a Method generated by respond_to_missing?" do + it "returns the owner of the method" do + @m = MethodSpecs::Methods.new + @m.method(:handled_via_method_missing).owner.should == MethodSpecs::Methods + end + end + + it "returns the class on which public was called for a private method in ancestor" do + MethodSpecs::InheritedMethods::C.new.method(:derp).owner.should == MethodSpecs::InheritedMethods::C + end +end diff --git a/spec/ruby/core/method/parameters_spec.rb b/spec/ruby/core/method/parameters_spec.rb new file mode 100644 index 0000000000..f1c2523cf0 --- /dev/null +++ b/spec/ruby/core/method/parameters_spec.rb @@ -0,0 +1,304 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Method#parameters" do + class MethodSpecs::Methods + def one_key(a: 1); end + def one_keyrest(**a); end + + def one_keyreq(a:); end + def one_nokey(**nil); end + + def one_splat_one_req(*a,b); end + def one_splat_two_req(*a,b,c); end + def one_splat_one_req_with_block(*a,b,&blk); end + + def one_opt_with_stabby(a=-> b { true }); end + + def one_unnamed_splat(*); end + def one_unnamed_keyrest(**); end + + def one_splat_one_block(*args, &block) + local_is_not_parameter = {} + end + + def forward_parameters(...) end + + def underscore_parameters(_, _, _ = 1, *_, _:, _: 2, **_, &_); end + + define_method(:one_optional_defined_method) {|x = 1|} + end + + it "returns an empty Array when the method expects no arguments" do + MethodSpecs::Methods.instance_method(:zero).parameters.should == [] + end + + it "returns [[:req,:name]] for a method expecting one required argument called 'name'" do + MethodSpecs::Methods.instance_method(:one_req).parameters.should == [[:req,:a]] + end + + it "returns [[:req,:a],[:req,:b]] for a method expecting two required arguments called 'a' and 'b''" do + m = MethodSpecs::Methods.instance_method(:two_req) + m.parameters.should == [[:req,:a], [:req,:b]] + end + + it "returns [[:block,:blk]] for a method expecting one block argument called 'a'" do + m = MethodSpecs::Methods.instance_method(:zero_with_block) + m.parameters.should == [[:block,:blk]] + end + + it "returns [[:req,:a],[:block,:b] for a method expecting a required argument ('a') and a block argument ('b')" do + m = MethodSpecs::Methods.instance_method(:one_req_with_block) + m.parameters.should == [[:req,:a], [:block,:blk]] + end + + it "returns [[:req,:a],[:req,:b],[:block,:c] for a method expecting two required arguments ('a','b') and a block argument ('c')" do + m = MethodSpecs::Methods.instance_method(:two_req_with_block) + m.parameters.should == [[:req,:a], [:req,:b], [:block,:blk]] + end + + it "returns [[:opt,:a]] for a method expecting one optional argument ('a')" do + m = MethodSpecs::Methods.instance_method(:one_opt) + m.parameters.should == [[:opt,:a]] + end + + it "returns [[:req,:a],[:opt,:b]] for a method expecting one required argument ('a') and one optional argument ('b')" do + m = MethodSpecs::Methods.instance_method(:one_req_one_opt) + m.parameters.should == [[:req,:a],[:opt,:b]] + end + + it "returns [[:req,:a],[:opt,:b]] for a method expecting one required argument ('a') and one optional argument ('b')" do + m = MethodSpecs::Methods.instance_method(:one_req_one_opt) + m.parameters.should == [[:req,:a],[:opt,:b]] + end + + it "returns [[:req,:a],[:opt,:b],[:opt,:c]] for a method expecting one required argument ('a') and two optional arguments ('b','c')" do + m = MethodSpecs::Methods.instance_method(:one_req_two_opt) + m.parameters.should == [[:req,:a],[:opt,:b],[:opt,:c]] + end + + it "returns [[:req,:a],[:req,:b],[:opt,:c]] for a method expecting two required arguments ('a','b') and one optional arguments ('c')" do + m = MethodSpecs::Methods.instance_method(:two_req_one_opt) + m.parameters.should == [[:req,:a],[:req,:b],[:opt,:c]] + end + + it "returns [[:opt,:a],[:block,:b]] for a method expecting one required argument ('a') and one block argument ('b')" do + m = MethodSpecs::Methods.instance_method(:one_opt_with_block) + m.parameters.should == [[:opt,:a],[:block,:blk]] + end + + it "returns [[:req,:a],[:opt,:b],[:block,:c]] for a method expecting one required argument ('a'), one optional argument ('b'), and a block ('c')" do + m = MethodSpecs::Methods.instance_method(:one_req_one_opt_with_block) + m.parameters.should == [[:req,:a],[:opt,:b],[:block,:blk]] + end + + it "returns [[:req,:a],[:opt,:b],[:opt,:c],[:block,:d]] for a method expecting one required argument ('a'), two optional arguments ('b','c'), and a block ('d')" do + m = MethodSpecs::Methods.instance_method(:one_req_two_opt_with_block) + m.parameters.should == [[:req,:a],[:opt,:b],[:opt,:c],[:block,:blk]] + end + + it "returns [[:rest,:a]] for a method expecting a single splat argument ('a')" do + m = MethodSpecs::Methods.instance_method(:zero_with_splat) + m.parameters.should == [[:rest,:a]] + end + + it "returns [[:req,:a],[:rest,:b]] for a method expecting a splat argument ('a') and a required argument ('b')" do + m = MethodSpecs::Methods.instance_method(:one_req_with_splat) + m.parameters.should == [[:req,:a],[:rest,:b]] + end + + it "returns [[:req,:a],[:req,:b],[:rest,:c]] for a method expecting two required arguments ('a','b') and a splat argument ('c')" do + m = MethodSpecs::Methods.instance_method(:two_req_with_splat) + m.parameters.should == [[:req,:a],[:req,:b],[:rest,:c]] + end + + it "returns [[:req,:a],[:opt,:b],[:rest,:c]] for a method expecting a required argument ('a','b'), an optional argument ('b'), and a splat argument ('c')" do + m = MethodSpecs::Methods.instance_method(:one_req_one_opt_with_splat) + m.parameters.should == [[:req,:a],[:opt,:b],[:rest,:c]] + end + + it "returns [[:req,:a],[:req,:b],[:opt,:b],[:rest,:d]] for a method expecting two required arguments ('a','b'), an optional argument ('c'), and a splat argument ('d')" do + m = MethodSpecs::Methods.instance_method(:two_req_one_opt_with_splat) + m.parameters.should == [[:req,:a],[:req,:b],[:opt,:c],[:rest,:d]] + end + + it "returns [[:req,:a],[:opt,:b],[:opt,:c],[:rest,:d]] for a method expecting a required argument ('a'), two optional arguments ('b','c'), and a splat argument ('d')" do + m = MethodSpecs::Methods.instance_method(:one_req_two_opt_with_splat) + m.parameters.should == [[:req,:a],[:opt,:b],[:opt,:c],[:rest,:d]] + end + + it "returns [[:rest,:a],[:block,:b]] for a method expecting a splat argument ('a') and a block argument ('b')" do + m = MethodSpecs::Methods.instance_method(:zero_with_splat_and_block) + m.parameters.should == [[:rest,:a],[:block,:blk]] + end + + it "returns [[:req,:a],[:rest,:b],[:block,:c]] for a method expecting a required argument ('a'), a splat argument ('b'), and a block ('c')" do + m = MethodSpecs::Methods.instance_method(:one_req_with_splat_and_block) + m.parameters.should == [[:req,:a],[:rest,:b],[:block,:blk]] + end + + it "returns [[:req,:a],[:req,:b],[:rest,:c],[:block,:d]] for a method expecting two required arguments ('a','b'), a splat argument ('c'), and a block ('d')" do + m = MethodSpecs::Methods.instance_method(:two_req_with_splat_and_block) + m.parameters.should == [[:req,:a],[:req,:b],[:rest,:c],[:block,:blk]] + end + + it "returns [[:req,:a],[:opt,:b],[:rest,:c],[:block,:d]] for a method expecting a required argument ('a'), a splat argument ('c'), and a block ('d')" do + m = MethodSpecs::Methods.instance_method(:one_req_one_opt_with_splat_and_block) + m.parameters.should == [[:req,:a],[:opt,:b],[:rest,:c],[:block,:blk]] + end + + it "returns [[:req,:a],[:req,:b],[:opt,:c],[:block,:d]] for a method expecting two required arguments ('a','b'), an optional argument ('c'), a splat argument ('d'), and a block ('e')" do + m = MethodSpecs::Methods.instance_method(:two_req_one_opt_with_splat_and_block) + m.parameters.should == [[:req,:a],[:req,:b],[:opt,:c],[:rest,:d],[:block,:blk]] + end + + it "returns [[:rest,:a],[:req,:b]] for a method expecting a splat argument ('a') and a required argument ('b')" do + m = MethodSpecs::Methods.instance_method(:one_splat_one_req) + m.parameters.should == [[:rest,:a],[:req,:b]] + end + + it "returns [[:rest,:a],[:req,:b],[:req,:c]] for a method expecting a splat argument ('a') and two required arguments ('b','c')" do + m = MethodSpecs::Methods.instance_method(:one_splat_two_req) + m.parameters.should == [[:rest,:a],[:req,:b],[:req,:c]] + end + + it "returns [[:rest,:a],[:req,:b],[:block,:c]] for a method expecting a splat argument ('a'), a required argument ('b'), and a block ('c')" do + m = MethodSpecs::Methods.instance_method(:one_splat_one_req_with_block) + m.parameters.should == [[:rest,:a],[:req,:b],[:block,:blk]] + end + + it "returns [[:key,:a]] for a method with a single optional keyword argument" do + m = MethodSpecs::Methods.instance_method(:one_key) + m.parameters.should == [[:key,:a]] + end + + it "returns [[:keyrest,:a]] for a method with a keyword rest argument" do + m = MethodSpecs::Methods.instance_method(:one_keyrest) + m.parameters.should == [[:keyrest,:a]] + end + + it "returns [[:keyreq,:a]] for a method with a single required keyword argument" do + m = MethodSpecs::Methods.instance_method(:one_keyreq) + m.parameters.should == [[:keyreq,:a]] + end + + it "returns [[:nokey]] for a method with a single **nil parameter" do + m = MethodSpecs::Methods.instance_method(:one_nokey) + m.parameters.should == [[:nokey]] + end + + it "works with ->(){} as the value of an optional argument" do + m = MethodSpecs::Methods.instance_method(:one_opt_with_stabby) + m.parameters.should == [[:opt,:a]] + end + + # define_method variants + it "returns [] for a define_method method with explicit no-args || specification" do + m = MethodSpecs::Methods.instance_method(:zero_defined_method) + m.parameters.should == [] + end + + it "returns [[:rest, :x]] for a define_method method with rest arg 'x' only" do + m = MethodSpecs::Methods.instance_method(:zero_with_splat_defined_method) + m.parameters.should == [[:rest, :x]] + end + + it "returns [[:req, :x]] for a define_method method expecting one required argument 'x'" do + m = MethodSpecs::Methods.instance_method(:one_req_defined_method) + m.parameters.should == [[:req, :x]] + end + + it "returns [[:req, :x], [:req, :y]] for a define_method method expecting two required arguments 'x' and 'y'" do + m = MethodSpecs::Methods.instance_method(:two_req_defined_method) + m.parameters.should == [[:req, :x], [:req, :y]] + end + + it "returns [] for a define_method method with no args specification" do + m = MethodSpecs::Methods.instance_method(:no_args_defined_method) + m.parameters.should == [] + end + + it "returns [[:req]] for a define_method method with a grouping as its only argument" do + m = MethodSpecs::Methods.instance_method(:two_grouped_defined_method) + m.parameters.should == [[:req]] + end + + it "returns [[:opt, :x]] for a define_method method with an optional argument 'x'" do + m = MethodSpecs::Methods.instance_method(:one_optional_defined_method) + m.parameters.should == [[:opt, :x]] + end + + it "returns [[:rest]] for a Method generated by respond_to_missing?" do + m = MethodSpecs::Methods.new + m.method(:handled_via_method_missing).parameters.should == [[:rest]] + end + + it "adds rest arg with name * for \"star\" argument" do + m = MethodSpecs::Methods.new + m.method(:one_unnamed_splat).parameters.should == [[:rest, :*]] + end + + it "adds keyrest arg with ** as a name for \"double star\" argument" do + m = MethodSpecs::Methods.new + m.method(:one_unnamed_keyrest).parameters.should == [[:keyrest, :**]] + end + + it "adds block arg with name & for anonymous block argument" do + object = Object.new + def object.foo(&) + end + + object.method(:foo).parameters.should == [[:block, :&]] + end + + it "returns [:rest, :*], [:keyrest, :**], [:block, :&] for forward parameters operator" do + m = MethodSpecs::Methods.new + m.method(:forward_parameters).parameters.should == [[:rest, :*], [:keyrest, :**], [:block, :&]] + end + + it "returns the args and block for a splat and block argument" do + m = MethodSpecs::Methods.new + m.method(:one_splat_one_block).parameters.should == [[:rest, :args], [:block, :block]] + end + + it "returns [] for a Method generated by attr_reader" do + m = MethodSpecs::Methods.new + m.method(:reader).parameters.should == [] + end + + it "return [[:req]] for a Method generated by attr_writer" do + m = MethodSpecs::Methods.new + m.method(:writer=).parameters.should == [[:req]] + end + + it "returns all parameters defined with the name _ as _" do + m = MethodSpecs::Methods.instance_method(:underscore_parameters) + m.parameters.should == [ + [:req, :_], + [:req, :_], + [:opt, :_], + [:rest, :_], + [:keyreq, :_], + [:key, :_], + [:keyrest, :_], + [:block, :_] + ] + end + + it "returns [[:rest]] for core methods with variable-length argument lists" do + # delete! takes rest args + "foo".method(:delete!).parameters.should == [[:rest]] + end + + it "returns [[:rest]] or [[:opt]] for core methods with optional arguments" do + # pop takes 1 optional argument + [ + [[:rest]], + [[:opt]] + ].should include([].method(:pop).parameters) + end + + it "returns [[:req]] for each parameter for core methods with fixed-length argument lists" do + "foo".method(:+).parameters.should == [[:req]] + end +end diff --git a/spec/ruby/core/method/private_spec.rb b/spec/ruby/core/method/private_spec.rb new file mode 100644 index 0000000000..e708542b2e --- /dev/null +++ b/spec/ruby/core/method/private_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Method#private?" do + it "has been removed" do + obj = MethodSpecs::Methods.new + obj.method(:my_private_method).should_not.respond_to?(:private?) + end +end diff --git a/spec/ruby/core/method/protected_spec.rb b/spec/ruby/core/method/protected_spec.rb new file mode 100644 index 0000000000..f9e422ae3d --- /dev/null +++ b/spec/ruby/core/method/protected_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Method#protected?" do + it "has been removed" do + obj = MethodSpecs::Methods.new + obj.method(:my_protected_method).should_not.respond_to?(:protected?) + end +end diff --git a/spec/ruby/core/method/public_spec.rb b/spec/ruby/core/method/public_spec.rb new file mode 100644 index 0000000000..4cb23f4cf1 --- /dev/null +++ b/spec/ruby/core/method/public_spec.rb @@ -0,0 +1,9 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Method#public?" do + it "has been removed" do + obj = MethodSpecs::Methods.new + obj.method(:my_public_method).should_not.respond_to?(:public?) + end +end diff --git a/spec/ruby/core/method/receiver_spec.rb b/spec/ruby/core/method/receiver_spec.rb new file mode 100644 index 0000000000..2b2e11dd2e --- /dev/null +++ b/spec/ruby/core/method/receiver_spec.rb @@ -0,0 +1,22 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Method#receiver" do + it "returns the receiver of the method" do + s = "abc" + s.method(:upcase).receiver.should equal(s) + end + + it "returns the right receiver even when aliased" do + obj = MethodSpecs::Methods.new + obj.method(:foo).receiver.should equal(obj) + obj.method(:bar).receiver.should equal(obj) + end + + describe "for a Method generated by respond_to_missing?" do + it "returns the receiver of the method" do + m = MethodSpecs::Methods.new + m.method(:handled_via_method_missing).receiver.should equal(m) + end + end +end diff --git a/spec/ruby/core/method/shared/call.rb b/spec/ruby/core/method/shared/call.rb new file mode 100644 index 0000000000..f26e373695 --- /dev/null +++ b/spec/ruby/core/method/shared/call.rb @@ -0,0 +1,51 @@ +describe :method_call, shared: true do + it "invokes the method with the specified arguments, returning the method's return value" do + m = 12.method("+") + m.send(@method, 3).should == 15 + m.send(@method, 20).should == 32 + + m = MethodSpecs::Methods.new.method(:attr=) + m.send(@method, 42).should == 42 + end + + it "raises an ArgumentError when given incorrect number of arguments" do + -> { + MethodSpecs::Methods.new.method(:two_req).send(@method, 1, 2, 3) + }.should raise_error(ArgumentError) + -> { + MethodSpecs::Methods.new.method(:two_req).send(@method, 1) + }.should raise_error(ArgumentError) + end + + describe "for a Method generated by respond_to_missing?" do + it "invokes method_missing with the specified arguments and returns the result" do + @m = MethodSpecs::Methods.new + meth = @m.method(:handled_via_method_missing) + meth.send(@method, :argument).should == [:argument] + end + + it "invokes method_missing with the method name and the specified arguments" do + @m = MethodSpecs::Methods.new + meth = @m.method(:handled_via_method_missing) + + @m.should_receive(:method_missing).with(:handled_via_method_missing, :argument) + meth.send(@method, :argument) + end + + it "invokes method_missing dynamically" do + @m = MethodSpecs::Methods.new + meth = @m.method(:handled_via_method_missing) + + def @m.method_missing(*); :changed; end + meth.send(@method, :argument).should == :changed + end + + it "does not call the original method name even if it now exists" do + @m = MethodSpecs::Methods.new + meth = @m.method(:handled_via_method_missing) + + def @m.handled_via_method_missing(*); :not_called; end + meth.send(@method, :argument).should == [:argument] + end + end +end diff --git a/spec/ruby/core/method/shared/dup.rb b/spec/ruby/core/method/shared/dup.rb new file mode 100644 index 0000000000..c74847083f --- /dev/null +++ b/spec/ruby/core/method/shared/dup.rb @@ -0,0 +1,32 @@ +describe :method_dup, shared: true do + it "returns a copy of self" do + a = Object.new.method(: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 = Object.new.method(: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 = Object.new.method(: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/method/shared/eql.rb b/spec/ruby/core/method/shared/eql.rb new file mode 100644 index 0000000000..5c720cbac1 --- /dev/null +++ b/spec/ruby/core/method/shared/eql.rb @@ -0,0 +1,94 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' + +describe :method_equal, shared: true do + before :each do + @m = MethodSpecs::Methods.new + @m_foo = @m.method(:foo) + @m2 = MethodSpecs::Methods.new + @a = MethodSpecs::A.new + end + + it "returns true if methods are the same" do + m2 = @m.method(:foo) + + @m_foo.send(@method, @m_foo).should be_true + @m_foo.send(@method, m2).should be_true + end + + it "returns true on aliased methods" do + m_bar = @m.method(:bar) + + m_bar.send(@method, @m_foo).should be_true + end + + it "returns true if the two core methods are aliases" do + s = "hello" + a = s.method(:size) + b = s.method(:length) + a.send(@method, b).should be_true + end + + it "returns false on a method which is neither aliased nor the same method" do + m2 = @m.method(:zero) + + @m_foo.send(@method, m2).should be_false + end + + it "returns false for a method which is not bound to the same object" do + m2_foo = @m2.method(:foo) + a_baz = @a.method(:baz) + + @m_foo.send(@method, m2_foo).should be_false + @m_foo.send(@method, a_baz).should be_false + end + + it "returns false if the two methods are bound to the same object but were defined independently" do + m2 = @m.method(:same_as_foo) + @m_foo.send(@method, m2).should be_false + end + + it "returns true if a method was defined using the other one" do + MethodSpecs::Methods.send :define_method, :defined_foo, MethodSpecs::Methods.instance_method(:foo) + m2 = @m.method(:defined_foo) + @m_foo.send(@method, m2).should be_true + end + + it "returns false if comparing a method defined via define_method and def" do + defn = @m.method(:zero) + defined = @m.method(:zero_defined_method) + + defn.send(@method, defined).should be_false + defined.send(@method, defn).should be_false + end + + describe 'missing methods' do + it "returns true for the same method missing" do + miss1 = @m.method(:handled_via_method_missing) + miss1bis = @m.method(:handled_via_method_missing) + miss2 = @m.method(:also_handled) + + miss1.send(@method, miss1bis).should be_true + miss1.send(@method, miss2).should be_false + end + + it 'calls respond_to_missing? with true to include private methods' do + @m.should_receive(:respond_to_missing?).with(:some_missing_method, true).and_return(true) + @m.method(:some_missing_method) + end + end + + it "returns false if the two methods are bound to different objects, have the same names, and identical bodies" do + a = MethodSpecs::Eql.instance_method(:same_body) + b = MethodSpecs::Eql2.instance_method(:same_body) + a.send(@method, b).should be_false + end + + it "returns false if the argument is not a Method object" do + String.instance_method(:size).send(@method, 7).should be_false + end + + it "returns false if the argument is an unbound version of self" do + method(:load).send(@method, method(:load).unbind).should be_false + end +end diff --git a/spec/ruby/core/method/shared/to_s.rb b/spec/ruby/core/method/shared/to_s.rb new file mode 100644 index 0000000000..b2d27d370f --- /dev/null +++ b/spec/ruby/core/method/shared/to_s.rb @@ -0,0 +1,81 @@ +require_relative '../../../spec_helper' +require_relative '../fixtures/classes' + +describe :method_to_s, shared: true do + before :each do + @m = MethodSpecs::MySub.new.method :bar + @string = @m.send(@method) + end + + it "returns a String" do + @m.send(@method).should be_kind_of(String) + end + + it "returns a String for methods defined with attr_accessor" do + m = MethodSpecs::Methods.new.method :attr + m.send(@method).should be_kind_of(String) + end + + it "returns a String containing 'Method'" do + @string.should =~ /\bMethod\b/ + end + + it "returns a String containing the method name" do + @string.should =~ /\#bar/ + end + + it "returns a String containing method arguments" do + obj = MethodSpecs::Methods.new + obj.method(:zero).send(@method).should.include?("()") + obj.method(:one_req).send(@method).should.include?("(a)") + obj.method(:one_req_named).send(@method).should.include?("(a:)") + obj.method(:zero_with_block).send(@method).should.include?("(&blk)") + obj.method(:one_opt).send(@method).should.include?("(a=...)") + obj.method(:one_opt_named).send(@method).should.include?("(a: ...)") + obj.method(:zero_with_splat).send(@method).should.include?("(*a)") + obj.method(:zero_with_double_splat).send(@method).should.include?("(**a)") + obj.method(:one_req_one_opt_with_splat_and_block).send(@method).should.include?("(a, b=..., *c, &blk)") + end + + it "returns a String containing the Module the method is defined in" do + @string.should =~ /MethodSpecs::MyMod/ + end + + it "returns a String containing the Module the method is referenced from" do + @string.should =~ /MethodSpecs::MySub/ + end + + it "returns a String including all details" do + @string.should.start_with? "#<Method: MethodSpecs::MySub(MethodSpecs::MyMod)#bar" + end + + it "does not show the defining module if it is the same as the receiver class" do + MethodSpecs::A.new.method(:baz).send(@method).should.start_with? "#<Method: MethodSpecs::A#baz" + end + + it "returns a String containing the Module containing the method if object has a singleton class but method is not defined in the singleton class" do + obj = MethodSpecs::MySub.new + obj.singleton_class + @m = obj.method(:bar) + @string = @m.send(@method) + @string.should.start_with? "#<Method: MethodSpecs::MySub(MethodSpecs::MyMod)#bar" + + c = MethodSpecs::MySub.dup + m = Module.new{def bar; end} + c.extend(m) + @string = c.method(:bar).send(@method) + @string.should.start_with? "#<Method: #<Class:#{c.inspect}>(#{m.inspect})#bar" + end + + it "returns a String containing the singleton class if method is defined in the singleton class" do + obj = MethodSpecs::MySub.new + def obj.bar; end + @m = obj.method(:bar) + @string = @m.send(@method).sub(/0x\h+/, '0xXXXXXX') + @string.should.start_with? "#<Method: #<MethodSpecs::MySub:0xXXXXXX>.bar" + end + + it "shows the metaclass and the owner for a Module instance method retrieved from a class" do + String.method(:include).inspect.should.start_with?("#<Method: #<Class:String>(Module)#include") + end +end diff --git a/spec/ruby/core/method/source_location_spec.rb b/spec/ruby/core/method/source_location_spec.rb new file mode 100644 index 0000000000..87413a2ab6 --- /dev/null +++ b/spec/ruby/core/method/source_location_spec.rb @@ -0,0 +1,126 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Method#source_location" do + before :each do + @method = MethodSpecs::SourceLocation.method(:location) + end + + it "returns an Array" do + @method.source_location.should be_an_instance_of(Array) + end + + it "sets the first value to the path of the file in which the method was defined" do + file = @method.source_location[0] + file.should be_an_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[1] + line.should be_an_instance_of(Integer) + line.should == 5 + end + + it "returns the last place the method was defined" do + MethodSpecs::SourceLocation.method(:redefined).source_location[1].should == 13 + end + + it "returns the location of the original method even if it was aliased" do + MethodSpecs::SourceLocation.new.method(:aka).source_location[1].should == 17 + end + + it "works for methods defined with a block" do + line = nil + klass = Class.new do + line = __LINE__ + 1 + define_method(:f) { } + end + + method = klass.new.method(:f) + method.source_location[0].should =~ /#{__FILE__}/ + method.source_location[1].should == line + end + + it "works for methods defined with a Method" do + line = nil + klass = Class.new do + line = __LINE__ + 1 + def f + end + define_method :g, new.method(:f) + end + + method = klass.new.method(:g) + method.source_location[0].should =~ /#{__FILE__}/ + method.source_location[1].should == line + end + + it "works for methods defined with an UnboundMethod" do + line = nil + klass = Class.new do + line = __LINE__ + 1 + def f + end + define_method :g, instance_method(:f) + end + + method = klass.new.method(:g) + method.source_location[0].should =~ /#{__FILE__}/ + method.source_location[1].should == line + end + + it "works for methods whose visibility has been overridden in a subclass" do + line = nil + superclass = Class.new do + line = __LINE__ + 1 + def f + end + end + subclass = Class.new(superclass) do + private :f + end + + method = subclass.new.method(:f) + method.source_location[0].should =~ /#{__FILE__}/ + method.source_location[1].should == line + end + + it "works for core methods where it returns nil or <internal:" do + loc = method(:__id__).source_location + if loc == nil + loc.should == nil + else + loc[0].should.start_with?('<internal:') + loc[1].should be_kind_of(Integer) + end + + loc = method(:tap).source_location + if loc == nil + loc.should == nil + else + loc[0].should.start_with?('<internal:') + loc[1].should be_kind_of(Integer) + end + end + + it "works for eval with a given line" do + c = Class.new do + eval('def self.m; end', nil, "foo", 100) + end + location = c.method(:m).source_location + ruby_version_is(""..."4.1") do + location.should == ["foo", 100] + end + ruby_version_is("4.1") do + location.should == ["foo", 100, 0, 100, 15] + end + end + + describe "for a Method generated by respond_to_missing?" do + it "returns nil" do + m = MethodSpecs::Methods.new + m.method(:handled_via_method_missing).source_location.should be_nil + end + end +end diff --git a/spec/ruby/core/method/super_method_spec.rb b/spec/ruby/core/method/super_method_spec.rb new file mode 100644 index 0000000000..c63a7aaa0f --- /dev/null +++ b/spec/ruby/core/method/super_method_spec.rb @@ -0,0 +1,64 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Method#super_method" do + it "returns the method that would be called by super in the method" do + obj = MethodSpecs::C.new + obj.extend MethodSpecs::OverrideAgain + meth = obj.method(:overridden) + + s_meth = meth.super_method + s_meth.owner.should == MethodSpecs::C + s_meth.receiver.should == obj + s_meth.name.should == :overridden + + ss_meth = meth.super_method.super_method + ss_meth.owner.should == MethodSpecs::BetweenBAndC + ss_meth.receiver.should == obj + ss_meth.name.should == :overridden + + sss_meth = meth.super_method.super_method.super_method + sss_meth.owner.should == MethodSpecs::B + sss_meth.receiver.should == obj + sss_meth.name.should == :overridden + end + + it "returns nil when there's no super method in the parent" do + method = Object.new.method(:method) + method.super_method.should == nil + end + + it "returns nil when the parent's method is removed" do + klass = Class.new do + def overridden; end + end + sub = Class.new(klass) do + def overridden; end + end + object = sub.new + method = object.method(:overridden) + + klass.class_eval { undef :overridden } + + 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 + MethodSpecs::InheritedMethods::C.new.derp.should == 'BA' + end + + it "returns the expected super_method" do + method = MethodSpecs::InheritedMethods::C.new.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.new.method(:meow) + method.super_method.owner.should == MethodSpecs::InheritedMethods::A + end + end +end diff --git a/spec/ruby/core/method/to_proc_spec.rb b/spec/ruby/core/method/to_proc_spec.rb new file mode 100644 index 0000000000..4993cce239 --- /dev/null +++ b/spec/ruby/core/method/to_proc_spec.rb @@ -0,0 +1,104 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Method#to_proc" do + before :each do + ScratchPad.record [] + + @m = MethodSpecs::Methods.new + @meth = @m.method(:foo) + end + + it "returns a Proc object corresponding to the method" do + @meth.to_proc.kind_of?(Proc).should == true + end + + it "returns a Proc which does not depends on the value of self" do + 3.instance_exec(4, &5.method(:+)).should == 9 + end + + + it "returns a Proc object with the correct arity" do + # This may seem redundant but this bug has cropped up in jruby, mri and yarv. + # http://jira.codehaus.org/browse/JRUBY-124 + [ :zero, :one_req, :two_req, + :zero_with_block, :one_req_with_block, :two_req_with_block, + :one_opt, :one_req_one_opt, :one_req_two_opt, :two_req_one_opt, + :one_opt_with_block, :one_req_one_opt_with_block, :one_req_two_opt_with_block, :two_req_one_opt_with_block, + :zero_with_splat, :one_req_with_splat, :two_req_with_splat, + :one_req_one_opt_with_splat, :one_req_two_opt_with_splat, :two_req_one_opt_with_splat, + :zero_with_splat_and_block, :one_req_with_splat_and_block, :two_req_with_splat_and_block, + :one_req_one_opt_with_splat_and_block, :one_req_two_opt_with_splat_and_block, :two_req_one_opt_with_splat_and_block + ].each do |m| + @m.method(m).to_proc.arity.should == @m.method(m).arity + end + end + + it "returns a proc that can be used by define_method" do + x = +'test' + to_s = class << x + define_method :foo, method(:to_s).to_proc + to_s + end + + x.foo.should == to_s + end + + it "returns a proc that can be yielded to" do + x = Object.new + def x.foo(*a); a; end + def x.bar; yield; end + def x.baz(*a); yield(*a); end + + m = x.method :foo + x.bar(&m).should == [] + x.baz(1,2,3,&m).should == [1,2,3] + end + + it "returns a proc whose binding has the same receiver as the method" do + @meth.receiver.should == @meth.to_proc.binding.receiver + end + + # #5926 + it "returns a proc that can receive a block" do + x = Object.new + def x.foo; yield 'bar'; end + + m = x.method :foo + result = nil + m.to_proc.call {|val| result = val} + result.should == 'bar' + end + + it "can be called directly and not unwrap arguments like a block" do + obj = MethodSpecs::ToProcBeta.new + obj.to_proc.call([1]).should == [1] + end + + it "should correct handle arguments (unwrap)" do + obj = MethodSpecs::ToProcBeta.new + + array = [[1]] + array.each(&obj) + ScratchPad.recorded.should == [[1]] + end + + it "executes method with whole array (one argument)" do + obj = MethodSpecs::ToProcBeta.new + + array = [[1, 2]] + array.each(&obj) + ScratchPad.recorded.should == [[1, 2]] + end + + it "returns a proc that properly invokes module methods with super" do + m1 = Module.new { def foo(ary); ary << :m1; end; } + m2 = Module.new { def foo(ary = []); super(ary); ary << :m2; end; } + c2 = Class.new do + include m1 + include m2 + end + + c2.new.method(:foo).to_proc.call.should == %i[m1 m2] + end +end diff --git a/spec/ruby/core/method/to_s_spec.rb b/spec/ruby/core/method/to_s_spec.rb new file mode 100644 index 0000000000..9f19011302 --- /dev/null +++ b/spec/ruby/core/method/to_s_spec.rb @@ -0,0 +1,6 @@ +require_relative '../../spec_helper' +require_relative 'shared/to_s' + +describe "Method#to_s" do + it_behaves_like :method_to_s, :to_s +end diff --git a/spec/ruby/core/method/unbind_spec.rb b/spec/ruby/core/method/unbind_spec.rb new file mode 100644 index 0000000000..0b630e4d88 --- /dev/null +++ b/spec/ruby/core/method/unbind_spec.rb @@ -0,0 +1,46 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +describe "Method#unbind" do + before :each do + @normal = MethodSpecs::Methods.new + @normal_m = @normal.method :foo + @normal_um = @normal_m.unbind + @pop_um = MethodSpecs::MySub.new.method(:bar).unbind + @string = @pop_um.inspect.sub(/0x\w+/, '0xXXXXXX') + end + + it "returns an UnboundMethod" do + @normal_um.should be_kind_of(UnboundMethod) + end + + describe "#inspect" do + it "returns a String containing 'UnboundMethod'" do + @string.should =~ /\bUnboundMethod\b/ + end + + it "returns a String containing the method name" do + @string.should =~ /\#bar/ + end + + it "returns a String containing the Module the method is defined in" do + @string.should =~ /MethodSpecs::MyMod/ + end + + it "returns a String containing the Module the method is referenced from" do + @string.should =~ /MethodSpecs::MyMod/ + end + end + + it "keeps the origin singleton class if there is one" do + obj = Object.new + def obj.foo + end + obj.method(:foo).unbind.inspect.should.start_with?("#<UnboundMethod: #{obj.singleton_class}#foo") + end + + specify "rebinding UnboundMethod to Method's obj produces exactly equivalent Methods" do + @normal_um.bind(@normal).should == @normal_m + @normal_m.should == @normal_um.bind(@normal) + end +end |
