diff options
Diffstat (limited to 'test/objspace/test_objspace.rb')
| -rw-r--r-- | test/objspace/test_objspace.rb | 248 |
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 |
