2019-04-16 23:17:25 -04:00
|
|
|
# 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
|
|
|
|
|
2019-04-17 00:05:29 -04:00
|
|
|
GC.verify_compaction_references
|
2019-04-16 23:17:25 -04:00
|
|
|
|
|
|
|
# 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)
|
|
|
|
|
2019-04-17 00:05:29 -04:00
|
|
|
GC.verify_compaction_references
|
2019-04-16 23:17:25 -04:00
|
|
|
|
|
|
|
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
|