diff options
Diffstat (limited to 'test/ruby/test_class.rb')
| -rw-r--r-- | test/ruby/test_class.rb | 627 |
1 files changed, 598 insertions, 29 deletions
diff --git a/test/ruby/test_class.rb b/test/ruby/test_class.rb index 3eaf103fd5..8f12e06685 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,13 +89,20 @@ 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 def test_superclass_of_basicobject assert_equal(nil, BasicObject.superclass) + + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") + begin; + module Mod end + BasicObject.include(Mod) + assert_equal(nil, BasicObject.superclass) + end; end def test_module_function @@ -131,6 +138,48 @@ class TestClass < Test::Unit::TestCase [: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]' @@ -144,23 +193,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 @@ -172,22 +219,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 @@ -198,6 +243,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 @@ -211,6 +259,46 @@ class TestClass < Test::Unit::TestCase assert_raise(TypeError) { BasicObject.dup } end + def test_class_hierarchy_inside_initialize_dup_bug_21538 + ancestors = sc_ancestors = nil + b = Class.new + b.define_singleton_method(:initialize_dup) do |x| + ancestors = self.ancestors + sc_ancestors = singleton_class.ancestors + super(x) + end + + a = Class.new(b) + + c = a.dup + + expected_ancestors = [c, b, *Object.ancestors] + expected_sc_ancestors = [c.singleton_class, b.singleton_class, *Object.singleton_class.ancestors] + assert_equal expected_ancestors, ancestors + assert_equal expected_sc_ancestors, sc_ancestors + assert_equal expected_ancestors, c.ancestors + assert_equal expected_sc_ancestors, c.singleton_class.ancestors + end + + def test_class_hierarchy_inside_initialize_clone_bug_21538 + ancestors = sc_ancestors = nil + a = Class.new + a.define_singleton_method(:initialize_clone) do |x| + ancestors = self.ancestors + sc_ancestors = singleton_class.ancestors + super(x) + end + + c = a.clone + + expected_ancestors = [c, *Object.ancestors] + expected_sc_ancestors = [c.singleton_class, *Object.singleton_class.ancestors] + assert_equal expected_ancestors, ancestors + assert_equal expected_sc_ancestors, sc_ancestors + assert_equal expected_ancestors, c.ancestors + assert_equal expected_sc_ancestors, c.singleton_class.ancestors + end + def test_singleton_class assert_raise(TypeError) { 1.extend(Module.new) } assert_raise(TypeError) { 1.0.extend(Module.new) } @@ -233,6 +321,10 @@ 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_nothing_raised { allocator.bind(Rational).call } + assert_nothing_raised { allocator.bind_call(Rational) } end def test_nonascii_name @@ -242,13 +334,31 @@ 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/) + assert_syntax_error("class << Object; 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 @@ -289,6 +399,17 @@ class TestClass < Test::Unit::TestCase assert_equal(42, PrivateClass.new.foo) end + def test_private_const_access + assert_raise_with_message NameError, /uninitialized/ do + begin + eval('class ::TestClass::PrivateClass; end') + rescue NameError + end + + Object.const_get "NOT_AVAILABLE_CONST_NAME_#{__LINE__}" + end + end + StrClone = String.clone Class.new(StrClone) @@ -298,7 +419,8 @@ class TestClass < Test::Unit::TestCase end def test_cannot_reinitialize_class_with_initialize_copy # [ruby-core:50869] - assert_in_out_err([], <<-'end;', ["Object"], []) + assert_in_out_err([], "#{<<~"begin;"}\n#{<<~'end;'}", ["Object"], []) + begin; class Class def initialize_copy(*); super; end end @@ -312,18 +434,22 @@ class TestClass < Test::Unit::TestCase end; end - module M - C = 1 + class CloneTest + def foo; TEST; end + end - def self.m - C - 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_module # [ruby-core:47834] - m = M.dup - assert_equal 1, m::C - assert_equal 1, m.m + 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 @@ -361,6 +487,41 @@ class TestClass < Test::Unit::TestCase 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 @@ -376,4 +537,412 @@ class TestClass < Test::Unit::TestCase 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" + EnvUtil.with_default_internal(Encoding::UTF_8) do + assert_raise_with_message(TypeError, /A\u{3042}/) { + o::Foo + } + end + 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; + module Bug + module Class + TestClassDefinedInC = (class C\u{1f5ff}; self; end).new + end + end + assert_raise_with_message(TypeError, /C\u{1f5ff}/) { + require '-test-/class' + } + 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_attached_object + c = Class.new + sc = c.singleton_class + obj = c.new + + assert_equal(obj, obj.singleton_class.attached_object) + assert_equal(c, sc.attached_object) + + assert_raise_with_message(TypeError, /is not a singleton class/) do + c.attached_object + end + + assert_raise_with_message(TypeError, /'NilClass' is not a singleton class/) do + nil.singleton_class.attached_object + end + + assert_raise_with_message(TypeError, /'FalseClass' is not a singleton class/) do + false.singleton_class.attached_object + end + + assert_raise_with_message(TypeError, /'TrueClass' is not a singleton class/) do + true.singleton_class.attached_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 + + def test_instance_freeze_dont_freeze_the_class_bug_19164 + klass = Class.new + klass.prepend(Module.new) + + klass.new.freeze + klass.define_method(:bar) {} + assert_equal klass, klass.remove_method(:bar), '[Bug #19164]' + end + + def test_method_table_assignment_just_after_class_init + assert_normal_exit "#{<<~"begin;"}\n#{<<~'end;'}", 'm_tbl assignment should be done only when Class object is not promoted' + begin; + GC.stress = true + class C; end + end; + end + + def test_singleton_cc_invalidation + assert_separately([], "#{<<~"begin;"}\n#{<<~"end;"}") + begin; + class T + def hi + "hi" + end + end + + t = T.new + t.singleton_class + + def hello(t) + t.hi + end + + 5.times do + hello(t) # populate inline cache on `t.singleton_class`. + end + + class T + remove_method :hi # invalidate `t.singleton_class` ccs for `hi` + end + + assert_raise NoMethodError do + hello(t) + end + end; + end + + def test_safe_multi_ractor_subclasses_list_mutation + assert_ractor "#{<<~"begin;"}\n#{<<~'end;'}" + begin; + 4.times.map do + Ractor.new do + 20_000.times do + Object.new.singleton_class + end + end + end.each(&:join) + end; + end + + def test_safe_multi_ractor_singleton_class_access + assert_ractor "#{<<~"begin;"}\n#{<<~'end;'}" + begin; + class A; end + 4.times.map do + Ractor.new do + a = A + 100.times do + a = a.singleton_class + end + end + end.each(&:join) + end; + end end |
