summaryrefslogtreecommitdiff
path: root/test/ruby/test_class.rb
diff options
context:
space:
mode:
Diffstat (limited to 'test/ruby/test_class.rb')
-rw-r--r--test/ruby/test_class.rb627
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