diff options
Diffstat (limited to 'test/ruby/test_class.rb')
| -rw-r--r-- | test/ruby/test_class.rb | 552 |
1 files changed, 533 insertions, 19 deletions
diff --git a/test/ruby/test_class.rb b/test/ruby/test_class.rb index a3384fa083..07c34ce9d5 100644 --- a/test/ruby/test_class.rb +++ b/test/ruby/test_class.rb @@ -1,5 +1,5 @@ +# frozen_string_literal: false require 'test/unit' -require_relative 'envutil' class TestClass < Test::Unit::TestCase # ------------------ @@ -46,9 +46,9 @@ class TestClass < Test::Unit::TestCase assert_same(Class, c.class) assert_same(Object, c.superclass) - c = Class.new(Fixnum) + c = Class.new(Integer) assert_same(Class, c.class) - assert_same(Fixnum, c.superclass) + assert_same(Integer, c.superclass) end def test_00_new_basic @@ -89,7 +89,7 @@ class TestClass < Test::Unit::TestCase end end - def test_instanciate_singleton_class + def test_instantiate_singleton_class c = class << Object.new; self; end assert_raise(TypeError) { c.new } end @@ -105,6 +105,74 @@ class TestClass < Test::Unit::TestCase end end + def test_extend_object + c = Class.new + assert_raise(TypeError) do + Module.instance_method(:extend_object).bind(c).call(Object.new) + end + end + + def test_append_features + c = Class.new + assert_raise(TypeError) do + Module.instance_method(:append_features).bind(c).call(Module.new) + end + end + + def test_prepend_features + c = Class.new + assert_raise(TypeError) do + Module.instance_method(:prepend_features).bind(c).call(Module.new) + end + end + + def test_module_specific_methods + assert_empty(Class.private_instance_methods(true) & + [:module_function, :extend_object, :append_features, :prepend_features]) + end + + def test_visibility_inside_method + assert_warn(/calling private without arguments inside a method may not have the intended effect/, '[ruby-core:79751]') do + Class.new do + def self.foo + private + end + foo + end + end + + assert_warn(/calling protected without arguments inside a method may not have the intended effect/, '[ruby-core:79751]') do + Class.new do + def self.foo + protected + end + foo + end + end + + assert_warn(/calling public without arguments inside a method may not have the intended effect/, '[ruby-core:79751]') do + Class.new do + def self.foo + public + end + foo + end + end + + assert_warn(/calling private without arguments inside a method may not have the intended effect/, '[ruby-core:79751]') do + Class.new do + class << self + alias priv private + end + + def self.foo + priv + end + foo + end + end + end + def test_method_redefinition feature2155 = '[ruby-dev:39400]' @@ -118,23 +186,21 @@ class TestClass < Test::Unit::TestCase assert_match(/:#{line}: warning: method redefined; discarding old foo/, stderr) assert_match(/:#{line-1}: warning: previous definition of foo/, stderr, feature2155) - stderr = EnvUtil.verbose_warning do + assert_warning '' do Class.new do def foo; end alias bar foo def foo; end end end - assert_equal("", stderr) - stderr = EnvUtil.verbose_warning do + assert_warning '' do Class.new do def foo; end alias bar foo alias bar foo end end - assert_equal("", stderr) line = __LINE__+4 stderr = EnvUtil.verbose_warning do @@ -146,22 +212,20 @@ class TestClass < Test::Unit::TestCase assert_match(/:#{line}: warning: method redefined; discarding old foo/, stderr) assert_match(/:#{line-1}: warning: previous definition of foo/, stderr, feature2155) - stderr = EnvUtil.verbose_warning do + assert_warning '' do Class.new do define_method(:foo) do end alias bar foo alias bar foo end end - assert_equal("", stderr) - stderr = EnvUtil.verbose_warning do + assert_warning '' do Class.new do def foo; end undef foo end end - assert_equal("", stderr) end def test_check_inheritable @@ -172,6 +236,9 @@ class TestClass < Test::Unit::TestCase assert_raise(TypeError) { Class.new(c) } assert_raise(TypeError) { Class.new(Class) } assert_raise(TypeError) { eval("class Foo < Class; end") } + m = "M\u{1f5ff}" + o = Class.new {break eval("class #{m}; self; end.new")} + assert_raise_with_message(TypeError, /#{m}/) {Class.new(o)} end def test_initialize_copy @@ -207,6 +274,14 @@ class TestClass < Test::Unit::TestCase assert_raise(TypeError) { Class.allocate.superclass } bug6863 = '[ruby-core:47148]' assert_raise(TypeError, bug6863) { Class.new(Class.allocate) } + + allocator = Class.instance_method(:allocate) + assert_raise_with_message(TypeError, /prohibited/) { + allocator.bind(Rational).call + } + assert_raise_with_message(TypeError, /prohibited/) { + allocator.bind_call(Rational) + } end def test_nonascii_name @@ -216,13 +291,30 @@ class TestClass < Test::Unit::TestCase assert_equal("TestClass::C\u{df}", c.name, '[ruby-core:24600]') end - def test_invalid_jump_from_class_definition - assert_raise(SyntaxError) { eval("class C; next; end") } - assert_raise(SyntaxError) { eval("class C; break; end") } - assert_raise(SyntaxError) { eval("class C; redo; end") } - assert_raise(SyntaxError) { eval("class C; retry; end") } - assert_raise(SyntaxError) { eval("class C; return; end") } - assert_raise(SyntaxError) { eval("class C; yield; end") } + def test_invalid_next_from_class_definition + assert_syntax_error("class C; next; end", /Invalid next/) + end + + def test_invalid_break_from_class_definition + assert_syntax_error("class C; break; end", /Invalid break/) + end + + def test_invalid_redo_from_class_definition + assert_syntax_error("class C; redo; end", /Invalid redo/) + end + + def test_invalid_retry_from_class_definition + assert_syntax_error("class C; retry; end", /Invalid retry/) + end + + def test_invalid_return_from_class_definition + assert_syntax_error("class C; return; end", /Invalid return/) + end + + def test_invalid_yield_from_class_definition + assert_raise(SyntaxError) { + EnvUtil.suppress_warning {eval("class C; yield; end")} + } end def test_clone @@ -270,4 +362,426 @@ class TestClass < Test::Unit::TestCase bug5274 = StrClone.new("[ruby-dev:44460]") assert_equal(bug5274, Marshal.load(Marshal.dump(bug5274))) end + + def test_cannot_reinitialize_class_with_initialize_copy # [ruby-core:50869] + assert_in_out_err([], "#{<<~"begin;"}\n#{<<~'end;'}", ["Object"], []) + begin; + class Class + def initialize_copy(*); super; end + end + + class A; end + class B; end + + A.send(:initialize_copy, Class.new(B)) rescue nil + + p A.superclass + end; + end + + class CloneTest + def foo; TEST; end + end + + CloneTest1 = CloneTest.clone + CloneTest2 = CloneTest.clone + class CloneTest1 + TEST = :C1 + end + class CloneTest2 + TEST = :C2 + end + + def test_constant_access_from_method_in_cloned_class + assert_equal :C1, CloneTest1.new.foo, '[Bug #15877]' + assert_equal :C2, CloneTest2.new.foo, '[Bug #15877]' + end + + def test_invalid_superclass + assert_raise(TypeError) do + eval <<-'end;' + class C < nil + end + end; + end + + assert_raise(TypeError) do + eval <<-'end;' + class C < false + end + end; + end + + assert_raise(TypeError) do + eval <<-'end;' + class C < true + end + end; + end + + assert_raise(TypeError) do + eval <<-'end;' + class C < 0 + end + end; + end + + assert_raise(TypeError) do + eval <<-'end;' + class C < "" + end + end; + end + + m = Module.new + n = "M\u{1f5ff}" + c = m.module_eval "class #{n}; new; end" + assert_raise_with_message(TypeError, /#{n}/) { + eval <<-"end;" + class C < c + end + end; + } + assert_raise_with_message(TypeError, /#{n}/) { + Class.new(c) + } + assert_raise_with_message(TypeError, /#{n}/) { + m.module_eval "class #{n} < Class.new; end" + } + end + + define_method :test_invalid_reset_superclass do + class A; end + class SuperclassCannotBeReset < A + end + assert_equal A, SuperclassCannotBeReset.superclass + + assert_raise_with_message(TypeError, /superclass mismatch/) { + class SuperclassCannotBeReset < String + end + } + + assert_raise_with_message(TypeError, /superclass mismatch/, "[ruby-core:75446]") { + class SuperclassCannotBeReset < Object + end + } + + assert_equal A, SuperclassCannotBeReset.superclass + end + + def test_cloned_singleton_method_added + bug5283 = '[ruby-dev:44477]' + added = [] + c = Class.new + c.singleton_class.class_eval do + define_method(:singleton_method_added) {|mid| added << [self, mid]} + def foo; :foo; end + end + added.clear + d = c.clone + assert_empty(added.grep(->(k) {c == k[0]}), bug5283) + assert_equal(:foo, d.foo) + end + + def test_clone_singleton_class_exists + klass = Class.new do + def self.bar; :bar; end + end + + o = klass.new + o.singleton_class + clone = o.clone + + assert_empty(o.singleton_class.instance_methods(false)) + assert_empty(clone.singleton_class.instance_methods(false)) + assert_empty(o.singleton_class.singleton_class.instance_methods(false)) + assert_empty(clone.singleton_class.singleton_class.instance_methods(false)) + end + + def test_clone_when_singleton_class_of_singleton_class_exists + klass = Class.new do + def self.bar; :bar; end + end + + o = klass.new + o.singleton_class.singleton_class + clone = o.clone + + assert_empty(o.singleton_class.instance_methods(false)) + assert_empty(clone.singleton_class.instance_methods(false)) + assert_empty(o.singleton_class.singleton_class.instance_methods(false)) + assert_empty(clone.singleton_class.singleton_class.instance_methods(false)) + end + + def test_clone_when_method_exists_on_singleton_class_of_singleton_class + klass = Class.new do + def self.bar; :bar; end + end + + o = klass.new + o.singleton_class.singleton_class.define_method(:s2_method) { :s2 } + clone = o.clone + + assert_empty(o.singleton_class.instance_methods(false)) + assert_empty(clone.singleton_class.instance_methods(false)) + assert_equal(:s2, o.singleton_class.s2_method) + assert_equal(:s2, clone.singleton_class.s2_method) + assert_equal([:s2_method], o.singleton_class.singleton_class.instance_methods(false)) + assert_equal([:s2_method], clone.singleton_class.singleton_class.instance_methods(false)) + end + + def test_singleton_class_p + feature7609 = '[ruby-core:51087] [Feature #7609]' + assert_predicate(self.singleton_class, :singleton_class?, feature7609) + assert_not_predicate(self.class, :singleton_class?, feature7609) + end + + def test_freeze_to_s + assert_nothing_raised("[ruby-core:41858] [Bug #5828]") { + Class.new.freeze.clone.to_s + } + end + + def test_singleton_class_of_frozen_object + obj = Object.new + c = obj.singleton_class + obj.freeze + assert_raise_with_message(FrozenError, /frozen object/) { + c.class_eval {def f; end} + } + end + + def test_singleton_class_message + c = Class.new.freeze + assert_raise_with_message(FrozenError, /frozen Class/) { + def c.f; end + } + end + + def test_singleton_class_should_has_own_namespace + # CONST in singleton class + objs = [] + $i = 0 + + 2.times{ + objs << obj = Object.new + class << obj + CONST = ($i += 1) + def foo + CONST + end + end + } + assert_equal(1, objs[0].foo, '[Bug #10943]') + assert_equal(2, objs[1].foo, '[Bug #10943]') + + # CONST in block in singleton class + objs = [] + $i = 0 + + 2.times{ + objs << obj = Object.new + class << obj + 1.times{ + CONST = ($i += 1) + } + def foo + [nil].map{ + CONST + } + end + end + } + assert_equal([1], objs[0].foo, '[Bug #10943]') + assert_equal([2], objs[1].foo, '[Bug #10943]') + + # class def in singleton class + objs = [] + $xs = [] + $i = 0 + + 2.times{ + objs << obj = Object.new + class << obj + CONST = ($i += 1) + class X + $xs << self + CONST = ($i += 1) + def foo + CONST + end + end + + def x + X + end + end + } + assert_not_equal($xs[0], $xs[1], '[Bug #10943]') + assert_not_equal(objs[0].x, objs[1].x, '[Bug #10943]') + assert_equal(2, $xs[0]::CONST, '[Bug #10943]') + assert_equal(2, $xs[0].new.foo, '[Bug #10943]') + assert_equal(4, $xs[1]::CONST, '[Bug #10943]') + assert_equal(4, $xs[1].new.foo, '[Bug #10943]') + + # class def in block in singleton class + objs = [] + $xs = [] + $i = 0 + + 2.times{ + objs << obj = Object.new + class << obj + 1.times{ + CONST = ($i += 1) + } + 1.times{ + class X + $xs << self + CONST = ($i += 1) + def foo + CONST + end + end + + def x + X + end + } + end + } + assert_not_equal($xs[0], $xs[1], '[Bug #10943]') + assert_not_equal(objs[0].x, objs[1].x, '[Bug #10943]') + assert_equal(2, $xs[0]::CONST, '[Bug #10943]') + assert_equal(2, $xs[0].new.foo, '[Bug #10943]') + assert_equal(4, $xs[1]::CONST, '[Bug #10943]') + assert_equal(4, $xs[1].new.foo, '[Bug #10943]') + + # method def in singleton class + ms = [] + ps = $test_singleton_class_shared_cref_ps = [] + 2.times{ + ms << Module.new do + class << self + $test_singleton_class_shared_cref_ps << Proc.new{ + def xyzzy + self + end + } + end + end + } + + ps.each{|p| p.call} # define xyzzy methods for each singleton classes + ms.each{|m| + assert_equal(m, m.xyzzy, "Bug #10871") + } + end + + def test_namescope_error_message + m = Module.new + o = m.module_eval "class A\u{3042}; self; end.new" + assert_raise_with_message(TypeError, /A\u{3042}/) { + o::Foo + } + end + + def test_redefinition_mismatch + m = Module.new + m.module_eval "A = 1", __FILE__, line = __LINE__ + e = assert_raise_with_message(TypeError, /is not a class/) { + m.module_eval "class A; end" + } + assert_include(e.message, "#{__FILE__}:#{line}: previous definition") + n = "M\u{1f5ff}" + m.module_eval "#{n} = 42", __FILE__, line = __LINE__ + e = assert_raise_with_message(TypeError, /#{n} is not a class/) { + m.module_eval "class #{n}; end" + } + assert_include(e.message, "#{__FILE__}:#{line}: previous definition") + + assert_separately([], "#{<<~"begin;"}\n#{<<~"end;"}") + begin; + Date = (class C\u{1f5ff}; self; end).new + assert_raise_with_message(TypeError, /C\u{1f5ff}/) { + require 'date' + } + end; + end + + def test_should_not_expose_singleton_class_without_metaclass + assert_normal_exit "#{<<~"begin;"}\n#{<<~'end;'}", '[Bug #11740]' + begin; + klass = Class.new(Array) + # The metaclass of +klass+ should handle #bla since it should inherit methods from meta:meta:Array + def (Array.singleton_class).bla; :bla; end + hidden = ObjectSpace.each_object(Class).find { |c| klass.is_a? c and c.inspect.include? klass.inspect } + raise unless hidden.nil? + end; + + assert_normal_exit "#{<<~"begin;"}\n#{<<~'end;'}", '[Bug #11740]' + begin; + klass = Class.new(Array) + klass.singleton_class + # The metaclass of +klass+ should handle #bla since it should inherit methods from meta:meta:Array + def (Array.singleton_class).bla; :bla; end + hidden = ObjectSpace.each_object(Class).find { |c| klass.is_a? c and c.inspect.include? klass.inspect } + raise if hidden.nil? + end; + + end + + def test_assign_frozen_class_to_const + c = Class.new.freeze + assert_same(c, Module.new.module_eval("self::Foo = c")) + c = Class.new.freeze + assert_same(c, Module.new.const_set(:Foo, c)) + end + + def test_subclasses + c = Class.new + sc = Class.new(c) + ssc = Class.new(sc) + [c, sc, ssc].each do |k| + k.include Module.new + k.new.define_singleton_method(:force_singleton_class){} + end + assert_equal([sc], c.subclasses) + assert_equal([ssc], sc.subclasses) + assert_equal([], ssc.subclasses) + + object_subclasses = Object.subclasses + assert_include(object_subclasses, c) + assert_not_include(object_subclasses, sc) + assert_not_include(object_subclasses, ssc) + object_subclasses.each do |subclass| + assert_equal Object, subclass.superclass, "Expected #{subclass}.superclass to be Object" + end + end + + def test_subclass_gc + c = Class.new + 10_000.times do + cc = Class.new(c) + 100.times { Class.new(cc) } + end + assert(c.subclasses.size <= 10_000) + end + + def test_subclass_gc_stress + 10000.times do + c = Class.new + 100.times { Class.new(c) } + assert(c.subclasses.size <= 100) + end + end + + def test_classext_memory_leak + assert_no_memory_leak([], <<-PREP, <<-CODE, rss: true) +code = proc { Class.new } +1_000.times(&code) +PREP +3_000_000.times(&code) +CODE + end end |
