diff options
Diffstat (limited to 'spec/ruby/language/def_spec.rb')
| -rw-r--r-- | spec/ruby/language/def_spec.rb | 815 |
1 files changed, 815 insertions, 0 deletions
diff --git a/spec/ruby/language/def_spec.rb b/spec/ruby/language/def_spec.rb new file mode 100644 index 0000000000..82c89a0d08 --- /dev/null +++ b/spec/ruby/language/def_spec.rb @@ -0,0 +1,815 @@ +require_relative '../spec_helper' +require_relative 'fixtures/def' + +# Language-level method behaviour +describe "Redefining a method" do + it "replaces the original method" do + def barfoo; 100; end + barfoo.should == 100 + + def barfoo; 200; end + barfoo.should == 200 + end +end + +describe "Defining a method at the top-level" do + it "defines it on Object with private visibility by default" do + Object.private_instance_methods(false).should.include?(:some_toplevel_method) + end + + it "defines it on Object with public visibility after calling public" do + Object.public_instance_methods(false).should.include?(:public_toplevel_method) + end +end + +describe "Defining an 'initialize' method" do + it "sets the method's visibility to private" do + class DefInitializeSpec + def initialize + end + end + DefInitializeSpec.private_instance_methods(false).should.include?(:initialize) + end +end + +describe "Defining an 'initialize_copy' method" do + it "sets the method's visibility to private" do + class DefInitializeCopySpec + def initialize_copy + end + end + DefInitializeCopySpec.private_instance_methods(false).should.include?(:initialize_copy) + end +end + +describe "Defining an 'initialize_dup' method" do + it "sets the method's visibility to private" do + class DefInitializeDupSpec + def initialize_dup + end + end + DefInitializeDupSpec.private_instance_methods(false).should.include?(:initialize_dup) + end +end + +describe "Defining an 'initialize_clone' method" do + it "sets the method's visibility to private" do + class DefInitializeCloneSpec + def initialize_clone + end + end + DefInitializeCloneSpec.private_instance_methods(false).should.include?(:initialize_clone) + end +end + +describe "Defining a 'respond_to_missing?' method" do + it "sets the method's visibility to private" do + class DefRespondToMissingPSpec + def respond_to_missing? + end + end + DefRespondToMissingPSpec.private_instance_methods(false).should.include?(:respond_to_missing?) + end +end + +describe "Defining a method" do + it "returns a symbol of the method name" do + method_name = def some_method; end + method_name.should == :some_method + end +end + +describe "An instance method" do + it "raises an error with too few arguments" do + def foo(a, b); end + -> { foo 1 }.should.raise(ArgumentError, 'wrong number of arguments (given 1, expected 2)') + end + + it "raises an error with too many arguments" do + def foo(a); end + -> { foo 1, 2 }.should.raise(ArgumentError, 'wrong number of arguments (given 2, expected 1)') + end + + it "raises FrozenError with the correct class name" do + -> { + Module.new do + self.freeze + def foo; end + end + }.should.raise(FrozenError) { |e| + msg_class = ruby_version_is("4.0") ? "Module" : "module" + e.message.should == "can't modify frozen #{msg_class}: #{e.receiver}" + } + + -> { + Class.new do + self.freeze + def foo; end + end + }.should.raise(FrozenError){ |e| + msg_class = ruby_version_is("4.0") ? "Class" : "class" + e.message.should == "can't modify frozen #{msg_class}: #{e.receiver}" + } + end +end + +describe "An instance method definition with a splat" do + it "accepts an unnamed '*' argument" do + def foo(*); end; + + foo.should == nil + foo(1, 2).should == nil + foo(1, 2, 3, 4, :a, :b, 'c', 'd').should == nil + end + + it "accepts a named * argument" do + def foo(*a); a; end; + foo.should == [] + foo(1, 2).should == [1, 2] + foo([:a]).should == [[:a]] + end + + it "accepts non-* arguments before the * argument" do + def foo(a, b, c, d, e, *f); [a, b, c, d, e, f]; end + foo(1, 2, 3, 4, 5, 6, 7, 8).should == [1, 2, 3, 4, 5, [6, 7, 8]] + end + + it "allows only a single * argument" do + -> { eval 'def foo(a, *b, *c); end' }.should.raise(SyntaxError) + end + + it "requires the presence of any arguments that precede the *" do + def foo(a, b, *c); end + -> { foo 1 }.should.raise(ArgumentError, 'wrong number of arguments (given 1, expected 2+)') + end +end + +describe "An instance method with a default argument" do + it "evaluates the default when no arguments are passed" do + def foo(a = 1) + a + end + foo.should == 1 + foo(2).should == 2 + end + + it "evaluates the default empty expression when no arguments are passed" do + def foo(a = ()) + a + end + foo.should == nil + foo(2).should == 2 + end + + it "assigns an empty Array to an unused splat argument" do + def foo(a = 1, *b) + [a,b] + end + foo.should == [1, []] + foo(2).should == [2, []] + end + + it "evaluates the default when required arguments precede it" do + def foo(a, b = 2) + [a,b] + end + -> { foo }.should.raise(ArgumentError, 'wrong number of arguments (given 0, expected 1..2)') + foo(1).should == [1, 2] + end + + it "prefers to assign to a default argument before a splat argument" do + def foo(a, b = 2, *c) + [a,b,c] + end + -> { foo }.should.raise(ArgumentError, 'wrong number of arguments (given 0, expected 1+)') + foo(1).should == [1,2,[]] + end + + it "prefers to assign to a default argument when there are no required arguments" do + def foo(a = 1, *args) + [a,args] + end + foo(2,2).should == [2,[2]] + end + + it "does not evaluate the default when passed a value and a * argument" do + def foo(a, b = 2, *args) + [a,b,args] + end + foo(2,3,3).should == [2,3,[3]] + end + + ruby_version_is ""..."3.4" do + it "raises a SyntaxError if using the argument in its default value" do + -> { + eval "def foo(bar = bar) + bar + end" + }.should.raise(SyntaxError) + end + end + + ruby_version_is "3.4" do + it "is nil if using the argument in its default value" do + -> { + eval "def foo(bar = bar) + bar + end + foo" + }.call.should == nil + end + end + + it "calls a method with the same name as the local when explicitly using ()" do + def bar + 1 + end + def foo(bar = bar()) + bar + end + foo.should == 1 + foo(2).should == 2 + end +end + +describe "A singleton method definition" do + it "can be declared for a local variable" do + a = Object.new + def a.foo + 5 + end + a.foo.should == 5 + end + + it "can be declared for an instance variable" do + @a = Object.new + def @a.foo + 6 + end + @a.foo.should == 6 + end + + it "can be declared for a global variable" do + $__a__ = +"hi" + def $__a__.foo + 7 + end + $__a__.foo.should == 7 + end + + it "can be declared with an empty method body" do + class DefSpec + def self.foo;end + end + DefSpec.foo.should == nil + end + + it "can be redefined" do + obj = Object.new + def obj.==(other) + 1 + end + (obj==1).should == 1 + def obj.==(other) + 2 + end + (obj==2).should == 2 + end + + it "raises FrozenError if frozen" do + obj = Object.new + obj.freeze + -> { def obj.foo; end }.should.raise(FrozenError) + end + + it "raises FrozenError with the correct class name" do + obj = Object.new + obj.freeze + msg_class = ruby_version_is("4.0") ? "Object" : "object" + -> { def obj.foo; end }.should.raise(FrozenError, "can't modify frozen #{msg_class}: #{obj}") + + obj = Object.new + c = obj.singleton_class + c.singleton_class.freeze + -> { def c.foo; end }.should.raise(FrozenError, "can't modify frozen Class: #{c}") + + c = Class.new + c.freeze + -> { def c.foo; end }.should.raise(FrozenError, "can't modify frozen Class: #{c}") + + m = Module.new + m.freeze + -> { def m.foo; end }.should.raise(FrozenError, "can't modify frozen Module: #{m}") + end +end + +describe "Redefining a singleton method" do + it "does not inherit a previously set visibility" do + o = Object.new + + class << o; private; def foo; end; end; + + class << o; should have_private_instance_method(:foo); end + + class << o; def foo; end; end; + + class << o; should_not have_private_instance_method(:foo); end + class << o; should have_instance_method(:foo); end + + end +end + +describe "Redefining a singleton method" do + it "does not inherit a previously set visibility" do + o = Object.new + + class << o; private; def foo; end; end; + + class << o; should have_private_instance_method(:foo); end + + class << o; def foo; end; end; + + class << o; should_not have_private_instance_method(:foo); end + class << o; should have_instance_method(:foo); end + + end +end + +describe "A method defined with extreme default arguments" do + it "can redefine itself when the default is evaluated" do + class DefSpecs + def foo(x = (def foo; "hello"; end;1));x;end + end + + d = DefSpecs.new + d.foo(42).should == 42 + d.foo.should == 1 + d.foo.should == 'hello' + end + + it "may use an fcall as a default" do + def bar + 1 + end + def foo(x = bar()) + x + end + foo.should == 1 + foo(2).should == 2 + end + + it "evaluates the defaults in the method's scope" do + def foo(x = ($foo_self = self; nil)); end + foo + $foo_self.should == self + end + + it "may use preceding arguments as defaults" do + def foo(obj, width=obj.length) + width + end + foo('abcde').should == 5 + end + + it "may use a lambda as a default" do + def foo(output = 'a', prc = -> n { output * n }) + prc.call(5) + end + foo.should == 'aaaaa' + end +end + +describe "A singleton method defined with extreme default arguments" do + it "may use a method definition as a default" do + $__a = Object.new + def $__a.foo(x = (def $__a.foo; "hello"; end;1));x;end + + $__a.foo(42).should == 42 + $__a.foo.should == 1 + $__a.foo.should == 'hello' + end + + it "may use an fcall as a default" do + a = Object.new + def a.bar + 1 + end + def a.foo(x = bar()) + x + end + a.foo.should == 1 + a.foo(2).should == 2 + end + + it "evaluates the defaults in the singleton scope" do + a = Object.new + def a.foo(x = ($foo_self = self; nil)); 5 ;end + a.foo + $foo_self.should == a + end + + it "may use preceding arguments as defaults" do + a = Object.new + def a.foo(obj, width=obj.length) + width + end + a.foo('abcde').should == 5 + end + + it "may use a lambda as a default" do + a = Object.new + def a.foo(output = 'a', prc = -> n { output * n }) + prc.call(5) + end + a.foo.should == 'aaaaa' + end +end + +describe "A method definition inside a metaclass scope" do + it "can create a class method" do + class DefSpecSingleton + class << self + def a_class_method;self;end + end + end + + DefSpecSingleton.a_class_method.should == DefSpecSingleton + -> { Object.a_class_method }.should.raise(NoMethodError) + end + + it "can create a singleton method" do + obj = Object.new + class << obj + def a_singleton_method;self;end + end + + obj.a_singleton_method.should == obj + -> { Object.new.a_singleton_method }.should.raise(NoMethodError) + end + + it "raises FrozenError if frozen" do + obj = Object.new + obj.freeze + + class << obj + -> { def foo; end }.should.raise(FrozenError) + end + end +end + +describe "A nested method definition" do + it "creates an instance method when evaluated in an instance method" do + class DefSpecNested + def create_instance_method + def an_instance_method;self;end + an_instance_method + end + end + + obj = DefSpecNested.new + obj.create_instance_method.should == obj + obj.an_instance_method.should == obj + + other = DefSpecNested.new + other.an_instance_method.should == other + + DefSpecNested.should.method_defined?(:an_instance_method, false) + end + + it "creates a class method when evaluated in a class method" do + class DefSpecNested + class << self + # cleanup + remove_method :a_class_method if method_defined? :a_class_method + def create_class_method + def a_class_method;self;end + a_class_method + end + end + end + + -> { DefSpecNested.a_class_method }.should.raise(NoMethodError) + DefSpecNested.create_class_method.should == DefSpecNested + DefSpecNested.a_class_method.should == DefSpecNested + -> { Object.a_class_method }.should.raise(NoMethodError) + -> { DefSpecNested.new.a_class_method }.should.raise(NoMethodError) + end + + it "creates a singleton method when evaluated in the metaclass of an instance" do + class DefSpecNested + def create_singleton_method + class << self + def a_singleton_method;self;end + end + a_singleton_method + end + end + + obj = DefSpecNested.new + obj.create_singleton_method.should == obj + obj.a_singleton_method.should == obj + + other = DefSpecNested.new + -> { other.a_singleton_method }.should.raise(NoMethodError) + end + + it "creates a method in the surrounding context when evaluated in a def expr.method" do + class DefSpecNested + TARGET = Object.new + def TARGET.defs_method + def inherited_method;self;end + end + end + + DefSpecNested::TARGET.defs_method + DefSpecNested.should.method_defined?(:inherited_method, false) + DefSpecNested::TARGET.should_not.respond_to? :inherited_method + + obj = DefSpecNested.new + obj.inherited_method.should == obj + ensure + DefSpecNested.send(:remove_const, :TARGET) + end + + # See http://yugui.jp/articles/846#label-3 + it "inside an instance_eval creates a singleton method" do + class DefSpecNested + OBJ = Object.new + OBJ.instance_eval do + def create_method_in_instance_eval(a = (def arg_method; end)) + def body_method; end + end + end + end + + obj = DefSpecNested::OBJ + obj.create_method_in_instance_eval + + obj.should.respond_to? :arg_method + obj.should.respond_to? :body_method + + DefSpecNested.should_not.method_defined? :arg_method + DefSpecNested.should_not.method_defined? :body_method + ensure + DefSpecNested.send(:remove_const, :OBJ) + end + + it "creates an instance method inside Class.new" do + cls = Class.new do + def do_def + def new_def + 1 + end + end + end + + obj = cls.new + obj.do_def + obj.new_def.should == 1 + + cls.new.new_def.should == 1 + + -> { Object.new.new_def }.should.raise(NoMethodError) + end +end + +describe "A method definition always resets the visibility to public for nested definitions" do + it "in Class.new" do + cls = Class.new do + private + def do_def + def new_def + 1 + end + end + end + + obj = cls.new + -> { obj.do_def }.should.raise(NoMethodError, /private/) + obj.send :do_def + obj.new_def.should == 1 + + cls.new.new_def.should == 1 + + -> { Object.new.new_def }.should.raise(NoMethodError) + end + + it "at the toplevel" do + obj = Object.new + -> { obj.toplevel_define_other_method }.should.raise(NoMethodError, /private/) + toplevel_define_other_method + nested_method_in_toplevel_method.should == 42 + + Object.new.nested_method_in_toplevel_method.should == 42 + end +end + +describe "A method definition inside an instance_eval" do + it "creates a singleton method" do + obj = Object.new + obj.instance_eval do + def an_instance_eval_method;self;end + end + obj.an_instance_eval_method.should == obj + + other = Object.new + -> { other.an_instance_eval_method }.should.raise(NoMethodError) + end + + it "creates a singleton method when evaluated inside a metaclass" do + obj = Object.new + obj.instance_eval do + class << self + def a_metaclass_eval_method;self;end + end + end + obj.a_metaclass_eval_method.should == obj + + other = Object.new + -> { other.a_metaclass_eval_method }.should.raise(NoMethodError) + end + + it "creates a class method when the receiver is a class" do + DefSpecNested.instance_eval do + def an_instance_eval_class_method;self;end + end + + DefSpecNested.an_instance_eval_class_method.should == DefSpecNested + -> { Object.an_instance_eval_class_method }.should.raise(NoMethodError) + end + + it "creates a class method when the receiver is an anonymous class" do + m = Class.new + m.instance_eval do + def klass_method + :test + end + end + + m.klass_method.should == :test + -> { Object.klass_method }.should.raise(NoMethodError) + end + + it "creates a class method when instance_eval is within class" do + m = Class.new do + instance_eval do + def klass_method + :test + end + end + end + + m.klass_method.should == :test + -> { Object.klass_method }.should.raise(NoMethodError) + end +end + +describe "A method definition inside an instance_exec" do + it "creates a class method when the receiver is a class" do + DefSpecNested.instance_exec(1) do |param| + @stuff = param + + def an_instance_exec_class_method; @stuff; end + end + + DefSpecNested.an_instance_exec_class_method.should == 1 + -> { Object.an_instance_exec_class_method }.should.raise(NoMethodError) + end + + it "creates a class method when the receiver is an anonymous class" do + m = Class.new + m.instance_exec(1) do |param| + @stuff = param + + def klass_method + @stuff + end + end + + m.klass_method.should == 1 + -> { Object.klass_method }.should.raise(NoMethodError) + end + + it "creates a class method when instance_exec is within class" do + m = Class.new do + instance_exec(2) do |param| + @stuff = param + + def klass_method + @stuff + end + end + end + + m.klass_method.should == 2 + -> { Object.klass_method }.should.raise(NoMethodError) + end +end + +describe "A method definition in an eval" do + it "creates an instance method" do + class DefSpecNested + def eval_instance_method + eval "def an_eval_instance_method;self;end", binding + an_eval_instance_method + end + end + + obj = DefSpecNested.new + obj.eval_instance_method.should == obj + obj.an_eval_instance_method.should == obj + + other = DefSpecNested.new + other.an_eval_instance_method.should == other + + -> { Object.new.an_eval_instance_method }.should.raise(NoMethodError) + end + + it "creates a class method" do + class DefSpecNestedB + class << self + def eval_class_method + eval "def an_eval_class_method;self;end" #, binding + an_eval_class_method + end + end + end + + DefSpecNestedB.eval_class_method.should == DefSpecNestedB + DefSpecNestedB.an_eval_class_method.should == DefSpecNestedB + + -> { Object.an_eval_class_method }.should.raise(NoMethodError) + -> { DefSpecNestedB.new.an_eval_class_method}.should.raise(NoMethodError) + end + + it "creates a singleton method" do + class DefSpecNested + def eval_singleton_method + class << self + eval "def an_eval_singleton_method;self;end", binding + end + an_eval_singleton_method + end + end + + obj = DefSpecNested.new + obj.eval_singleton_method.should == obj + obj.an_eval_singleton_method.should == obj + + other = DefSpecNested.new + -> { other.an_eval_singleton_method }.should.raise(NoMethodError) + end +end + +describe "a method definition that sets more than one default parameter all to the same value" do + def foo(a=b=c={}) + [a,b,c] + end + it "assigns them all the same object by default" do + foo.should == [{},{},{}] + a, b, c = foo + a.should.eql?(b) + a.should.eql?(c) + end + + it "allows the first argument to be given, and sets the rest to null" do + foo(1).should == [1,nil,nil] + end + + it "assigns the parameters different objects across different default calls" do + a, _b, _c = foo + d, _e, _f = foo + a.should_not.equal?(d) + end + + it "only allows overriding the default value of the first such parameter in each set" do + -> { foo(1,2) }.should.raise(ArgumentError, 'wrong number of arguments (given 2, expected 0..1)') + end + + def bar(a=b=c=1,d=2) + [a,b,c,d] + end + + it "treats the argument after the multi-parameter normally" do + bar.should == [1,1,1,2] + bar(3).should == [3,nil,nil,2] + bar(3,4).should == [3,nil,nil,4] + -> { bar(3,4,5) }.should.raise(ArgumentError, 'wrong number of arguments (given 3, expected 0..2)') + end +end + +describe "The def keyword" do + describe "within a closure" do + it "looks outside the closure for the visibility" do + module DefSpecsLambdaVisibility + private + + -> { + def some_method; end + }.call + end + + DefSpecsLambdaVisibility.private_instance_methods(false).should.include?(:some_method) + end + end +end |
