zache 0.13.1 → 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/README.md CHANGED
@@ -1,66 +1,95 @@
1
- <img src="/logo.svg" width="64px" height="64px"/>
1
+ # In Memory Cache for Ruby
2
2
 
3
3
  [![EO principles respected here](https://www.elegantobjects.org/badge.svg)](https://www.elegantobjects.org)
4
- [![DevOps By Rultor.com](http://www.rultor.com/b/yegor256/zache)](http://www.rultor.com/p/yegor256/zache)
4
+ [![DevOps By Rultor.com](https://www.rultor.com/b/yegor256/zache)](https://www.rultor.com/p/yegor256/zache)
5
5
  [![We recommend RubyMine](https://www.elegantobjects.org/rubymine.svg)](https://www.jetbrains.com/ruby/)
6
6
 
7
7
  [![rake](https://github.com/yegor256/zache/actions/workflows/rake.yml/badge.svg)](https://github.com/yegor256/zache/actions/workflows/rake.yml)
8
- [![Gem Version](https://badge.fury.io/rb/zache.svg)](http://badge.fury.io/rb/zache)
8
+ [![Gem Version](https://badge.fury.io/rb/zache.svg)](https://badge.fury.io/rb/zache)
9
9
  [![Maintainability](https://api.codeclimate.com/v1/badges/c136afe340fa94f14696/maintainability)](https://codeclimate.com/github/yegor256/zache/maintainability)
10
- [![Yard Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://rubydoc.info/github/yegor256/zache/master/frames)
10
+ [![Yard Docs](https://img.shields.io/badge/yard-docs-blue.svg)](https://rubydoc.info/github/yegor256/zache/master/frames)
11
11
  [![License](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/yegor256/zache/blob/master/LICENSE.txt)
12
12
  [![Test Coverage](https://img.shields.io/codecov/c/github/yegor256/zache.svg)](https://codecov.io/github/yegor256/zache?branch=master)
13
13
  [![Hits-of-Code](https://hitsofcode.com/github/yegor256/zache)](https://hitsofcode.com/view/github/yegor256/zache)
14
14
 
15
- It's a simple Ruby gem for in-memory cache.
15
+ This is a simple Ruby gem for in-memory caching.
16
16
  Read [this blog post](https://www.yegor256.com/2019/02/05/zache.html)
17
- to understand what Zache is for.
17
+ to understand what Zache is designed for.
18
18
 
19
19
  First, install it:
20
20
 
21
21
  ```bash
22
- $ gem install zache
22
+ gem install zache
23
23
  ```
24
24
 
25
- Then, use it like this
25
+ Then, use it like this:
26
26
 
27
27
  ```ruby
28
28
  require 'zache'
29
29
  zache = Zache.new
30
30
  # Expires in 5 minutes
31
- v = zache.get(:count, lifetime: 5 * 60) { expensive() }
31
+ v = zache.get(:count, lifetime: 5 * 60) { expensive_calculation() }
32
32
  ```
33
33
 
34
+ If you omit the `lifetime` parameter, the key will never expire.
35
+
34
36
  By default `Zache` is thread-safe. It locks the entire cache on each
35
- `get` call. You turn that off by using `sync` argument:
37
+ `get` call. You can turn that off by using the `sync` argument:
36
38
 
37
39
  ```ruby
38
40
  zache = Zache.new(sync: false)
39
- v = zache.get(:count) { expensive() }
41
+ v = zache.get(:count) { expensive_calculation() }
40
42
  ```
41
43
 
42
- You may use "dirty" mode, which will return you an expired value, while
43
- calculation is waiting. Say, you have something in the cache, but it's
44
- expired. Then, you call `get` with a long running block. The thread waits,
45
- while another one calls `get` again. That second thread won't wait, but will
46
- receive what's left in the cache. This is a very convenient mode for situations
47
- when you don't really care about data accuracy, but performance is an issue.
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:
50
+
51
+ ```ruby
52
+ zache = Zache.new(dirty: true)
53
+ # Or enable dirty mode for a specific get call
54
+ value = zache.get(:key, dirty: true) { expensive_calculation() }
55
+ ```
56
+
57
+ The entire API is documented
58
+ [here](https://www.rubydoc.info/github/yegor256/zache/master/Zache).
59
+ Here are some additional useful methods:
60
+
61
+ ```ruby
62
+ # Check if a key exists
63
+ zache.exists?(:key)
48
64
 
49
- The entire API is documented [here](https://www.rubydoc.info/github/yegor256/zache/master/Zache)
50
- (there are many other convenient methods).
65
+ # Remove a key
66
+ zache.remove(:key)
51
67
 
52
- That's it.
68
+ # Remove all keys
69
+ zache.remove_all
70
+
71
+ # Remove keys that match a condition
72
+ zache.remove_by { |key| key.to_s.start_with?('temp_') }
73
+
74
+ # Clean up expired keys
75
+ zache.clean
76
+
77
+ # Check if cache is empty
78
+ zache.empty?
79
+ ```
53
80
 
54
81
  ## How to contribute
55
82
 
56
- Read [these guidelines](https://www.yegor256.com/2014/04/15/github-guidelines.html).
57
- Make sure you build is green before you contribute
58
- your pull request. You will need to have [Ruby](https://www.ruby-lang.org/en/) 2.3+ and
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
59
88
  [Bundler](https://bundler.io/) installed. Then:
60
89
 
61
- ```
62
- $ bundle update
63
- $ bundle exec rake
90
+ ```bash
91
+ bundle update
92
+ bundle exec rake
64
93
  ```
65
94
 
66
95
  If it's clean and you don't see any error messages, submit your pull request.
data/REUSE.toml ADDED
@@ -0,0 +1,31 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025 Yegor Bugayenko
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ version = 1
5
+ [[annotations]]
6
+ path = [
7
+ "**/*.csv",
8
+ "**/*.jpg",
9
+ "**/*.json",
10
+ "**/*.md",
11
+ "**/*.pdf",
12
+ "**/*.png",
13
+ "**/*.svg",
14
+ "**/*.txt",
15
+ "**/*.vm",
16
+ "**/.DS_Store",
17
+ "**/.gitignore",
18
+ "**/.pdd",
19
+ "**/CNAME",
20
+ "**/Gemfile.lock",
21
+ ".DS_Store",
22
+ ".gitattributes",
23
+ ".gitignore",
24
+ ".pdd",
25
+ "Gemfile.lock",
26
+ "README.md",
27
+ "renovate.json",
28
+ ]
29
+ precedence = "override"
30
+ SPDX-FileCopyrightText = "Copyright (c) 2018-2025 Yegor Bugayenko"
31
+ SPDX-License-Identifier = "MIT"
data/Rakefile CHANGED
@@ -1,25 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright (c) 2018-2023 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 NONINFRINGEMENT. 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
 
6
+ require 'os'
7
+ require 'qbash'
23
8
  require 'rubygems'
24
9
  require 'rake'
25
10
  require 'rake/clean'
@@ -34,7 +19,7 @@ def version
34
19
  Gem::Specification.load(Dir['*.gemspec'].first).version
35
20
  end
36
21
 
37
- task default: %i[clean test rubocop copyright]
22
+ task default: %i[clean test picks rubocop yard]
38
23
 
39
24
  require 'rake/testtask'
40
25
  desc 'Run all unit tests'
@@ -44,24 +29,24 @@ Rake::TestTask.new(:test) do |test|
44
29
  test.verbose = false
45
30
  end
46
31
 
47
- require 'rdoc/task'
48
- RDoc::Task.new do |rdoc|
49
- rdoc.main = 'README.md'
50
- rdoc.rdoc_dir = 'rdoc'
51
- rdoc.rdoc_files.include('README.md', 'lib/**/*.rb')
32
+ desc 'Run them via Ruby, one by one'
33
+ task :picks do
34
+ next if OS.windows?
35
+ %w[test lib].each do |d|
36
+ Dir["#{d}/**/*.rb"].each do |f|
37
+ qbash("bundle exec ruby #{Shellwords.escape(f)}", log: $stdout, env: { 'PICKS' => 'yes' })
38
+ end
39
+ end
40
+ end
41
+
42
+ require 'yard'
43
+ desc 'Build Yard documentation'
44
+ YARD::Rake::YardocTask.new do |t|
45
+ t.files = ['lib/**/*.rb']
52
46
  end
53
47
 
54
48
  require 'rubocop/rake_task'
55
49
  desc 'Run RuboCop on all directories'
56
50
  RuboCop::RakeTask.new(:rubocop) do |task|
57
51
  task.fail_on_error = true
58
- task.requires << 'rubocop-rspec'
59
- end
60
-
61
- task :copyright do
62
- sh "grep -q -r '2018-#{Date.today.strftime('%Y')}' \
63
- --include '*.rb' \
64
- --include '*.txt' \
65
- --include 'Rakefile' \
66
- ."
67
52
  end
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-2023 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-2023 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] = {
@@ -155,33 +206,67 @@ class Zache
155
206
  end
156
207
  end
157
208
 
158
- # Removes the value from the hash, by the provied key. If the key is absent
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?(value) } }
249
+ synchronized do
250
+ size_before = @hash.size
251
+ @hash.delete_if { |key, _value| expired?(key) }
252
+ size_before - @hash.size
253
+ end
254
+ end
255
+
256
+ # Returns TRUE if the cache is empty, FALSE otherwise.
257
+ #
258
+ # @return [Boolean] True if the cache is empty
259
+ def empty?
260
+ @hash.empty?
181
261
  end
182
262
 
183
263
  private
184
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
185
270
  def calc(key, lifetime)
186
271
  rec = @hash[key]
187
272
  rec = nil if expired?(key)
@@ -195,11 +280,15 @@ class Zache
195
280
  @hash[key][:value]
196
281
  end
197
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
198
287
  def synchronized(&block)
199
288
  if @sync
200
289
  @mutex.synchronize(&block)
201
290
  else
202
- block.call
291
+ yield
203
292
  end
204
293
  end
205
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-2023 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]