zache 0.14.0 → 0.15.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c247c0f146ef35562b8d56169fdb69ef0a9a02796d0e49dfb6c8ed83094dfd24
4
- data.tar.gz: d0b659e1315eee6f19b6c708da5b49073d3e0d9a5d5809a424c9b641d330e992
3
+ metadata.gz: 0ed02e68e04482b0ac7df8addc19dd0c02683191c53b3f3b391930e9b8ed4842
4
+ data.tar.gz: ec695943e4b8e9ba6793dd8db9dde826e6bc23cbd2ba92122aae5f52567aace2
5
5
  SHA512:
6
- metadata.gz: 23c14258f92e749ee9876b4fae20274a954d71a34a95b427dafd9bef201d17c91c1731d1733345ed564f9e7d74e340c68d9325127a55ab73e5d654ac8d5cb866
7
- data.tar.gz: 9232c93c2027dbe83015974315e5c609f3392216c77a96f465cc13f0ddd309fc11d4147234ff23892037619491bb037bc466e28f0075a8be424ef60c28014327
6
+ metadata.gz: d8758c5554cc1686ec3b82819977982103754cccebefb4b4e3df38d23b77541d82b13ef36626677b5fc4685ea6c7013e14f6e2da113b8cc6d6af376feb56fef3
7
+ data.tar.gz: 261616852da299a0376422d7f86bd62c3e89497874e859c620ba71017825f3e6be9db1cc6beca6feeacb091432137d2fac0fe8b79b1bd8a4595c67f322900810
data/Gemfile CHANGED
@@ -12,11 +12,12 @@ gem 'minitest-reporters', '~>1.7', require: false
12
12
  gem 'os', '>0', require: false
13
13
  gem 'qbash', '>0', require: false
14
14
  gem 'rake', '~>13.2', require: false
15
+ gem 'rdoc', '~>6.15', require: false
15
16
  gem 'rubocop', '~>1.75', require: false
16
- gem 'rubocop-minitest', '>0', require: false
17
- gem 'rubocop-performance', '>0', require: false
18
- gem 'rubocop-rake', '>0', require: false
17
+ gem 'rubocop-minitest', '~>0.38', require: false
18
+ gem 'rubocop-performance', '~>1.25', require: false
19
+ gem 'rubocop-rake', '~>0.7', require: false
19
20
  gem 'simplecov', '~>0.22', require: false
20
- gem 'simplecov-cobertura', '~>2.1', require: false
21
+ gem 'simplecov-cobertura', '~>3.0', require: false
21
22
  gem 'threads', '~>0.4', require: false
22
23
  gem 'yard', '~>0.9', require: false
data/Gemfile.lock CHANGED
@@ -8,40 +8,51 @@ GEM
8
8
  specs:
9
9
  ansi (1.5.0)
10
10
  ast (2.4.3)
11
- backtrace (0.4.0)
11
+ backtrace (0.4.1)
12
12
  builder (3.3.0)
13
13
  concurrent-ruby (1.3.5)
14
+ date (3.5.0)
14
15
  docile (1.4.1)
15
- elapsed (0.0.1)
16
- loog (> 0)
17
- tago (> 0)
18
- json (2.10.2)
19
- language_server-protocol (3.17.0.4)
16
+ elapsed (0.2.0)
17
+ loog (~> 0.6)
18
+ tago (~> 0.1)
19
+ erb (6.0.0)
20
+ json (2.16.0)
21
+ language_server-protocol (3.17.0.5)
20
22
  lint_roller (1.1.0)
21
- loog (0.6.0)
22
- minitest (5.25.5)
23
+ logger (1.7.0)
24
+ loog (0.6.1)
25
+ logger (~> 1.0)
26
+ minitest (5.26.2)
23
27
  minitest-reporters (1.7.1)
24
28
  ansi
25
29
  builder
26
30
  minitest (>= 5.0)
27
31
  ruby-progressbar
28
32
  os (1.1.4)
29
- parallel (1.26.3)
30
- parser (3.3.7.4)
33
+ parallel (1.27.0)
34
+ parser (3.3.10.0)
31
35
  ast (~> 2.4.1)
32
36
  racc
33
- prism (1.4.0)
34
- qbash (0.4.0)
37
+ prism (1.6.0)
38
+ psych (5.2.6)
39
+ date
40
+ stringio
41
+ qbash (0.4.8)
35
42
  backtrace (> 0)
36
43
  elapsed (> 0)
37
44
  loog (> 0)
38
45
  tago (> 0)
39
46
  racc (1.8.1)
40
47
  rainbow (3.1.1)
41
- rake (13.2.1)
42
- regexp_parser (2.10.0)
43
- rexml (3.4.1)
44
- rubocop (1.75.2)
48
+ rake (13.3.1)
49
+ rdoc (6.15.1)
50
+ erb
51
+ psych (>= 4.0.0)
52
+ tsort
53
+ regexp_parser (2.11.3)
54
+ rexml (3.4.4)
55
+ rubocop (1.81.7)
45
56
  json (~> 2.3)
46
57
  language_server-protocol (~> 3.17.0.2)
47
58
  lint_roller (~> 1.1.0)
@@ -49,20 +60,20 @@ GEM
49
60
  parser (>= 3.3.0.2)
50
61
  rainbow (>= 2.2.2, < 4.0)
51
62
  regexp_parser (>= 2.9.3, < 3.0)
52
- rubocop-ast (>= 1.44.0, < 2.0)
63
+ rubocop-ast (>= 1.47.1, < 2.0)
53
64
  ruby-progressbar (~> 1.7)
54
65
  unicode-display_width (>= 2.4.0, < 4.0)
55
- rubocop-ast (1.44.1)
66
+ rubocop-ast (1.48.0)
56
67
  parser (>= 3.3.7.2)
57
68
  prism (~> 1.4)
58
- rubocop-minitest (0.38.0)
69
+ rubocop-minitest (0.38.2)
59
70
  lint_roller (~> 1.1)
60
71
  rubocop (>= 1.75.0, < 2.0)
61
72
  rubocop-ast (>= 1.38.0, < 2.0)
62
- rubocop-performance (1.25.0)
73
+ rubocop-performance (1.26.1)
63
74
  lint_roller (~> 1.1)
64
75
  rubocop (>= 1.75.0, < 2.0)
65
- rubocop-ast (>= 1.38.0, < 2.0)
76
+ rubocop-ast (>= 1.47.1, < 2.0)
66
77
  rubocop-rake (0.7.1)
67
78
  lint_roller (~> 1.1)
68
79
  rubocop (>= 1.72.1)
@@ -71,18 +82,20 @@ GEM
71
82
  docile (~> 1.1)
72
83
  simplecov-html (~> 0.11)
73
84
  simplecov_json_formatter (~> 0.1)
74
- simplecov-cobertura (2.1.0)
85
+ simplecov-cobertura (3.1.0)
75
86
  rexml
76
87
  simplecov (~> 0.19)
77
- simplecov-html (0.13.1)
88
+ simplecov-html (0.13.2)
78
89
  simplecov_json_formatter (0.1.4)
79
- tago (0.1.0)
80
- threads (0.4.1)
90
+ stringio (3.1.8)
91
+ tago (0.4.0)
92
+ threads (0.5.0)
81
93
  backtrace (~> 0)
82
94
  concurrent-ruby (~> 1.0)
83
- unicode-display_width (3.1.4)
84
- unicode-emoji (~> 4.0, >= 4.0.4)
85
- unicode-emoji (4.0.4)
95
+ tsort (0.2.0)
96
+ unicode-display_width (3.2.0)
97
+ unicode-emoji (~> 4.1)
98
+ unicode-emoji (4.1.0)
86
99
  yard (0.9.37)
87
100
 
88
101
  PLATFORMS
@@ -101,12 +114,13 @@ DEPENDENCIES
101
114
  os (> 0)
102
115
  qbash (> 0)
103
116
  rake (~> 13.2)
117
+ rdoc (~> 6.15)
104
118
  rubocop (~> 1.75)
105
- rubocop-minitest (> 0)
106
- rubocop-performance (> 0)
107
- rubocop-rake (> 0)
119
+ rubocop-minitest (~> 0.38)
120
+ rubocop-performance (~> 1.25)
121
+ rubocop-rake (~> 0.7)
108
122
  simplecov (~> 0.22)
109
- simplecov-cobertura (~> 2.1)
123
+ simplecov-cobertura (~> 3.0)
110
124
  threads (~> 0.4)
111
125
  yard (~> 0.9)
112
126
  zache!
data/README.md CHANGED
@@ -13,8 +13,7 @@
13
13
  [![Hits-of-Code](https://hitsofcode.com/github/yegor256/zache)](https://hitsofcode.com/view/github/yegor256/zache)
14
14
 
15
15
  This is a simple Ruby gem for in-memory caching.
16
- Read [this blog post](https://www.yegor256.com/2019/02/05/zache.html)
17
- to understand what Zache is designed for.
16
+ Read this [blog post] to understand what Zache is designed for.
18
17
 
19
18
  First, install it:
20
19
 
@@ -31,22 +30,26 @@ zache = Zache.new
31
30
  v = zache.get(:count, lifetime: 5 * 60) { expensive_calculation() }
32
31
  ```
33
32
 
34
- If you omit the `lifetime` parameter, the key will never expire.
33
+ If you omit the `lifetime` parameter or set it to `nil`,
34
+ the key will never expire.
35
35
 
36
- By default `Zache` is thread-safe. It locks the entire cache on each
37
- `get` call. You can turn that off by using the `sync` argument:
36
+ By default `Zache` is thread-safe.
37
+ It locks the entire cache on each `get` call.
38
+ You can turn that off by using the `sync` argument:
38
39
 
39
40
  ```ruby
40
41
  zache = Zache.new(sync: false)
41
42
  v = zache.get(:count) { expensive_calculation() }
42
43
  ```
43
44
 
44
- You may use "dirty" mode, which will return an expired value while
45
- calculation is in progress. For example, if you have a value in the cache that's
46
- expired, and you call `get` with a long-running block, the thread waits.
47
- If another thread calls `get` again, that second thread won't wait, but will
48
- receive the expired value from the cache. This is a very convenient mode for situations
49
- where absolute data accuracy is less important than performance:
45
+ You may use "dirty" mode, which will return
46
+ an expired value while calculation is in progress.
47
+ For example, if you have a value in the cache that's expired,
48
+ and you call `get` with a long-running block, the thread waits.
49
+ If another thread calls `get` again, that second thread won't wait,
50
+ but will receive the expired value from the cache.
51
+ This is a very convenient mode for situations
52
+ where absolute data accuracy is less important than performance:
50
53
 
51
54
  ```ruby
52
55
  zache = Zache.new(dirty: true)
@@ -54,8 +57,19 @@ zache = Zache.new(dirty: true)
54
57
  value = zache.get(:key, dirty: true) { expensive_calculation() }
55
58
  ```
56
59
 
57
- The entire API is documented
58
- [here](https://www.rubydoc.info/github/yegor256/zache/master/Zache).
60
+ You may use "eager" mode with a placeholder value to return immediately
61
+ while the calculation happens in the background.
62
+ The cache returns the placeholder instantly and spawns
63
+ a thread to calculate the actual value.
64
+ This is useful when you need to avoid blocking
65
+ while expensive operations complete:
66
+
67
+ ```ruby
68
+ # Returns 0 immediately, calculates in background
69
+ value = zache.get(:key, eager: true, placeholder: 0) { expensive_calculation() }
70
+ ```
71
+
72
+ The entire API is [documented][rubydoc].
59
73
  Here are some additional useful methods:
60
74
 
61
75
  ```ruby
@@ -80,12 +94,11 @@ zache.empty?
80
94
 
81
95
  ## How to contribute
82
96
 
83
- Read
84
- [these guidelines](https://www.yegor256.com/2014/04/15/github-guidelines.html).
85
- Make sure your build is green before you contribute
86
- your pull request. You will need to have
87
- [Ruby](https://www.ruby-lang.org/en/) 2.3+ and
88
- [Bundler](https://bundler.io/) installed. Then:
97
+ Read these [guidelines].
98
+ Make sure your build is green before you contribute your pull request.
99
+ You will need to have [Ruby](https://www.ruby-lang.org/en/) 2.3+ and
100
+ [Bundler](https://bundler.io/) installed.
101
+ Then:
89
102
 
90
103
  ```bash
91
104
  bundle update
@@ -93,3 +106,7 @@ bundle exec rake
93
106
  ```
94
107
 
95
108
  If it's clean and you don't see any error messages, submit your pull request.
109
+
110
+ [blog post]: https://www.yegor256.com/2019/02/05/zache.html
111
+ [rubydoc]: https://www.rubydoc.info/github/yegor256/zache/master/Zache
112
+ [guidelines]: https://www.yegor256.com/2014/04/15/github-guidelines.html
data/REUSE.toml CHANGED
@@ -4,6 +4,18 @@
4
4
  version = 1
5
5
  [[annotations]]
6
6
  path = [
7
+ ".DS_Store",
8
+ ".gitattributes",
9
+ ".gitignore",
10
+ ".pdd",
11
+ "**.json",
12
+ "**.md",
13
+ "**.png",
14
+ "**.svg",
15
+ "**.txt",
16
+ "**/.DS_Store",
17
+ "**/.gitignore",
18
+ "**/.pdd",
7
19
  "**/*.csv",
8
20
  "**/*.jpg",
9
21
  "**/*.json",
@@ -13,15 +25,8 @@ path = [
13
25
  "**/*.svg",
14
26
  "**/*.txt",
15
27
  "**/*.vm",
16
- "**/.DS_Store",
17
- "**/.gitignore",
18
- "**/.pdd",
19
28
  "**/CNAME",
20
29
  "**/Gemfile.lock",
21
- ".DS_Store",
22
- ".gitattributes",
23
- ".gitignore",
24
- ".pdd",
25
30
  "Gemfile.lock",
26
31
  "README.md",
27
32
  "renovate.json",
data/Rakefile CHANGED
@@ -8,6 +8,7 @@ require 'qbash'
8
8
  require 'rubygems'
9
9
  require 'rake'
10
10
  require 'rake/clean'
11
+ require 'shellwords'
11
12
 
12
13
  CLEAN = FileList['coverage']
13
14
 
@@ -43,6 +44,7 @@ require 'yard'
43
44
  desc 'Build Yard documentation'
44
45
  YARD::Rake::YardocTask.new do |t|
45
46
  t.files = ['lib/**/*.rb']
47
+ t.options = ['--fail-on-warning']
46
48
  end
47
49
 
48
50
  require 'rubocop/rake_task'
data/lib/zache.rb CHANGED
@@ -29,8 +29,6 @@ class Zache
29
29
  end
30
30
 
31
31
  # Always returns the result of the block, never caches.
32
- # @param [Object] key Ignored
33
- # @param [Hash] opts Ignored
34
32
  # @yield Block that provides the value
35
33
  # @return [Object] The result of the block
36
34
  def get(*)
@@ -38,23 +36,19 @@ class Zache
38
36
  end
39
37
 
40
38
  # Always returns true regardless of the key.
41
- # @param [Object] key Ignored
42
- # @param [Hash] opts Ignored
43
39
  # @return [Boolean] Always returns true
44
40
  def exists?(*)
45
41
  true
46
42
  end
47
43
 
48
44
  # Always returns false.
45
+ # @param [Object] _key Ignored
49
46
  # @return [Boolean] Always returns false
50
- def locked?
47
+ def locked?(_key)
51
48
  false
52
49
  end
53
50
 
54
51
  # No-op method that ignores the input.
55
- # @param [Object] key Ignored
56
- # @param [Object] value Ignored
57
- # @param [Hash] opts Ignored
58
52
  # @return [nil] Always returns nil
59
53
  def put(*); end
60
54
 
@@ -89,13 +83,14 @@ class Zache
89
83
  @sync = sync
90
84
  @dirty = dirty
91
85
  @mutex = Mutex.new
86
+ @locks = {}
92
87
  end
93
88
 
94
89
  # Total number of keys currently in cache.
95
90
  #
96
91
  # @return [Integer] Number of keys in the cache
97
92
  def size
98
- @hash.size
93
+ synchronize_all { @hash.size }
99
94
  end
100
95
 
101
96
  # Gets the value from the cache by the provided key.
@@ -120,30 +115,33 @@ class Zache
120
115
  # @return [Object] The cached value
121
116
  def get(key, lifetime: 2**32, dirty: false, placeholder: nil, eager: false, &block)
122
117
  if block_given?
123
- return @hash[key][:value] if (dirty || @dirty) && locked? && expired?(key) && @hash.key?(key)
118
+ if (dirty || @dirty) && locked?(key)
119
+ synchronize_all do
120
+ return @hash[key][:value] if expired_unsafe?(key) && @hash.key?(key)
121
+ end
122
+ end
124
123
  if eager
125
- return @hash[key][:value] if @hash.key?(key)
124
+ has_key = synchronize_all { @hash.key?(key) }
125
+ return synchronize_all { @hash[key][:value] } if has_key
126
126
  put(key, placeholder, lifetime: 0)
127
127
  Thread.new do
128
- synchronized do
129
- calc(key, lifetime, &block)
130
- end
128
+ synchronize_one(key) { calc(key, lifetime, &block) }
131
129
  end
132
130
  placeholder
133
131
  else
134
- synchronized do
135
- calc(key, lifetime, &block)
136
- end
132
+ synchronize_one(key) { calc(key, lifetime, &block) }
137
133
  end
138
134
  else
139
- rec = @hash[key]
140
- if expired?(key)
141
- return rec[:value] if dirty || @dirty
142
- @hash.delete(key)
143
- rec = nil
135
+ synchronize_all do
136
+ rec = @hash[key]
137
+ if expired_unsafe?(key)
138
+ return rec[:value] if dirty || @dirty
139
+ @hash.delete(key)
140
+ rec = nil
141
+ end
142
+ raise 'The key is absent in the cache' if rec.nil?
143
+ rec[:value]
144
144
  end
145
- raise 'The key is absent in the cache' if rec.nil?
146
- rec[:value]
147
145
  end
148
146
  end
149
147
 
@@ -155,12 +153,14 @@ class Zache
155
153
  # @param dirty [Boolean] Whether to consider expired values as existing
156
154
  # @return [Boolean] True if the key exists and is not expired (unless dirty is true)
157
155
  def exists?(key, dirty: false)
158
- rec = @hash[key]
159
- if expired?(key) && !dirty && !@dirty
160
- @hash.delete(key)
161
- rec = nil
156
+ synchronize_all do
157
+ rec = @hash[key]
158
+ if expired_unsafe?(key) && !dirty && !@dirty
159
+ @hash.delete(key)
160
+ rec = nil
161
+ end
162
+ !rec.nil?
162
163
  end
163
- !rec.nil?
164
164
  end
165
165
 
166
166
  # Checks whether the key exists in the cache and is expired. If the
@@ -169,8 +169,7 @@ class Zache
169
169
  # @param key [Object] The key to check in the cache
170
170
  # @return [Boolean] True if the key exists and is expired
171
171
  def expired?(key)
172
- rec = @hash[key]
173
- !rec.nil? && rec[:start] < Time.now - rec[:lifetime]
172
+ synchronize_all { expired_unsafe?(key) }
174
173
  end
175
174
 
176
175
  # Returns the modification time of the key, if it exists.
@@ -179,15 +178,18 @@ class Zache
179
178
  # @param key [Object] The key to get the modification time for
180
179
  # @return [Time] The modification time of the key or current time if key doesn't exist
181
180
  def mtime(key)
182
- rec = @hash[key]
183
- rec.nil? ? Time.now : rec[:start]
181
+ synchronize_all do
182
+ rec = @hash[key]
183
+ rec.nil? ? Time.now : rec[:start]
184
+ end
184
185
  end
185
186
 
186
- # Is cache currently locked doing something?
187
+ # Is key currently locked doing something?
187
188
  #
189
+ # @param [Object] key The key to check
188
190
  # @return [Boolean] True if the cache is locked
189
- def locked?
190
- @mutex.locked?
191
+ def locked?(key)
192
+ synchronize_all { @locks[key]&.locked? }
191
193
  end
192
194
 
193
195
  # Put a value into the cache.
@@ -197,7 +199,7 @@ class Zache
197
199
  # @param lifetime [Integer] Time in seconds until the key expires (default: never expires)
198
200
  # @return [Object] The value stored
199
201
  def put(key, value, lifetime: 2**32)
200
- synchronized do
202
+ synchronize_one(key) do
201
203
  @hash[key] = {
202
204
  value: value,
203
205
  start: Time.now,
@@ -213,14 +215,14 @@ class Zache
213
215
  # @yield Block to call if the key is not found
214
216
  # @return [Object] The removed value or the result of the block
215
217
  def remove(key)
216
- synchronized { @hash.delete(key) { yield if block_given? } }
218
+ synchronize_one(key) { @hash.delete(key) { yield if block_given? } }
217
219
  end
218
220
 
219
221
  # Remove all keys from the cache.
220
222
  #
221
223
  # @return [Hash] Empty hash
222
224
  def remove_all
223
- synchronized { @hash = {} }
225
+ synchronize_all { @hash = {} }
224
226
  end
225
227
 
226
228
  # Remove all keys that match the block.
@@ -229,7 +231,7 @@ class Zache
229
231
  # @yieldparam key [Object] The cache key to evaluate
230
232
  # @return [Integer] Number of keys removed
231
233
  def remove_by
232
- synchronized do
234
+ synchronize_all do
233
235
  count = 0
234
236
  @hash.each_key do |k|
235
237
  if yield(k)
@@ -246,9 +248,9 @@ class Zache
246
248
  #
247
249
  # @return [Integer] Number of keys removed
248
250
  def clean
249
- synchronized do
251
+ synchronize_all do
250
252
  size_before = @hash.size
251
- @hash.delete_if { |key, _value| expired?(key) }
253
+ @hash.delete_if { |key, _value| expired_unsafe?(key) }
252
254
  size_before - @hash.size
253
255
  end
254
256
  end
@@ -257,7 +259,7 @@ class Zache
257
259
  #
258
260
  # @return [Boolean] True if the cache is empty
259
261
  def empty?
260
- @hash.empty?
262
+ synchronize_all { @hash.empty? }
261
263
  end
262
264
 
263
265
  private
@@ -269,26 +271,46 @@ class Zache
269
271
  # @return [Object] The cached or newly calculated value
270
272
  def calc(key, lifetime)
271
273
  rec = @hash[key]
272
- rec = nil if expired?(key)
274
+ rec = nil if expired_unsafe?(key)
273
275
  if rec.nil?
274
- @hash[key] = {
276
+ rec = {
275
277
  value: yield,
276
278
  start: Time.now,
277
279
  lifetime: lifetime
278
280
  }
281
+ @hash[key] = rec
279
282
  end
280
- @hash[key][:value]
283
+ rec[:value]
284
+ end
285
+
286
+ # Internal method that checks if a key is expired without acquiring locks.
287
+ # This should only be called from within a synchronized block.
288
+ # @param key [Object] The key to check in the cache
289
+ # @return [Boolean] True if the key exists and is expired
290
+ def expired_unsafe?(key)
291
+ rec = @hash[key]
292
+ !rec.nil? && rec[:lifetime] && rec[:start] < Time.now - rec[:lifetime]
281
293
  end
282
294
 
283
295
  # Executes a block within a synchronized context if sync is enabled.
284
296
  # @param block [Proc] The block to execute
285
297
  # @yield The block to execute in a synchronized context
286
298
  # @return [Object] The result of the block
287
- def synchronized(&block)
288
- if @sync
289
- @mutex.synchronize(&block)
290
- else
291
- yield
299
+ def synchronize_all(&block)
300
+ return yield unless @sync
301
+ @mutex.synchronize(&block)
302
+ end
303
+
304
+ # Executes a block within a synchronized context if sync is enabled.
305
+ # @param key [Object] The object to sync
306
+ # @param block [Proc] The block to execute
307
+ # @yield The block to execute in a synchronized context
308
+ # @return [Object] The result of the block
309
+ def synchronize_one(key, &block)
310
+ return yield unless @sync
311
+ @mutex.synchronize do
312
+ @locks[key] ||= Mutex.new
292
313
  end
314
+ @locks[key].synchronize(&block)
293
315
  end
294
316
  end
data/zache.gemspec CHANGED
@@ -8,14 +8,14 @@ Gem::Specification.new do |s|
8
8
  s.required_rubygems_version = Gem::Requirement.new('>= 0') if s.respond_to? :required_rubygems_version=
9
9
  s.required_ruby_version = '>= 2.5'
10
10
  s.name = 'zache'
11
- s.version = '0.14.0' # Version should be updated before release
11
+ s.version = '0.15.1' # Version should be updated before release
12
12
  s.license = 'MIT'
13
13
  s.summary = 'In-memory Cache'
14
14
  s.description = 'Zero-footprint in-memory thread-safe cache'
15
15
  s.authors = ['Yegor Bugayenko']
16
16
  s.email = 'yegor256@gmail.com'
17
17
  s.homepage = 'https://github.com/yegor256/zache'
18
- s.files = `git ls-files`.split($RS)
18
+ s.files = `git ls-files | grep -v -E '^(test/|\\.|renovate)'`.split($RS)
19
19
  s.rdoc_options = ['--charset=UTF-8']
20
20
  s.extra_rdoc_files = ['README.md']
21
21
  s.metadata['rubygems_mfa_required'] = 'true'
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zache
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.14.0
4
+ version: 0.15.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-04-12 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies: []
12
12
  description: Zero-footprint in-memory thread-safe cache
13
13
  email: yegor256@gmail.com
@@ -16,21 +16,6 @@ extensions: []
16
16
  extra_rdoc_files:
17
17
  - README.md
18
18
  files:
19
- - ".0pdd.yml"
20
- - ".github/workflows/actionlint.yml"
21
- - ".github/workflows/codecov.yml"
22
- - ".github/workflows/copyrights.yml"
23
- - ".github/workflows/license.yml"
24
- - ".github/workflows/markdown-lint.yml"
25
- - ".github/workflows/pdd.yml"
26
- - ".github/workflows/rake.yml"
27
- - ".github/workflows/reuse.yml"
28
- - ".github/workflows/xcop.yml"
29
- - ".github/workflows/yamllint.yml"
30
- - ".gitignore"
31
- - ".pdd"
32
- - ".rubocop.yml"
33
- - ".rultor.yml"
34
19
  - Gemfile
35
20
  - Gemfile.lock
36
21
  - LICENSE.txt
@@ -40,9 +25,6 @@ files:
40
25
  - Rakefile
41
26
  - lib/zache.rb
42
27
  - logo.svg
43
- - renovate.json
44
- - test/test__helper.rb
45
- - test/test_zache.rb
46
28
  - zache.gemspec
47
29
  homepage: https://github.com/yegor256/zache
48
30
  licenses:
@@ -64,7 +46,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
64
46
  - !ruby/object:Gem::Version
65
47
  version: '0'
66
48
  requirements: []
67
- rubygems_version: 3.6.2
49
+ rubygems_version: 3.6.9
68
50
  specification_version: 4
69
51
  summary: In-memory Cache
70
52
  test_files: []
data/.0pdd.yml DELETED
@@ -1,12 +0,0 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko
2
- # SPDX-License-Identifier: MIT
3
- ---
4
- errors:
5
- - yegor256@gmail.com
6
- # alerts:
7
- # github:
8
- # - yegor256
9
-
10
- tags:
11
- - pdd
12
- - bug
@@ -1,25 +0,0 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko
2
- # SPDX-License-Identifier: MIT
3
- ---
4
- # yamllint disable rule:line-length
5
- name: actionlint
6
- 'on':
7
- push:
8
- branches:
9
- - master
10
- pull_request:
11
- branches:
12
- - master
13
- jobs:
14
- actionlint:
15
- timeout-minutes: 15
16
- runs-on: ubuntu-24.04
17
- steps:
18
- - uses: actions/checkout@v4
19
- - name: Download actionlint
20
- id: get_actionlint
21
- run: bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash)
22
- shell: bash
23
- - name: Check workflow files
24
- run: ${{ steps.get_actionlint.outputs.executable }} -color
25
- shell: bash
@@ -1,25 +0,0 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko
2
- # SPDX-License-Identifier: MIT
3
- ---
4
- # yamllint disable rule:line-length
5
- name: codecov
6
- 'on':
7
- push:
8
- branches:
9
- - master
10
- jobs:
11
- codecov:
12
- timeout-minutes: 15
13
- runs-on: ubuntu-24.04
14
- steps:
15
- - uses: actions/checkout@v4
16
- - uses: actions/setup-ruby@v1
17
- with:
18
- ruby-version: 3.3
19
- bundler-cache: true
20
- - run: bundle config set --global path "$(pwd)/vendor/bundle"
21
- - run: bundle install --no-color
22
- - run: bundle exec rake
23
- - uses: codecov/codecov-action@v5
24
- with:
25
- token: ${{ secrets.CODECOV_TOKEN }}
@@ -1,15 +0,0 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko
2
- # SPDX-License-Identifier: MIT
3
- ---
4
- # yamllint disable rule:line-length
5
- name: copyrights
6
- 'on':
7
- push:
8
- pull_request:
9
- jobs:
10
- copyrights:
11
- timeout-minutes: 15
12
- runs-on: ubuntu-24.04
13
- steps:
14
- - uses: actions/checkout@v4
15
- - uses: yegor256/copyrights-action@0.0.8
@@ -1,42 +0,0 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko
2
- # SPDX-License-Identifier: MIT
3
- ---
4
- # yamllint disable rule:line-length
5
- name: license
6
- 'on':
7
- push:
8
- branches:
9
- - master
10
- pull_request:
11
- branches:
12
- - master
13
- jobs:
14
- license:
15
- timeout-minutes: 15
16
- runs-on: ubuntu-24.04
17
- steps:
18
- - uses: actions/checkout@v4
19
- - shell: bash
20
- run: |
21
- header="Copyright (c) 2018-$(date +%Y) Yegor Bugayenko"
22
- failed="false"
23
- while IFS= read -r file; do
24
- if ! grep -q "${header}" "${file}"; then
25
- failed="true"
26
- echo "⚠️ Copyright header is not found in: ${file}"
27
- else
28
- echo "File looks good: ${file}"
29
- fi
30
- done < <(find . -type f \( \
31
- -name "Dockerfile" -o \
32
- -name "LICENSE.txt" -o \
33
- -name "Makefile" -o \
34
- -name "Rakefile" -o \
35
- -name "*.sh" -o \
36
- -name "*.rb" -o \
37
- -name "*.fe" -o \
38
- -name "*.yml" \
39
- \) -print)
40
- if [ "${failed}" = "true" ]; then
41
- exit 1
42
- fi
@@ -1,23 +0,0 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko
2
- # SPDX-License-Identifier: MIT
3
- ---
4
- # yamllint disable rule:line-length
5
- name: markdown-lint
6
- 'on':
7
- push:
8
- branches:
9
- - master
10
- pull_request:
11
- branches:
12
- - master
13
- paths-ignore: ['paper/**', 'sandbox/**']
14
- concurrency:
15
- group: markdown-lint-${{ github.ref }}
16
- cancel-in-progress: true
17
- jobs:
18
- markdown-lint:
19
- timeout-minutes: 15
20
- runs-on: ubuntu-24.04
21
- steps:
22
- - uses: actions/checkout@v4
23
- - uses: articulate/actions-markdownlint@v1
@@ -1,19 +0,0 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko
2
- # SPDX-License-Identifier: MIT
3
- ---
4
- # yamllint disable rule:line-length
5
- name: pdd
6
- 'on':
7
- push:
8
- branches:
9
- - master
10
- pull_request:
11
- branches:
12
- - master
13
- jobs:
14
- pdd:
15
- timeout-minutes: 15
16
- runs-on: ubuntu-24.04
17
- steps:
18
- - uses: actions/checkout@v4
19
- - uses: volodya-lombrozo/pdd-action@master
@@ -1,28 +0,0 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko
2
- # SPDX-License-Identifier: MIT
3
- ---
4
- # yamllint disable rule:line-length
5
- name: rake
6
- 'on':
7
- push:
8
- branches:
9
- - master
10
- pull_request:
11
- branches:
12
- - master
13
- jobs:
14
- test:
15
- strategy:
16
- matrix:
17
- os: [ubuntu-24.04, macos-15]
18
- ruby: [3.3]
19
- runs-on: ${{ matrix.os }}
20
- steps:
21
- - uses: actions/checkout@v4
22
- - uses: ruby/setup-ruby@v1
23
- with:
24
- ruby-version: ${{ matrix.ruby }}
25
- bundler-cache: true
26
- - run: bundle config set --global path "$(pwd)/vendor/bundle"
27
- - run: bundle install --no-color
28
- - run: bundle exec rake
@@ -1,19 +0,0 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko
2
- # SPDX-License-Identifier: MIT
3
- ---
4
- # yamllint disable rule:line-length
5
- name: reuse
6
- 'on':
7
- push:
8
- branches:
9
- - master
10
- pull_request:
11
- branches:
12
- - master
13
- jobs:
14
- reuse:
15
- timeout-minutes: 15
16
- runs-on: ubuntu-24.04
17
- steps:
18
- - uses: actions/checkout@v4
19
- - uses: fsfe/reuse-action@v5
@@ -1,21 +0,0 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko
2
- # SPDX-License-Identifier: MIT
3
- ---
4
- # yamllint disable rule:line-length
5
- name: xcop
6
- "on":
7
- push:
8
- branches:
9
- - master
10
- pull_request:
11
- branches:
12
- - master
13
- jobs:
14
- xcop:
15
- timeout-minutes: 15
16
- runs-on: ubuntu-24.04
17
- steps:
18
- - uses: actions/checkout@v4
19
- - uses: g4s8/xcop-action@master
20
- with:
21
- files: '**/*.xml'
@@ -1,19 +0,0 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko
2
- # SPDX-License-Identifier: MIT
3
- ---
4
- # yamllint disable rule:line-length
5
- name: yamllint
6
- 'on':
7
- push:
8
- branches:
9
- - master
10
- pull_request:
11
- branches:
12
- - master
13
- jobs:
14
- yamllint:
15
- timeout-minutes: 15
16
- runs-on: ubuntu-24.04
17
- steps:
18
- - uses: actions/checkout@v4
19
- - uses: ibiqlik/action-yamllint@v3
data/.gitignore DELETED
@@ -1,10 +0,0 @@
1
- *.gem
2
- .DS_Store
3
- .bundle/
4
- .idea/
5
- .yardoc/
6
- coverage/
7
- doc/
8
- node_modules/
9
- rdoc/
10
- vendor/
data/.pdd DELETED
@@ -1,5 +0,0 @@
1
- --source=.
2
- --verbose
3
- --rule min-words:20
4
- --rule min-estimate:15
5
- --rule max-estimate:90
data/.rubocop.yml DELETED
@@ -1,40 +0,0 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko
2
- # SPDX-License-Identifier: MIT
3
- ---
4
- AllCops:
5
- Exclude:
6
- - 'bin/**/*'
7
- - 'assets/**/*'
8
- - 'vendor/**/**'
9
- DisplayCopNames: true
10
- TargetRubyVersion: 2.5
11
- NewCops: enable
12
- SuggestExtensions: false
13
- plugins:
14
- - rubocop-rake
15
- - rubocop-minitest
16
- - rubocop-performance
17
- Minitest/EmptyLineBeforeAssertionMethods:
18
- Enabled: false
19
- Minitest/AssertOperator:
20
- Enabled: false
21
- Layout/EmptyLineAfterGuardClause:
22
- Enabled: false
23
- Layout/MultilineMethodCallIndentation:
24
- Enabled: false
25
- Metrics/ClassLength:
26
- Max: 300
27
- Minitest/MultipleAssertions:
28
- Max: 5
29
- Metrics/AbcSize:
30
- Max: 30
31
- Metrics/MethodLength:
32
- Max: 30
33
- Metrics/CyclomaticComplexity:
34
- Max: 20
35
- Metrics/PerceivedComplexity:
36
- Max: 20
37
- Layout/EndOfLine:
38
- EnforcedStyle: lf
39
- Metrics/BlockLength:
40
- Max: 28
data/.rultor.yml DELETED
@@ -1,26 +0,0 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko
2
- # SPDX-License-Identifier: MIT
3
- ---
4
- # yamllint disable rule:line-length
5
- docker:
6
- image: yegor256/ruby
7
- assets:
8
- rubygems.yml: yegor256/home#assets/rubygems.yml
9
- install: |
10
- pdd -f /dev/null
11
- sudo bundle install --no-color "--gemfile=$(pwd)/Gemfile"
12
- release:
13
- pre: false
14
- script: |-
15
- bundle exec rake
16
- pdd -f /dev/null
17
- rm -rf *.gem
18
- sed -i "s/0\.0\.0/${tag}/g" zache.gemspec
19
- git add zache.gemspec
20
- git commit -m "Version set to ${tag}"
21
- gem build zache.gemspec
22
- chmod 0600 ../rubygems.yml
23
- gem push *.gem --config-file ../rubygems.yml
24
- merge:
25
- script: |-
26
- bundle exec rake clean test rubocop copyright
data/renovate.json DELETED
@@ -1,6 +0,0 @@
1
- {
2
- "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3
- "extends": [
4
- "config:base"
5
- ]
6
- }
data/test/test__helper.rb DELETED
@@ -1,31 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko
4
- # SPDX-License-Identifier: MIT
5
-
6
- $stdout.sync = true
7
-
8
- require 'simplecov'
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
-
29
- require 'minitest/autorun'
30
- require 'minitest/reporters'
31
- Minitest::Reporters.use! [Minitest::Reporters::SpecReporter.new]
data/test/test_zache.rb DELETED
@@ -1,297 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko
4
- # SPDX-License-Identifier: MIT
5
-
6
- require 'concurrent'
7
- require 'minitest/autorun'
8
- require 'securerandom'
9
- require 'threads'
10
- require 'timeout'
11
- require_relative '../lib/zache'
12
- require_relative 'test__helper'
13
-
14
- Thread.report_on_exception = true
15
-
16
- # Cache test.
17
- # Author:: Yegor Bugayenko (yegor256@gmail.com)
18
- # Copyright:: Copyright (c) 2018-2025 Yegor Bugayenko
19
- # License:: MIT
20
- class ZacheTest < Minitest::Test
21
- def test_caches
22
- cache = Zache.new(sync: false)
23
- first = cache.get(:hey, lifetime: 5) { rand }
24
- second = cache.get(:hey) { rand }
25
- assert_equal(first, second)
26
- assert_equal(1, cache.size)
27
- end
28
-
29
- def test_caches_and_expires
30
- cache = Zache.new
31
- first = cache.get(:hey, lifetime: 0.01) { rand }
32
- sleep 0.1
33
- second = cache.get(:hey) { rand }
34
- refute_equal(first, second)
35
- end
36
-
37
- def test_calculates_age
38
- cache = Zache.new
39
- cache.get(:hey) { rand }
40
- sleep 0.1
41
- assert_operator(cache.mtime(:hey), :<, Time.now - 0.05)
42
- end
43
-
44
- def test_caches_in_threads
45
- cache = Zache.new
46
- Threads.new(10).assert(100) do
47
- cache.get(:hey, lifetime: 0.0001) { rand }
48
- end
49
- end
50
-
51
- def test_key_exists
52
- cache = Zache.new
53
- cache.get(:hey) { rand }
54
- exists_result = cache.exists?(:hey)
55
- not_exists_result = cache.exists?(:bye)
56
- assert(exists_result)
57
- refute(not_exists_result)
58
- end
59
-
60
- def test_put_and_exists
61
- cache = Zache.new
62
- cache.put(:hey, 'hello', lifetime: 0.1)
63
- sleep 0.2
64
- refute(cache.exists?(:hey))
65
- end
66
-
67
- def test_remove_key
68
- cache = Zache.new
69
- cache.get(:hey) { rand }
70
- cache.get(:wey) { rand }
71
- assert(cache.exists?(:hey))
72
- assert(cache.exists?(:wey))
73
- cache.remove(:hey)
74
- refute(cache.exists?(:hey))
75
- assert(cache.exists?(:wey))
76
- end
77
-
78
- def test_remove_by_block
79
- cache = Zache.new
80
- cache.get('first') { rand }
81
- cache.get('second') { rand }
82
- cache.remove_by { |k| k == 'first' }
83
- refute(cache.exists?('first'))
84
- assert(cache.exists?('second'))
85
- end
86
-
87
- def test_remove_key_with_sync_false
88
- cache = Zache.new(sync: false)
89
- cache.get(:hey) { rand }
90
- cache.get(:wey) { rand }
91
- assert(cache.exists?(:hey))
92
- assert(cache.exists?(:wey))
93
- cache.remove(:hey)
94
- refute(cache.exists?(:hey))
95
- assert(cache.exists?(:wey))
96
- end
97
-
98
- def test_clean_with_threads
99
- cache = Zache.new
100
- Threads.new(300).assert(3000) do
101
- cache.get(:hey) { rand }
102
- cache.get(:bye, lifetime: 0.01) { rand }
103
- sleep 0.1
104
- cache.clean
105
- end
106
- assert(cache.exists?(:hey))
107
- refute(cache.exists?(:bye))
108
- end
109
-
110
- def test_clean
111
- cache = Zache.new
112
- cache.get(:hey) { rand }
113
- cache.get(:bye, lifetime: 0.01) { rand }
114
- sleep 0.1
115
- cache.clean
116
- assert(cache.exists?(:hey))
117
- refute(cache.exists?(:bye))
118
- end
119
-
120
- def test_clean_size
121
- cache = Zache.new
122
- cache.get(:hey, lifetime: 0.01) { rand }
123
- sleep 0.1
124
- cache.clean
125
- assert_empty(cache)
126
- end
127
-
128
- def test_clean_with_sync_false
129
- cache = Zache.new(sync: false)
130
- cache.get(:hey) { rand }
131
- cache.get(:bye, lifetime: 0.01) { rand }
132
- sleep 0.1
133
- cache.clean
134
- assert(cache.exists?(:hey))
135
- refute(cache.exists?(:bye))
136
- end
137
-
138
- def test_remove_absent_key
139
- cache = Zache.new
140
- cache.remove(:hey)
141
- end
142
-
143
- def test_check_and_remove
144
- cache = Zache.new
145
- cache.get(:hey, lifetime: 0) { rand }
146
- refute(cache.exists?(:hey))
147
- end
148
-
149
- def test_remove_all_with_threads
150
- cache = Zache.new
151
- Threads.new(10).assert(100) do |i|
152
- cache.get(:"hey#{i}") { rand }
153
- assert(cache.exists?(:"hey#{i}"))
154
- cache.remove_all
155
- end
156
- 10.times do |i|
157
- refute(cache.exists?(:"hey#{i}"))
158
- end
159
- end
160
-
161
- def test_remove_all_with_sync
162
- cache = Zache.new
163
- cache.get(:hey) { rand }
164
- cache.get(:bye) { rand }
165
- cache.remove_all
166
- refute(cache.exists?(:hey))
167
- refute(cache.exists?(:bye))
168
- end
169
-
170
- def test_remove_all_without_sync
171
- cache = Zache.new(sync: false)
172
- cache.get(:hey) { rand }
173
- cache.get(:bye) { rand }
174
- cache.remove_all
175
- refute(cache.exists?(:hey))
176
- refute(cache.exists?(:bye))
177
- end
178
-
179
- def test_puts_something_in
180
- cache = Zache.new(sync: false)
181
- cache.get(:hey) { rand }
182
- cache.put(:hey, 123)
183
- assert_equal(123, cache.get(:hey))
184
- end
185
-
186
- def test_sync_zache_is_not_reentrant
187
- cache = Zache.new
188
- assert_raises ThreadError do
189
- cache.get(:first) { cache.get(:second) { 1 } }
190
- end
191
- end
192
-
193
- def test_calculates_only_once
194
- cache = Zache.new
195
- long = Thread.start do
196
- cache.get(:x) do
197
- sleep 0.5
198
- 'first'
199
- end
200
- end
201
- sleep 0.1
202
- assert_predicate(cache, :locked?)
203
- cache.get(:x) { 'second' }
204
- refute_predicate(cache, :locked?)
205
- long.kill
206
- end
207
-
208
- def test_checks_locked_status_from_inside
209
- cache = Zache.new
210
- cache.get(:x) do
211
- assert_predicate(cache, :locked?)
212
- 'done'
213
- end
214
- refute_predicate(cache, :locked?)
215
- end
216
-
217
- def test_returns_dirty_result
218
- cache = Zache.new(dirty: true)
219
- cache.get(:x, lifetime: 0) { 1 }
220
- long = Thread.start do
221
- cache.get(:x) do
222
- sleep 1000
223
- 2
224
- end
225
- end
226
- sleep 0.1
227
- Timeout.timeout(1) do
228
- assert(cache.exists?(:x))
229
- assert(cache.expired?(:x))
230
- assert_equal(1, cache.get(:x))
231
- assert_equal(1, cache.get(:x) { 2 })
232
- end
233
- long.kill
234
- end
235
-
236
- def test_returns_dirty_result_when_not_locked
237
- cache = Zache.new(dirty: true)
238
- cache.get(:x, lifetime: 0) { 1 }
239
- assert(cache.exists?(:x))
240
- assert_equal(1, cache.get(:x))
241
- assert_equal(2, cache.get(:x) { 2 })
242
- end
243
-
244
- def test_fetches_multiple_keys_in_many_threads_in_dirty_mode
245
- cache = Zache.new(dirty: true)
246
- set = Concurrent::Set.new
247
- threads = 50
248
- barrier = Concurrent::CyclicBarrier.new(threads)
249
- Threads.new(threads).assert(threads * 2) do |i|
250
- barrier.wait if i < threads
251
- set << cache.get(i, lifetime: 0.001) { i }
252
- end
253
- assert_equal(threads, set.size)
254
- end
255
-
256
- def test_fetches_multiple_keys_in_many_threads
257
- cache = Zache.new
258
- set = Concurrent::Set.new
259
- threads = 50
260
- barrier = Concurrent::CyclicBarrier.new(threads)
261
- Threads.new(threads).assert(threads * 2) do |i|
262
- barrier.wait if i < threads
263
- set << cache.get(i) { i }
264
- end
265
- assert_equal(threads, set.size)
266
- end
267
-
268
- def test_fake_class_works
269
- cache = Zache::Fake.new
270
- assert_equal(1, cache.get(:x) { 1 })
271
- end
272
-
273
- def test_rethrows
274
- cache = Zache.new
275
- assert_raises RuntimeError do
276
- cache.get(:hey) { raise 'intentional' }
277
- end
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
297
- end