summaryrefslogtreecommitdiff
path: root/test/objspace/test_objspace.rb
diff options
context:
space:
mode:
Diffstat (limited to 'test/objspace/test_objspace.rb')
-rw-r--r--test/objspace/test_objspace.rb248
1 files changed, 194 insertions, 54 deletions
diff --git a/test/objspace/test_objspace.rb b/test/objspace/test_objspace.rb
index b798b897b4..faa22f1424 100644
--- a/test/objspace/test_objspace.rb
+++ b/test/objspace/test_objspace.rb
@@ -32,8 +32,8 @@ class TestObjSpace < Test::Unit::TestCase
a = "a" * GC::INTERNAL_CONSTANTS[:RVARGC_MAX_ALLOCATE_SIZE]
b = a.dup
c = nil
- ObjectSpace.each_object(String) {|x| break c = x if x == a and x.frozen?}
- rv_size = GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE]
+ ObjectSpace.each_object(String) {|x| break c = x if a == x and x.frozen?}
+ rv_size = Integer(ObjectSpace.dump(a)[/"slot_size":(\d+)/, 1])
assert_equal([rv_size, rv_size, a.length + 1 + rv_size], [a, b, c].map {|x| ObjectSpace.memsize_of(x)})
end
@@ -54,7 +54,11 @@ class TestObjSpace < Test::Unit::TestCase
assert_operator(a, :>, b)
assert_operator(a, :>, 0)
assert_operator(b, :>, 0)
- assert_raise(TypeError) {ObjectSpace.memsize_of_all('error')}
+ assert_kind_of(Integer, ObjectSpace.memsize_of_all(Enumerable))
+ end
+
+ def test_memsize_of_all_with_wrong_type
+ assert_raise(TypeError) { ObjectSpace.memsize_of_all(Object.new) }
end
def test_count_objects_size
@@ -76,16 +80,6 @@ class TestObjSpace < Test::Unit::TestCase
assert_raise(TypeError) { ObjectSpace.count_objects_size(0) }
end
- def test_count_nodes
- res = ObjectSpace.count_nodes
- assert_not_empty(res)
- arg = {}
- ObjectSpace.count_nodes(arg)
- assert_not_empty(arg)
- bug8014 = '[ruby-core:53130] [Bug #8014]'
- assert_empty(arg.select {|k, v| !(Symbol === k && Integer === v)}, bug8014)
- end if false
-
def test_count_tdata_objects
res = ObjectSpace.count_tdata_objects
assert_not_empty(res)
@@ -143,7 +137,7 @@ class TestObjSpace < Test::Unit::TestCase
def test_reachable_objects_during_iteration
omit 'flaky on Visual Studio with: [BUG] Unnormalized Fixnum value' if /mswin/ =~ RUBY_PLATFORM
opts = %w[--disable-gem --disable=frozen-string-literal -robjspace]
- assert_separately opts, "#{<<-"begin;"}\n#{<<-'end;'}"
+ assert_ruby_status opts, "#{<<-"begin;"}\n#{<<-'end;'}"
begin;
ObjectSpace.each_object{|o|
o.inspect
@@ -179,7 +173,7 @@ class TestObjSpace < Test::Unit::TestCase
end
def test_trace_object_allocations_stop_first
- assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}")
begin;
require "objspace"
# Make sure stopping before the tracepoints are initialized doesn't raise. See [Bug #17020]
@@ -203,8 +197,9 @@ class TestObjSpace < Test::Unit::TestCase
assert_equal(line1, ObjectSpace.allocation_sourceline(o1))
assert_equal(__FILE__, ObjectSpace.allocation_sourcefile(o1))
assert_equal(c1, ObjectSpace.allocation_generation(o1))
- assert_equal(Class.name, ObjectSpace.allocation_class_path(o1))
- assert_equal(:new, ObjectSpace.allocation_method_id(o1))
+ # These assertions fail under coverage measurement: https://bugs.ruby-lang.org/issues/21298
+ #assert_equal(self.class.name, ObjectSpace.allocation_class_path(o1))
+ #assert_equal(__method__, ObjectSpace.allocation_method_id(o1))
assert_equal(__FILE__, ObjectSpace.allocation_sourcefile(o2))
assert_equal(line2, ObjectSpace.allocation_sourceline(o2))
@@ -244,39 +239,38 @@ class TestObjSpace < Test::Unit::TestCase
def test_trace_object_allocations_start_stop_clear
ObjectSpace.trace_object_allocations_clear # clear object_table to get rid of erroneous detection for obj3
- GC.disable # suppress potential object reuse. see [Bug #11271]
- begin
- ObjectSpace.trace_object_allocations_start
+ EnvUtil.without_gc do # suppress potential object reuse. see [Bug #11271]
begin
ObjectSpace.trace_object_allocations_start
begin
ObjectSpace.trace_object_allocations_start
- obj0 = Object.new
+ begin
+ ObjectSpace.trace_object_allocations_start
+ obj0 = Object.new
+ ensure
+ ObjectSpace.trace_object_allocations_stop
+ obj1 = Object.new
+ end
ensure
ObjectSpace.trace_object_allocations_stop
- obj1 = Object.new
+ obj2 = Object.new
end
ensure
ObjectSpace.trace_object_allocations_stop
- obj2 = Object.new
+ obj3 = Object.new
end
- ensure
- ObjectSpace.trace_object_allocations_stop
- obj3 = Object.new
- end
- assert_equal(__FILE__, ObjectSpace.allocation_sourcefile(obj0))
- assert_equal(__FILE__, ObjectSpace.allocation_sourcefile(obj1))
- assert_equal(__FILE__, ObjectSpace.allocation_sourcefile(obj2))
- assert_equal(nil , ObjectSpace.allocation_sourcefile(obj3)) # after tracing
+ assert_equal(__FILE__, ObjectSpace.allocation_sourcefile(obj0))
+ assert_equal(__FILE__, ObjectSpace.allocation_sourcefile(obj1))
+ assert_equal(__FILE__, ObjectSpace.allocation_sourcefile(obj2))
+ assert_equal(nil , ObjectSpace.allocation_sourcefile(obj3)) # after tracing
- ObjectSpace.trace_object_allocations_clear
- assert_equal(nil, ObjectSpace.allocation_sourcefile(obj0))
- assert_equal(nil, ObjectSpace.allocation_sourcefile(obj1))
- assert_equal(nil, ObjectSpace.allocation_sourcefile(obj2))
- assert_equal(nil, ObjectSpace.allocation_sourcefile(obj3))
- ensure
- GC.enable
+ ObjectSpace.trace_object_allocations_clear
+ assert_equal(nil, ObjectSpace.allocation_sourcefile(obj0))
+ assert_equal(nil, ObjectSpace.allocation_sourcefile(obj1))
+ assert_equal(nil, ObjectSpace.allocation_sourcefile(obj2))
+ assert_equal(nil, ObjectSpace.allocation_sourcefile(obj3))
+ end
end
def test_trace_object_allocations_gc_stress
@@ -288,19 +282,101 @@ class TestObjSpace < Test::Unit::TestCase
assert true # success
end
+ def test_trace_object_allocations_with_other_tracepoint
+ # Test that ObjectSpace.trace_object_allocations isn't changed by changes
+ # to another tracepoint
+ line_tp = TracePoint.new(:line) { }
+
+ ObjectSpace.trace_object_allocations_start
+
+ obj1 = Object.new; line1 = __LINE__
+ assert_equal __FILE__, ObjectSpace.allocation_sourcefile(obj1)
+ assert_equal line1, ObjectSpace.allocation_sourceline(obj1)
+
+ line_tp.enable
+
+ obj2 = Object.new; line2 = __LINE__
+ assert_equal __FILE__, ObjectSpace.allocation_sourcefile(obj2)
+ assert_equal line2, ObjectSpace.allocation_sourceline(obj2)
+
+ line_tp.disable
+
+ obj3 = Object.new; line3 = __LINE__
+ assert_equal __FILE__, ObjectSpace.allocation_sourcefile(obj3)
+ assert_equal line3, ObjectSpace.allocation_sourceline(obj3)
+ ensure
+ ObjectSpace.trace_object_allocations_stop
+ ObjectSpace.trace_object_allocations_clear
+ end
+
+ def test_trace_object_allocations_compaction
+ omit "compaction is not supported on this platform" unless GC.respond_to?(:compact)
+
+ assert_separately(%w(-robjspace), <<~RUBY)
+ ObjectSpace.trace_object_allocations do
+ objs = 100.times.map do
+ Object.new
+ end
+
+ assert_equal(__FILE__, ObjectSpace.allocation_sourcefile(objs[0]))
+
+ GC.verify_compaction_references(expand_heap: true, toward: :empty)
+
+ assert_equal(__FILE__, ObjectSpace.allocation_sourcefile(objs[0]))
+ end
+ RUBY
+ end
+
+ def test_trace_object_allocations_compaction_freed_pages
+ omit "compaction is not supported on this platform" unless GC.respond_to?(:compact)
+
+ assert_normal_exit(<<~RUBY, timeout: 60)
+ require "objspace"
+
+ objs = []
+ ObjectSpace.trace_object_allocations do
+ 1_000_000.times do
+ objs << Object.new
+ end
+ end
+
+ objs = nil
+
+ # Free pages that the objs were on
+ GC.start
+
+ # Run compaction and check that it doesn't crash
+ GC.compact
+ RUBY
+ end
+
def test_dump_flags
# Ensure that the fstring is promoted to old generation
4.times { GC.start }
info = ObjectSpace.dump("foo".freeze)
- assert_match(/"wb_protected":true, "old":true/, info)
+ assert_include(info, '"wb_protected":true')
+ assert_include(info, '"age":3')
+ assert_include(info, '"old":true')
assert_match(/"fstring":true/, info)
JSON.parse(info) if defined?(JSON)
end
+ def test_dump_flag_age
+ EnvUtil.without_gc do
+ o = Object.new
+
+ assert_include(ObjectSpace.dump(o), '"age":0')
+
+ GC.start
+
+ assert_include(ObjectSpace.dump(o), '"age":1')
+ end
+ end
+
if defined?(RubyVM::Shape)
class TooComplex; end
- def test_dump_too_complex_shape
+ def test_dump_complex_shape
omit "flaky test"
RubyVM::Shape::SHAPE_MAX_VARIATIONS.times do
@@ -309,26 +385,26 @@ class TestObjSpace < Test::Unit::TestCase
tc = TooComplex.new
info = ObjectSpace.dump(tc)
- assert_not_match(/"too_complex_shape"/, info)
+ assert_not_match(/"complex_shape"/, info)
tc.instance_variable_set(:@new_ivar, 1)
info = ObjectSpace.dump(tc)
- assert_match(/"too_complex_shape":true/, info)
+ assert_match(/"complex_shape":true/, info)
if defined?(JSON)
- assert_true(JSON.parse(info)["too_complex_shape"])
+ assert_true(JSON.parse(info)["complex_shape"])
end
end
end
class NotTooComplex ; end
- def test_dump_not_too_complex_shape
+ def test_dump_not_complex_shape
tc = NotTooComplex.new
tc.instance_variable_set(:@new_ivar, 1)
info = ObjectSpace.dump(tc)
- assert_not_match(/"too_complex_shape"/, info)
+ assert_not_match(/"complex_shape"/, info)
if defined?(JSON)
- assert_nil(JSON.parse(info)["too_complex_shape"])
+ assert_nil(JSON.parse(info)["complex_shape"])
end
end
@@ -397,12 +473,12 @@ class TestObjSpace < Test::Unit::TestCase
assert_include(info, '"embedded":true')
assert_include(info, '"ivars":0')
- # Non-embed object
+ # Non-embed object (needs > 6 ivars to exceed pool 0 embed capacity)
obj = klass.new
- 5.times { |i| obj.instance_variable_set("@ivar#{i}", 0) }
+ 7.times { |i| obj.instance_variable_set("@ivar#{i}", 0) }
info = ObjectSpace.dump(obj)
assert_not_include(info, '"embedded":true')
- assert_include(info, '"ivars":5')
+ assert_include(info, '"ivars":7')
end
def test_dump_control_char
@@ -416,7 +492,7 @@ class TestObjSpace < Test::Unit::TestCase
assert_equal('true', ObjectSpace.dump(true))
assert_equal('false', ObjectSpace.dump(false))
assert_equal('0', ObjectSpace.dump(0))
- assert_equal('{"type":"SYMBOL", "value":"foo"}', ObjectSpace.dump(:foo))
+ assert_equal('{"type":"SYMBOL", "value":"test_dump_special_consts"}', ObjectSpace.dump(:test_dump_special_consts))
end
def test_dump_singleton_class
@@ -446,6 +522,20 @@ class TestObjSpace < Test::Unit::TestCase
assert_match(/"value":"foobar\h+"/, dump)
end
+ def test_dump_outputs_object_id
+ obj = Object.new
+
+ # Doesn't output object_id when it has not been seen
+ dump = ObjectSpace.dump(obj)
+ assert_not_include(dump, "\"object_id\"")
+
+ id = obj.object_id
+
+ # Outputs object_id when it has been seen
+ dump = ObjectSpace.dump(obj)
+ assert_include(dump, "\"object_id\":#{id}")
+ end
+
def test_dump_includes_imemo_type
assert_in_out_err(%w[-robjspace], "#{<<-"begin;"}\n#{<<-'end;'}") do |output, error|
begin;
@@ -558,7 +648,8 @@ class TestObjSpace < Test::Unit::TestCase
next if obj["type"] == "SHAPE"
assert_not_nil obj["slot_size"]
- assert_equal 0, obj["slot_size"] % GC::INTERNAL_CONSTANTS[:RVALUE_SIZE]
+ slot_sizes = GC::INTERNAL_CONSTANTS[:HEAP_COUNT].times.map { |i| GC.stat_heap(i, :slot_size) }
+ assert_include slot_sizes, obj["slot_size"]
}
end
end
@@ -613,10 +704,11 @@ class TestObjSpace < Test::Unit::TestCase
end
def test_dump_includes_slot_size
- str = "TEST"
- dump = ObjectSpace.dump(str)
+ klass = Class.new
+ obj = klass.new
+ dump = ObjectSpace.dump(obj)
- assert_includes dump, "\"slot_size\":#{GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE]}"
+ assert_includes dump, "\"slot_size\":#{GC.stat_heap(0, :slot_size) - GC::INTERNAL_CONSTANTS[:RVALUE_OVERHEAD]}"
end
def test_dump_reference_addresses_match_dump_all_addresses
@@ -731,6 +823,27 @@ class TestObjSpace < Test::Unit::TestCase
end
end
+ def test_dump_all_with_ractors
+ assert_ractor("#{<<-"begin;"}#{<<-'end;'}")
+ begin;
+ require "objspace"
+ require "tempfile"
+ require "json"
+ rs = 4.times.map do
+ Ractor.new do
+ Tempfile.create do |f|
+ ObjectSpace.dump_all(output: f)
+ f.close
+ File.readlines(f.path).each do |line|
+ JSON.parse(line)
+ end
+ end
+ end
+ end
+ rs.each(&:join)
+ end;
+ end
+
def test_dump_uninitialized_file
assert_in_out_err(%[-robjspace], <<-RUBY) do |(output), (error)|
puts ObjectSpace.dump(File.allocate)
@@ -903,6 +1016,33 @@ class TestObjSpace < Test::Unit::TestCase
# load_allocation_path_helper 'iseq = RubyVM::InstructionSequence.load_from_binary(File.binread(path))', to_binary: true
end
+ def test_escape_class_name
+ class_name = '" little boby table [Bug #20892]'
+ json = ObjectSpace.dump(Class.new.tap { |c| c.set_temporary_name(class_name) })
+ assert_equal class_name, JSON.parse(json)["name"]
+ end
+
+ def test_dump_free_immediately
+ require '-test-/typeddata'
+
+ # Bug::TypedData has flags=0 (no FREE_IMMEDIATELY)
+ info = ObjectSpace.dump(Bug::TypedData.new)
+ assert_include(info, '"struct":"typed_data"')
+ assert_include(info, '"free_immediately":false')
+
+ # Most typed data objects have FREE_IMMEDIATELY, so the field should be absent
+ info = ObjectSpace.dump(Thread.current.group)
+ assert_include(info, '"struct":"thgroup"')
+ assert_not_include(info, '"free_immediately"')
+ end
+
+ def test_dump_include_shareable
+ omit 'Not provided by mmtk' if RUBY_DESCRIPTION.include?("+GC[mmtk]")
+
+ assert_include(ObjectSpace.dump(ENV), '"shareable":true')
+ assert_not_include(ObjectSpace.dump([]), '"shareable":true')
+ end
+
def test_utf8_method_names
name = "utf8_❨╯°□°❩╯︵┻━┻"
obj = ObjectSpace.trace_object_allocations do