thread_safe 0.1.1-java
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +21 -0
- data/Gemfile +4 -0
- data/LICENSE +144 -0
- data/README.md +34 -0
- data/Rakefile +36 -0
- data/examples/bench_cache.rb +35 -0
- data/ext/org/jruby/ext/thread_safe/JRubyCacheBackendLibrary.java +200 -0
- data/ext/org/jruby/ext/thread_safe/jsr166e/ConcurrentHashMapV8.java +3842 -0
- data/ext/org/jruby/ext/thread_safe/jsr166e/LongAdder.java +204 -0
- data/ext/org/jruby/ext/thread_safe/jsr166e/Striped64.java +342 -0
- data/ext/org/jruby/ext/thread_safe/jsr166y/ThreadLocalRandom.java +199 -0
- data/ext/thread_safe/JrubyCacheBackendService.java +15 -0
- data/lib/thread_safe.rb +65 -0
- data/lib/thread_safe/atomic_reference_cache_backend.rb +922 -0
- data/lib/thread_safe/cache.rb +137 -0
- data/lib/thread_safe/mri_cache_backend.rb +62 -0
- data/lib/thread_safe/non_concurrent_cache_backend.rb +133 -0
- data/lib/thread_safe/synchronized_cache_backend.rb +76 -0
- data/lib/thread_safe/synchronized_delegator.rb +35 -0
- data/lib/thread_safe/util.rb +16 -0
- data/lib/thread_safe/util/adder.rb +59 -0
- data/lib/thread_safe/util/atomic_reference.rb +12 -0
- data/lib/thread_safe/util/cheap_lockable.rb +105 -0
- data/lib/thread_safe/util/power_of_two_tuple.rb +26 -0
- data/lib/thread_safe/util/striped64.rb +226 -0
- data/lib/thread_safe/util/volatile.rb +62 -0
- data/lib/thread_safe/util/volatile_tuple.rb +46 -0
- data/lib/thread_safe/util/xor_shift_random.rb +39 -0
- data/lib/thread_safe/version.rb +3 -0
- data/test/test_array.rb +20 -0
- data/test/test_cache.rb +792 -0
- data/test/test_cache_loops.rb +453 -0
- data/test/test_hash.rb +20 -0
- data/test/test_helper.rb +73 -0
- data/test/test_synchronized_delegator.rb +42 -0
- data/thread_safe.gemspec +21 -0
- metadata +100 -0
@@ -0,0 +1,453 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'thread_safe'
|
4
|
+
require File.join(File.dirname(__FILE__), "test_helper")
|
5
|
+
|
6
|
+
Thread.abort_on_exception = true
|
7
|
+
|
8
|
+
class TestCacheTorture < Test::Unit::TestCase
|
9
|
+
THREAD_COUNT = 40
|
10
|
+
KEY_COUNT = (((2**13) - 2) * 0.75).to_i # get close to the doubling cliff
|
11
|
+
LOW_KEY_COUNT = (((2**8 ) - 2) * 0.75).to_i # get close to the doubling cliff
|
12
|
+
|
13
|
+
INITIAL_VALUE_CACHE_SETUP = lambda do |options, keys|
|
14
|
+
cache = ThreadSafe::Cache.new
|
15
|
+
initial_value = options[:initial_value] || 0
|
16
|
+
keys.each {|key| cache[key] = initial_value}
|
17
|
+
cache
|
18
|
+
end
|
19
|
+
ZERO_VALUE_CACHE_SETUP = lambda do |options, keys|
|
20
|
+
INITIAL_VALUE_CACHE_SETUP.call(options.merge(:initial_value => 0), keys)
|
21
|
+
end
|
22
|
+
|
23
|
+
DEFAULTS = {
|
24
|
+
:key_count => KEY_COUNT,
|
25
|
+
:thread_count => THREAD_COUNT,
|
26
|
+
:loop_count => 1,
|
27
|
+
:prelude => '',
|
28
|
+
:cache_setup => lambda {|options, keys| ThreadSafe::Cache.new}
|
29
|
+
}
|
30
|
+
|
31
|
+
LOW_KEY_COUNT_OPTIONS = {:loop_count => 150, :key_count => LOW_KEY_COUNT}
|
32
|
+
SINGLE_KEY_COUNT_OPTIONS = {:loop_count => 100_000, :key_count => 1}
|
33
|
+
|
34
|
+
def test_concurrency
|
35
|
+
code = <<-RUBY_EVAL
|
36
|
+
cache[key]
|
37
|
+
cache[key] = key
|
38
|
+
cache[key]
|
39
|
+
cache.delete(key)
|
40
|
+
RUBY_EVAL
|
41
|
+
do_thread_loop(:concurrency, code)
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_put_if_absent
|
45
|
+
do_thread_loop(:put_if_absent, 'acc += 1 unless cache.put_if_absent(key, key)', :key_count => 100_000) do |result, cache, options, keys|
|
46
|
+
assert_standard_accumulator_test_result(result, cache, options, keys)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_compute_if_absent
|
51
|
+
code = 'cache.compute_if_absent(key) { acc += 1; key }'
|
52
|
+
do_thread_loop(:compute_if_absent, code) do |result, cache, options, keys|
|
53
|
+
assert_standard_accumulator_test_result(result, cache, options, keys)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_compute_put_if_absent
|
58
|
+
code = <<-RUBY_EVAL
|
59
|
+
if key.even?
|
60
|
+
cache.compute_if_absent(key) { acc += 1; key }
|
61
|
+
else
|
62
|
+
acc += 1 unless cache.put_if_absent(key, key)
|
63
|
+
end
|
64
|
+
RUBY_EVAL
|
65
|
+
do_thread_loop(:compute_put_if_absent, code) do |result, cache, options, keys|
|
66
|
+
assert_standard_accumulator_test_result(result, cache, options, keys)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_compute_if_absent_and_present
|
71
|
+
compute_if_absent_and_present
|
72
|
+
compute_if_absent_and_present(LOW_KEY_COUNT_OPTIONS)
|
73
|
+
compute_if_absent_and_present(SINGLE_KEY_COUNT_OPTIONS)
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_add_remove_to_zero
|
77
|
+
add_remove_to_zero
|
78
|
+
add_remove_to_zero(LOW_KEY_COUNT_OPTIONS)
|
79
|
+
add_remove_to_zero(SINGLE_KEY_COUNT_OPTIONS)
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_add_remove_to_zero_via_merge_pair
|
83
|
+
add_remove_to_zero_via_merge_pair
|
84
|
+
add_remove_to_zero_via_merge_pair(LOW_KEY_COUNT_OPTIONS)
|
85
|
+
add_remove_to_zero_via_merge_pair(SINGLE_KEY_COUNT_OPTIONS)
|
86
|
+
end
|
87
|
+
|
88
|
+
def test_add_remove
|
89
|
+
add_remove
|
90
|
+
add_remove(LOW_KEY_COUNT_OPTIONS)
|
91
|
+
add_remove(SINGLE_KEY_COUNT_OPTIONS)
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_add_remove_via_compute
|
95
|
+
add_remove_via_compute
|
96
|
+
add_remove_via_compute(LOW_KEY_COUNT_OPTIONS)
|
97
|
+
add_remove_via_compute(SINGLE_KEY_COUNT_OPTIONS)
|
98
|
+
end
|
99
|
+
|
100
|
+
def add_remove_via_compute_if_absent_present
|
101
|
+
add_remove_via_compute_if_absent_present
|
102
|
+
add_remove_via_compute_if_absent_present(LOW_KEY_COUNT_OPTIONS)
|
103
|
+
add_remove_via_compute_if_absent_present(SINGLE_KEY_COUNT_OPTIONS)
|
104
|
+
end
|
105
|
+
|
106
|
+
def test_add_remove_indiscriminate
|
107
|
+
add_remove_indiscriminate
|
108
|
+
add_remove_indiscriminate(LOW_KEY_COUNT_OPTIONS)
|
109
|
+
add_remove_indiscriminate(SINGLE_KEY_COUNT_OPTIONS)
|
110
|
+
end
|
111
|
+
|
112
|
+
def test_count_up
|
113
|
+
count_up
|
114
|
+
count_up(LOW_KEY_COUNT_OPTIONS)
|
115
|
+
count_up(SINGLE_KEY_COUNT_OPTIONS)
|
116
|
+
end
|
117
|
+
|
118
|
+
def test_count_up_via_compute
|
119
|
+
count_up_via_compute
|
120
|
+
count_up_via_compute(LOW_KEY_COUNT_OPTIONS)
|
121
|
+
count_up_via_compute(SINGLE_KEY_COUNT_OPTIONS)
|
122
|
+
end
|
123
|
+
|
124
|
+
def test_count_up_via_merge_pair
|
125
|
+
count_up_via_merge_pair
|
126
|
+
count_up_via_merge_pair(LOW_KEY_COUNT_OPTIONS)
|
127
|
+
count_up_via_merge_pair(SINGLE_KEY_COUNT_OPTIONS)
|
128
|
+
end
|
129
|
+
|
130
|
+
def test_count_race
|
131
|
+
prelude = 'change = (rand(2) == 1) ? 1 : -1'
|
132
|
+
code = <<-RUBY_EVAL
|
133
|
+
v = cache[key]
|
134
|
+
acc += change if cache.replace_pair(key, v, v + change)
|
135
|
+
RUBY_EVAL
|
136
|
+
do_thread_loop(:count_race, code, :loop_count => 5, :prelude => prelude, :cache_setup => ZERO_VALUE_CACHE_SETUP) do |result, cache, options, keys|
|
137
|
+
result_sum = sum(result)
|
138
|
+
assert_equal(sum(keys.map {|key| cache[key]}), result_sum)
|
139
|
+
assert_equal(sum(cache.values), result_sum)
|
140
|
+
assert_equal(options[:key_count], cache.size)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def test_get_and_set_new
|
145
|
+
code = 'acc += 1 unless cache.get_and_set(key, key)'
|
146
|
+
do_thread_loop(:get_and_set_new, code) do |result, cache, options, keys|
|
147
|
+
assert_standard_accumulator_test_result(result, cache, options, keys)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def test_get_and_set_existing
|
152
|
+
code = 'acc += 1 if cache.get_and_set(key, key) == -1'
|
153
|
+
do_thread_loop(:get_and_set_existing, code, :cache_setup => INITIAL_VALUE_CACHE_SETUP, :initial_value => -1) do |result, cache, options, keys|
|
154
|
+
assert_standard_accumulator_test_result(result, cache, options, keys)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
private
|
159
|
+
def compute_if_absent_and_present(opts = {})
|
160
|
+
prelude = 'on_present = rand(2) == 1'
|
161
|
+
code = <<-RUBY_EVAL
|
162
|
+
if on_present
|
163
|
+
cache.compute_if_present(key) {|old_value| acc += 1; old_value + 1}
|
164
|
+
else
|
165
|
+
cache.compute_if_absent(key) { acc += 1; 1 }
|
166
|
+
end
|
167
|
+
RUBY_EVAL
|
168
|
+
do_thread_loop(:compute_if_absent_and_present, code, {:loop_count => 5, :prelude => prelude}.merge(opts)) do |result, cache, options, keys|
|
169
|
+
stored_sum = 0
|
170
|
+
stored_key_count = 0
|
171
|
+
keys.each do |k|
|
172
|
+
if value = cache[k]
|
173
|
+
stored_sum += value
|
174
|
+
stored_key_count += 1
|
175
|
+
end
|
176
|
+
end
|
177
|
+
assert_equal(stored_sum, sum(result))
|
178
|
+
assert_equal(stored_key_count, cache.size)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def add_remove(opts = {})
|
183
|
+
prelude = 'do_add = rand(2) == 1'
|
184
|
+
code = <<-RUBY_EVAL
|
185
|
+
if do_add
|
186
|
+
acc += 1 unless cache.put_if_absent(key, key)
|
187
|
+
else
|
188
|
+
acc -= 1 if cache.delete_pair(key, key)
|
189
|
+
end
|
190
|
+
RUBY_EVAL
|
191
|
+
do_thread_loop(:add_remove, code, {:loop_count => 5, :prelude => prelude}.merge(opts)) do |result, cache, options, keys|
|
192
|
+
assert_all_key_mappings_exist(cache, keys, false)
|
193
|
+
assert_equal(cache.size, sum(result))
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def add_remove_via_compute(opts = {})
|
198
|
+
prelude = 'do_add = rand(2) == 1'
|
199
|
+
code = <<-RUBY_EVAL
|
200
|
+
cache.compute(key) do |old_value|
|
201
|
+
if do_add
|
202
|
+
acc += 1 unless old_value
|
203
|
+
key
|
204
|
+
else
|
205
|
+
acc -= 1 if old_value
|
206
|
+
nil
|
207
|
+
end
|
208
|
+
end
|
209
|
+
RUBY_EVAL
|
210
|
+
do_thread_loop(:add_remove_via_compute, code, {:loop_count => 5, :prelude => prelude}.merge(opts)) do |result, cache, options, keys|
|
211
|
+
assert_all_key_mappings_exist(cache, keys, false)
|
212
|
+
assert_equal(cache.size, sum(result))
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def add_remove_via_compute_if_absent_present(opts = {})
|
217
|
+
prelude = 'do_add = rand(2) == 1'
|
218
|
+
code = <<-RUBY_EVAL
|
219
|
+
if do_add
|
220
|
+
cache.compute_if_absent(key) { acc += 1; key }
|
221
|
+
else
|
222
|
+
cache.compute_if_present(key) { acc -= 1; nil }
|
223
|
+
end
|
224
|
+
RUBY_EVAL
|
225
|
+
do_thread_loop(:add_remove_via_compute_if_absent_present, code, {:loop_count => 5, :prelude => prelude}.merge(opts)) do |result, cache, options, keys|
|
226
|
+
assert_all_key_mappings_exist(cache, keys, false)
|
227
|
+
assert_equal(cache.size, sum(result))
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def add_remove_indiscriminate(opts = {})
|
232
|
+
prelude = 'do_add = rand(2) == 1'
|
233
|
+
code = <<-RUBY_EVAL
|
234
|
+
if do_add
|
235
|
+
acc += 1 unless cache.put_if_absent(key, key)
|
236
|
+
else
|
237
|
+
acc -= 1 if cache.delete(key)
|
238
|
+
end
|
239
|
+
RUBY_EVAL
|
240
|
+
do_thread_loop(:add_remove, code, {:loop_count => 5, :prelude => prelude}.merge(opts)) do |result, cache, options, keys|
|
241
|
+
assert_all_key_mappings_exist(cache, keys, false)
|
242
|
+
assert_equal(cache.size, sum(result))
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
def count_up(opts = {})
|
247
|
+
code = <<-RUBY_EVAL
|
248
|
+
v = cache[key]
|
249
|
+
acc += 1 if cache.replace_pair(key, v, v + 1)
|
250
|
+
RUBY_EVAL
|
251
|
+
do_thread_loop(:count_up, code, {:loop_count => 5, :cache_setup => ZERO_VALUE_CACHE_SETUP}.merge(opts)) do |result, cache, options, keys|
|
252
|
+
assert_count_up(result, cache, options, keys)
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
def count_up_via_compute(opts = {})
|
257
|
+
code = <<-RUBY_EVAL
|
258
|
+
cache.compute(key) do |old_value|
|
259
|
+
acc += 1
|
260
|
+
old_value ? old_value + 1 : 1
|
261
|
+
end
|
262
|
+
RUBY_EVAL
|
263
|
+
do_thread_loop(:count_up_via_compute, code, {:loop_count => 5}.merge(opts)) do |result, cache, options, keys|
|
264
|
+
assert_count_up(result, cache, options, keys)
|
265
|
+
result.inject(nil) do |previous_value, next_value| # since compute guarantees atomicity all count ups should be equal
|
266
|
+
assert_equal previous_value, next_value if previous_value
|
267
|
+
next_value
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
def count_up_via_merge_pair(opts = {})
|
273
|
+
code = <<-RUBY_EVAL
|
274
|
+
cache.merge_pair(key, 1) {|old_value| old_value + 1}
|
275
|
+
RUBY_EVAL
|
276
|
+
do_thread_loop(:count_up_via_merge_pair, code, {:loop_count => 5}.merge(opts)) do |result, cache, options, keys|
|
277
|
+
all_match = true
|
278
|
+
expected_value = options[:loop_count] * options[:thread_count]
|
279
|
+
keys.each do |key|
|
280
|
+
if expected_value != (value = cache[key])
|
281
|
+
all_match = false
|
282
|
+
break
|
283
|
+
end
|
284
|
+
end
|
285
|
+
assert all_match
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
def add_remove_to_zero(opts = {})
|
290
|
+
code = <<-RUBY_EVAL
|
291
|
+
acc += 1 unless cache.put_if_absent(key, key)
|
292
|
+
acc -= 1 if cache.delete_pair(key, key)
|
293
|
+
RUBY_EVAL
|
294
|
+
do_thread_loop(:add_remove_to_zero, code, {:loop_count => 5}.merge(opts)) do |result, cache, options, keys|
|
295
|
+
assert_all_key_mappings_exist(cache, keys, false)
|
296
|
+
assert_equal(cache.size, sum(result))
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
def add_remove_to_zero_via_merge_pair(opts = {})
|
301
|
+
code = <<-RUBY_EVAL
|
302
|
+
acc += (cache.merge_pair(key, key) {}) ? -1 : 1
|
303
|
+
RUBY_EVAL
|
304
|
+
do_thread_loop(:add_remove_to_zero_via_merge_pair, code, {:loop_count => 5}.merge(opts)) do |result, cache, options, keys|
|
305
|
+
assert_all_key_mappings_exist(cache, keys, false)
|
306
|
+
assert_equal(cache.size, sum(result))
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
def do_thread_loop(name, code, options = {}, &block)
|
311
|
+
options = DEFAULTS.merge(options)
|
312
|
+
meth = define_loop name, code, options[:prelude]
|
313
|
+
assert_nothing_raised do
|
314
|
+
keys = to_keys_array(options[:key_count])
|
315
|
+
run_thread_loop(meth, keys, options, &block)
|
316
|
+
|
317
|
+
if options[:key_count] > 1
|
318
|
+
options[:key_count] = (options[:key_count] / 40).to_i
|
319
|
+
keys = to_hash_collision_keys_array(options[:key_count])
|
320
|
+
run_thread_loop(meth, keys, options.merge(:loop_count => (options[:loop_count] * 5)), &block)
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
def run_thread_loop(meth, keys, options)
|
326
|
+
cache = options[:cache_setup].call(options, keys)
|
327
|
+
barrier = ThreadSafe::Test::Barrier.new(options[:thread_count])
|
328
|
+
t = Time.now
|
329
|
+
result = (1..options[:thread_count]).map do
|
330
|
+
Thread.new do
|
331
|
+
setup_sync_and_start_loop(meth, cache, keys, barrier, options[:loop_count])
|
332
|
+
end
|
333
|
+
end.map(&:value)
|
334
|
+
yield result, cache, options, keys if block_given?
|
335
|
+
end
|
336
|
+
|
337
|
+
def setup_sync_and_start_loop(meth, cache, keys, barrier, loop_count)
|
338
|
+
my_keys = keys.shuffle
|
339
|
+
barrier.await
|
340
|
+
if my_keys.size == 1
|
341
|
+
key = my_keys.first
|
342
|
+
send("#{meth}_single_key", cache, key, loop_count)
|
343
|
+
else
|
344
|
+
send("#{meth}_multiple_keys", cache, my_keys, loop_count)
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
def define_loop(name, body, prelude)
|
349
|
+
inner_meth_name = :"_#{name}_loop_inner"
|
350
|
+
outer_meth_name = :"_#{name}_loop_outer"
|
351
|
+
# looping is splitted into the "loop methods" to trigger the JIT
|
352
|
+
self.class.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
|
353
|
+
def #{inner_meth_name}_multiple_keys(cache, keys, i, length, acc)
|
354
|
+
#{prelude}
|
355
|
+
target = i + length
|
356
|
+
while i < target
|
357
|
+
key = keys[i]
|
358
|
+
#{body}
|
359
|
+
i += 1
|
360
|
+
end
|
361
|
+
acc
|
362
|
+
end unless method_defined?(:#{inner_meth_name}_multiple_keys)
|
363
|
+
RUBY_EVAL
|
364
|
+
|
365
|
+
self.class.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
|
366
|
+
def #{inner_meth_name}_single_key(cache, key, i, length, acc)
|
367
|
+
#{prelude}
|
368
|
+
target = i + length
|
369
|
+
while i < target
|
370
|
+
#{body}
|
371
|
+
i += 1
|
372
|
+
end
|
373
|
+
acc
|
374
|
+
end unless method_defined?(:#{inner_meth_name}_single_key)
|
375
|
+
RUBY_EVAL
|
376
|
+
|
377
|
+
self.class.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
|
378
|
+
def #{outer_meth_name}_multiple_keys(cache, keys, loop_count)
|
379
|
+
total_length = keys.size
|
380
|
+
acc = 0
|
381
|
+
inc = 100
|
382
|
+
loop_count.times do
|
383
|
+
i = 0
|
384
|
+
pre_loop_inc = total_length % inc
|
385
|
+
acc = #{inner_meth_name}_multiple_keys(cache, keys, i, pre_loop_inc, acc)
|
386
|
+
i += pre_loop_inc
|
387
|
+
while i < total_length
|
388
|
+
acc = #{inner_meth_name}_multiple_keys(cache, keys, i, inc, acc)
|
389
|
+
i += inc
|
390
|
+
end
|
391
|
+
end
|
392
|
+
acc
|
393
|
+
end unless method_defined?(:#{outer_meth_name}_multiple_keys)
|
394
|
+
RUBY_EVAL
|
395
|
+
|
396
|
+
self.class.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
|
397
|
+
def #{outer_meth_name}_single_key(cache, key, loop_count)
|
398
|
+
acc = 0
|
399
|
+
i = 0
|
400
|
+
inc = 100
|
401
|
+
|
402
|
+
pre_loop_inc = loop_count % inc
|
403
|
+
acc = #{inner_meth_name}_single_key(cache, key, i, pre_loop_inc, acc)
|
404
|
+
i += pre_loop_inc
|
405
|
+
|
406
|
+
while i < loop_count
|
407
|
+
acc = #{inner_meth_name}_single_key(cache, key, i, inc, acc)
|
408
|
+
i += inc
|
409
|
+
end
|
410
|
+
acc
|
411
|
+
end unless method_defined?(:#{outer_meth_name}_single_key)
|
412
|
+
RUBY_EVAL
|
413
|
+
outer_meth_name
|
414
|
+
end
|
415
|
+
|
416
|
+
def to_keys_array(key_count)
|
417
|
+
arr = []
|
418
|
+
key_count.times {|i| arr << i}
|
419
|
+
arr
|
420
|
+
end
|
421
|
+
|
422
|
+
def to_hash_collision_keys_array(key_count)
|
423
|
+
to_keys_array(key_count).map {|key| ThreadSafe::Test::HashCollisionKey(key)}
|
424
|
+
end
|
425
|
+
|
426
|
+
def sum(result)
|
427
|
+
result.inject(0) {|acc, i| acc + i}
|
428
|
+
end
|
429
|
+
|
430
|
+
def assert_standard_accumulator_test_result(result, cache, options, keys)
|
431
|
+
assert_all_key_mappings_exist(cache, keys)
|
432
|
+
assert_equal(options[:key_count], sum(result))
|
433
|
+
assert_equal(options[:key_count], cache.size)
|
434
|
+
end
|
435
|
+
|
436
|
+
def assert_all_key_mappings_exist(cache, keys, all_must_exist = true)
|
437
|
+
keys.each do |key|
|
438
|
+
if (value = cache[key]) || all_must_exist
|
439
|
+
assert_equal key, value unless key == value # don't do a bazzilion assertions unless necessary
|
440
|
+
end
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
def assert_count_up(result, cache, options, keys)
|
445
|
+
keys.each do |key|
|
446
|
+
unless value = cache[key]
|
447
|
+
assert value
|
448
|
+
end
|
449
|
+
end
|
450
|
+
assert_equal(sum(cache.values), sum(result))
|
451
|
+
assert_equal(options[:key_count], cache.size)
|
452
|
+
end
|
453
|
+
end
|