thread_safe 0.3.5 → 0.3.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,17 @@
1
+ module ThreadSafe
2
+ describe Hash do
3
+ let!(:hsh) { described_class.new }
4
+
5
+ it 'concurrency' do
6
+ (1..THREADS).map do |i|
7
+ Thread.new do
8
+ 1000.times do |j|
9
+ hsh[i * 1000 + j] = i
10
+ hsh[i * 1000 + j]
11
+ hsh.delete(i * 1000 + j)
12
+ end
13
+ end
14
+ end.map(&:join)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,27 @@
1
+ if defined?(JRUBY_VERSION) && ENV['TEST_NO_UNSAFE']
2
+ # to be used like this: rake test TEST_NO_UNSAFE=true
3
+ load 'test/package.jar'
4
+ java_import 'thread_safe.SecurityManager'
5
+ manager = SecurityManager.new
6
+
7
+ # Prevent accessing internal classes
8
+ manager.deny(java.lang.RuntimePermission.new('accessClassInPackage.sun.misc'))
9
+ java.lang.System.setSecurityManager(manager)
10
+
11
+ module ThreadSafe
12
+ describe 'no_unsafe' do
13
+ it 'security_manager_is_used' do
14
+ begin
15
+ java_import 'sun.misc.Unsafe'
16
+ fail
17
+ rescue SecurityError
18
+ end
19
+ end
20
+
21
+ it 'no_unsafe_version_of_chmv8_is_used' do
22
+ require 'thread_safe/jruby_cache_backend' # make sure the jar has been loaded
23
+ expect(!Java::OrgJrubyExtThread_safe::JRubyCacheBackendLibrary::JRubyCacheBackend::CAN_USE_UNSAFE_CHM).to be_truthy
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,85 @@
1
+ require 'thread_safe/synchronized_delegator.rb'
2
+
3
+ module ThreadSafe
4
+ describe SynchronizedDelegator do
5
+ it 'wraps array' do
6
+ array = ::Array.new
7
+ sync_array = described_class.new(array)
8
+
9
+ array << 1
10
+ expect(1).to eq sync_array[0]
11
+
12
+ sync_array << 2
13
+ expect(2).to eq array[1]
14
+ end
15
+
16
+ it 'synchronizes access' do
17
+ t1_continue, t2_continue = false, false
18
+
19
+ hash = ::Hash.new do |hash, key|
20
+ t2_continue = true
21
+ unless hash.find { |e| e[1] == key.to_s } # just to do something
22
+ hash[key] = key.to_s
23
+ Thread.pass until t1_continue
24
+ end
25
+ end
26
+ sync_hash = described_class.new(hash)
27
+ sync_hash[1] = 'egy'
28
+
29
+ t1 = Thread.new do
30
+ sync_hash[2] = 'dva'
31
+ sync_hash[3] # triggers t2_continue
32
+ end
33
+
34
+ t2 = Thread.new do
35
+ Thread.pass until t2_continue
36
+ sync_hash[4] = '42'
37
+ end
38
+
39
+ sleep(0.05) # sleep some to allow threads to boot
40
+
41
+ until t2.status == 'sleep' do
42
+ Thread.pass
43
+ end
44
+
45
+ expect(3).to eq hash.keys.size
46
+
47
+ t1_continue = true
48
+ t1.join; t2.join
49
+
50
+ expect(4).to eq sync_hash.size
51
+ end
52
+
53
+ it 'synchronizes access with block' do
54
+ t1_continue, t2_continue = false, false
55
+
56
+ array = ::Array.new
57
+ sync_array = described_class.new(array)
58
+
59
+ t1 = Thread.new do
60
+ sync_array << 1
61
+ sync_array.each do
62
+ t2_continue = true
63
+ Thread.pass until t1_continue
64
+ end
65
+ end
66
+
67
+ t2 = Thread.new do
68
+ # sleep(0.01)
69
+ Thread.pass until t2_continue
70
+ sync_array << 2
71
+ end
72
+
73
+ until t2.status == 'sleep' || t2.status == false
74
+ Thread.pass
75
+ end
76
+
77
+ expect(1).to eq array.size
78
+
79
+ t1_continue = true
80
+ t1.join; t2.join
81
+
82
+ expect([1, 2]).to eq array
83
+ end
84
+ end
85
+ end
@@ -5,8 +5,8 @@ require 'thread_safe/version'
5
5
  Gem::Specification.new do |gem|
6
6
  gem.authors = ["Charles Oliver Nutter", "thedarkone"]
7
7
  gem.email = ["headius@headius.com", "thedarkone2@gmail.com"]
8
- gem.description = %q{Thread-safe collections and utilities for Ruby}
9
- gem.summary = %q{A collection of data structures and utilities to make thread-safe programming in Ruby easier}
8
+ gem.summary = %q{Thread-safe collections and utilities for Ruby}
9
+ gem.description = %q{A collection of data structures and utilities to make thread-safe programming in Ruby easier}
10
10
  gem.homepage = "https://github.com/ruby-concurrency/thread_safe"
11
11
 
12
12
  gem.files = `git ls-files`.split($\)
@@ -21,6 +21,6 @@ Gem::Specification.new do |gem|
21
21
  gem.license = "Apache-2.0"
22
22
 
23
23
  gem.add_development_dependency 'atomic', '= 1.1.16'
24
- gem.add_development_dependency 'rake'
25
- gem.add_development_dependency 'minitest', '>= 4'
24
+ gem.add_development_dependency 'rake', '< 12.0'
25
+ gem.add_development_dependency 'rspec', '~> 3.2'
26
26
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: thread_safe
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.5
4
+ version: 0.3.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Charles Oliver Nutter
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-03-11 00:00:00.000000000 Z
12
+ date: 2017-02-22 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: atomic
@@ -29,31 +29,32 @@ dependencies:
29
29
  name: rake
30
30
  requirement: !ruby/object:Gem::Requirement
31
31
  requirements:
32
- - - ">="
32
+ - - "<"
33
33
  - !ruby/object:Gem::Version
34
- version: '0'
34
+ version: '12.0'
35
35
  type: :development
36
36
  prerelease: false
37
37
  version_requirements: !ruby/object:Gem::Requirement
38
38
  requirements:
39
- - - ">="
39
+ - - "<"
40
40
  - !ruby/object:Gem::Version
41
- version: '0'
41
+ version: '12.0'
42
42
  - !ruby/object:Gem::Dependency
43
- name: minitest
43
+ name: rspec
44
44
  requirement: !ruby/object:Gem::Requirement
45
45
  requirements:
46
- - - ">="
46
+ - - "~>"
47
47
  - !ruby/object:Gem::Version
48
- version: '4'
48
+ version: '3.2'
49
49
  type: :development
50
50
  prerelease: false
51
51
  version_requirements: !ruby/object:Gem::Requirement
52
52
  requirements:
53
- - - ">="
53
+ - - "~>"
54
54
  - !ruby/object:Gem::Version
55
- version: '4'
56
- description: Thread-safe collections and utilities for Ruby
55
+ version: '3.2'
56
+ description: A collection of data structures and utilities to make thread-safe programming
57
+ in Ruby easier
57
58
  email:
58
59
  - headius@headius.com
59
60
  - thedarkone2@gmail.com
@@ -61,6 +62,7 @@ executables: []
61
62
  extensions: []
62
63
  extra_rdoc_files: []
63
64
  files:
65
+ - ".rspec"
64
66
  - ".travis.yml"
65
67
  - ".yardopts"
66
68
  - Gemfile
@@ -95,14 +97,20 @@ files:
95
97
  - lib/thread_safe/util/volatile_tuple.rb
96
98
  - lib/thread_safe/util/xor_shift_random.rb
97
99
  - lib/thread_safe/version.rb
100
+ - spec/.gitignore
101
+ - spec/spec_helper.rb
102
+ - spec/src/thread_safe/SecurityManager.java
103
+ - spec/support/.gitignore
104
+ - spec/support/threads.rb
105
+ - spec/support/threadsafe_test.rb
106
+ - spec/thread_safe/.gitignore
107
+ - spec/thread_safe/array_spec.rb
108
+ - spec/thread_safe/cache_loops_spec.rb
109
+ - spec/thread_safe/cache_spec.rb
110
+ - spec/thread_safe/hash_spec.rb
111
+ - spec/thread_safe/no_unsafe_spec.rb
112
+ - spec/thread_safe/synchronized_delegator_spec.rb
98
113
  - tasks/update_doc.rake
99
- - test/src/thread_safe/SecurityManager.java
100
- - test/test_array.rb
101
- - test/test_cache.rb
102
- - test/test_cache_loops.rb
103
- - test/test_hash.rb
104
- - test/test_helper.rb
105
- - test/test_synchronized_delegator.rb
106
114
  - thread_safe.gemspec
107
115
  - yard-template/default/fulldoc/html/css/common.css
108
116
  - yard-template/default/layout/html/footer.erb
@@ -126,17 +134,22 @@ required_rubygems_version: !ruby/object:Gem::Requirement
126
134
  version: '0'
127
135
  requirements: []
128
136
  rubyforge_project:
129
- rubygems_version: 2.4.6
137
+ rubygems_version: 2.6.10
130
138
  signing_key:
131
139
  specification_version: 4
132
- summary: A collection of data structures and utilities to make thread-safe programming
133
- in Ruby easier
140
+ summary: Thread-safe collections and utilities for Ruby
134
141
  test_files:
135
- - test/src/thread_safe/SecurityManager.java
136
- - test/test_array.rb
137
- - test/test_cache.rb
138
- - test/test_cache_loops.rb
139
- - test/test_hash.rb
140
- - test/test_helper.rb
141
- - test/test_synchronized_delegator.rb
142
+ - spec/.gitignore
143
+ - spec/spec_helper.rb
144
+ - spec/src/thread_safe/SecurityManager.java
145
+ - spec/support/.gitignore
146
+ - spec/support/threads.rb
147
+ - spec/support/threadsafe_test.rb
148
+ - spec/thread_safe/.gitignore
149
+ - spec/thread_safe/array_spec.rb
150
+ - spec/thread_safe/cache_loops_spec.rb
151
+ - spec/thread_safe/cache_spec.rb
152
+ - spec/thread_safe/hash_spec.rb
153
+ - spec/thread_safe/no_unsafe_spec.rb
154
+ - spec/thread_safe/synchronized_delegator_spec.rb
142
155
  has_rdoc:
@@ -1,18 +0,0 @@
1
- require 'thread_safe'
2
- require File.join(File.dirname(__FILE__), "test_helper")
3
-
4
- class TestArray < Minitest::Test
5
- def test_concurrency
6
- ary = ThreadSafe::Array.new
7
- (1..THREADS).map do |i|
8
- Thread.new do
9
- 1000.times do
10
- ary << i
11
- ary.each {|x| x * 2}
12
- ary.shift
13
- ary.last
14
- end
15
- end
16
- end.map(&:join)
17
- end
18
- end
@@ -1,901 +0,0 @@
1
- require 'thread_safe'
2
- require 'thread'
3
- require File.join(File.dirname(__FILE__), "test_helper")
4
-
5
- Thread.abort_on_exception = true
6
-
7
- class TestCache < Minitest::Test
8
- def setup
9
- @cache = ThreadSafe::Cache.new
10
- end
11
-
12
- def test_concurrency
13
- cache = @cache
14
- (1..THREADS).map do |i|
15
- Thread.new do
16
- 1000.times do |j|
17
- key = i*1000+j
18
- cache[key] = i
19
- cache[key]
20
- cache.delete(key)
21
- end
22
- end
23
- end.map(&:join)
24
- end
25
-
26
- def test_retrieval
27
- assert_size_change 1 do
28
- assert_equal nil, @cache[:a]
29
- assert_equal nil, @cache.get(:a)
30
- @cache[:a] = 1
31
- assert_equal 1, @cache[:a]
32
- assert_equal 1, @cache.get(:a)
33
- end
34
- end
35
-
36
- def test_put_if_absent
37
- with_or_without_default_proc do
38
- assert_size_change 1 do
39
- assert_equal nil, @cache.put_if_absent(:a, 1)
40
- assert_equal 1, @cache.put_if_absent(:a, 1)
41
- assert_equal 1, @cache.put_if_absent(:a, 2)
42
- assert_equal 1, @cache[:a]
43
- end
44
- end
45
- end
46
-
47
- def test_compute_if_absent
48
- with_or_without_default_proc do
49
- assert_size_change 3 do
50
- assert_equal(1, (@cache.compute_if_absent(:a) {1}))
51
- assert_equal(1, (@cache.compute_if_absent(:a) {2}))
52
- assert_equal 1, @cache[:a]
53
- @cache[:b] = nil
54
- assert_equal(nil, (@cache.compute_if_absent(:b) {1}))
55
- assert_equal(nil, (@cache.compute_if_absent(:c) {}))
56
- assert_equal nil, @cache[:c]
57
- assert_equal true, @cache.key?(:c)
58
- end
59
- end
60
- end
61
-
62
- def test_compute_if_absent_with_return
63
- with_or_without_default_proc { assert_handles_return_lambda(:compute_if_absent, :a) }
64
- end
65
-
66
- def test_compute_if_absent_exception
67
- with_or_without_default_proc { assert_handles_exception(:compute_if_absent, :a) }
68
- end
69
-
70
- def test_compute_if_absent_atomicity
71
- late_compute_threads_count = 10
72
- late_put_if_absent_threads_count = 10
73
- getter_threads_count = 5
74
- compute_started = ThreadSafe::Test::Latch.new(1)
75
- compute_proceed = ThreadSafe::Test::Latch.new(late_compute_threads_count + late_put_if_absent_threads_count + getter_threads_count)
76
- block_until_compute_started = lambda do |name|
77
- if (v = @cache[:a]) != nil
78
- assert_equal nil, v
79
- end
80
- compute_proceed.release
81
- compute_started.await
82
- end
83
-
84
- assert_size_change 1 do
85
- late_compute_threads = Array.new(late_compute_threads_count) do
86
- Thread.new do
87
- block_until_compute_started.call('compute_if_absent')
88
- assert_equal(1, (@cache.compute_if_absent(:a) { flunk }))
89
- end
90
- end
91
-
92
- late_put_if_absent_threads = Array.new(late_put_if_absent_threads_count) do
93
- Thread.new do
94
- block_until_compute_started.call('put_if_absent')
95
- assert_equal(1, @cache.put_if_absent(:a, 2))
96
- end
97
- end
98
-
99
- getter_threads = Array.new(getter_threads_count) do
100
- Thread.new do
101
- block_until_compute_started.call('getter')
102
- Thread.pass while @cache[:a].nil?
103
- assert_equal 1, @cache[:a]
104
- end
105
- end
106
-
107
- Thread.new do
108
- @cache.compute_if_absent(:a) do
109
- compute_started.release
110
- compute_proceed.await
111
- sleep(0.2)
112
- 1
113
- end
114
- end.join
115
- (late_compute_threads + late_put_if_absent_threads + getter_threads).each(&:join)
116
- end
117
- end
118
-
119
- def test_compute_if_present
120
- with_or_without_default_proc do
121
- assert_no_size_change do
122
- assert_equal(nil, @cache.compute_if_present(:a) {})
123
- assert_equal(nil, @cache.compute_if_present(:a) {1})
124
- assert_equal(nil, @cache.compute_if_present(:a) {flunk})
125
- assert_equal false, @cache.key?(:a)
126
- end
127
-
128
- @cache[:a] = 1
129
- assert_no_size_change do
130
- assert_equal(1, @cache.compute_if_present(:a) {1})
131
- assert_equal(1, @cache[:a])
132
- assert_equal(2, @cache.compute_if_present(:a) {2})
133
- assert_equal(2, @cache[:a])
134
- assert_equal(false, @cache.compute_if_present(:a) {false})
135
- assert_equal(false, @cache[:a])
136
-
137
- @cache[:a] = 1
138
- yielded = false
139
- @cache.compute_if_present(:a) do |old_value|
140
- yielded = true
141
- assert_equal 1, old_value
142
- 2
143
- end
144
- assert yielded
145
- end
146
-
147
- assert_size_change -1 do
148
- assert_equal(nil, @cache.compute_if_present(:a) {})
149
- assert_equal(false, @cache.key?(:a))
150
- assert_equal(nil, @cache.compute_if_present(:a) {1})
151
- assert_equal(false, @cache.key?(:a))
152
- end
153
- end
154
- end
155
-
156
- def test_compute_if_present_with_return
157
- with_or_without_default_proc do
158
- @cache[:a] = 1
159
- assert_handles_return_lambda(:compute_if_present, :a)
160
- end
161
- end
162
-
163
- def test_compute_if_present_exception
164
- with_or_without_default_proc do
165
- @cache[:a] = 1
166
- assert_handles_exception(:compute_if_present, :a)
167
- end
168
- end
169
-
170
- def test_compute
171
- with_or_without_default_proc do
172
- assert_no_size_change do
173
- assert_compute(:a, nil, nil) {}
174
- end
175
-
176
- assert_size_change 1 do
177
- assert_compute(:a, nil, 1) {1}
178
- assert_compute(:a, 1, 2) {2}
179
- assert_compute(:a, 2, false) {false}
180
- assert_equal false, @cache[:a]
181
- end
182
-
183
- assert_size_change -1 do
184
- assert_compute(:a, false, nil) {}
185
- end
186
- end
187
- end
188
-
189
- def test_compute_with_return
190
- with_or_without_default_proc do
191
- assert_handles_return_lambda(:compute, :a)
192
- @cache[:a] = 1
193
- assert_handles_return_lambda(:compute, :a)
194
- end
195
- end
196
-
197
- def test_compute_exception
198
- with_or_without_default_proc do
199
- assert_handles_exception(:compute, :a)
200
- @cache[:a] = 1
201
- assert_handles_exception(:compute, :a)
202
- end
203
- end
204
-
205
- def test_merge_pair
206
- with_or_without_default_proc do
207
- assert_size_change 1 do
208
- assert_equal(nil, @cache.merge_pair(:a, nil) {flunk})
209
- assert_equal true, @cache.key?(:a)
210
- assert_equal nil, @cache[:a]
211
- end
212
-
213
- assert_no_size_change do
214
- assert_merge_pair(:a, nil, nil, false) {false}
215
- assert_merge_pair(:a, nil, false, 1) {1}
216
- assert_merge_pair(:a, nil, 1, 2) {2}
217
- end
218
-
219
- assert_size_change -1 do
220
- assert_merge_pair(:a, nil, 2, nil) {}
221
- assert_equal false, @cache.key?(:a)
222
- end
223
- end
224
- end
225
-
226
- def test_merge_pair_with_return
227
- with_or_without_default_proc do
228
- @cache[:a] = 1
229
- assert_handles_return_lambda(:merge_pair, :a, 2)
230
- end
231
- end
232
-
233
- def test_merge_pair_exception
234
- with_or_without_default_proc do
235
- @cache[:a] = 1
236
- assert_handles_exception(:merge_pair, :a, 2)
237
- end
238
- end
239
-
240
- def test_updates_dont_block_reads
241
- getters_count = 20
242
- key_klass = ThreadSafe::Test::HashCollisionKey
243
- keys = [key_klass.new(1, 100), key_klass.new(2, 100), key_klass.new(3, 100)] # hash colliding keys
244
- inserted_keys = []
245
-
246
- keys.each do |key, i|
247
- compute_started = ThreadSafe::Test::Latch.new(1)
248
- compute_finished = ThreadSafe::Test::Latch.new(1)
249
- getters_started = ThreadSafe::Test::Latch.new(getters_count)
250
- getters_finished = ThreadSafe::Test::Latch.new(getters_count)
251
-
252
- computer_thread = Thread.new do
253
- getters_started.await
254
- @cache.compute_if_absent(key) do
255
- compute_started.release
256
- getters_finished.await
257
- 1
258
- end
259
- compute_finished.release
260
- end
261
-
262
- getter_threads = (1..getters_count).map do
263
- Thread.new do
264
- getters_started.release
265
- inserted_keys.each do |inserted_key|
266
- assert_equal true, @cache.key?(inserted_key)
267
- assert_equal 1, @cache[inserted_key]
268
- end
269
- assert_equal false, @cache.key?(key)
270
- compute_started.await
271
- inserted_keys.each do |inserted_key|
272
- assert_equal true, @cache.key?(inserted_key)
273
- assert_equal 1, @cache[inserted_key]
274
- end
275
- assert_equal false, @cache.key?(key)
276
- assert_equal nil, @cache[key]
277
- getters_finished.release
278
- compute_finished.await
279
- assert_equal true, @cache.key?(key)
280
- assert_equal 1, @cache[key]
281
- end
282
- end
283
-
284
- (getter_threads << computer_thread).map {|t| assert(t.join(2))} # asserting no deadlocks
285
- inserted_keys << key
286
- end
287
- end
288
-
289
- def test_collision_resistance
290
- assert_collision_resistance((0..1000).map {|i| ThreadSafe::Test::HashCollisionKey(i, 1)})
291
- end
292
-
293
- def test_collision_resistance_with_arrays
294
- special_array_class = Class.new(Array) do
295
- def key # assert_collision_resistance expects to be able to call .key to get the "real" key
296
- first.key
297
- end
298
- end
299
- # Test collision resistance with a keys that say they responds_to <=>, but then raise exceptions
300
- # when actually called (ie: an Array filled with non-comparable keys).
301
- # See https://github.com/headius/thread_safe/issues/19 for more info.
302
- assert_collision_resistance((0..100).map do |i|
303
- special_array_class.new([ThreadSafe::Test::HashCollisionKeyNonComparable.new(i, 1)])
304
- end)
305
- end
306
-
307
- def test_replace_pair
308
- with_or_without_default_proc do
309
- assert_no_size_change do
310
- assert_equal false, @cache.replace_pair(:a, 1, 2)
311
- assert_equal false, @cache.replace_pair(:a, nil, nil)
312
- assert_equal false, @cache.key?(:a)
313
- end
314
-
315
- @cache[:a] = 1
316
- assert_no_size_change do
317
- assert_equal true, @cache.replace_pair(:a, 1, 2)
318
- assert_equal false, @cache.replace_pair(:a, 1, 2)
319
- assert_equal 2, @cache[:a]
320
- assert_equal true, @cache.replace_pair(:a, 2, 2)
321
- assert_equal 2, @cache[:a]
322
- assert_equal true, @cache.replace_pair(:a, 2, nil)
323
- assert_equal false, @cache.replace_pair(:a, 2, nil)
324
- assert_equal nil, @cache[:a]
325
- assert_equal true, @cache.key?(:a)
326
- assert_equal true, @cache.replace_pair(:a, nil, nil)
327
- assert_equal true, @cache.key?(:a)
328
- assert_equal true, @cache.replace_pair(:a, nil, 1)
329
- assert_equal 1, @cache[:a]
330
- end
331
- end
332
- end
333
-
334
- def test_replace_if_exists
335
- with_or_without_default_proc do
336
- assert_no_size_change do
337
- assert_equal nil, @cache.replace_if_exists(:a, 1)
338
- assert_equal false, @cache.key?(:a)
339
- end
340
-
341
- @cache[:a] = 1
342
- assert_no_size_change do
343
- assert_equal 1, @cache.replace_if_exists(:a, 2)
344
- assert_equal 2, @cache[:a]
345
- assert_equal 2, @cache.replace_if_exists(:a, nil)
346
- assert_equal nil, @cache[:a]
347
- assert_equal true, @cache.key?(:a)
348
- assert_equal nil, @cache.replace_if_exists(:a, 1)
349
- assert_equal 1, @cache[:a]
350
- end
351
- end
352
- end
353
-
354
- def test_get_and_set
355
- with_or_without_default_proc do
356
- assert_size_change 1 do
357
- assert_equal nil, @cache.get_and_set(:a, 1)
358
- assert_equal true, @cache.key?(:a)
359
- assert_equal 1, @cache[:a]
360
- assert_equal 1, @cache.get_and_set(:a, 2)
361
- assert_equal 2, @cache.get_and_set(:a, nil)
362
- assert_equal nil, @cache[:a]
363
- assert_equal true, @cache.key?(:a)
364
- assert_equal nil, @cache.get_and_set(:a, 1)
365
- assert_equal 1, @cache[:a]
366
- end
367
- end
368
- end
369
-
370
- def test_key
371
- with_or_without_default_proc do
372
- assert_equal nil, @cache.key(1)
373
- @cache[:a] = 1
374
- assert_equal :a, @cache.key(1)
375
- assert_equal nil, @cache.key(0)
376
- assert_equal :a, @cache.index(1) if RUBY_VERSION =~ /1\.8/
377
- end
378
- end
379
-
380
- def test_key?
381
- with_or_without_default_proc do
382
- assert_equal false, @cache.key?(:a)
383
- @cache[:a] = 1
384
- assert_equal true, @cache.key?(:a)
385
- end
386
- end
387
-
388
- def test_value?
389
- with_or_without_default_proc do
390
- assert_equal false, @cache.value?(1)
391
- @cache[:a] = 1
392
- assert_equal true, @cache.value?(1)
393
- end
394
- end
395
-
396
- def test_delete
397
- with_or_without_default_proc do |default_proc_set|
398
- assert_no_size_change do
399
- assert_equal nil, @cache.delete(:a)
400
- end
401
- @cache[:a] = 1
402
- assert_size_change -1 do
403
- assert_equal 1, @cache.delete(:a)
404
- end
405
- assert_no_size_change do
406
- assert_equal nil, @cache[:a] unless default_proc_set
407
-
408
- assert_equal false, @cache.key?(:a)
409
- assert_equal nil, @cache.delete(:a)
410
- end
411
- end
412
- end
413
-
414
- def test_delete_pair
415
- with_or_without_default_proc do
416
- assert_no_size_change do
417
- assert_equal false, @cache.delete_pair(:a, 2)
418
- assert_equal false, @cache.delete_pair(:a, nil)
419
- end
420
- @cache[:a] = 1
421
- assert_no_size_change do
422
- assert_equal false, @cache.delete_pair(:a, 2)
423
- end
424
- assert_size_change -1 do
425
- assert_equal 1, @cache[:a]
426
- assert_equal true, @cache.delete_pair(:a, 1)
427
- assert_equal false, @cache.delete_pair(:a, 1)
428
- assert_equal false, @cache.key?(:a)
429
- end
430
- end
431
- end
432
-
433
- def test_default_proc
434
- @cache = cache_with_default_proc(1)
435
- assert_no_size_change do
436
- assert_equal false, @cache.key?(:a)
437
- end
438
- assert_size_change 1 do
439
- assert_equal 1, @cache[:a]
440
- assert_equal true, @cache.key?(:a)
441
- end
442
- end
443
-
444
- def test_falsy_default_proc
445
- @cache = cache_with_default_proc(nil)
446
- assert_no_size_change do
447
- assert_equal false, @cache.key?(:a)
448
- end
449
- assert_size_change 1 do
450
- assert_equal nil, @cache[:a]
451
- assert_equal true, @cache.key?(:a)
452
- end
453
- end
454
-
455
- def test_fetch
456
- with_or_without_default_proc do |default_proc_set|
457
- assert_no_size_change do
458
- assert_equal 1, @cache.fetch(:a, 1)
459
- assert_equal(1, (@cache.fetch(:a) {1}))
460
- assert_equal false, @cache.key?(:a)
461
-
462
- assert_equal nil, @cache[:a] unless default_proc_set
463
- end
464
-
465
- @cache[:a] = 1
466
- assert_no_size_change do
467
- assert_equal(1, (@cache.fetch(:a) {flunk}))
468
- end
469
-
470
- assert_raises(ThreadSafe::Cache::KEY_ERROR) do
471
- @cache.fetch(:b)
472
- end
473
-
474
- assert_no_size_change do
475
- assert_equal 1, (@cache.fetch(:b, :c) {1}) # assert block supersedes default value argument
476
- assert_equal false, @cache.key?(:b)
477
- end
478
- end
479
- end
480
-
481
- def test_falsy_fetch
482
- with_or_without_default_proc do
483
- assert_equal false, @cache.key?(:a)
484
-
485
- assert_no_size_change do
486
- assert_equal(nil, @cache.fetch(:a, nil))
487
- assert_equal(false, @cache.fetch(:a, false))
488
- assert_equal(nil, (@cache.fetch(:a) {}))
489
- assert_equal(false, (@cache.fetch(:a) {false}))
490
- end
491
-
492
- @cache[:a] = nil
493
- assert_no_size_change do
494
- assert_equal true, @cache.key?(:a)
495
- assert_equal(nil, (@cache.fetch(:a) {flunk}))
496
- end
497
- end
498
- end
499
-
500
- def test_fetch_with_return
501
- with_or_without_default_proc do
502
- r = lambda do
503
- @cache.fetch(:a) { return 10 }
504
- end.call
505
-
506
- assert_no_size_change do
507
- assert_equal 10, r
508
- assert_equal false, @cache.key?(:a)
509
- end
510
- end
511
- end
512
-
513
- def test_fetch_or_store
514
- with_or_without_default_proc do |default_proc_set|
515
- assert_size_change 1 do
516
- assert_equal 1, @cache.fetch_or_store(:a, 1)
517
- assert_equal 1, @cache[:a]
518
- end
519
-
520
- @cache.delete(:a)
521
-
522
- assert_size_change 1 do
523
- assert_equal 1, (@cache.fetch_or_store(:a) {1})
524
- assert_equal 1, @cache[:a]
525
- end
526
-
527
- assert_no_size_change do
528
- assert_equal(1, (@cache.fetch_or_store(:a) {flunk}))
529
- end
530
-
531
- assert_raises(ThreadSafe::Cache::KEY_ERROR) do
532
- @cache.fetch_or_store(:b)
533
- end
534
-
535
- assert_size_change 1 do
536
- assert_equal 1, (@cache.fetch_or_store(:b, :c) {1}) # assert block supersedes default value argument
537
- assert_equal 1, @cache[:b]
538
- end
539
- end
540
- end
541
-
542
- def test_falsy_fetch_or_store
543
- with_or_without_default_proc do
544
- assert_equal false, @cache.key?(:a)
545
-
546
- assert_size_change 1 do
547
- assert_equal(nil, @cache.fetch_or_store(:a, nil))
548
- assert_equal nil, @cache[:a]
549
- assert_equal true, @cache.key?(:a)
550
- end
551
- @cache.delete(:a)
552
-
553
- assert_size_change 1 do
554
- assert_equal(false, @cache.fetch_or_store(:a, false))
555
- assert_equal false, @cache[:a]
556
- assert_equal true, @cache.key?(:a)
557
- end
558
- @cache.delete(:a)
559
-
560
- assert_size_change 1 do
561
- assert_equal(nil, (@cache.fetch_or_store(:a) {}))
562
- assert_equal nil, @cache[:a]
563
- assert_equal true, @cache.key?(:a)
564
- end
565
- @cache.delete(:a)
566
-
567
- assert_size_change 1 do
568
- assert_equal(false, (@cache.fetch_or_store(:a) {false}))
569
- assert_equal false, @cache[:a]
570
- assert_equal true, @cache.key?(:a)
571
- end
572
-
573
- @cache[:a] = nil
574
- assert_no_size_change do
575
- assert_equal(nil, (@cache.fetch_or_store(:a) {flunk}))
576
- end
577
- end
578
- end
579
-
580
- def test_fetch_or_store_with_return
581
- with_or_without_default_proc do
582
- r = lambda do
583
- @cache.fetch_or_store(:a) { return 10 }
584
- end.call
585
-
586
- assert_no_size_change do
587
- assert_equal 10, r
588
- assert_equal false, @cache.key?(:a)
589
- end
590
- end
591
- end
592
-
593
- def test_clear
594
- @cache[:a] = 1
595
- assert_size_change -1 do
596
- assert_equal @cache, @cache.clear
597
- assert_equal false, @cache.key?(:a)
598
- assert_equal nil, @cache[:a]
599
- end
600
- end
601
-
602
- def test_each_pair
603
- @cache.each_pair {|k, v| flunk}
604
- assert_equal(@cache, (@cache.each_pair {}))
605
- @cache[:a] = 1
606
-
607
- h = {}
608
- @cache.each_pair {|k, v| h[k] = v}
609
- assert_equal({:a => 1}, h)
610
-
611
- @cache[:b] = 2
612
- h = {}
613
- @cache.each_pair {|k, v| h[k] = v}
614
- assert_equal({:a => 1, :b => 2}, h)
615
- end
616
-
617
- def test_each_pair_iterator
618
- @cache[:a] = 1
619
- @cache[:b] = 2
620
- i = 0
621
- r = @cache.each_pair do |k, v|
622
- if i == 0
623
- i += 1
624
- next
625
- flunk
626
- elsif i == 1
627
- break :breaked
628
- end
629
- end
630
-
631
- assert_equal :breaked, r
632
- end
633
-
634
- def test_each_pair_allows_modification
635
- @cache[:a] = 1
636
- @cache[:b] = 1
637
- @cache[:c] = 1
638
-
639
- assert_size_change 1 do
640
- @cache.each_pair do |k, v|
641
- @cache[:z] = 1
642
- end
643
- end
644
- end
645
-
646
- def test_keys
647
- assert_equal [], @cache.keys
648
-
649
- @cache[1] = 1
650
- assert_equal [1], @cache.keys
651
-
652
- @cache[2] = 2
653
- assert_equal [1, 2], @cache.keys.sort
654
- end
655
-
656
- def test_values
657
- assert_equal [], @cache.values
658
-
659
- @cache[1] = 1
660
- assert_equal [1], @cache.values
661
-
662
- @cache[2] = 2
663
- assert_equal [1, 2], @cache.values.sort
664
- end
665
-
666
- def test_each_key
667
- assert_equal(@cache, (@cache.each_key {flunk}))
668
-
669
- @cache[1] = 1
670
- arr = []
671
- @cache.each_key {|k| arr << k}
672
- assert_equal [1], arr
673
-
674
- @cache[2] = 2
675
- arr = []
676
- @cache.each_key {|k| arr << k}
677
- assert_equal [1, 2], arr.sort
678
- end
679
-
680
- def test_each_value
681
- assert_equal(@cache, (@cache.each_value {flunk}))
682
-
683
- @cache[1] = 1
684
- arr = []
685
- @cache.each_value {|k| arr << k}
686
- assert_equal [1], arr
687
-
688
- @cache[2] = 2
689
- arr = []
690
- @cache.each_value {|k| arr << k}
691
- assert_equal [1, 2], arr.sort
692
- end
693
-
694
- def test_empty
695
- assert_equal true, @cache.empty?
696
- @cache[:a] = 1
697
- assert_equal false, @cache.empty?
698
- end
699
-
700
- def test_options_validation
701
- assert_valid_options(nil)
702
- assert_valid_options({})
703
- assert_valid_options(:foo => :bar)
704
- end
705
-
706
- def test_initial_capacity_options_validation
707
- assert_valid_option(:initial_capacity, nil)
708
- assert_valid_option(:initial_capacity, 1)
709
- assert_invalid_option(:initial_capacity, '')
710
- assert_invalid_option(:initial_capacity, 1.0)
711
- assert_invalid_option(:initial_capacity, -1)
712
- end
713
-
714
- def test_load_factor_options_validation
715
- assert_valid_option(:load_factor, nil)
716
- assert_valid_option(:load_factor, 0.01)
717
- assert_valid_option(:load_factor, 0.75)
718
- assert_valid_option(:load_factor, 1)
719
- assert_invalid_option(:load_factor, '')
720
- assert_invalid_option(:load_factor, 0)
721
- assert_invalid_option(:load_factor, 1.1)
722
- assert_invalid_option(:load_factor, 2)
723
- assert_invalid_option(:load_factor, -1)
724
- end
725
-
726
- def test_size
727
- assert_equal 0, @cache.size
728
- @cache[:a] = 1
729
- assert_equal 1, @cache.size
730
- @cache[:b] = 1
731
- assert_equal 2, @cache.size
732
- @cache.delete(:a)
733
- assert_equal 1, @cache.size
734
- @cache.delete(:b)
735
- assert_equal 0, @cache.size
736
- end
737
-
738
- def test_get_or_default
739
- with_or_without_default_proc do
740
- assert_equal 1, @cache.get_or_default(:a, 1)
741
- assert_equal nil, @cache.get_or_default(:a, nil)
742
- assert_equal false, @cache.get_or_default(:a, false)
743
- assert_equal false, @cache.key?(:a)
744
-
745
- @cache[:a] = 1
746
- assert_equal 1, @cache.get_or_default(:a, 2)
747
- end
748
- end
749
-
750
- def test_dup_clone
751
- [:dup, :clone].each do |meth|
752
- cache = cache_with_default_proc(:default_value)
753
- cache[:a] = 1
754
- dupped = cache.send(meth)
755
- assert_equal 1, dupped[:a]
756
- assert_equal 1, dupped.size
757
- assert_size_change 1, cache do
758
- assert_no_size_change dupped do
759
- cache[:b] = 1
760
- end
761
- end
762
- assert_equal false, dupped.key?(:b)
763
- assert_no_size_change cache do
764
- assert_size_change -1, dupped do
765
- dupped.delete(:a)
766
- end
767
- end
768
- assert_equal false, dupped.key?(:a)
769
- assert_equal true, cache.key?(:a)
770
- # test default proc
771
- assert_size_change 1, cache do
772
- assert_no_size_change dupped do
773
- assert_equal :default_value, cache[:c]
774
- assert_equal false, dupped.key?(:c)
775
- end
776
- end
777
- assert_no_size_change cache do
778
- assert_size_change 1, dupped do
779
- assert_equal :default_value, dupped[:d]
780
- assert_equal false, cache.key?(:d)
781
- end
782
- end
783
- end
784
- end
785
-
786
- def test_is_unfreezable
787
- assert_raises(NoMethodError) { @cache.freeze }
788
- end
789
-
790
- def test_marshal_dump_load
791
- new_cache = Marshal.load(Marshal.dump(@cache))
792
- assert_instance_of ThreadSafe::Cache, new_cache
793
- assert_equal 0, new_cache.size
794
- @cache[:a] = 1
795
- new_cache = Marshal.load(Marshal.dump(@cache))
796
- assert_equal 1, @cache[:a]
797
- assert_equal 1, new_cache.size
798
- end
799
-
800
- def test_marshal_dump_doesnt_work_with_default_proc
801
- assert_raises(TypeError) do
802
- Marshal.dump(ThreadSafe::Cache.new {})
803
- end
804
- end
805
-
806
- private
807
- def with_or_without_default_proc
808
- yield false
809
- @cache = ThreadSafe::Cache.new {|h, k| h[k] = :default_value}
810
- yield true
811
- end
812
-
813
- def cache_with_default_proc(default_value = 1)
814
- ThreadSafe::Cache.new {|cache, k| cache[k] = default_value}
815
- end
816
-
817
- def assert_valid_option(option_name, value)
818
- assert_valid_options(option_name => value)
819
- end
820
-
821
- def assert_valid_options(options)
822
- c = ThreadSafe::Cache.new(options)
823
- assert_instance_of ThreadSafe::Cache, c
824
- end
825
-
826
- def assert_invalid_option(option_name, value)
827
- assert_invalid_options(option_name => value)
828
- end
829
-
830
- def assert_invalid_options(options)
831
- assert_raises(ArgumentError) { ThreadSafe::Cache.new(options) }
832
- end
833
-
834
- def assert_size_change(change, cache = @cache)
835
- start = cache.size
836
- yield
837
- assert_equal change, cache.size - start
838
- end
839
-
840
- def assert_no_size_change(cache = @cache, &block)
841
- assert_size_change(0, cache, &block)
842
- end
843
-
844
- def assert_handles_return_lambda(method, key, *args)
845
- before_had_key = @cache.key?(key)
846
- before_had_value = before_had_key ? @cache[key] : nil
847
-
848
- returning_lambda = lambda do
849
- @cache.send(method, key, *args) { return :direct_return }
850
- end
851
-
852
- assert_no_size_change do
853
- assert_equal(:direct_return, returning_lambda.call)
854
- assert_equal before_had_key, @cache.key?(key)
855
- assert_equal before_had_value, @cache[key] if before_had_value
856
- end
857
- end
858
-
859
- class TestException < Exception; end
860
- def assert_handles_exception(method, key, *args)
861
- before_had_key = @cache.key?(key)
862
- before_had_value = before_had_key ? @cache[key] : nil
863
-
864
- assert_no_size_change do
865
- assert_raises(TestException) do
866
- @cache.send(method, key, *args) { raise TestException, '' }
867
- end
868
- assert_equal before_had_key, @cache.key?(key)
869
- assert_equal before_had_value, @cache[key] if before_had_value
870
- end
871
- end
872
-
873
- def assert_compute(key, expected_old_value, expected_result)
874
- result = @cache.compute(:a) do |old_value|
875
- assert_equal expected_old_value, old_value
876
- yield
877
- end
878
- assert_equal expected_result, result
879
- end
880
-
881
- def assert_merge_pair(key, value, expected_old_value, expected_result)
882
- result = @cache.merge_pair(key, value) do |old_value|
883
- assert_equal expected_old_value, old_value
884
- yield
885
- end
886
- assert_equal expected_result, result
887
- end
888
-
889
- def assert_collision_resistance(keys)
890
- keys.each {|k| @cache[k] = k.key}
891
- 10.times do |i|
892
- size = keys.size
893
- while i < size
894
- k = keys[i]
895
- assert(k.key == @cache.delete(k) && !@cache.key?(k) && (@cache[k] = k.key; @cache[k] == k.key))
896
- i += 10
897
- end
898
- end
899
- assert(keys.all? {|k| @cache[k] == k.key})
900
- end
901
- end