1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
|
# 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(level = 10)
if level > 0
big_list(level - 1)
else
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
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
100_000.times do
new_object = Object.new
if addresses.index memory_location(new_object)
break
end
end
new_object
end
def try_to_move_objects
10.times do
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(toward: :empty)
# 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)
return [list_of_objects, addresses, new_tenant] if new_tenant
end
flunk "Couldn't get objects to move"
end
def test_find_collided_object
skip "figure out how to guarantee move"
list_of_objects, addresses, new_tenant = try_to_move_objects
# This is the object that used to be in new_object's position
loc = memory_location(new_tenant)
assert loc, "should have a memory location"
if (ENV['TRAVIS'] && RUBY_PLATFORM =~ /darwin/)
skip "tests are failing on Travis osx / Wercker from here"
end
address_idx = addresses.index(loc)
assert address_idx, "should have an address index"
previous_tenant = list_of_objects[address_idx]
assert previous_tenant, "should have a previous 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(toward: :empty)
skip "time consuming"
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
ids.clear
new_tenants.clear
end
def test_complex_hash_keys
list_of_objects = big_list
hash = list_of_objects.hash
GC.verify_compaction_references(toward: :empty)
assert_equal hash, list_of_objects.hash
end
def walk_ast ast
children = ast.children.grep(RubyVM::AbstractSyntaxTree::Node)
children.each do |child|
assert child.type
walk_ast child
end
end
def test_ast_compacts
ast = RubyVM::AbstractSyntaxTree.parse_file __FILE__
assert GC.compact
walk_ast ast
end
end
|