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.
data/lib/zache.rb CHANGED
@@ -1,26 +1,7 @@
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
  # 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-2024 Yegor Bugayenko
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 and is already expired.
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 (unless <tt>dirty</tt>
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 and is already expired.
100
- def get(key, lifetime: 2**32, dirty: false, &block)
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
- synchronized { calc(key, lifetime, &block) }
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 hash,
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
- @hash.delete(k) if yield(k)
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 { @hash.delete_if { |key, _value| expired?(key) } }
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
- block.call
291
+ yield
207
292
  end
208
293
  end
209
294
  end
data/logo.svg CHANGED
@@ -16,4 +16,4 @@
16
16
  </g>
17
17
  </g>
18
18
  </g>
19
- </svg>
19
+ </svg>
data/test/test__helper.rb CHANGED
@@ -1,28 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright (c) 2018-2024 Yegor Bugayenko
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
- SimpleCov.start
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
- require_relative '../lib/zache'
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
- # (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
22
  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)
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) { Random.rand }
31
+ first = cache.get(:hey, lifetime: 0.01) { rand }
49
32
  sleep 0.1
50
- second = cache.get(:hey) { Random.rand }
51
- assert(first != second)
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) { Random.rand }
39
+ cache.get(:hey) { rand }
57
40
  sleep 0.1
58
- assert(cache.mtime(:hey) < Time.now - 0.05)
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) { Random.rand }
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) { Random.rand }
53
+ cache.get(:hey) { rand }
71
54
  exists_result = cache.exists?(:hey)
72
55
  not_exists_result = cache.exists?(:bye)
73
- assert(exists_result == true)
74
- assert(not_exists_result == false)
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
- assert(!cache.exists?(:hey))
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) { Random.rand }
87
- cache.get(:wey) { Random.rand }
88
- assert(cache.exists?(:hey) == true)
89
- assert(cache.exists?(:wey) == true)
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
- assert(cache.exists?(:hey) == false)
92
- assert(cache.exists?(:wey) == true)
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') { Random.rand }
98
- cache.get('second') { Random.rand }
80
+ cache.get('first') { rand }
81
+ cache.get('second') { rand }
99
82
  cache.remove_by { |k| k == 'first' }
100
- assert(cache.exists?('first') == false)
101
- assert(cache.exists?('second') == true)
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) { Random.rand }
107
- cache.get(:wey) { Random.rand }
108
- assert(cache.exists?(:hey) == true)
109
- assert(cache.exists?(:wey) == true)
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
- assert(cache.exists?(:hey) == false)
112
- assert(cache.exists?(:wey) == true)
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) { Random.rand }
119
- cache.get(:bye, lifetime: 0.01) { Random.rand }
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) == true)
124
- assert(cache.exists?(:bye) == false)
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) { Random.rand }
130
- cache.get(:bye, lifetime: 0.01) { Random.rand }
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) == true)
134
- assert(cache.exists?(:bye) == false)
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) { Random.rand }
122
+ cache.get(:hey, lifetime: 0.01) { rand }
140
123
  sleep 0.1
141
124
  cache.clean
142
- assert(cache.empty?)
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) { Random.rand }
148
- cache.get(:bye, lifetime: 0.01) { Random.rand }
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) == true)
152
- assert(cache.exists?(:bye) == false)
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) { Random.rand }
163
- assert(!cache.exists?(:hey))
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}") { Random.rand }
170
- assert(cache.exists?(:"hey#{i}") == true)
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
- assert(cache.exists?(:"hey#{i}") == false)
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) { Random.rand }
181
- cache.get(:bye) { Random.rand }
163
+ cache.get(:hey) { rand }
164
+ cache.get(:bye) { rand }
182
165
  cache.remove_all
183
- assert(cache.exists?(:hey) == false)
184
- assert(cache.exists?(:bye) == false)
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) { Random.rand }
190
- cache.get(:bye) { Random.rand }
172
+ cache.get(:hey) { rand }
173
+ cache.get(:bye) { rand }
191
174
  cache.remove_all
192
- assert(cache.exists?(:hey) == false)
193
- assert(cache.exists?(:bye) == false)
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) { Random.rand }
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
- assert(cache.locked?)
202
+ assert_predicate(cache, :locked?)
220
203
  cache.get(:x) { 'second' }
221
- assert(!cache.locked?)
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
- assert(cache.locked?)
211
+ assert_predicate(cache, :locked?)
229
212
  'done'
230
213
  end
231
- assert(!cache.locked?)
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