zache 0.13.2 → 0.15.0

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.
data/test/test_zache.rb CHANGED
@@ -1,296 +1,311 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # (The MIT License)
4
- #
5
- # Copyright (c) 2018-2024 Yegor Bugayenko
6
- #
7
- # Permission is hereby granted, free of charge, to any person obtaining a copy
8
- # of this software and associated documentation files (the 'Software'), to deal
9
- # in the Software without restriction, including without limitation the rights
10
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
- # copies of the Software, and to permit persons to whom the Software is
12
- # furnished to do so, subject to the following conditions:
13
- #
14
- # The above copyright notice and this permission notice shall be included in all
15
- # copies or substantial portions of the Software.
16
- #
17
- # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
- # SOFTWARE.
3
+ # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko
4
+ # SPDX-License-Identifier: MIT
24
5
 
6
+ require 'concurrent'
25
7
  require 'minitest/autorun'
8
+ require 'securerandom'
26
9
  require 'threads'
27
10
  require 'timeout'
28
- require 'concurrent'
29
11
  require_relative '../lib/zache'
12
+ require_relative 'test__helper'
30
13
 
31
14
  Thread.report_on_exception = true
32
15
 
33
16
  # Cache test.
34
17
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
35
- # Copyright:: Copyright (c) 2018-2024 Yegor Bugayenko
18
+ # Copyright:: Copyright (c) 2018-2025 Yegor Bugayenko
36
19
  # License:: MIT
37
20
  class ZacheTest < Minitest::Test
38
21
  def test_caches
39
- cache = Zache.new(sync: false)
40
- first = cache.get(:hey, lifetime: 5) { Random.rand }
41
- second = cache.get(:hey) { Random.rand }
42
- assert(first == second)
43
- assert_equal(1, cache.size)
22
+ z = Zache.new(sync: false)
23
+ first = z.get(:hey, lifetime: 5) { rand }
24
+ second = z.get(:hey) { rand }
25
+ assert_equal(first, second)
26
+ assert_equal(1, z.size)
44
27
  end
45
28
 
46
29
  def test_caches_and_expires
47
- cache = Zache.new
48
- first = cache.get(:hey, lifetime: 0.01) { Random.rand }
30
+ z = Zache.new
31
+ first = z.get(:hey, lifetime: 0.01) { rand }
49
32
  sleep 0.1
50
- second = cache.get(:hey) { Random.rand }
51
- assert(first != second)
33
+ second = z.get(:hey) { rand }
34
+ refute_equal(first, second)
52
35
  end
53
36
 
54
37
  def test_calculates_age
55
- cache = Zache.new
56
- cache.get(:hey) { Random.rand }
38
+ z = Zache.new
39
+ z.get(:hey) { rand }
57
40
  sleep 0.1
58
- assert(cache.mtime(:hey) < Time.now - 0.05)
41
+ assert_operator(z.mtime(:hey), :<, Time.now - 0.05)
59
42
  end
60
43
 
61
44
  def test_caches_in_threads
62
- cache = Zache.new
45
+ z = Zache.new
63
46
  Threads.new(10).assert(100) do
64
- cache.get(:hey, lifetime: 0.0001) { Random.rand }
47
+ z.get(:hey, lifetime: 0.0001) { rand }
65
48
  end
66
49
  end
67
50
 
68
51
  def test_key_exists
69
- cache = Zache.new
70
- cache.get(:hey) { Random.rand }
71
- exists_result = cache.exists?(:hey)
72
- not_exists_result = cache.exists?(:bye)
73
- assert(exists_result == true)
74
- assert(not_exists_result == false)
52
+ z = Zache.new
53
+ z.get(:hey) { rand }
54
+ exists_result = z.exists?(:hey)
55
+ not_exists_result = z.exists?(:bye)
56
+ assert(exists_result)
57
+ refute(not_exists_result)
75
58
  end
76
59
 
77
60
  def test_put_and_exists
78
- cache = Zache.new
79
- cache.put(:hey, 'hello', lifetime: 0.1)
61
+ z = Zache.new
62
+ z.put(:hey, 'hello', lifetime: 0.1)
80
63
  sleep 0.2
81
- assert(!cache.exists?(:hey))
64
+ refute(z.exists?(:hey))
82
65
  end
83
66
 
84
67
  def test_remove_key
85
- cache = Zache.new
86
- cache.get(:hey) { Random.rand }
87
- cache.get(:wey) { Random.rand }
88
- assert(cache.exists?(:hey) == true)
89
- assert(cache.exists?(:wey) == true)
90
- cache.remove(:hey)
91
- assert(cache.exists?(:hey) == false)
92
- assert(cache.exists?(:wey) == true)
68
+ z = Zache.new
69
+ z.get(:hey) { rand }
70
+ z.get(:wey) { rand }
71
+ assert(z.exists?(:hey))
72
+ assert(z.exists?(:wey))
73
+ z.remove(:hey)
74
+ refute(z.exists?(:hey))
75
+ assert(z.exists?(:wey))
93
76
  end
94
77
 
95
78
  def test_remove_by_block
96
- cache = Zache.new
97
- cache.get('first') { Random.rand }
98
- cache.get('second') { Random.rand }
99
- cache.remove_by { |k| k == 'first' }
100
- assert(cache.exists?('first') == false)
101
- assert(cache.exists?('second') == true)
79
+ z = Zache.new
80
+ z.get('first') { rand }
81
+ z.get('second') { rand }
82
+ z.remove_by { |k| k == 'first' }
83
+ refute(z.exists?('first'))
84
+ assert(z.exists?('second'))
102
85
  end
103
86
 
104
87
  def test_remove_key_with_sync_false
105
- cache = Zache.new(sync: false)
106
- cache.get(:hey) { Random.rand }
107
- cache.get(:wey) { Random.rand }
108
- assert(cache.exists?(:hey) == true)
109
- assert(cache.exists?(:wey) == true)
110
- cache.remove(:hey)
111
- assert(cache.exists?(:hey) == false)
112
- assert(cache.exists?(:wey) == true)
88
+ z = Zache.new(sync: false)
89
+ z.get(:hey) { rand }
90
+ z.get(:wey) { rand }
91
+ assert(z.exists?(:hey))
92
+ assert(z.exists?(:wey))
93
+ z.remove(:hey)
94
+ refute(z.exists?(:hey))
95
+ assert(z.exists?(:wey))
113
96
  end
114
97
 
115
98
  def test_clean_with_threads
116
- cache = Zache.new
99
+ z = Zache.new
117
100
  Threads.new(300).assert(3000) do
118
- cache.get(:hey) { Random.rand }
119
- cache.get(:bye, lifetime: 0.01) { Random.rand }
101
+ z.get(:hey) { rand }
102
+ z.get(:bye, lifetime: 0.01) { rand }
120
103
  sleep 0.1
121
- cache.clean
104
+ z.clean
122
105
  end
123
- assert(cache.exists?(:hey) == true)
124
- assert(cache.exists?(:bye) == false)
106
+ assert(z.exists?(:hey))
107
+ refute(z.exists?(:bye))
125
108
  end
126
109
 
127
110
  def test_clean
128
- cache = Zache.new
129
- cache.get(:hey) { Random.rand }
130
- cache.get(:bye, lifetime: 0.01) { Random.rand }
111
+ z = Zache.new
112
+ z.get(:hey) { rand }
113
+ z.get(:bye, lifetime: 0.01) { rand }
131
114
  sleep 0.1
132
- cache.clean
133
- assert(cache.exists?(:hey) == true)
134
- assert(cache.exists?(:bye) == false)
115
+ z.clean
116
+ assert(z.exists?(:hey))
117
+ refute(z.exists?(:bye))
135
118
  end
136
119
 
137
120
  def test_clean_size
138
- cache = Zache.new
139
- cache.get(:hey, lifetime: 0.01) { Random.rand }
121
+ z = Zache.new
122
+ z.get(:hey, lifetime: 0.01) { rand }
140
123
  sleep 0.1
141
- cache.clean
142
- assert(cache.empty?)
124
+ z.clean
125
+ assert_empty(z)
143
126
  end
144
127
 
145
128
  def test_clean_with_sync_false
146
- cache = Zache.new(sync: false)
147
- cache.get(:hey) { Random.rand }
148
- cache.get(:bye, lifetime: 0.01) { Random.rand }
129
+ z = Zache.new(sync: false)
130
+ z.get(:hey) { rand }
131
+ z.get(:bye, lifetime: 0.01) { rand }
149
132
  sleep 0.1
150
- cache.clean
151
- assert(cache.exists?(:hey) == true)
152
- assert(cache.exists?(:bye) == false)
133
+ z.clean
134
+ assert(z.exists?(:hey))
135
+ refute(z.exists?(:bye))
153
136
  end
154
137
 
155
138
  def test_remove_absent_key
156
- cache = Zache.new
157
- cache.remove(:hey)
139
+ z = Zache.new
140
+ z.remove(:hey)
158
141
  end
159
142
 
160
143
  def test_check_and_remove
161
- cache = Zache.new
162
- cache.get(:hey, lifetime: 0) { Random.rand }
163
- assert(!cache.exists?(:hey))
144
+ z = Zache.new
145
+ z.get(:hey, lifetime: 0) { rand }
146
+ refute(z.exists?(:hey))
164
147
  end
165
148
 
166
149
  def test_remove_all_with_threads
167
- cache = Zache.new
150
+ z = Zache.new
168
151
  Threads.new(10).assert(100) do |i|
169
- cache.get(:"hey#{i}") { Random.rand }
170
- assert(cache.exists?(:"hey#{i}") == true)
171
- cache.remove_all
152
+ z.get(:"hey#{i}") { rand }
153
+ assert(z.exists?(:"hey#{i}"))
154
+ z.remove_all
172
155
  end
173
156
  10.times do |i|
174
- assert(cache.exists?(:"hey#{i}") == false)
157
+ refute(z.exists?(:"hey#{i}"))
175
158
  end
176
159
  end
177
160
 
178
161
  def test_remove_all_with_sync
179
- cache = Zache.new
180
- cache.get(:hey) { Random.rand }
181
- cache.get(:bye) { Random.rand }
182
- cache.remove_all
183
- assert(cache.exists?(:hey) == false)
184
- assert(cache.exists?(:bye) == false)
162
+ z = Zache.new
163
+ z.get(:hey) { rand }
164
+ z.get(:bye) { rand }
165
+ z.remove_all
166
+ refute(z.exists?(:hey))
167
+ refute(z.exists?(:bye))
185
168
  end
186
169
 
187
170
  def test_remove_all_without_sync
188
- cache = Zache.new(sync: false)
189
- cache.get(:hey) { Random.rand }
190
- cache.get(:bye) { Random.rand }
191
- cache.remove_all
192
- assert(cache.exists?(:hey) == false)
193
- assert(cache.exists?(:bye) == false)
171
+ z = Zache.new(sync: false)
172
+ z.get(:hey) { rand }
173
+ z.get(:bye) { rand }
174
+ z.remove_all
175
+ refute(z.exists?(:hey))
176
+ refute(z.exists?(:bye))
194
177
  end
195
178
 
196
179
  def test_puts_something_in
197
- cache = Zache.new(sync: false)
198
- cache.get(:hey) { Random.rand }
199
- cache.put(:hey, 123)
200
- assert_equal(123, cache.get(:hey))
180
+ z = Zache.new(sync: false)
181
+ z.get(:hey) { rand }
182
+ z.put(:hey, 123)
183
+ assert_equal(123, z.get(:hey))
201
184
  end
202
185
 
203
186
  def test_sync_zache_is_not_reentrant
204
- cache = Zache.new
187
+ z = Zache.new
205
188
  assert_raises ThreadError do
206
- cache.get(:first) { cache.get(:second) { 1 } }
189
+ z.get(:first) { z.get(:first) { 1 } }
207
190
  end
208
191
  end
209
192
 
193
+ def test_sync_zache_is_reentrant_for_different_keys
194
+ z = Zache.new
195
+ z.get(:first) { z.get(:second) { 1 } }
196
+ end
197
+
210
198
  def test_calculates_only_once
211
- cache = Zache.new
199
+ z = Zache.new
212
200
  long = Thread.start do
213
- cache.get(:x) do
201
+ z.get(:x) do
214
202
  sleep 0.5
215
203
  'first'
216
204
  end
217
205
  end
218
206
  sleep 0.1
219
- assert(cache.locked?)
220
- cache.get(:x) { 'second' }
221
- assert(!cache.locked?)
207
+ assert(z.locked?(:x))
208
+ z.get(:x) { 'second' }
209
+ refute(z.locked?(:x))
222
210
  long.kill
223
211
  end
224
212
 
225
213
  def test_checks_locked_status_from_inside
226
- cache = Zache.new
227
- cache.get(:x) do
228
- assert(cache.locked?)
214
+ z = Zache.new
215
+ z.get(:x) do
216
+ assert(z.locked?(:x))
229
217
  'done'
230
218
  end
231
- assert(!cache.locked?)
219
+ refute(z.locked?(:x))
232
220
  end
233
221
 
234
222
  def test_returns_dirty_result
235
- cache = Zache.new(dirty: true)
236
- cache.get(:x, lifetime: 0) { 1 }
223
+ z = Zache.new(dirty: true)
224
+ z.get(:x, lifetime: 0) { 1 }
237
225
  long = Thread.start do
238
- cache.get(:x) do
226
+ z.get(:x) do
239
227
  sleep 1000
240
228
  2
241
229
  end
242
230
  end
243
231
  sleep 0.1
244
232
  Timeout.timeout(1) do
245
- assert(cache.exists?(:x))
246
- assert(cache.expired?(:x))
247
- assert_equal(1, cache.get(:x))
248
- assert_equal(1, cache.get(:x) { 2 })
233
+ assert(z.exists?(:x))
234
+ assert(z.expired?(:x))
235
+ assert_equal(1, z.get(:x))
236
+ assert_equal(1, z.get(:x) { 2 })
249
237
  end
250
238
  long.kill
251
239
  end
252
240
 
253
241
  def test_returns_dirty_result_when_not_locked
254
- cache = Zache.new(dirty: true)
255
- cache.get(:x, lifetime: 0) { 1 }
256
- assert(cache.exists?(:x))
257
- assert_equal(1, cache.get(:x))
258
- assert_equal(2, cache.get(:x) { 2 })
242
+ z = Zache.new(dirty: true)
243
+ z.get(:x, lifetime: 0) { 1 }
244
+ assert(z.exists?(:x))
245
+ assert_equal(1, z.get(:x))
246
+ assert_equal(2, z.get(:x) { 2 })
259
247
  end
260
248
 
261
249
  def test_fetches_multiple_keys_in_many_threads_in_dirty_mode
262
- cache = Zache.new(dirty: true)
250
+ z = Zache.new(dirty: true)
263
251
  set = Concurrent::Set.new
264
252
  threads = 50
265
253
  barrier = Concurrent::CyclicBarrier.new(threads)
266
254
  Threads.new(threads).assert(threads * 2) do |i|
267
255
  barrier.wait if i < threads
268
- set << cache.get(i, lifetime: 0.001) { i }
256
+ set << z.get(i, lifetime: 0.001) { i }
269
257
  end
270
258
  assert_equal(threads, set.size)
271
259
  end
272
260
 
273
261
  def test_fetches_multiple_keys_in_many_threads
274
- cache = Zache.new
262
+ z = Zache.new
275
263
  set = Concurrent::Set.new
276
264
  threads = 50
277
265
  barrier = Concurrent::CyclicBarrier.new(threads)
278
266
  Threads.new(threads).assert(threads * 2) do |i|
279
267
  barrier.wait if i < threads
280
- set << cache.get(i) { i }
268
+ set << z.get(i) { i }
281
269
  end
282
270
  assert_equal(threads, set.size)
283
271
  end
284
272
 
285
273
  def test_fake_class_works
286
- cache = Zache::Fake.new
287
- assert_equal(1, cache.get(:x) { 1 })
274
+ z = Zache::Fake.new
275
+ assert_equal(1, z.get(:x) { 1 })
288
276
  end
289
277
 
290
278
  def test_rethrows
291
- cache = Zache.new
279
+ z = Zache.new
292
280
  assert_raises RuntimeError do
293
- cache.get(:hey) { raise 'intentional' }
281
+ z.get(:hey) { raise 'intentional' }
282
+ end
283
+ end
284
+
285
+ def test_returns_placeholder_in_eager_mode
286
+ z = Zache.new
287
+ a = z.get(:me, placeholder: 42, eager: true) do
288
+ sleep 0.1
289
+ 43
294
290
  end
291
+ assert_equal(42, a)
292
+ sleep 0.2
293
+ b = z.get(:me)
294
+ assert_equal(43, b)
295
+ end
296
+
297
+ def test_returns_placeholder_and_releases_lock
298
+ z = Zache.new
299
+ z.get(:slow, placeholder: 42, eager: true) do
300
+ sleep 9999
301
+ end
302
+ sleep 0.1
303
+ assert_equal(555, z.get(:fast) { 555 })
304
+ end
305
+
306
+ private
307
+
308
+ def rand
309
+ SecureRandom.uuid
295
310
  end
296
311
  end
data/zache.gemspec CHANGED
@@ -1,39 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # (The MIT License)
4
- #
5
- # Copyright (c) 2018-2024 Yegor Bugayenko
6
- #
7
- # Permission is hereby granted, free of charge, to any person obtaining a copy
8
- # of this software and associated documentation files (the 'Software'), to deal
9
- # in the Software without restriction, including without limitation the rights
10
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
- # copies of the Software, and to permit persons to whom the Software is
12
- # furnished to do so, subject to the following conditions:
13
- #
14
- # The above copyright notice and this permission notice shall be included in all
15
- # copies or substantial portions of the Software.
16
- #
17
- # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
- # SOFTWARE.
3
+ # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko
4
+ # SPDX-License-Identifier: MIT
24
5
 
25
6
  require 'English'
26
7
  Gem::Specification.new do |s|
27
8
  s.required_rubygems_version = Gem::Requirement.new('>= 0') if s.respond_to? :required_rubygems_version=
28
- s.required_ruby_version = '>=2.5'
9
+ s.required_ruby_version = '>= 2.5'
29
10
  s.name = 'zache'
30
- s.version = '0.13.2'
11
+ s.version = '0.15.0' # Version should be updated before release
31
12
  s.license = 'MIT'
32
13
  s.summary = 'In-memory Cache'
33
14
  s.description = 'Zero-footprint in-memory thread-safe cache'
34
15
  s.authors = ['Yegor Bugayenko']
35
16
  s.email = 'yegor256@gmail.com'
36
- s.homepage = 'http://github.com/yegor256/zache'
17
+ s.homepage = 'https://github.com/yegor256/zache'
37
18
  s.files = `git ls-files`.split($RS)
38
19
  s.rdoc_options = ['--charset=UTF-8']
39
20
  s.extra_rdoc_files = ['README.md']
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zache
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.2
4
+ version: 0.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-07-18 00:00:00.000000000 Z
10
+ date: 2025-04-15 00:00:00.000000000 Z
12
11
  dependencies: []
13
12
  description: Zero-footprint in-memory thread-safe cache
14
13
  email: yegor256@gmail.com
@@ -25,6 +24,7 @@ files:
25
24
  - ".github/workflows/markdown-lint.yml"
26
25
  - ".github/workflows/pdd.yml"
27
26
  - ".github/workflows/rake.yml"
27
+ - ".github/workflows/reuse.yml"
28
28
  - ".github/workflows/xcop.yml"
29
29
  - ".github/workflows/yamllint.yml"
30
30
  - ".gitignore"
@@ -34,7 +34,9 @@ files:
34
34
  - Gemfile
35
35
  - Gemfile.lock
36
36
  - LICENSE.txt
37
+ - LICENSES/MIT.txt
37
38
  - README.md
39
+ - REUSE.toml
38
40
  - Rakefile
39
41
  - lib/zache.rb
40
42
  - logo.svg
@@ -42,12 +44,11 @@ files:
42
44
  - test/test__helper.rb
43
45
  - test/test_zache.rb
44
46
  - zache.gemspec
45
- homepage: http://github.com/yegor256/zache
47
+ homepage: https://github.com/yegor256/zache
46
48
  licenses:
47
49
  - MIT
48
50
  metadata:
49
51
  rubygems_mfa_required: 'true'
50
- post_install_message:
51
52
  rdoc_options:
52
53
  - "--charset=UTF-8"
53
54
  require_paths:
@@ -63,8 +64,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
63
64
  - !ruby/object:Gem::Version
64
65
  version: '0'
65
66
  requirements: []
66
- rubygems_version: 3.4.10
67
- signing_key:
67
+ rubygems_version: 3.6.2
68
68
  specification_version: 4
69
69
  summary: In-memory Cache
70
70
  test_files: []