zache 0.13.2 → 0.14.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.
- checksums.yaml +4 -4
- data/.0pdd.yml +2 -21
- data/.github/workflows/actionlint.yml +5 -23
- data/.github/workflows/codecov.yml +11 -26
- data/.github/workflows/copyrights.yml +6 -23
- data/.github/workflows/license.yml +5 -22
- data/.github/workflows/markdown-lint.yml +5 -22
- data/.github/workflows/pdd.yml +6 -23
- data/.github/workflows/rake.yml +8 -26
- data/.github/workflows/reuse.yml +19 -0
- data/.github/workflows/xcop.yml +5 -22
- data/.github/workflows/yamllint.yml +5 -22
- data/.gitignore +6 -2
- data/.rubocop.yml +20 -28
- data/.rultor.yml +5 -22
- data/Gemfile +16 -27
- data/Gemfile.lock +78 -40
- data/LICENSE.txt +1 -1
- data/LICENSES/MIT.txt +21 -0
- data/README.md +45 -19
- data/REUSE.toml +31 -0
- data/Rakefile +19 -34
- data/lib/zache.rb +118 -33
- data/logo.svg +1 -1
- data/test/test__helper.rb +24 -21
- data/test/test_zache.rb +85 -84
- data/zache.gemspec +5 -24
- metadata +7 -7
data/lib/zache.rb
CHANGED
@@ -1,26 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# (
|
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
|
# It is a very simple thread-safe in-memory cache with an ability to expire
|
26
7
|
# keys automatically, when their lifetime is over. Use it like this:
|
@@ -34,35 +15,60 @@
|
|
34
15
|
# {README}[https://github.com/yegor256/zache/blob/master/README.md] file.
|
35
16
|
#
|
36
17
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
37
|
-
# Copyright:: Copyright (c) 2018-
|
18
|
+
# Copyright:: Copyright (c) 2018-2025 Yegor Bugayenko
|
38
19
|
# License:: MIT
|
39
20
|
class Zache
|
40
21
|
# Fake implementation that doesn't cache anything, but behaves like it
|
41
22
|
# does. It implements all methods of the original class, but doesn't do
|
42
23
|
# any caching. This is very useful for testing.
|
43
24
|
class Fake
|
25
|
+
# Returns a fixed size of 1.
|
26
|
+
# @return [Integer] Always returns 1
|
44
27
|
def size
|
45
28
|
1
|
46
29
|
end
|
47
30
|
|
31
|
+
# Always returns the result of the block, never caches.
|
32
|
+
# @param [Object] key Ignored
|
33
|
+
# @param [Hash] opts Ignored
|
34
|
+
# @yield Block that provides the value
|
35
|
+
# @return [Object] The result of the block
|
48
36
|
def get(*)
|
49
37
|
yield
|
50
38
|
end
|
51
39
|
|
40
|
+
# Always returns true regardless of the key.
|
41
|
+
# @param [Object] key Ignored
|
42
|
+
# @param [Hash] opts Ignored
|
43
|
+
# @return [Boolean] Always returns true
|
52
44
|
def exists?(*)
|
53
45
|
true
|
54
46
|
end
|
55
47
|
|
48
|
+
# Always returns false.
|
49
|
+
# @return [Boolean] Always returns false
|
56
50
|
def locked?
|
57
51
|
false
|
58
52
|
end
|
59
53
|
|
54
|
+
# No-op method that ignores the input.
|
55
|
+
# @param [Object] key Ignored
|
56
|
+
# @param [Object] value Ignored
|
57
|
+
# @param [Hash] opts Ignored
|
58
|
+
# @return [nil] Always returns nil
|
60
59
|
def put(*); end
|
61
60
|
|
61
|
+
# No-op method that ignores the key.
|
62
|
+
# @param [Object] _key Ignored
|
63
|
+
# @return [nil] Always returns nil
|
62
64
|
def remove(_key); end
|
63
65
|
|
66
|
+
# No-op method.
|
67
|
+
# @return [nil] Always returns nil
|
64
68
|
def remove_all; end
|
65
69
|
|
70
|
+
# No-op method.
|
71
|
+
# @return [nil] Always returns nil
|
66
72
|
def clean; end
|
67
73
|
end
|
68
74
|
|
@@ -73,7 +79,11 @@ class Zache
|
|
73
79
|
# unless you really know what you are doing.
|
74
80
|
#
|
75
81
|
# If the <tt>dirty</tt> argument is set to <tt>true</tt>, a previously
|
76
|
-
# calculated result will be returned if it exists
|
82
|
+
# calculated result will be returned if it exists, even if it is already expired.
|
83
|
+
#
|
84
|
+
# @param sync [Boolean] Whether the hash is thread-safe
|
85
|
+
# @param dirty [Boolean] Whether to return expired values
|
86
|
+
# @return [Zache] A new instance of the cache
|
77
87
|
def initialize(sync: true, dirty: false)
|
78
88
|
@hash = {}
|
79
89
|
@sync = sync
|
@@ -82,6 +92,8 @@ class Zache
|
|
82
92
|
end
|
83
93
|
|
84
94
|
# Total number of keys currently in cache.
|
95
|
+
#
|
96
|
+
# @return [Integer] Number of keys in the cache
|
85
97
|
def size
|
86
98
|
@hash.size
|
87
99
|
end
|
@@ -90,17 +102,39 @@ class Zache
|
|
90
102
|
#
|
91
103
|
# If the value is not
|
92
104
|
# found in the cache, it will be calculated via the provided block. If
|
93
|
-
# the block is not given, an exception will be raised
|
94
|
-
# is set to <tt>true</tt>). The lifetime
|
105
|
+
# the block is not given and the key doesn't exist or is expired, an exception will be raised
|
106
|
+
# (unless <tt>dirty</tt> is set to <tt>true</tt>). The lifetime
|
95
107
|
# must be in seconds. The default lifetime is huge, which means that the
|
96
108
|
# key will never be expired.
|
97
109
|
#
|
98
110
|
# If the <tt>dirty</tt> argument is set to <tt>true</tt>, a previously
|
99
|
-
# calculated result will be returned if it exists
|
100
|
-
|
111
|
+
# calculated result will be returned if it exists, even if it is already expired.
|
112
|
+
#
|
113
|
+
# @param key [Object] The key to retrieve from the cache
|
114
|
+
# @param lifetime [Integer] Time in seconds until the key expires
|
115
|
+
# @param dirty [Boolean] Whether to return expired values
|
116
|
+
# @param eager [Boolean] Whether to return placeholder while working?
|
117
|
+
# @param placeholder [Object] The placeholder to return in eager mode
|
118
|
+
# @yield Block to calculate the value if not in cache
|
119
|
+
# @yieldreturn [Object] The value to cache
|
120
|
+
# @return [Object] The cached value
|
121
|
+
def get(key, lifetime: 2**32, dirty: false, placeholder: nil, eager: false, &block)
|
101
122
|
if block_given?
|
102
123
|
return @hash[key][:value] if (dirty || @dirty) && locked? && expired?(key) && @hash.key?(key)
|
103
|
-
|
124
|
+
if eager
|
125
|
+
return @hash[key][:value] if @hash.key?(key)
|
126
|
+
put(key, placeholder, lifetime: 0)
|
127
|
+
Thread.new do
|
128
|
+
synchronized do
|
129
|
+
calc(key, lifetime, &block)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
placeholder
|
133
|
+
else
|
134
|
+
synchronized do
|
135
|
+
calc(key, lifetime, &block)
|
136
|
+
end
|
137
|
+
end
|
104
138
|
else
|
105
139
|
rec = @hash[key]
|
106
140
|
if expired?(key)
|
@@ -114,8 +148,12 @@ class Zache
|
|
114
148
|
end
|
115
149
|
|
116
150
|
# Checks whether the value exists in the cache by the provided key. Returns
|
117
|
-
# TRUE if the value is here. If the key is already expired in the
|
151
|
+
# TRUE if the value is here. If the key is already expired in the cache,
|
118
152
|
# it will be removed by this method and the result will be FALSE.
|
153
|
+
#
|
154
|
+
# @param key [Object] The key to check in the cache
|
155
|
+
# @param dirty [Boolean] Whether to consider expired values as existing
|
156
|
+
# @return [Boolean] True if the key exists and is not expired (unless dirty is true)
|
119
157
|
def exists?(key, dirty: false)
|
120
158
|
rec = @hash[key]
|
121
159
|
if expired?(key) && !dirty && !@dirty
|
@@ -127,6 +165,9 @@ class Zache
|
|
127
165
|
|
128
166
|
# Checks whether the key exists in the cache and is expired. If the
|
129
167
|
# key is absent FALSE is returned.
|
168
|
+
#
|
169
|
+
# @param key [Object] The key to check in the cache
|
170
|
+
# @return [Boolean] True if the key exists and is expired
|
130
171
|
def expired?(key)
|
131
172
|
rec = @hash[key]
|
132
173
|
!rec.nil? && rec[:start] < Time.now - rec[:lifetime]
|
@@ -134,17 +175,27 @@ class Zache
|
|
134
175
|
|
135
176
|
# Returns the modification time of the key, if it exists.
|
136
177
|
# If not, current time is returned.
|
178
|
+
#
|
179
|
+
# @param key [Object] The key to get the modification time for
|
180
|
+
# @return [Time] The modification time of the key or current time if key doesn't exist
|
137
181
|
def mtime(key)
|
138
182
|
rec = @hash[key]
|
139
183
|
rec.nil? ? Time.now : rec[:start]
|
140
184
|
end
|
141
185
|
|
142
186
|
# Is cache currently locked doing something?
|
187
|
+
#
|
188
|
+
# @return [Boolean] True if the cache is locked
|
143
189
|
def locked?
|
144
190
|
@mutex.locked?
|
145
191
|
end
|
146
192
|
|
147
193
|
# Put a value into the cache.
|
194
|
+
#
|
195
|
+
# @param key [Object] The key to store the value under
|
196
|
+
# @param value [Object] The value to store in the cache
|
197
|
+
# @param lifetime [Integer] Time in seconds until the key expires (default: never expires)
|
198
|
+
# @return [Object] The value stored
|
148
199
|
def put(key, value, lifetime: 2**32)
|
149
200
|
synchronized do
|
150
201
|
@hash[key] = {
|
@@ -157,35 +208,65 @@ class Zache
|
|
157
208
|
|
158
209
|
# Removes the value from the cache, by the provided key. If the key is absent
|
159
210
|
# and the block is provided, the block will be called.
|
211
|
+
#
|
212
|
+
# @param key [Object] The key to remove from the cache
|
213
|
+
# @yield Block to call if the key is not found
|
214
|
+
# @return [Object] The removed value or the result of the block
|
160
215
|
def remove(key)
|
161
216
|
synchronized { @hash.delete(key) { yield if block_given? } }
|
162
217
|
end
|
163
218
|
|
164
219
|
# Remove all keys from the cache.
|
220
|
+
#
|
221
|
+
# @return [Hash] Empty hash
|
165
222
|
def remove_all
|
166
223
|
synchronized { @hash = {} }
|
167
224
|
end
|
168
225
|
|
169
226
|
# Remove all keys that match the block.
|
227
|
+
#
|
228
|
+
# @yield [key] Block that should return true for keys to be removed
|
229
|
+
# @yieldparam key [Object] The cache key to evaluate
|
230
|
+
# @return [Integer] Number of keys removed
|
170
231
|
def remove_by
|
171
232
|
synchronized do
|
233
|
+
count = 0
|
172
234
|
@hash.each_key do |k|
|
173
|
-
|
235
|
+
if yield(k)
|
236
|
+
@hash.delete(k)
|
237
|
+
count += 1
|
238
|
+
end
|
174
239
|
end
|
240
|
+
count
|
175
241
|
end
|
176
242
|
end
|
177
243
|
|
178
|
-
# Remove keys that are expired.
|
244
|
+
# Remove keys that are expired. This cleans up the cache by removing all keys
|
245
|
+
# where the lifetime has been exceeded.
|
246
|
+
#
|
247
|
+
# @return [Integer] Number of keys removed
|
179
248
|
def clean
|
180
|
-
synchronized
|
249
|
+
synchronized do
|
250
|
+
size_before = @hash.size
|
251
|
+
@hash.delete_if { |key, _value| expired?(key) }
|
252
|
+
size_before - @hash.size
|
253
|
+
end
|
181
254
|
end
|
182
255
|
|
256
|
+
# Returns TRUE if the cache is empty, FALSE otherwise.
|
257
|
+
#
|
258
|
+
# @return [Boolean] True if the cache is empty
|
183
259
|
def empty?
|
184
260
|
@hash.empty?
|
185
261
|
end
|
186
262
|
|
187
263
|
private
|
188
264
|
|
265
|
+
# Calculates or retrieves a cached value for the given key.
|
266
|
+
# @param key [Object] The key to store the value under
|
267
|
+
# @param lifetime [Integer] Time in seconds until the key expires
|
268
|
+
# @yield Block that provides the value if not cached
|
269
|
+
# @return [Object] The cached or newly calculated value
|
189
270
|
def calc(key, lifetime)
|
190
271
|
rec = @hash[key]
|
191
272
|
rec = nil if expired?(key)
|
@@ -199,11 +280,15 @@ class Zache
|
|
199
280
|
@hash[key][:value]
|
200
281
|
end
|
201
282
|
|
283
|
+
# Executes a block within a synchronized context if sync is enabled.
|
284
|
+
# @param block [Proc] The block to execute
|
285
|
+
# @yield The block to execute in a synchronized context
|
286
|
+
# @return [Object] The result of the block
|
202
287
|
def synchronized(&block)
|
203
288
|
if @sync
|
204
289
|
@mutex.synchronize(&block)
|
205
290
|
else
|
206
|
-
|
291
|
+
yield
|
207
292
|
end
|
208
293
|
end
|
209
294
|
end
|
data/logo.svg
CHANGED
data/test/test__helper.rb
CHANGED
@@ -1,28 +1,31 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# Copyright (c) 2018-
|
4
|
-
#
|
5
|
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
-
# of this software and associated documentation files (the 'Software'), to deal
|
7
|
-
# in the Software without restriction, including without limitation the rights
|
8
|
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
-
# copies of the Software, and to permit persons to whom the Software is
|
10
|
-
# furnished to do so, subject to the following conditions:
|
11
|
-
#
|
12
|
-
# The above copyright notice and this permission notice shall be included in all
|
13
|
-
# copies or substantial portions of the Software.
|
14
|
-
#
|
15
|
-
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
|
18
|
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
-
# SOFTWARE.
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko
|
4
|
+
# SPDX-License-Identifier: MIT
|
22
5
|
|
23
6
|
$stdout.sync = true
|
24
7
|
|
25
8
|
require 'simplecov'
|
26
|
-
|
9
|
+
require 'simplecov-cobertura'
|
10
|
+
unless SimpleCov.running || ENV['PICKS']
|
11
|
+
SimpleCov.command_name('test')
|
12
|
+
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new(
|
13
|
+
[
|
14
|
+
SimpleCov::Formatter::HTMLFormatter,
|
15
|
+
SimpleCov::Formatter::CoberturaFormatter
|
16
|
+
]
|
17
|
+
)
|
18
|
+
SimpleCov.minimum_coverage 90
|
19
|
+
SimpleCov.minimum_coverage_by_file 90
|
20
|
+
SimpleCov.start do
|
21
|
+
add_filter 'test/'
|
22
|
+
add_filter 'vendor/'
|
23
|
+
add_filter 'target/'
|
24
|
+
track_files 'lib/**/*.rb'
|
25
|
+
track_files '*.rb'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
27
29
|
require 'minitest/autorun'
|
28
|
-
|
30
|
+
require 'minitest/reporters'
|
31
|
+
Minitest::Reporters.use! [Minitest::Reporters::SpecReporter.new]
|
data/test/test_zache.rb
CHANGED
@@ -1,155 +1,138 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# (
|
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-
|
18
|
+
# Copyright:: Copyright (c) 2018-2025 Yegor Bugayenko
|
36
19
|
# License:: MIT
|
37
20
|
class ZacheTest < Minitest::Test
|
38
21
|
def test_caches
|
39
22
|
cache = Zache.new(sync: false)
|
40
|
-
first = cache.get(:hey, lifetime: 5) {
|
41
|
-
second = cache.get(:hey) {
|
42
|
-
|
23
|
+
first = cache.get(:hey, lifetime: 5) { rand }
|
24
|
+
second = cache.get(:hey) { rand }
|
25
|
+
assert_equal(first, second)
|
43
26
|
assert_equal(1, cache.size)
|
44
27
|
end
|
45
28
|
|
46
29
|
def test_caches_and_expires
|
47
30
|
cache = Zache.new
|
48
|
-
first = cache.get(:hey, lifetime: 0.01) {
|
31
|
+
first = cache.get(:hey, lifetime: 0.01) { rand }
|
49
32
|
sleep 0.1
|
50
|
-
second = cache.get(:hey) {
|
51
|
-
|
33
|
+
second = cache.get(:hey) { rand }
|
34
|
+
refute_equal(first, second)
|
52
35
|
end
|
53
36
|
|
54
37
|
def test_calculates_age
|
55
38
|
cache = Zache.new
|
56
|
-
cache.get(:hey) {
|
39
|
+
cache.get(:hey) { rand }
|
57
40
|
sleep 0.1
|
58
|
-
|
41
|
+
assert_operator(cache.mtime(:hey), :<, Time.now - 0.05)
|
59
42
|
end
|
60
43
|
|
61
44
|
def test_caches_in_threads
|
62
45
|
cache = Zache.new
|
63
46
|
Threads.new(10).assert(100) do
|
64
|
-
cache.get(:hey, lifetime: 0.0001) {
|
47
|
+
cache.get(:hey, lifetime: 0.0001) { rand }
|
65
48
|
end
|
66
49
|
end
|
67
50
|
|
68
51
|
def test_key_exists
|
69
52
|
cache = Zache.new
|
70
|
-
cache.get(:hey) {
|
53
|
+
cache.get(:hey) { rand }
|
71
54
|
exists_result = cache.exists?(:hey)
|
72
55
|
not_exists_result = cache.exists?(:bye)
|
73
|
-
assert(exists_result
|
74
|
-
|
56
|
+
assert(exists_result)
|
57
|
+
refute(not_exists_result)
|
75
58
|
end
|
76
59
|
|
77
60
|
def test_put_and_exists
|
78
61
|
cache = Zache.new
|
79
62
|
cache.put(:hey, 'hello', lifetime: 0.1)
|
80
63
|
sleep 0.2
|
81
|
-
|
64
|
+
refute(cache.exists?(:hey))
|
82
65
|
end
|
83
66
|
|
84
67
|
def test_remove_key
|
85
68
|
cache = Zache.new
|
86
|
-
cache.get(:hey) {
|
87
|
-
cache.get(:wey) {
|
88
|
-
assert(cache.exists?(:hey)
|
89
|
-
assert(cache.exists?(:wey)
|
69
|
+
cache.get(:hey) { rand }
|
70
|
+
cache.get(:wey) { rand }
|
71
|
+
assert(cache.exists?(:hey))
|
72
|
+
assert(cache.exists?(:wey))
|
90
73
|
cache.remove(:hey)
|
91
|
-
|
92
|
-
assert(cache.exists?(:wey)
|
74
|
+
refute(cache.exists?(:hey))
|
75
|
+
assert(cache.exists?(:wey))
|
93
76
|
end
|
94
77
|
|
95
78
|
def test_remove_by_block
|
96
79
|
cache = Zache.new
|
97
|
-
cache.get('first') {
|
98
|
-
cache.get('second') {
|
80
|
+
cache.get('first') { rand }
|
81
|
+
cache.get('second') { rand }
|
99
82
|
cache.remove_by { |k| k == 'first' }
|
100
|
-
|
101
|
-
assert(cache.exists?('second')
|
83
|
+
refute(cache.exists?('first'))
|
84
|
+
assert(cache.exists?('second'))
|
102
85
|
end
|
103
86
|
|
104
87
|
def test_remove_key_with_sync_false
|
105
88
|
cache = Zache.new(sync: false)
|
106
|
-
cache.get(:hey) {
|
107
|
-
cache.get(:wey) {
|
108
|
-
assert(cache.exists?(:hey)
|
109
|
-
assert(cache.exists?(:wey)
|
89
|
+
cache.get(:hey) { rand }
|
90
|
+
cache.get(:wey) { rand }
|
91
|
+
assert(cache.exists?(:hey))
|
92
|
+
assert(cache.exists?(:wey))
|
110
93
|
cache.remove(:hey)
|
111
|
-
|
112
|
-
assert(cache.exists?(:wey)
|
94
|
+
refute(cache.exists?(:hey))
|
95
|
+
assert(cache.exists?(:wey))
|
113
96
|
end
|
114
97
|
|
115
98
|
def test_clean_with_threads
|
116
99
|
cache = Zache.new
|
117
100
|
Threads.new(300).assert(3000) do
|
118
|
-
cache.get(:hey) {
|
119
|
-
cache.get(:bye, lifetime: 0.01) {
|
101
|
+
cache.get(:hey) { rand }
|
102
|
+
cache.get(:bye, lifetime: 0.01) { rand }
|
120
103
|
sleep 0.1
|
121
104
|
cache.clean
|
122
105
|
end
|
123
|
-
assert(cache.exists?(:hey)
|
124
|
-
|
106
|
+
assert(cache.exists?(:hey))
|
107
|
+
refute(cache.exists?(:bye))
|
125
108
|
end
|
126
109
|
|
127
110
|
def test_clean
|
128
111
|
cache = Zache.new
|
129
|
-
cache.get(:hey) {
|
130
|
-
cache.get(:bye, lifetime: 0.01) {
|
112
|
+
cache.get(:hey) { rand }
|
113
|
+
cache.get(:bye, lifetime: 0.01) { rand }
|
131
114
|
sleep 0.1
|
132
115
|
cache.clean
|
133
|
-
assert(cache.exists?(:hey)
|
134
|
-
|
116
|
+
assert(cache.exists?(:hey))
|
117
|
+
refute(cache.exists?(:bye))
|
135
118
|
end
|
136
119
|
|
137
120
|
def test_clean_size
|
138
121
|
cache = Zache.new
|
139
|
-
cache.get(:hey, lifetime: 0.01) {
|
122
|
+
cache.get(:hey, lifetime: 0.01) { rand }
|
140
123
|
sleep 0.1
|
141
124
|
cache.clean
|
142
|
-
|
125
|
+
assert_empty(cache)
|
143
126
|
end
|
144
127
|
|
145
128
|
def test_clean_with_sync_false
|
146
129
|
cache = Zache.new(sync: false)
|
147
|
-
cache.get(:hey) {
|
148
|
-
cache.get(:bye, lifetime: 0.01) {
|
130
|
+
cache.get(:hey) { rand }
|
131
|
+
cache.get(:bye, lifetime: 0.01) { rand }
|
149
132
|
sleep 0.1
|
150
133
|
cache.clean
|
151
|
-
assert(cache.exists?(:hey)
|
152
|
-
|
134
|
+
assert(cache.exists?(:hey))
|
135
|
+
refute(cache.exists?(:bye))
|
153
136
|
end
|
154
137
|
|
155
138
|
def test_remove_absent_key
|
@@ -159,43 +142,43 @@ class ZacheTest < Minitest::Test
|
|
159
142
|
|
160
143
|
def test_check_and_remove
|
161
144
|
cache = Zache.new
|
162
|
-
cache.get(:hey, lifetime: 0) {
|
163
|
-
|
145
|
+
cache.get(:hey, lifetime: 0) { rand }
|
146
|
+
refute(cache.exists?(:hey))
|
164
147
|
end
|
165
148
|
|
166
149
|
def test_remove_all_with_threads
|
167
150
|
cache = Zache.new
|
168
151
|
Threads.new(10).assert(100) do |i|
|
169
|
-
cache.get(:"hey#{i}") {
|
170
|
-
assert(cache.exists?(:"hey#{i}")
|
152
|
+
cache.get(:"hey#{i}") { rand }
|
153
|
+
assert(cache.exists?(:"hey#{i}"))
|
171
154
|
cache.remove_all
|
172
155
|
end
|
173
156
|
10.times do |i|
|
174
|
-
|
157
|
+
refute(cache.exists?(:"hey#{i}"))
|
175
158
|
end
|
176
159
|
end
|
177
160
|
|
178
161
|
def test_remove_all_with_sync
|
179
162
|
cache = Zache.new
|
180
|
-
cache.get(:hey) {
|
181
|
-
cache.get(:bye) {
|
163
|
+
cache.get(:hey) { rand }
|
164
|
+
cache.get(:bye) { rand }
|
182
165
|
cache.remove_all
|
183
|
-
|
184
|
-
|
166
|
+
refute(cache.exists?(:hey))
|
167
|
+
refute(cache.exists?(:bye))
|
185
168
|
end
|
186
169
|
|
187
170
|
def test_remove_all_without_sync
|
188
171
|
cache = Zache.new(sync: false)
|
189
|
-
cache.get(:hey) {
|
190
|
-
cache.get(:bye) {
|
172
|
+
cache.get(:hey) { rand }
|
173
|
+
cache.get(:bye) { rand }
|
191
174
|
cache.remove_all
|
192
|
-
|
193
|
-
|
175
|
+
refute(cache.exists?(:hey))
|
176
|
+
refute(cache.exists?(:bye))
|
194
177
|
end
|
195
178
|
|
196
179
|
def test_puts_something_in
|
197
180
|
cache = Zache.new(sync: false)
|
198
|
-
cache.get(:hey) {
|
181
|
+
cache.get(:hey) { rand }
|
199
182
|
cache.put(:hey, 123)
|
200
183
|
assert_equal(123, cache.get(:hey))
|
201
184
|
end
|
@@ -216,19 +199,19 @@ class ZacheTest < Minitest::Test
|
|
216
199
|
end
|
217
200
|
end
|
218
201
|
sleep 0.1
|
219
|
-
|
202
|
+
assert_predicate(cache, :locked?)
|
220
203
|
cache.get(:x) { 'second' }
|
221
|
-
|
204
|
+
refute_predicate(cache, :locked?)
|
222
205
|
long.kill
|
223
206
|
end
|
224
207
|
|
225
208
|
def test_checks_locked_status_from_inside
|
226
209
|
cache = Zache.new
|
227
210
|
cache.get(:x) do
|
228
|
-
|
211
|
+
assert_predicate(cache, :locked?)
|
229
212
|
'done'
|
230
213
|
end
|
231
|
-
|
214
|
+
refute_predicate(cache, :locked?)
|
232
215
|
end
|
233
216
|
|
234
217
|
def test_returns_dirty_result
|
@@ -293,4 +276,22 @@ class ZacheTest < Minitest::Test
|
|
293
276
|
cache.get(:hey) { raise 'intentional' }
|
294
277
|
end
|
295
278
|
end
|
279
|
+
|
280
|
+
def test_returns_placeholder_in_eager_mode
|
281
|
+
cache = Zache.new
|
282
|
+
a = cache.get(:me, placeholder: 42, eager: true) do
|
283
|
+
sleep 0.1
|
284
|
+
43
|
285
|
+
end
|
286
|
+
assert_equal(42, a)
|
287
|
+
sleep 0.2
|
288
|
+
b = cache.get(:me)
|
289
|
+
assert_equal(43, b)
|
290
|
+
end
|
291
|
+
|
292
|
+
private
|
293
|
+
|
294
|
+
def rand
|
295
|
+
SecureRandom.uuid
|
296
|
+
end
|
296
297
|
end
|