thread_safe 0.3.5-java → 0.3.6-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 +4 -4
- data/.rspec +2 -0
- data/.travis.yml +23 -20
- data/Gemfile +5 -2
- data/README.md +5 -1
- data/Rakefile +10 -9
- data/lib/thread_safe/cache.rb +3 -5
- data/lib/thread_safe/jruby_cache_backend.jar +0 -0
- data/lib/thread_safe/mri_cache_backend.rb +0 -9
- data/lib/thread_safe/synchronized_delegator.rb +0 -17
- data/lib/thread_safe/util/atomic_reference.rb +0 -1
- data/lib/thread_safe/version.rb +1 -1
- data/spec/.gitignore +0 -0
- data/spec/spec_helper.rb +31 -0
- data/{test → spec}/src/thread_safe/SecurityManager.java +0 -0
- data/spec/support/.gitignore +0 -0
- data/spec/support/threads.rb +1 -0
- data/{test/test_helper.rb → spec/support/threadsafe_test.rb} +0 -59
- data/spec/thread_safe/.gitignore +0 -0
- data/spec/thread_safe/array_spec.rb +18 -0
- data/spec/thread_safe/cache_loops_spec.rb +507 -0
- data/spec/thread_safe/cache_spec.rb +943 -0
- data/spec/thread_safe/hash_spec.rb +17 -0
- data/spec/thread_safe/no_unsafe_spec.rb +27 -0
- data/spec/thread_safe/synchronized_delegator_spec.rb +85 -0
- data/thread_safe.gemspec +4 -4
- metadata +54 -41
- data/test/test_array.rb +0 -18
- data/test/test_cache.rb +0 -901
- data/test/test_cache_loops.rb +0 -449
- data/test/test_hash.rb +0 -17
- data/test/test_synchronized_delegator.rb +0 -84
@@ -0,0 +1,943 @@
|
|
1
|
+
Thread.abort_on_exception = true
|
2
|
+
|
3
|
+
module ThreadSafe
|
4
|
+
describe Cache do
|
5
|
+
before(:each) do
|
6
|
+
@cache = described_class.new
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'concurrency' do
|
10
|
+
(1..THREADS).map do |i|
|
11
|
+
Thread.new do
|
12
|
+
1000.times do |j|
|
13
|
+
key = i * 1000 + j
|
14
|
+
@cache[key] = i
|
15
|
+
@cache[key]
|
16
|
+
@cache.delete(key)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end.map(&:join)
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'retrieval' do
|
23
|
+
expect_size_change(1) do
|
24
|
+
expect(nil).to eq @cache[:a]
|
25
|
+
expect(nil).to eq @cache.get(:a)
|
26
|
+
@cache[:a] = 1
|
27
|
+
expect(1).to eq @cache[:a]
|
28
|
+
expect(1).to eq @cache.get(:a)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
it '#put_if_absent' do
|
33
|
+
with_or_without_default_proc do
|
34
|
+
expect_size_change(1) do
|
35
|
+
expect(nil).to eq @cache.put_if_absent(:a, 1)
|
36
|
+
expect(1).to eq @cache.put_if_absent(:a, 1)
|
37
|
+
expect(1).to eq @cache.put_if_absent(:a, 2)
|
38
|
+
expect(1).to eq @cache[:a]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe '#compute_if_absent' do
|
44
|
+
it 'common' do
|
45
|
+
with_or_without_default_proc do
|
46
|
+
expect_size_change(3) do
|
47
|
+
expect(1).to eq @cache.compute_if_absent(:a) { 1 }
|
48
|
+
expect(1).to eq @cache.compute_if_absent(:a) { 2 }
|
49
|
+
expect(1).to eq @cache[:a]
|
50
|
+
|
51
|
+
@cache[:b] = nil
|
52
|
+
expect(nil).to eq @cache.compute_if_absent(:b) { 1 }
|
53
|
+
expect(nil).to eq @cache.compute_if_absent(:c) {}
|
54
|
+
expect(nil).to eq @cache[:c]
|
55
|
+
expect(true).to eq @cache.key?(:c)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'with return' do
|
61
|
+
with_or_without_default_proc do
|
62
|
+
expect_handles_return_lambda(:compute_if_absent, :a)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'exception' do
|
67
|
+
with_or_without_default_proc do
|
68
|
+
expect_handles_exception(:compute_if_absent, :a)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'atomicity' do
|
73
|
+
late_compute_threads_count = 10
|
74
|
+
late_put_if_absent_threads_count = 10
|
75
|
+
getter_threads_count = 5
|
76
|
+
compute_started = ThreadSafe::Test::Latch.new(1)
|
77
|
+
compute_proceed = ThreadSafe::Test::Latch.new(
|
78
|
+
late_compute_threads_count +
|
79
|
+
late_put_if_absent_threads_count +
|
80
|
+
getter_threads_count
|
81
|
+
)
|
82
|
+
block_until_compute_started = lambda do |name|
|
83
|
+
# what does it mean?
|
84
|
+
if (v = @cache[:a]) != nil
|
85
|
+
expect(nil).to v
|
86
|
+
end
|
87
|
+
compute_proceed.release
|
88
|
+
compute_started.await
|
89
|
+
end
|
90
|
+
|
91
|
+
expect_size_change 1 do
|
92
|
+
late_compute_threads = Array.new(late_compute_threads_count) do
|
93
|
+
Thread.new do
|
94
|
+
block_until_compute_started.call('compute_if_absent')
|
95
|
+
expect(1).to eq @cache.compute_if_absent(:a) { fail }
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
late_put_if_absent_threads = Array.new(late_put_if_absent_threads_count) do
|
100
|
+
Thread.new do
|
101
|
+
block_until_compute_started.call('put_if_absent')
|
102
|
+
expect(1).to eq @cache.put_if_absent(:a, 2)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
getter_threads = Array.new(getter_threads_count) do
|
107
|
+
Thread.new do
|
108
|
+
block_until_compute_started.call('getter')
|
109
|
+
Thread.pass while @cache[:a].nil?
|
110
|
+
expect(1).to eq @cache[:a]
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
Thread.new do
|
115
|
+
@cache.compute_if_absent(:a) do
|
116
|
+
compute_started.release
|
117
|
+
compute_proceed.await
|
118
|
+
sleep(0.2)
|
119
|
+
1
|
120
|
+
end
|
121
|
+
end.join
|
122
|
+
(late_compute_threads +
|
123
|
+
late_put_if_absent_threads +
|
124
|
+
getter_threads).each(&:join)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
describe '#compute_if_present' do
|
130
|
+
it 'common' do
|
131
|
+
with_or_without_default_proc do
|
132
|
+
expect_no_size_change do
|
133
|
+
expect(nil).to eq @cache.compute_if_present(:a) {}
|
134
|
+
expect(nil).to eq @cache.compute_if_present(:a) { 1 }
|
135
|
+
expect(nil).to eq @cache.compute_if_present(:a) { fail }
|
136
|
+
expect(false).to eq @cache.key?(:a)
|
137
|
+
end
|
138
|
+
|
139
|
+
@cache[:a] = 1
|
140
|
+
expect_no_size_change do
|
141
|
+
expect(1).to eq @cache.compute_if_present(:a) { 1 }
|
142
|
+
expect(1).to eq @cache[:a]
|
143
|
+
expect(2).to eq @cache.compute_if_present(:a) { 2 }
|
144
|
+
expect(2).to eq @cache[:a]
|
145
|
+
expect(false).to eq @cache.compute_if_present(:a) { false }
|
146
|
+
expect(false).to eq @cache[:a]
|
147
|
+
|
148
|
+
@cache[:a] = 1
|
149
|
+
yielded = false
|
150
|
+
@cache.compute_if_present(:a) do |old_value|
|
151
|
+
yielded = true
|
152
|
+
expect(1).to eq old_value
|
153
|
+
2
|
154
|
+
end
|
155
|
+
expect(true).to eq yielded
|
156
|
+
end
|
157
|
+
|
158
|
+
expect_size_change(-1) do
|
159
|
+
expect(nil).to eq @cache.compute_if_present(:a) {}
|
160
|
+
expect(false).to eq @cache.key?(:a)
|
161
|
+
expect(nil).to eq @cache.compute_if_present(:a) { 1 }
|
162
|
+
expect(false).to eq @cache.key?(:a)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
it 'with return' do
|
168
|
+
with_or_without_default_proc do
|
169
|
+
@cache[:a] = 1
|
170
|
+
expect_handles_return_lambda(:compute_if_present, :a)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
it 'exception' do
|
175
|
+
with_or_without_default_proc do
|
176
|
+
@cache[:a] = 1
|
177
|
+
expect_handles_exception(:compute_if_present, :a)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
describe '#compute' do
|
183
|
+
it 'common' do
|
184
|
+
with_or_without_default_proc do
|
185
|
+
expect_no_size_change do
|
186
|
+
expect_compute(:a, nil, nil) {}
|
187
|
+
end
|
188
|
+
|
189
|
+
expect_size_change(1) do
|
190
|
+
expect_compute(:a, nil, 1) { 1 }
|
191
|
+
expect_compute(:a, 1, 2) { 2 }
|
192
|
+
expect_compute(:a, 2, false) { false }
|
193
|
+
expect(false).to eq @cache[:a]
|
194
|
+
end
|
195
|
+
|
196
|
+
expect_size_change(-1) do
|
197
|
+
expect_compute(:a, false, nil) {}
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
it 'with return' do
|
203
|
+
with_or_without_default_proc do
|
204
|
+
expect_handles_return_lambda(:compute, :a)
|
205
|
+
@cache[:a] = 1
|
206
|
+
expect_handles_return_lambda(:compute, :a)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
it 'exception' do
|
211
|
+
with_or_without_default_proc do
|
212
|
+
expect_handles_exception(:compute, :a)
|
213
|
+
@cache[:a] = 2
|
214
|
+
expect_handles_exception(:compute, :a)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
describe '#merge_pair' do
|
220
|
+
it 'common' do
|
221
|
+
with_or_without_default_proc do
|
222
|
+
expect_size_change(1) do
|
223
|
+
expect(nil).to eq @cache.merge_pair(:a, nil) { fail }
|
224
|
+
expect(true).to eq @cache.key?(:a)
|
225
|
+
expect(nil).to eq @cache[:a]
|
226
|
+
end
|
227
|
+
|
228
|
+
expect_no_size_change do
|
229
|
+
expect_merge_pair(:a, nil, nil, false) { false }
|
230
|
+
expect_merge_pair(:a, nil, false, 1) { 1 }
|
231
|
+
expect_merge_pair(:a, nil, 1, 2) { 2 }
|
232
|
+
end
|
233
|
+
|
234
|
+
expect_size_change(-1) do
|
235
|
+
expect_merge_pair(:a, nil, 2, nil) {}
|
236
|
+
expect(false).to eq @cache.key?(:a)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
it 'with return' do
|
242
|
+
with_or_without_default_proc do
|
243
|
+
@cache[:a] = 1
|
244
|
+
expect_handles_return_lambda(:merge_pair, :a, 2)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
it 'exception' do
|
249
|
+
with_or_without_default_proc do
|
250
|
+
@cache[:a] = 1
|
251
|
+
expect_handles_exception(:merge_pair, :a, 2)
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
it 'updates dont block reads' do
|
257
|
+
getters_count = 20
|
258
|
+
key_klass = ThreadSafe::Test::HashCollisionKey
|
259
|
+
keys = [key_klass.new(1, 100),
|
260
|
+
key_klass.new(2, 100),
|
261
|
+
key_klass.new(3, 100)] # hash colliding keys
|
262
|
+
inserted_keys = []
|
263
|
+
|
264
|
+
keys.each do |key, i|
|
265
|
+
compute_started = ThreadSafe::Test::Latch.new(1)
|
266
|
+
compute_finished = ThreadSafe::Test::Latch.new(1)
|
267
|
+
getters_started = ThreadSafe::Test::Latch.new(getters_count)
|
268
|
+
getters_finished = ThreadSafe::Test::Latch.new(getters_count)
|
269
|
+
|
270
|
+
computer_thread = Thread.new do
|
271
|
+
getters_started.await
|
272
|
+
@cache.compute_if_absent(key) do
|
273
|
+
compute_started.release
|
274
|
+
getters_finished.await
|
275
|
+
1
|
276
|
+
end
|
277
|
+
compute_finished.release
|
278
|
+
end
|
279
|
+
|
280
|
+
getter_threads = (1..getters_count).map do
|
281
|
+
Thread.new do
|
282
|
+
getters_started.release
|
283
|
+
inserted_keys.each do |inserted_key|
|
284
|
+
expect(true).to eq @cache.key?(inserted_key)
|
285
|
+
expect(1).to eq @cache[inserted_key]
|
286
|
+
end
|
287
|
+
expect(false).to eq @cache.key?(key)
|
288
|
+
compute_started.await
|
289
|
+
inserted_keys.each do |inserted_key|
|
290
|
+
expect(true).to eq @cache.key?(inserted_key)
|
291
|
+
expect(1).to eq @cache[inserted_key]
|
292
|
+
end
|
293
|
+
expect(false).to eq @cache.key?(key)
|
294
|
+
expect(nil).to eq @cache[key]
|
295
|
+
getters_finished.release
|
296
|
+
compute_finished.await
|
297
|
+
expect(true).to eq @cache.key?(key)
|
298
|
+
expect(1).to eq @cache[key]
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
(getter_threads << computer_thread).map do |t|
|
303
|
+
expect(t.join(2)).to be_truthy
|
304
|
+
end # asserting no deadlocks
|
305
|
+
inserted_keys << key
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
specify 'collision resistance' do
|
310
|
+
expect_collision_resistance(
|
311
|
+
(0..1000).map { |i| ThreadSafe::Test::HashCollisionKey(i, 1) }
|
312
|
+
)
|
313
|
+
end
|
314
|
+
|
315
|
+
specify 'collision resistance with arrays' do
|
316
|
+
special_array_class = Class.new(Array) do
|
317
|
+
def key # assert_collision_resistance expects to be able to call .key to get the "real" key
|
318
|
+
first.key
|
319
|
+
end
|
320
|
+
end
|
321
|
+
# Test collision resistance with a keys that say they responds_to <=>, but then raise exceptions
|
322
|
+
# when actually called (ie: an Array filled with non-comparable keys).
|
323
|
+
# See https://github.com/headius/thread_safe/issues/19 for more info.
|
324
|
+
expect_collision_resistance(
|
325
|
+
(0..100).map do |i|
|
326
|
+
special_array_class.new(
|
327
|
+
[ThreadSafe::Test::HashCollisionKeyNonComparable.new(i, 1)]
|
328
|
+
)
|
329
|
+
end
|
330
|
+
)
|
331
|
+
end
|
332
|
+
|
333
|
+
it '#replace_pair' do
|
334
|
+
with_or_without_default_proc do
|
335
|
+
expect_no_size_change do
|
336
|
+
expect(false).to eq @cache.replace_pair(:a, 1, 2)
|
337
|
+
expect(false).to eq @cache.replace_pair(:a, nil, nil)
|
338
|
+
expect(false).to eq @cache.key?(:a)
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
@cache[:a] = 1
|
343
|
+
expect_no_size_change do
|
344
|
+
expect(true).to eq @cache.replace_pair(:a, 1, 2)
|
345
|
+
expect(false).to eq @cache.replace_pair(:a, 1, 2)
|
346
|
+
expect(2).to eq @cache[:a]
|
347
|
+
expect(true).to eq @cache.replace_pair(:a, 2, 2)
|
348
|
+
expect(2).to eq @cache[:a]
|
349
|
+
expect(true).to eq @cache.replace_pair(:a, 2, nil)
|
350
|
+
expect(false).to eq @cache.replace_pair(:a, 2, nil)
|
351
|
+
expect(nil).to eq @cache[:a]
|
352
|
+
expect(true).to eq @cache.key?(:a)
|
353
|
+
expect(true).to eq @cache.replace_pair(:a, nil, nil)
|
354
|
+
expect(true).to eq @cache.key?(:a)
|
355
|
+
expect(true).to eq @cache.replace_pair(:a, nil, 1)
|
356
|
+
expect(1).to eq @cache[:a]
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
it '#replace_if_exists' do
|
361
|
+
with_or_without_default_proc do
|
362
|
+
expect_no_size_change do
|
363
|
+
expect(nil).to eq @cache.replace_if_exists(:a, 1)
|
364
|
+
expect(false).to eq @cache.key?(:a)
|
365
|
+
end
|
366
|
+
|
367
|
+
@cache[:a] = 1
|
368
|
+
expect_no_size_change do
|
369
|
+
expect(1).to eq @cache.replace_if_exists(:a, 2)
|
370
|
+
expect(2).to eq @cache[:a]
|
371
|
+
expect(2).to eq @cache.replace_if_exists(:a, nil)
|
372
|
+
expect(nil).to eq @cache[:a]
|
373
|
+
expect(true).to eq @cache.key?(:a)
|
374
|
+
expect(nil).to eq @cache.replace_if_exists(:a, 1)
|
375
|
+
expect(1).to eq @cache[:a]
|
376
|
+
end
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
it '#get_and_set' do
|
381
|
+
with_or_without_default_proc do
|
382
|
+
expect(nil).to eq @cache.get_and_set(:a, 1)
|
383
|
+
expect(true).to eq @cache.key?(:a)
|
384
|
+
expect(1).to eq @cache[:a]
|
385
|
+
expect(1).to eq @cache.get_and_set(:a, 2)
|
386
|
+
expect(2).to eq @cache.get_and_set(:a, nil)
|
387
|
+
expect(nil).to eq @cache[:a]
|
388
|
+
expect(true).to eq @cache.key?(:a)
|
389
|
+
expect(nil).to eq @cache.get_and_set(:a, 1)
|
390
|
+
expect(1).to eq @cache[:a]
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
it '#key' do
|
395
|
+
with_or_without_default_proc do
|
396
|
+
expect(nil).to eq @cache.key(1)
|
397
|
+
@cache[:a] = 1
|
398
|
+
expect(:a).to eq @cache.key(1)
|
399
|
+
expect(nil).to eq @cache.key(0)
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
it '#key?' do
|
404
|
+
with_or_without_default_proc do
|
405
|
+
expect(false).to eq @cache.key?(:a)
|
406
|
+
@cache[:a] = 1
|
407
|
+
expect(true).to eq @cache.key?(:a)
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
411
|
+
it '#value?' do
|
412
|
+
with_or_without_default_proc do
|
413
|
+
expect(false).to eq @cache.value?(1)
|
414
|
+
@cache[:a] = 1
|
415
|
+
expect(true).to eq @cache.value?(1)
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
it '#delete' do
|
420
|
+
with_or_without_default_proc do |default_proc_set|
|
421
|
+
expect_no_size_change do
|
422
|
+
expect(nil).to eq @cache.delete(:a)
|
423
|
+
end
|
424
|
+
@cache[:a] = 1
|
425
|
+
expect_size_change -1 do
|
426
|
+
expect(1).to eq @cache.delete(:a)
|
427
|
+
end
|
428
|
+
expect_no_size_change do
|
429
|
+
expect(nil).to eq @cache[:a] unless default_proc_set
|
430
|
+
|
431
|
+
expect(false).to eq @cache.key?(:a)
|
432
|
+
expect(nil).to eq @cache.delete(:a)
|
433
|
+
end
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
it '#delete_pair' do
|
438
|
+
with_or_without_default_proc do
|
439
|
+
expect_no_size_change do
|
440
|
+
expect(false).to eq @cache.delete_pair(:a, 2)
|
441
|
+
expect(false).to eq @cache.delete_pair(:a, nil)
|
442
|
+
end
|
443
|
+
@cache[:a] = 1
|
444
|
+
expect_no_size_change do
|
445
|
+
expect(false).to eq @cache.delete_pair(:a, 2)
|
446
|
+
end
|
447
|
+
expect_size_change(-1) do
|
448
|
+
expect(1).to eq @cache[:a]
|
449
|
+
expect(true).to eq @cache.delete_pair(:a, 1)
|
450
|
+
expect(false).to eq @cache.delete_pair(:a, 1)
|
451
|
+
expect(false).to eq @cache.key?(:a)
|
452
|
+
end
|
453
|
+
end
|
454
|
+
end
|
455
|
+
|
456
|
+
specify 'default proc' do
|
457
|
+
@cache = cache_with_default_proc(1)
|
458
|
+
expect_no_size_change do
|
459
|
+
expect(false).to eq @cache.key?(:a)
|
460
|
+
end
|
461
|
+
expect_size_change(1) do
|
462
|
+
expect(1).to eq @cache[:a]
|
463
|
+
expect(true).to eq @cache.key?(:a)
|
464
|
+
end
|
465
|
+
end
|
466
|
+
|
467
|
+
specify 'falsy default proc' do
|
468
|
+
@cache = cache_with_default_proc(nil)
|
469
|
+
expect_no_size_change do
|
470
|
+
expect(false).to eq @cache.key?(:a)
|
471
|
+
end
|
472
|
+
expect_size_change(1) do
|
473
|
+
expect(nil).to eq @cache[:a]
|
474
|
+
expect(true).to eq @cache.key?(:a)
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
478
|
+
describe '#fetch' do
|
479
|
+
it 'common' do
|
480
|
+
with_or_without_default_proc do |default_proc_set|
|
481
|
+
expect_no_size_change do
|
482
|
+
expect(1).to eq @cache.fetch(:a, 1)
|
483
|
+
expect(1).to eq @cache.fetch(:a) { 1 }
|
484
|
+
expect(false).to eq @cache.key?(:a)
|
485
|
+
|
486
|
+
expect(nil).to eq @cache[:a] unless default_proc_set
|
487
|
+
end
|
488
|
+
|
489
|
+
@cache[:a] = 1
|
490
|
+
expect_no_size_change do
|
491
|
+
expect(1).to eq @cache.fetch(:a) { fail }
|
492
|
+
end
|
493
|
+
|
494
|
+
expect { @cache.fetch(:b) }.to raise_error(KeyError)
|
495
|
+
|
496
|
+
expect_no_size_change do
|
497
|
+
expect(1).to eq @cache.fetch(:b, :c) {1} # assert block supersedes default value argument
|
498
|
+
expect(false).to eq @cache.key?(:b)
|
499
|
+
end
|
500
|
+
end
|
501
|
+
end
|
502
|
+
|
503
|
+
it 'falsy' do
|
504
|
+
with_or_without_default_proc do
|
505
|
+
expect(false).to eq @cache.key?(:a)
|
506
|
+
|
507
|
+
expect_no_size_change do
|
508
|
+
expect(nil).to eq @cache.fetch(:a, nil)
|
509
|
+
expect(false).to eq @cache.fetch(:a, false)
|
510
|
+
expect(nil).to eq @cache.fetch(:a) {}
|
511
|
+
expect(false).to eq @cache.fetch(:a) { false }
|
512
|
+
end
|
513
|
+
|
514
|
+
@cache[:a] = nil
|
515
|
+
expect_no_size_change do
|
516
|
+
expect(true).to eq @cache.key?(:a)
|
517
|
+
expect(nil).to eq @cache.fetch(:a) { fail }
|
518
|
+
end
|
519
|
+
end
|
520
|
+
end
|
521
|
+
|
522
|
+
it 'with return' do
|
523
|
+
with_or_without_default_proc do
|
524
|
+
r = fetch_with_return
|
525
|
+
# r = lambda do
|
526
|
+
# @cache.fetch(:a) { return 10 }
|
527
|
+
# end.call
|
528
|
+
|
529
|
+
expect_no_size_change do
|
530
|
+
expect(10).to eq r
|
531
|
+
expect(false).to eq @cache.key?(:a)
|
532
|
+
end
|
533
|
+
end
|
534
|
+
end
|
535
|
+
end
|
536
|
+
|
537
|
+
describe '#fetch_or_store' do
|
538
|
+
it 'common' do
|
539
|
+
with_or_without_default_proc do |default_proc_set|
|
540
|
+
expect_size_change(1) do
|
541
|
+
expect(1).to eq @cache.fetch_or_store(:a, 1)
|
542
|
+
expect(1).to eq @cache[:a]
|
543
|
+
end
|
544
|
+
|
545
|
+
@cache.delete(:a)
|
546
|
+
|
547
|
+
expect_size_change 1 do
|
548
|
+
expect(1).to eq @cache.fetch_or_store(:a) { 1 }
|
549
|
+
expect(1).to eq @cache[:a]
|
550
|
+
end
|
551
|
+
|
552
|
+
expect_no_size_change do
|
553
|
+
expect(1).to eq @cache.fetch_or_store(:a) { fail }
|
554
|
+
end
|
555
|
+
|
556
|
+
expect { @cache.fetch_or_store(:b) }.
|
557
|
+
to raise_error(KeyError)
|
558
|
+
|
559
|
+
expect_size_change(1) do
|
560
|
+
expect(1).to eq @cache.fetch_or_store(:b, :c) { 1 } # assert block supersedes default value argument
|
561
|
+
expect(1).to eq @cache[:b]
|
562
|
+
end
|
563
|
+
end
|
564
|
+
end
|
565
|
+
|
566
|
+
it 'falsy' do
|
567
|
+
with_or_without_default_proc do
|
568
|
+
expect(false).to eq @cache.key?(:a)
|
569
|
+
|
570
|
+
expect_size_change(1) do
|
571
|
+
expect(nil).to eq @cache.fetch_or_store(:a, nil)
|
572
|
+
expect(nil).to eq @cache[:a]
|
573
|
+
expect(true).to eq @cache.key?(:a)
|
574
|
+
end
|
575
|
+
@cache.delete(:a)
|
576
|
+
|
577
|
+
expect_size_change(1) do
|
578
|
+
expect(false).to eq @cache.fetch_or_store(:a, false)
|
579
|
+
expect(false).to eq @cache[:a]
|
580
|
+
expect(true).to eq @cache.key?(:a)
|
581
|
+
end
|
582
|
+
@cache.delete(:a)
|
583
|
+
|
584
|
+
expect_size_change(1) do
|
585
|
+
expect(nil).to eq @cache.fetch_or_store(:a) {}
|
586
|
+
expect(nil).to eq @cache[:a]
|
587
|
+
expect(true).to eq @cache.key?(:a)
|
588
|
+
end
|
589
|
+
@cache.delete(:a)
|
590
|
+
|
591
|
+
expect_size_change(1) do
|
592
|
+
expect(false).to eq @cache.fetch_or_store(:a) { false }
|
593
|
+
expect(false).to eq @cache[:a]
|
594
|
+
expect(true).to eq @cache.key?(:a)
|
595
|
+
end
|
596
|
+
|
597
|
+
@cache[:a] = nil
|
598
|
+
expect_no_size_change do
|
599
|
+
expect(nil).to eq @cache.fetch_or_store(:a) { fail }
|
600
|
+
end
|
601
|
+
end
|
602
|
+
end
|
603
|
+
|
604
|
+
it 'with return' do
|
605
|
+
with_or_without_default_proc do
|
606
|
+
r = fetch_or_store_with_return
|
607
|
+
|
608
|
+
expect_no_size_change do
|
609
|
+
expect(10).to eq r
|
610
|
+
expect(false).to eq @cache.key?(:a)
|
611
|
+
end
|
612
|
+
end
|
613
|
+
end
|
614
|
+
end
|
615
|
+
|
616
|
+
it '#clear' do
|
617
|
+
@cache[:a] = 1
|
618
|
+
expect_size_change(-1) do
|
619
|
+
expect(@cache).to eq @cache.clear
|
620
|
+
expect(false).to eq @cache.key?(:a)
|
621
|
+
expect(nil).to eq @cache[:a]
|
622
|
+
end
|
623
|
+
end
|
624
|
+
|
625
|
+
describe '#each_pair' do
|
626
|
+
it 'common' do
|
627
|
+
@cache.each_pair { |k, v| fail }
|
628
|
+
expect(@cache).to eq @cache.each_pair {}
|
629
|
+
@cache[:a] = 1
|
630
|
+
|
631
|
+
h = {}
|
632
|
+
@cache.each_pair { |k, v| h[k] = v }
|
633
|
+
expect({:a => 1}).to eq h
|
634
|
+
|
635
|
+
@cache[:b] = 2
|
636
|
+
h = {}
|
637
|
+
@cache.each_pair { |k, v| h[k] = v }
|
638
|
+
expect({:a => 1, :b => 2}).to eq h
|
639
|
+
end
|
640
|
+
|
641
|
+
it 'pair iterator' do
|
642
|
+
@cache[:a] = 1
|
643
|
+
@cache[:b] = 2
|
644
|
+
i = 0
|
645
|
+
r = @cache.each_pair do |k, v|
|
646
|
+
if i == 0
|
647
|
+
i += 1
|
648
|
+
next
|
649
|
+
fail
|
650
|
+
elsif i == 1
|
651
|
+
break :breaked
|
652
|
+
end
|
653
|
+
end
|
654
|
+
|
655
|
+
expect(:breaked).to eq r
|
656
|
+
end
|
657
|
+
|
658
|
+
it 'allows modification' do
|
659
|
+
@cache[:a] = 1
|
660
|
+
@cache[:b] = 1
|
661
|
+
@cache[:c] = 1
|
662
|
+
|
663
|
+
expect_size_change(1) do
|
664
|
+
@cache.each_pair do |k, v|
|
665
|
+
@cache[:z] = 1
|
666
|
+
end
|
667
|
+
end
|
668
|
+
end
|
669
|
+
end
|
670
|
+
|
671
|
+
it '#keys' do
|
672
|
+
expect([]).to eq @cache.keys
|
673
|
+
|
674
|
+
@cache[1] = 1
|
675
|
+
expect([1]).to eq @cache.keys
|
676
|
+
|
677
|
+
@cache[2] = 2
|
678
|
+
expect([1, 2]).to eq @cache.keys.sort
|
679
|
+
end
|
680
|
+
|
681
|
+
it '#values' do
|
682
|
+
expect([]).to eq @cache.values
|
683
|
+
|
684
|
+
@cache[1] = 1
|
685
|
+
expect([1]).to eq @cache.values
|
686
|
+
|
687
|
+
@cache[2] = 2
|
688
|
+
expect([1, 2]).to eq @cache.values.sort
|
689
|
+
end
|
690
|
+
|
691
|
+
it '#each_key' do
|
692
|
+
expect(@cache).to eq @cache.each_key { fail }
|
693
|
+
|
694
|
+
@cache[1] = 1
|
695
|
+
arr = []
|
696
|
+
@cache.each_key { |k| arr << k }
|
697
|
+
expect([1]).to eq arr
|
698
|
+
|
699
|
+
@cache[2] = 2
|
700
|
+
arr = []
|
701
|
+
@cache.each_key { |k| arr << k }
|
702
|
+
expect([1, 2]).to eq arr.sort
|
703
|
+
end
|
704
|
+
|
705
|
+
it '#each_value' do
|
706
|
+
expect(@cache).to eq @cache.each_value { fail }
|
707
|
+
|
708
|
+
@cache[1] = 1
|
709
|
+
arr = []
|
710
|
+
@cache.each_value { |k| arr << k }
|
711
|
+
expect([1]).to eq arr
|
712
|
+
|
713
|
+
@cache[2] = 2
|
714
|
+
arr = []
|
715
|
+
@cache.each_value { |k| arr << k }
|
716
|
+
expect([1, 2]).to eq arr.sort
|
717
|
+
end
|
718
|
+
|
719
|
+
it '#empty' do
|
720
|
+
expect(true).to eq @cache.empty?
|
721
|
+
@cache[:a] = 1
|
722
|
+
expect(false).to eq @cache.empty?
|
723
|
+
end
|
724
|
+
|
725
|
+
it 'options validation' do
|
726
|
+
expect_valid_options(nil)
|
727
|
+
expect_valid_options({})
|
728
|
+
expect_valid_options(foo: :bar)
|
729
|
+
end
|
730
|
+
|
731
|
+
it 'initial capacity options validation' do
|
732
|
+
expect_valid_option(:initial_capacity, nil)
|
733
|
+
expect_valid_option(:initial_capacity, 1)
|
734
|
+
expect_invalid_option(:initial_capacity, '')
|
735
|
+
expect_invalid_option(:initial_capacity, 1.0)
|
736
|
+
expect_invalid_option(:initial_capacity, -1)
|
737
|
+
end
|
738
|
+
|
739
|
+
it 'load factor options validation' do
|
740
|
+
expect_valid_option(:load_factor, nil)
|
741
|
+
expect_valid_option(:load_factor, 0.01)
|
742
|
+
expect_valid_option(:load_factor, 0.75)
|
743
|
+
expect_valid_option(:load_factor, 1)
|
744
|
+
expect_invalid_option(:load_factor, '')
|
745
|
+
expect_invalid_option(:load_factor, 0)
|
746
|
+
expect_invalid_option(:load_factor, 1.1)
|
747
|
+
expect_invalid_option(:load_factor, 2)
|
748
|
+
expect_invalid_option(:load_factor, -1)
|
749
|
+
end
|
750
|
+
|
751
|
+
it '#size' do
|
752
|
+
expect(0).to eq @cache.size
|
753
|
+
@cache[:a] = 1
|
754
|
+
expect(1).to eq @cache.size
|
755
|
+
@cache[:b] = 1
|
756
|
+
expect(2).to eq @cache.size
|
757
|
+
@cache.delete(:a)
|
758
|
+
expect(1).to eq @cache.size
|
759
|
+
@cache.delete(:b)
|
760
|
+
expect(0).to eq @cache.size
|
761
|
+
end
|
762
|
+
|
763
|
+
it '#get_or_default' do
|
764
|
+
with_or_without_default_proc do
|
765
|
+
expect(1).to eq @cache.get_or_default(:a, 1)
|
766
|
+
expect(nil).to eq @cache.get_or_default(:a, nil)
|
767
|
+
expect(false).to eq @cache.get_or_default(:a, false)
|
768
|
+
expect(false).to eq @cache.key?(:a)
|
769
|
+
|
770
|
+
@cache[:a] = 1
|
771
|
+
expect(1).to eq @cache.get_or_default(:a, 2)
|
772
|
+
end
|
773
|
+
end
|
774
|
+
|
775
|
+
it '#dup,#clone' do
|
776
|
+
[:dup, :clone].each do |meth|
|
777
|
+
cache = cache_with_default_proc(:default_value)
|
778
|
+
cache[:a] = 1
|
779
|
+
dupped = cache.send(meth)
|
780
|
+
expect(1).to eq dupped[:a]
|
781
|
+
expect(1).to eq dupped.size
|
782
|
+
expect_size_change(1, cache) do
|
783
|
+
expect_no_size_change(dupped) do
|
784
|
+
cache[:b] = 1
|
785
|
+
end
|
786
|
+
end
|
787
|
+
expect(false).to eq dupped.key?(:b)
|
788
|
+
expect_no_size_change(cache) do
|
789
|
+
expect_size_change(-1, dupped) do
|
790
|
+
dupped.delete(:a)
|
791
|
+
end
|
792
|
+
end
|
793
|
+
expect(false).to eq dupped.key?(:a)
|
794
|
+
expect(true).to eq cache.key?(:a)
|
795
|
+
# test default proc
|
796
|
+
expect_size_change(1, cache) do
|
797
|
+
expect_no_size_change dupped do
|
798
|
+
expect(:default_value).to eq cache[:c]
|
799
|
+
expect(false).to eq dupped.key?(:c)
|
800
|
+
end
|
801
|
+
end
|
802
|
+
expect_no_size_change cache do
|
803
|
+
expect_size_change 1, dupped do
|
804
|
+
expect(:default_value).to eq dupped[:d]
|
805
|
+
expect(false).to eq cache.key?(:d)
|
806
|
+
end
|
807
|
+
end
|
808
|
+
end
|
809
|
+
end
|
810
|
+
|
811
|
+
it 'is unfreezable' do
|
812
|
+
expect { @cache.freeze }.to raise_error(NoMethodError)
|
813
|
+
end
|
814
|
+
|
815
|
+
it 'marshal dump load' do
|
816
|
+
new_cache = Marshal.load(Marshal.dump(@cache))
|
817
|
+
expect(new_cache).to be_an_instance_of ThreadSafe::Cache
|
818
|
+
expect(0).to eq new_cache.size
|
819
|
+
@cache[:a] = 1
|
820
|
+
new_cache = Marshal.load(Marshal.dump(@cache))
|
821
|
+
expect(1).to eq @cache[:a]
|
822
|
+
expect(1).to eq new_cache.size
|
823
|
+
end
|
824
|
+
|
825
|
+
it 'marshal dump doesnt work with default proc' do
|
826
|
+
expect { Marshal.dump(ThreadSafe::Cache.new {}) }.to raise_error(TypeError)
|
827
|
+
end
|
828
|
+
|
829
|
+
private
|
830
|
+
|
831
|
+
def with_or_without_default_proc(&block)
|
832
|
+
block.call(false)
|
833
|
+
@cache = ThreadSafe::Cache.new { |h, k| h[k] = :default_value }
|
834
|
+
block.call(true)
|
835
|
+
end
|
836
|
+
|
837
|
+
def cache_with_default_proc(default_value = 1)
|
838
|
+
ThreadSafe::Cache.new { |cache, k| cache[k] = default_value }
|
839
|
+
end
|
840
|
+
|
841
|
+
def expect_size_change(change, cache = @cache, &block)
|
842
|
+
start = cache.size
|
843
|
+
block.call
|
844
|
+
expect(change).to eq cache.size - start
|
845
|
+
end
|
846
|
+
|
847
|
+
def expect_valid_option(option_name, value)
|
848
|
+
expect_valid_options(option_name => value)
|
849
|
+
end
|
850
|
+
|
851
|
+
def expect_valid_options(options)
|
852
|
+
c = ThreadSafe::Cache.new(options)
|
853
|
+
expect(c).to be_an_instance_of ThreadSafe::Cache
|
854
|
+
end
|
855
|
+
|
856
|
+
def expect_invalid_option(option_name, value)
|
857
|
+
expect_invalid_options(option_name => value)
|
858
|
+
end
|
859
|
+
|
860
|
+
def expect_invalid_options(options)
|
861
|
+
expect { ThreadSafe::Cache.new(options) }.to raise_error(ArgumentError)
|
862
|
+
end
|
863
|
+
|
864
|
+
def expect_no_size_change(cache = @cache, &block)
|
865
|
+
expect_size_change(0, cache, &block)
|
866
|
+
end
|
867
|
+
|
868
|
+
def expect_handles_return_lambda(method, key, *args)
|
869
|
+
before_had_key = @cache.key?(key)
|
870
|
+
before_had_value = before_had_key ? @cache[key] : nil
|
871
|
+
|
872
|
+
returning_lambda = lambda do
|
873
|
+
@cache.send(method, key, *args) { return :direct_return }
|
874
|
+
end
|
875
|
+
|
876
|
+
expect_no_size_change do
|
877
|
+
expect(:direct_return).to eq returning_lambda.call
|
878
|
+
expect(before_had_key).to eq @cache.key?(key)
|
879
|
+
expect(before_had_value).to eq @cache[key] if before_had_value
|
880
|
+
end
|
881
|
+
end
|
882
|
+
|
883
|
+
class TestException < Exception; end
|
884
|
+
def expect_handles_exception(method, key, *args)
|
885
|
+
before_had_key = @cache.key?(key)
|
886
|
+
before_had_value = before_had_key ? @cache[key] : nil
|
887
|
+
|
888
|
+
expect_no_size_change do
|
889
|
+
expect { @cache.send(method, key, *args) { raise TestException, '' } }.
|
890
|
+
to raise_error(TestException)
|
891
|
+
|
892
|
+
expect(before_had_key).to eq @cache.key?(key)
|
893
|
+
expect(before_had_value).to eq @cache[key] if before_had_value
|
894
|
+
end
|
895
|
+
end
|
896
|
+
|
897
|
+
def expect_compute(key, expected_old_value, expected_result, &block)
|
898
|
+
result = @cache.compute(:a) do |old_value|
|
899
|
+
expect(expected_old_value).to eq old_value
|
900
|
+
block.call
|
901
|
+
end
|
902
|
+
expect(expected_result).to eq result
|
903
|
+
end
|
904
|
+
|
905
|
+
def expect_merge_pair(key, value, expected_old_value, expected_result, &block)
|
906
|
+
result = @cache.merge_pair(key, value) do |old_value|
|
907
|
+
expect(expected_old_value).to eq old_value
|
908
|
+
block.call
|
909
|
+
end
|
910
|
+
expect(expected_result).to eq result
|
911
|
+
end
|
912
|
+
|
913
|
+
def expect_collision_resistance(keys)
|
914
|
+
keys.each { |k| @cache[k] = k.key }
|
915
|
+
10.times do |i|
|
916
|
+
size = keys.size
|
917
|
+
while i < size
|
918
|
+
k = keys[i]
|
919
|
+
expect(k.key == @cache.delete(k) &&
|
920
|
+
!@cache.key?(k) &&
|
921
|
+
(@cache[k] = k.key; @cache[k] == k.key)).to be_truthy
|
922
|
+
i += 10
|
923
|
+
end
|
924
|
+
end
|
925
|
+
expect(keys.all? { |k| @cache[k] == k.key }).to be_truthy
|
926
|
+
end
|
927
|
+
|
928
|
+
# Took out for compatibility with Rubinius, see https://github.com/rubinius/rubinius/issues/1312
|
929
|
+
def fetch_with_return
|
930
|
+
lambda do
|
931
|
+
@cache.fetch(:a) { return 10 }
|
932
|
+
end.call
|
933
|
+
end
|
934
|
+
|
935
|
+
# Took out for compatibility with Rubinius, see https://github.com/rubinius/rubinius/issues/1312
|
936
|
+
def fetch_or_store_with_return
|
937
|
+
lambda do
|
938
|
+
@cache.fetch_or_store(:a) { return 10 }
|
939
|
+
end.call
|
940
|
+
end
|
941
|
+
|
942
|
+
end
|
943
|
+
end
|