# frozen_string_literal: true require 'test/unit' require 'fiddle' class TestGCCompact < Test::Unit::TestCase def memory_location(obj) (Fiddle.dlwrap(obj) >> 1) end def assert_object_ids(list) same_count = list.find_all { |obj| memory_location(obj) == obj.object_id }.count list.count - same_count end def big_list 1000.times.map { # try to make some empty slots by allocating an object and discarding Object.new Object.new } # likely next to each other end # Find an object that's allocated in a slot that had a previous # tenant, and that tenant moved and is still alive def find_object_in_recycled_slot(addresses) new_object = nil loop do new_object = Object.new if addresses.include? memory_location(new_object) break end end new_object end def test_find_collided_object list_of_objects = big_list ids = list_of_objects.map(&:object_id) # store id in map addresses = list_of_objects.map(&self.:memory_location) assert_equal ids, addresses # All object ids should be equal assert_equal 0, assert_object_ids(list_of_objects) # should be 0 GC.verify_compaction_references # Some should have moved id_count = assert_object_ids(list_of_objects) skip "couldn't get objects to move" if id_count == 0 assert_operator id_count, :>, 0 new_ids = list_of_objects.map(&:object_id) # Object ids should not change after compaction assert_equal ids, new_ids new_tenant = find_object_in_recycled_slot(addresses) assert new_tenant # This is the object that used to be in new_object's position previous_tenant = list_of_objects[addresses.index(memory_location(new_tenant))] assert_not_equal previous_tenant.object_id, new_tenant.object_id # Should be able to look up object by object_id assert_equal new_tenant, ObjectSpace._id2ref(new_tenant.object_id) # Should be able to look up object by object_id assert_equal previous_tenant, ObjectSpace._id2ref(previous_tenant.object_id) int = (new_tenant.object_id >> 1) # These two should be the same! but they are not :( assert_equal int, ObjectSpace._id2ref(int.object_id) end def test_many_collisions list_of_objects = big_list ids = list_of_objects.map(&:object_id) addresses = list_of_objects.map(&self.:memory_location) GC.verify_compaction_references new_tenants = 10.times.map { find_object_in_recycled_slot(addresses) } collisions = GC.stat(:object_id_collisions) skip "couldn't get objects to collide" if collisions == 0 assert_operator collisions, :>, 0 end end