File: //usr/local/rvm/src/ruby-2.5.9/spec/ruby/core/module/define_method_spec.rb
require File.expand_path('../../../spec_helper', __FILE__)
require File.expand_path('../fixtures/classes', __FILE__)
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
lambda { @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
lambda { @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
lambda { foo.new.ziggy }.should raise_error(NoMethodError)
foo.new.piggy.should == 'piggy'
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
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, &lambda { self })
end
o = DefineMethodSpecClass.new
o.block_test1.should == o
o.block_test2.should == o
end
it "raises a TypeError when the given method is no Method/Proc" do
lambda {
Class.new { define_method(:test, "self") }
}.should raise_error(TypeError)
lambda {
Class.new { define_method(:test, 1234) }
}.should raise_error(TypeError)
lambda {
Class.new { define_method(:test, nil) }
}.should raise_error(TypeError)
end
it "raises an ArgumentError when no block is given" do
lambda {
Class.new { define_method(:test) }
}.should raise_error(ArgumentError)
end
ruby_version_is "2.3" do
it "does not use the caller block when no block is given" do
o = Object.new
def o.define(name)
self.class.class_eval do
define_method(name)
end
end
lambda {
o.define(:foo) { raise "not used" }
}.should raise_error(ArgumentError)
end
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
lambda { obj.proc_style_test :arg }.should raise_error(ArgumentError)
end
it "raises a RuntimeError if frozen" do
lambda {
Class.new { freeze; define_method(:foo) {} }
}.should raise_error(RuntimeError)
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"
lambda{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)
lambda {
Class.new { define_method :bar, m }
}.should raise_error(TypeError)
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)
lambda {
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)
lambda { 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 "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
ruby_version_is ''...'2.5' do
it "is a private method" do
Module.should have_private_instance_method(:define_method)
end
end
ruby_version_is '2.5' do
it "is a public method" do
Module.should have_public_instance_method(:define_method)
end
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 "raises a TypeError when an UnboundMethod from a child class is defined on a parent class" do
lambda {
ParentClass = Class.new { define_method(:foo) { :bar } }
ChildClass = Class.new(ParentClass) { define_method(:foo) { :baz } }
ParentClass.send :define_method, :foo, ChildClass.instance_method(:foo)
}.should raise_error(TypeError)
end
it "raises a TypeError when an UnboundMethod from one class is defined on an unrelated class" do
lambda {
DestinationClass = Class.new {
define_method :bar, ModuleSpecs::InstanceMeth.instance_method(:foo)
}
}.should raise_error(TypeError)
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
lambda { @klass.new.m 1 }.should raise_error(ArgumentError)
end
it "raises an ArgumentError when passed two arguments" do
lambda { @klass.new.m 1, 2 }.should raise_error(ArgumentError)
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
lambda { @klass.new.m 1 }.should raise_error(ArgumentError)
end
it "raises an ArgumentError when passed two arguments" do
lambda { @klass.new.m 1, 2 }.should raise_error(ArgumentError)
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
lambda { @klass.new.m }.should raise_error(ArgumentError)
end
it "raises an ArgumentError when passed zero arguments and a block" do
lambda { @klass.new.m { :computed } }.should raise_error(ArgumentError)
end
it "raises an ArgumentError when passed two arguments" do
lambda { @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 "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
lambda { @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
lambda { @klass.new.m }.should raise_error(ArgumentError)
end
it "raises an ArgumentError when passed one argument" do
lambda { @klass.new.m 1 }.should raise_error(ArgumentError)
end
it "raises an ArgumentError when passed one argument and a block" do
lambda { @klass.new.m(1) { } }.should raise_error(ArgumentError)
end
it "raises an ArgumentError when passed three arguments" do
lambda { @klass.new.m 1, 2, 3 }.should raise_error(ArgumentError)
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
lambda { @klass.new.m }.should raise_error(ArgumentError)
end
it "raises an ArgumentError when passed one argument" do
lambda { @klass.new.m 1 }.should raise_error(ArgumentError)
end
it "raises an ArgumentError when passed one argument and a block" do
lambda { @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 "Method#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 "Method#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