coverband 6.0.3.rc.4 → 6.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE/bug_report.md +0 -6
- data/.github/workflows/main.yml +1 -1
- data/README.md +25 -1
- data/changes.md +22 -4
- data/coverband.gemspec +4 -2
- data/lib/coverband/adapters/hash_redis_store.rb +42 -22
- data/lib/coverband/adapters/memcached_store.rb +89 -0
- data/lib/coverband/collectors/route_tracker.rb +9 -6
- data/lib/coverband/configuration.rb +26 -9
- data/lib/coverband/reporters/json_report.rb +1 -1
- data/lib/coverband/reporters/web.rb +1 -0
- data/lib/coverband/utils/html_formatter.rb +3 -3
- data/lib/coverband/utils/source_file.rb +17 -2
- data/lib/coverband/version.rb +1 -1
- data/lua/lib/persist-coverage.lua +3 -0
- data/public/application.js +16 -15
- data/public/dependencies.js +3 -3
- data/test/coverband/adapters/hash_redis_store_test.rb +64 -14
- data/test/coverband/collectors/route_tracker_test.rb +41 -2
- data/test/test_helper.rb +1 -0
- data/views/source_file.erb +1 -0
- metadata +26 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ed2b063179cbc773c9b3b8fab3403820f9855c32a9de007c347b58873eecf13a
|
4
|
+
data.tar.gz: 7968b26ac22e3209b0bf2a9baabdfe107ed9029a0d4557eea5b80c22439e3278
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3bccf2dbc8e92d5980365a99b5ae2852fd8e04d224202a58e3a8490991b88eb88a881fbf620e4ea60d66cd4714a70f11a8015ebd96aa772a65a35e1fb642ea24
|
7
|
+
data.tar.gz: 784f5e0905989f865199261270072c397e520b2269ed387f0ecd1276ef5a7395fc5d4de7ec9990aceeda1cc883778bcfcf4c7c2260c792e81557914298778d87
|
@@ -28,11 +28,5 @@ If applicable, add screenshots to help explain your problem.
|
|
28
28
|
- Browser [e.g. chrome, safari]
|
29
29
|
- Version [e.g. 22]
|
30
30
|
|
31
|
-
**Smartphone (please complete the following information):**
|
32
|
-
- Device: [e.g. iPhone6]
|
33
|
-
- OS: [e.g. iOS8.1]
|
34
|
-
- Browser [e.g. stock browser, safari]
|
35
|
-
- Version [e.g. 22]
|
36
|
-
|
37
31
|
**Additional context**
|
38
32
|
Add any other context about the problem here.
|
data/.github/workflows/main.yml
CHANGED
@@ -26,7 +26,7 @@ jobs:
|
|
26
26
|
runs-on: ${{ matrix.os }}-latest
|
27
27
|
steps:
|
28
28
|
- uses: actions/checkout@v4
|
29
|
-
- uses: supercharge/redis-github-action@1.
|
29
|
+
- uses: supercharge/redis-github-action@1.8.0
|
30
30
|
with:
|
31
31
|
redis-version: ${{ matrix.redis-version }}
|
32
32
|
- uses: ruby/setup-ruby@v1
|
data/README.md
CHANGED
@@ -101,6 +101,22 @@ run ActionController::Dispatcher.new
|
|
101
101
|
|
102
102
|
This triggers coverage collection on the current webserver process. Useful in development but confusing in production environments where many ruby processes are usually running.
|
103
103
|
|
104
|
+
#### Interpreting results
|
105
|
+
|
106
|
+
The columns in the web UI are as follows:
|
107
|
+
|
108
|
+
- **% covered** - Percentage of total relevant lines covered
|
109
|
+
- **% runtime** - Percentage of the runtime lines covered where runtime lines are lines that are hit after the application has been eagerly loaded
|
110
|
+
- **Lines** - Total lines in the file including lines unreachable or uncover-able. An unreachable line would be an empty line with no code, comments, or `end` statements.
|
111
|
+
- **Relevant lines** - Lines that are coverable, i.e. not empty
|
112
|
+
- **Lines runtime** - Total lines minus uncoverable lines minus the lines that are only hit during eager loading of application
|
113
|
+
- **Lines missed** - Relevant lines not covered
|
114
|
+
- **Avg hits/line** - Total of coverage to the file divided by relevant lines.
|
115
|
+
|
116
|
+
When viewing an individual file, a line of code such as a class or method definition may appear green because it is eager loaded by the application, but still not be hit at all in runtime by actual users.
|
117
|
+
|
118
|
+
![example of a file with lines not hit at runtime](https://user-images.githubusercontent.com/96786/63541229-aa98a580-c4eb-11e9-8eb8-c004fe1369db.png)
|
119
|
+
|
104
120
|
### Mounting as a Rack App
|
105
121
|
|
106
122
|
Coverband comes with a mountable rack app for viewing reports. For Rails this can be done in `config/routes.rb` with:
|
@@ -191,7 +207,7 @@ Do you use figaro, mc-settings, dotenv or something else to inject environment v
|
|
191
207
|
For example if you use dotenv, you need to do this, see https://github.com/bkeepers/dotenv#note-on-load-order
|
192
208
|
|
193
209
|
```
|
194
|
-
gem 'dotenv-rails', require: 'dotenv/
|
210
|
+
gem 'dotenv-rails', require: 'dotenv/load'
|
195
211
|
gem 'coverband'
|
196
212
|
gem 'other-gem-that-requires-env-variables'
|
197
213
|
```
|
@@ -222,6 +238,14 @@ This feature is enabled by default. To stop this feature, disable the feature in
|
|
222
238
|
|
223
239
|
![image](https://raw.github.com/danmayer/coverband/master/docs/coverband_view_tracker.png)
|
224
240
|
|
241
|
+
### Hiding settings
|
242
|
+
|
243
|
+
Coverband provides a view of all of its current settings. Sometimes you might want to hide this view,
|
244
|
+
such as when sharing coverband data with a large number of developers of varying trust levels.
|
245
|
+
You can disable the settings view like so:
|
246
|
+
|
247
|
+
`config.hide_settings = false`
|
248
|
+
|
225
249
|
### Fixing Coverage Only Shows Loading Hits
|
226
250
|
|
227
251
|
If all your coverage is being counted as loading or eager_loading coverage, and nothing is showing as runtime Coverage the initialization hook failed for some reason. The most likely reason for this issue is manually calling `eager_load!` on some Plugin/Gem. If you or a plugin is altering the Rails initialization process, you can manually flip Coverband to runtime coverage by calling these two lines, in an `after_initialize` block, in `application.rb`.
|
data/changes.md
CHANGED
@@ -1,3 +1,25 @@
|
|
1
|
+
### Coverband 6.1.1
|
2
|
+
|
3
|
+
* Performance fix making paged report loading 10X faster
|
4
|
+
|
5
|
+
### Coverband 6.1.0
|
6
|
+
|
7
|
+
This release has a number of smaller fixes and improvements. It includes a sizable refactoring around the UI which should simplify improvements going forward. This release is mostly targetting large projects with 6K+ ruby files, use the new `config.paged_reporting = true` option with the HashRedisStore to enable paged reporting for large projects. The HashRedisStore now also includes the last time a line in a file was executed.
|
8
|
+
|
9
|
+
* Thanks to @FeLvi-zzz for the last time accessed support for the Hash Redis Store
|
10
|
+
* Thanks to @alpaca-tc for the improvements on the route tracker
|
11
|
+
* Thanks to @ydah for typo fixes, doc updates, adding ruby 3.3 to build matrix, improvements on CI, standardrb fixes
|
12
|
+
* Thanks to @trivett, @khaled-badenjki, @IsabelleLePivain for improved docs
|
13
|
+
* Thanks to @prastamaha for the memcached adapter
|
14
|
+
* Thanks to @ursm for a yaml fix
|
15
|
+
* Thanks to @Drowze for a layered cache approach for perf improvements
|
16
|
+
* Thanks to @vs37559 for a sinatra pandrino fix
|
17
|
+
* This release addresses large projects and adds in paged reporting
|
18
|
+
* to ensure even on projects with 10K+ files it can load on heroku under the 30s timeout
|
19
|
+
* only supports HashRedis store
|
20
|
+
* faster UI for web UI in general which should be noticable on non paged reports
|
21
|
+
* reduce redis calls
|
22
|
+
|
1
23
|
### Coverband 6.0.2
|
2
24
|
|
3
25
|
* thanks makicamel for improved deferred eager loading
|
@@ -11,16 +33,12 @@
|
|
11
33
|
|
12
34
|
### Coverband 6.0.0
|
13
35
|
|
14
|
-
__NOTE: I ended up having 5.2.6 in various RCs for a long time, mostly because I had some breaking changes that were related to dropping support for old versions of Ruby and Rails__
|
15
|
-
|
16
36
|
* The 6.0.0 release is all that was the 6 different RC releases of 5.2.6
|
17
37
|
* Added Rails test matrix to github actions to test on all the supported versions
|
18
38
|
- Rails: 6.0.x, 6.1.x, 7.0.x, 7.1.x
|
19
39
|
|
20
40
|
### Coverband 5.2.6
|
21
41
|
|
22
|
-
__NOTE: the current RCs include below, but this might turn into coverband 6.0__
|
23
|
-
|
24
42
|
- add support for translation keys
|
25
43
|
- refactor non Coverage.so based trackers
|
26
44
|
- adds CSP report support (thanks @jwg2s)
|
data/coverband.gemspec
CHANGED
@@ -36,6 +36,7 @@ Gem::Specification.new do |spec|
|
|
36
36
|
spec.add_development_dependency "memory_profiler"
|
37
37
|
# breaking change in minitest and mocha...
|
38
38
|
# note: we are also adding 'spy' as mocha doesn't want us to spy on redis calls...
|
39
|
+
spec.add_development_dependency "spy"
|
39
40
|
# ^^^ probably need a large test cleanup refactor
|
40
41
|
spec.add_development_dependency "minitest", "= 5.18.1"
|
41
42
|
spec.add_development_dependency "minitest-fork_executor"
|
@@ -45,8 +46,9 @@ Gem::Specification.new do |spec|
|
|
45
46
|
spec.add_development_dependency "rack-test"
|
46
47
|
spec.add_development_dependency "rake"
|
47
48
|
spec.add_development_dependency "resque"
|
48
|
-
spec.add_development_dependency "standard", "
|
49
|
-
|
49
|
+
spec.add_development_dependency "standard", "= 1.34.0"
|
50
|
+
# breaking changes in various rubocop versions
|
51
|
+
spec.add_development_dependency "rubocop", "= 1.60.0"
|
50
52
|
|
51
53
|
spec.add_development_dependency "coveralls"
|
52
54
|
# minitest-profile is not compatible with Rails 7.1.0 setup... dropping it for now
|
@@ -116,8 +116,8 @@ module Coverband
|
|
116
116
|
|
117
117
|
def supported?
|
118
118
|
Gem::Version.new(@redis.info["redis_version"]) >= Gem::Version.new("2.6.0")
|
119
|
-
rescue Redis::CannotConnectError =>
|
120
|
-
Coverband.configuration.logger.info "Redis is not available (#{
|
119
|
+
rescue Redis::CannotConnectError => e
|
120
|
+
Coverband.configuration.logger.info "Redis is not available (#{e}), Coverband not configured"
|
121
121
|
Coverband.configuration.logger.info "If this is a setup task like assets:precompile feel free to ignore"
|
122
122
|
end
|
123
123
|
|
@@ -149,7 +149,7 @@ module Coverband
|
|
149
149
|
updated_time = (type == Coverband::EAGER_TYPE) ? nil : report_time
|
150
150
|
keys = []
|
151
151
|
report.each_slice(@save_report_batch_size) do |slice|
|
152
|
-
files_data = slice.map
|
152
|
+
files_data = slice.map do |(file, data)|
|
153
153
|
relative_file = @relative_file_converter.convert(file)
|
154
154
|
file_hash = file_hash(relative_file)
|
155
155
|
key = key(relative_file, file_hash: file_hash)
|
@@ -162,12 +162,12 @@ module Coverband
|
|
162
162
|
report_time: report_time,
|
163
163
|
updated_time: updated_time
|
164
164
|
)
|
165
|
-
|
165
|
+
end
|
166
166
|
next unless files_data.any?
|
167
167
|
|
168
168
|
arguments_key = [@redis_namespace, SecureRandom.uuid].compact.join(".")
|
169
169
|
@redis.set(arguments_key, {ttl: @ttl, files_data: files_data}.to_json, ex: JSON_PAYLOAD_EXPIRATION)
|
170
|
-
@redis.evalsha(hash_incr_script, [arguments_key])
|
170
|
+
@redis.evalsha(hash_incr_script, [arguments_key], [report_time])
|
171
171
|
end
|
172
172
|
@redis.sadd(files_key, keys) if keys.any?
|
173
173
|
end
|
@@ -182,7 +182,9 @@ module Coverband
|
|
182
182
|
elsif opts[:filename]
|
183
183
|
type_key_prefix = key_prefix(local_type)
|
184
184
|
# NOTE: a better way to extract filename from key would be better
|
185
|
-
files_set(local_type).select
|
185
|
+
files_set(local_type).select do |cache_key|
|
186
|
+
cache_key.sub(type_key_prefix, "").match(short_name(opts[:filename]))
|
187
|
+
end || {}
|
186
188
|
else
|
187
189
|
files_set(local_type)
|
188
190
|
end
|
@@ -213,13 +215,13 @@ module Coverband
|
|
213
215
|
end
|
214
216
|
end
|
215
217
|
|
216
|
-
def coverage_for_types(
|
218
|
+
def coverage_for_types(_types, opts = {})
|
217
219
|
page_size = opts[:page_size] || 250
|
218
|
-
|
219
|
-
local_type = Coverband::RUNTIME_TYPE
|
220
220
|
hash_data = {}
|
221
221
|
|
222
|
-
runtime_file_set = files_set(
|
222
|
+
runtime_file_set = files_set(Coverband::RUNTIME_TYPE)
|
223
|
+
@cached_file_count = runtime_file_set.length
|
224
|
+
runtime_file_set = runtime_file_set.each_slice(page_size).to_a[opts[:page] - 1] || []
|
223
225
|
|
224
226
|
hash_data[Coverband::RUNTIME_TYPE] = runtime_file_set.each_slice(page_size).flat_map do |key_batch|
|
225
227
|
@redis.pipelined do |pipeline|
|
@@ -229,14 +231,14 @@ module Coverband
|
|
229
231
|
end
|
230
232
|
end
|
231
233
|
|
234
|
+
# NOTE: This is kind of hacky, we find all the matching eager loading data
|
235
|
+
# for current page of runtime data.
|
232
236
|
eager_key_pre = key_prefix(Coverband::EAGER_TYPE)
|
233
237
|
runtime_key_pre = key_prefix(Coverband::RUNTIME_TYPE)
|
234
|
-
matched_file_set =
|
235
|
-
.
|
236
|
-
|
237
|
-
|
238
|
-
}
|
239
|
-
} || []
|
238
|
+
matched_file_set = runtime_file_set.map do |runtime_key|
|
239
|
+
runtime_key.sub(runtime_key_pre, eager_key_pre)
|
240
|
+
end
|
241
|
+
|
240
242
|
hash_data[Coverband::EAGER_TYPE] = matched_file_set.each_slice(page_size).flat_map do |key_batch|
|
241
243
|
@redis.pipelined do |pipeline|
|
242
244
|
key_batch.each do |key|
|
@@ -244,6 +246,7 @@ module Coverband
|
|
244
246
|
end
|
245
247
|
end
|
246
248
|
end
|
249
|
+
|
247
250
|
hash_data[Coverband::RUNTIME_TYPE] = hash_data[Coverband::RUNTIME_TYPE].each_with_object({}) do |data_from_redis, hash|
|
248
251
|
add_coverage_for_file(data_from_redis, hash)
|
249
252
|
end
|
@@ -255,13 +258,17 @@ module Coverband
|
|
255
258
|
|
256
259
|
def short_name(filename)
|
257
260
|
filename.sub(/^#{Coverband.configuration.root}/, ".")
|
258
|
-
.gsub(%r{
|
261
|
+
.gsub(%r{^\./}, "")
|
259
262
|
end
|
260
263
|
|
261
264
|
def file_count(local_type = nil)
|
262
265
|
files_set(local_type).count { |filename| !Coverband.configuration.ignore.any? { |i| filename.match(i) } }
|
263
266
|
end
|
264
267
|
|
268
|
+
def cached_file_count
|
269
|
+
@cached_file_count ||= file_count(Coverband::RUNTIME_TYPE)
|
270
|
+
end
|
271
|
+
|
265
272
|
def raw_store
|
266
273
|
@redis
|
267
274
|
end
|
@@ -283,9 +290,14 @@ module Coverband
|
|
283
290
|
return unless file_hash(file) == data_from_redis[FILE_HASH]
|
284
291
|
|
285
292
|
data = coverage_data_from_redis(data_from_redis)
|
286
|
-
|
287
|
-
hash[file]
|
288
|
-
|
293
|
+
timedata = coverage_time_data_from_redis(data_from_redis)
|
294
|
+
hash[file] = data_from_redis.select do |meta_data_key, _value|
|
295
|
+
META_DATA_KEYS.include?(meta_data_key)
|
296
|
+
end.merge!("data" => data, "timedata" => timedata)
|
297
|
+
hash[file][LAST_UPDATED_KEY] =
|
298
|
+
(hash[file][LAST_UPDATED_KEY].nil? || hash[file][LAST_UPDATED_KEY] == "") ? nil : hash[file][LAST_UPDATED_KEY].to_i
|
299
|
+
hash[file].merge!(LAST_UPDATED_KEY => hash[file][LAST_UPDATED_KEY],
|
300
|
+
FIRST_UPDATED_KEY => hash[file][FIRST_UPDATED_KEY].to_i)
|
289
301
|
end
|
290
302
|
|
291
303
|
def coverage_data_from_redis(data_from_redis)
|
@@ -296,10 +308,18 @@ module Coverband
|
|
296
308
|
end
|
297
309
|
end
|
298
310
|
|
311
|
+
def coverage_time_data_from_redis(data_from_redis)
|
312
|
+
max = data_from_redis[FILE_LENGTH_KEY].to_i - 1
|
313
|
+
Array.new(max + 1) do |index|
|
314
|
+
unixtime = data_from_redis["#{index}_last_posted"]
|
315
|
+
unixtime.nil? ? nil : Time.at(unixtime.to_i)
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
299
319
|
def script_input(key:, file:, file_hash:, data:, report_time:, updated_time:)
|
300
|
-
coverage_data = data.each_with_index.each_with_object({})
|
320
|
+
coverage_data = data.each_with_index.each_with_object({}) do |(coverage, index), hash|
|
301
321
|
hash[index] = coverage if coverage
|
302
|
-
|
322
|
+
end
|
303
323
|
meta = {
|
304
324
|
first_updated_at: report_time,
|
305
325
|
file: file,
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Coverband
|
4
|
+
module Adapters
|
5
|
+
class MemcachedStore < Base
|
6
|
+
STORAGE_FORMAT_VERSION = "coverband_3_2"
|
7
|
+
|
8
|
+
attr_reader :memcached_namespace
|
9
|
+
|
10
|
+
def initialize(memcached, opts = {})
|
11
|
+
super()
|
12
|
+
@memcached = memcached
|
13
|
+
@memcached_namespace = opts[:memcached_namespace]
|
14
|
+
@format_version = STORAGE_FORMAT_VERSION
|
15
|
+
@keys = {}
|
16
|
+
Coverband::TYPES.each do |type|
|
17
|
+
@keys[type] = [@format_version, @memcached_namespace, type].compact.join(".")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def clear!
|
22
|
+
Coverband::TYPES.each do |type|
|
23
|
+
@memcached.delete(type_base_key(type))
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def clear_file!(filename)
|
28
|
+
Coverband::TYPES.each do |type|
|
29
|
+
data = coverage(type)
|
30
|
+
data.delete(filename)
|
31
|
+
save_coverage(data, type)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def size
|
36
|
+
@memcached.read(base_key) ? @memcached.read(base_key).bytesize : "N/A"
|
37
|
+
end
|
38
|
+
|
39
|
+
def migrate!
|
40
|
+
raise NotImplementedError, "MemcachedStore doesn't support migrations"
|
41
|
+
end
|
42
|
+
|
43
|
+
def type=(type)
|
44
|
+
super
|
45
|
+
reset_base_key
|
46
|
+
end
|
47
|
+
|
48
|
+
def coverage(local_type = nil, opts = {})
|
49
|
+
local_type ||= opts.key?(:override_type) ? opts[:override_type] : type
|
50
|
+
data = memcached.read(type_base_key(local_type))
|
51
|
+
data = data ? JSON.parse(data) : {}
|
52
|
+
data.delete_if { |file_path, file_data| file_hash(file_path) != file_data["file_hash"] } unless opts[:skip_hash_check]
|
53
|
+
data
|
54
|
+
end
|
55
|
+
|
56
|
+
def save_report(report)
|
57
|
+
data = report.dup
|
58
|
+
data = merge_reports(data, coverage(nil, skip_hash_check: true))
|
59
|
+
save_coverage(data)
|
60
|
+
end
|
61
|
+
|
62
|
+
def raw_store
|
63
|
+
raise NotImplementedError, "MemcachedStore doesn't support raw_store"
|
64
|
+
end
|
65
|
+
|
66
|
+
attr_reader :memcached
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def reset_base_key
|
71
|
+
@base_key = nil
|
72
|
+
end
|
73
|
+
|
74
|
+
def base_key
|
75
|
+
@base_key ||= [@format_version, @memcached_namespace, type].compact.join(".")
|
76
|
+
end
|
77
|
+
|
78
|
+
def type_base_key(local_type)
|
79
|
+
@keys[local_type]
|
80
|
+
end
|
81
|
+
|
82
|
+
def save_coverage(data, local_type = nil)
|
83
|
+
local_type ||= type
|
84
|
+
key = type_base_key(local_type)
|
85
|
+
memcached.write(key, data.to_json)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -25,7 +25,10 @@ module Coverband
|
|
25
25
|
# and ensure high performance
|
26
26
|
###
|
27
27
|
def track_key(payload)
|
28
|
-
route = if payload
|
28
|
+
route = if payload.key?(:location)
|
29
|
+
# For redirect.action_dispatch
|
30
|
+
return unless Coverband.configuration.track_redirect_routes
|
31
|
+
|
29
32
|
{
|
30
33
|
controller: nil,
|
31
34
|
action: nil,
|
@@ -33,6 +36,7 @@ module Coverband
|
|
33
36
|
verb: payload[:request].method
|
34
37
|
}
|
35
38
|
else
|
39
|
+
# For start_processing.action_controller
|
36
40
|
{
|
37
41
|
controller: payload[:params]["controller"],
|
38
42
|
action: payload[:action],
|
@@ -40,11 +44,10 @@ module Coverband
|
|
40
44
|
verb: payload[:method]
|
41
45
|
}
|
42
46
|
end
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
end
|
47
|
+
|
48
|
+
if newly_seen_key?(route)
|
49
|
+
@logged_keys << route
|
50
|
+
@keys_to_record << route if track_key?(route)
|
48
51
|
end
|
49
52
|
end
|
50
53
|
|
@@ -1,6 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Coverband
|
4
|
+
###
|
5
|
+
# Configuration parsing and options for the coverband gem.
|
6
|
+
###
|
4
7
|
class Configuration
|
5
8
|
attr_accessor :root_paths, :root,
|
6
9
|
:verbose,
|
@@ -9,9 +12,9 @@ module Coverband
|
|
9
12
|
:test_env, :web_enable_clear, :gem_details, :web_debug, :report_on_exit,
|
10
13
|
:simulate_oneshot_lines_coverage,
|
11
14
|
:view_tracker, :defer_eager_loading_data,
|
12
|
-
:track_routes, :route_tracker,
|
15
|
+
:track_routes, :track_redirect_routes, :route_tracker,
|
13
16
|
:track_translations, :translations_tracker,
|
14
|
-
:trackers, :csp_policy
|
17
|
+
:trackers, :csp_policy, :hide_settings
|
15
18
|
|
16
19
|
attr_writer :logger, :s3_region, :s3_bucket, :s3_access_key_id,
|
17
20
|
:s3_secret_access_key, :password, :api_key, :service_url, :coverband_timeout, :service_dev_mode,
|
@@ -74,6 +77,7 @@ module Coverband
|
|
74
77
|
@track_views = true
|
75
78
|
@view_tracker = nil
|
76
79
|
@track_routes = false
|
80
|
+
@track_redirect_routes = true
|
77
81
|
@route_tracker = nil
|
78
82
|
@track_translations = false
|
79
83
|
@translations_tracker = nil
|
@@ -86,6 +90,7 @@ module Coverband
|
|
86
90
|
@all_root_patterns = nil
|
87
91
|
@password = nil
|
88
92
|
@csp_policy = false
|
93
|
+
@hide_settings = false
|
89
94
|
|
90
95
|
# coverband service settings
|
91
96
|
@api_key = nil
|
@@ -131,8 +136,8 @@ module Coverband
|
|
131
136
|
trackers << Coverband.configuration.view_tracker
|
132
137
|
end
|
133
138
|
trackers.each { |tracker| tracker.railtie! }
|
134
|
-
rescue Redis::CannotConnectError =>
|
135
|
-
Coverband.configuration.logger.info "Redis is not available (#{
|
139
|
+
rescue Redis::CannotConnectError => e
|
140
|
+
Coverband.configuration.logger.info "Redis is not available (#{e}), Coverband not configured"
|
136
141
|
Coverband.configuration.logger.info "If this is a setup task like assets:precompile feel free to ignore"
|
137
142
|
end
|
138
143
|
|
@@ -169,7 +174,10 @@ module Coverband
|
|
169
174
|
|
170
175
|
def store
|
171
176
|
@store ||= if service?
|
172
|
-
|
177
|
+
if ENV["COVERBAND_REDIS_URL"]
|
178
|
+
raise "invalid configuration: unclear default store coverband expects either api_key or redis_url"
|
179
|
+
end
|
180
|
+
|
173
181
|
require "coverband/adapters/web_service_store"
|
174
182
|
Coverband::Adapters::WebServiceStore.new(service_url)
|
175
183
|
else
|
@@ -179,8 +187,14 @@ module Coverband
|
|
179
187
|
|
180
188
|
def store=(store)
|
181
189
|
raise "Pass in an instance of Coverband::Adapters" unless store.is_a?(Coverband::Adapters::Base)
|
182
|
-
|
183
|
-
|
190
|
+
if api_key && store.class.to_s != "Coverband::Adapters::WebServiceStore"
|
191
|
+
raise "invalid configuration: only coverband service expects an API Key"
|
192
|
+
end
|
193
|
+
if ENV["COVERBAND_REDIS_URL"] &&
|
194
|
+
defined?(::Coverband::Adapters::WebServiceStore) &&
|
195
|
+
store.instance_of?(::Coverband::Adapters::WebServiceStore)
|
196
|
+
raise "invalid configuration: coverband service shouldn't have redis url set"
|
197
|
+
end
|
184
198
|
|
185
199
|
@store = store
|
186
200
|
end
|
@@ -240,7 +254,10 @@ module Coverband
|
|
240
254
|
end
|
241
255
|
|
242
256
|
def use_oneshot_lines_coverage=(value)
|
243
|
-
|
257
|
+
unless one_shot_coverage_implemented_in_ruby_version? || !value
|
258
|
+
raise(StandardError,
|
259
|
+
"One shot line coverage is only available in ruby >= 2.6")
|
260
|
+
end
|
244
261
|
|
245
262
|
@use_oneshot_lines_coverage = value
|
246
263
|
end
|
@@ -321,7 +338,7 @@ module Coverband
|
|
321
338
|
end
|
322
339
|
|
323
340
|
def track_gems=(_value)
|
324
|
-
puts "gem tracking is deprecated, setting this will be ignored"
|
341
|
+
puts "gem tracking is deprecated, setting this will be ignored & eventually removed"
|
325
342
|
end
|
326
343
|
|
327
344
|
private
|
@@ -72,7 +72,7 @@ module Coverband
|
|
72
72
|
data[:covered_strength].to_s
|
73
73
|
]
|
74
74
|
end
|
75
|
-
filesreported = store.
|
75
|
+
filesreported = store.cached_file_count
|
76
76
|
data["iTotalRecords"] = filesreported
|
77
77
|
data["iTotalDisplayRecords"] = filesreported
|
78
78
|
data["aaData"] = row_data
|
@@ -155,11 +155,11 @@ module Coverband
|
|
155
155
|
Digest::SHA1.hexdigest(source_file.filename)
|
156
156
|
end
|
157
157
|
|
158
|
-
def timeago(time)
|
159
|
-
if time
|
158
|
+
def timeago(time, err_msg = "Not Available")
|
159
|
+
if time.respond_to?(:iso8601)
|
160
160
|
"<abbr class=\"timeago\" title=\"#{time.iso8601}\">#{time.iso8601}</abbr>"
|
161
161
|
else
|
162
|
-
|
162
|
+
err_msg
|
163
163
|
end
|
164
164
|
end
|
165
165
|
|
@@ -25,13 +25,15 @@ module Coverband
|
|
25
25
|
attr_reader :coverage
|
26
26
|
# Whether this line was skipped
|
27
27
|
attr_reader :skipped
|
28
|
+
# The coverage data posted time for this line: either nil (never), nil (missed) or Time instance (last posted)
|
29
|
+
attr_reader :coverage_posted
|
28
30
|
|
29
31
|
# Lets grab some fancy aliases, shall we?
|
30
32
|
alias source src
|
31
33
|
alias line line_number
|
32
34
|
alias number line_number
|
33
35
|
|
34
|
-
def initialize(src, line_number, coverage)
|
36
|
+
def initialize(src, line_number, coverage, coverage_posted = nil)
|
35
37
|
raise ArgumentError, "Only String accepted for source" unless src.is_a?(String)
|
36
38
|
raise ArgumentError, "Only Integer accepted for line_number" unless line_number.is_a?(Integer)
|
37
39
|
raise ArgumentError, "Only Integer and nil accepted for coverage" unless coverage.is_a?(Integer) || coverage.nil?
|
@@ -40,6 +42,7 @@ module Coverband
|
|
40
42
|
@line_number = line_number
|
41
43
|
@coverage = coverage
|
42
44
|
@skipped = false
|
45
|
+
@coverage_posted = coverage_posted
|
43
46
|
end
|
44
47
|
|
45
48
|
# Returns true if this is a line that should have been covered, but was not
|
@@ -82,6 +85,8 @@ module Coverband
|
|
82
85
|
attr_reader :filename
|
83
86
|
# The array of coverage data received from the Coverage.result
|
84
87
|
attr_reader :coverage
|
88
|
+
# The array of coverage timedata received from the Coverage.result
|
89
|
+
attr_reader :coverage_posted
|
85
90
|
|
86
91
|
# the date this version of the file first started to record coverage
|
87
92
|
attr_reader :first_updated_at
|
@@ -96,6 +101,7 @@ module Coverband
|
|
96
101
|
@runtime_relavant_lines = nil
|
97
102
|
if file_data.is_a?(Hash)
|
98
103
|
@coverage = file_data["data"]
|
104
|
+
@coverage_posted = file_data["timedata"] || [] # NOTE: only implement timedata for HashRedisStore
|
99
105
|
@first_updated_at = @last_updated_at = NOT_AVAILABLE
|
100
106
|
@first_updated_at = Time.at(file_data["first_updated_at"]) if file_data["first_updated_at"]
|
101
107
|
@last_updated_at = Time.at(file_data["last_updated_at"]) if file_data["last_updated_at"]
|
@@ -139,7 +145,12 @@ module Coverband
|
|
139
145
|
coverage_exceeding_source_warn if coverage.size > src.size
|
140
146
|
|
141
147
|
lines = src.map.with_index(1) { |src, i|
|
142
|
-
Coverband::Utils::SourceFile::Line.new(
|
148
|
+
Coverband::Utils::SourceFile::Line.new(
|
149
|
+
src,
|
150
|
+
i,
|
151
|
+
never_loaded ? 0 : coverage[i - 1],
|
152
|
+
(never_loaded || !coverage_posted.is_a?(Array)) ? nil : coverage_posted[i - 1]
|
153
|
+
)
|
143
154
|
}
|
144
155
|
|
145
156
|
process_skipped_lines(lines)
|
@@ -200,6 +211,10 @@ module Coverband
|
|
200
211
|
lines[index]&.coverage
|
201
212
|
end
|
202
213
|
|
214
|
+
def line_coverage_posted(index)
|
215
|
+
lines[index]&.coverage_posted
|
216
|
+
end
|
217
|
+
|
203
218
|
# Returns all lines that should have been, but were not covered
|
204
219
|
# as instances of SimpleCov::SourceFile::Line
|
205
220
|
def missed_lines
|
data/lib/coverband/version.rb
CHANGED
@@ -21,6 +21,9 @@ for _, file_data in ipairs(files_data) do
|
|
21
21
|
redis.call('HSETNX', hash_key, 'first_updated_at', first_updated_at)
|
22
22
|
for line, coverage in pairs(file_data.coverage) do
|
23
23
|
redis.call("HINCRBY", hash_key, line, coverage)
|
24
|
+
if coverage > 0 then
|
25
|
+
redis.call("HSET", hash_key, line .. "_last_posted", ARGV[1])
|
26
|
+
end
|
24
27
|
end
|
25
28
|
if ttl and ttl ~= cjson.null then
|
26
29
|
redis.call("EXPIRE", hash_key, ttl)
|
data/public/application.js
CHANGED
@@ -39,15 +39,16 @@ $(document).ready(function() {
|
|
39
39
|
// best docs on our version of datatables 1.7 https://datatables.net/beta/1.7/examples/server_side/server_side.html
|
40
40
|
if ($(".file_list.unsorted").length == 1) {
|
41
41
|
$(".dataTables_empty").html("loading...");
|
42
|
-
var current_rows = 0;
|
43
42
|
var total_rows = 0;
|
44
43
|
var page = 1;
|
44
|
+
var all_data = [];
|
45
45
|
|
46
46
|
// load and render page content before we start the loop
|
47
47
|
// perhaps move this into a datatable ready event
|
48
|
+
$(".dataTables_empty").html("loading...");
|
48
49
|
setTimeout(() => {
|
49
50
|
get_page(page);
|
50
|
-
},
|
51
|
+
}, 1200);
|
51
52
|
|
52
53
|
function get_page(page) {
|
53
54
|
$.ajax({
|
@@ -56,23 +57,23 @@ $(document).ready(function() {
|
|
56
57
|
dataType: 'json',
|
57
58
|
success: function(data) {
|
58
59
|
total_rows = data["iTotalRecords"];
|
59
|
-
|
60
|
-
|
61
|
-
// this 250 at the moment is synced to the 250 in the hash redis store
|
62
|
-
current_rows += 250; //data["aaData"].length;
|
63
|
-
$(".file_list.unsorted").dataTable().fnAddData(data["aaData"]);
|
60
|
+
all_data = all_data.concat(data["aaData"]);
|
61
|
+
$(".dataTables_empty").html("loading... on " + all_data.length + " of " + total_rows + " files");
|
64
62
|
page += 1;
|
65
|
-
|
66
|
-
setTimeout(() => {
|
67
|
-
if (window.auto_click_anchor && $(window.auto_click_anchor).length > 0) {
|
68
|
-
$(window.auto_click_anchor).click();
|
69
|
-
}
|
70
|
-
}, 50);
|
63
|
+
;
|
71
64
|
// the page less than 100 is to stop infinite loop in case of folks never clearing out old coverage reports
|
72
|
-
if (page <
|
65
|
+
if (page < 50 && all_data.length < total_rows) {
|
73
66
|
setTimeout(() => {
|
74
67
|
get_page(page);
|
75
|
-
},
|
68
|
+
}, 10);
|
69
|
+
} else {
|
70
|
+
$(".file_list.unsorted").dataTable().fnAddData(all_data);
|
71
|
+
// allow rendering to complete before we click the anchor
|
72
|
+
setTimeout(() => {
|
73
|
+
if (window.auto_click_anchor && $(window.auto_click_anchor).length > 0) {
|
74
|
+
$(window.auto_click_anchor).click();
|
75
|
+
}
|
76
|
+
}, 50)
|
76
77
|
}
|
77
78
|
}
|
78
79
|
});
|
data/public/dependencies.js
CHANGED
@@ -88,7 +88,7 @@ var hljs=new function(){function l(o){return o.replace(/&/gm,"&").replace(/<
|
|
88
88
|
xhrError: "This content failed to load.",
|
89
89
|
imgError: "This image failed to load.",
|
90
90
|
|
91
|
-
//
|
91
|
+
// accessibility
|
92
92
|
returnFocus: true,
|
93
93
|
trapFocus: true,
|
94
94
|
|
@@ -103,7 +103,7 @@ var hljs=new function(){function l(o){return o.replace(/&/gm,"&").replace(/<
|
|
103
103
|
return this.rel;
|
104
104
|
},
|
105
105
|
href: function() {
|
106
|
-
// using this.href would give the absolute url, when the href may have been
|
106
|
+
// using this.href would give the absolute url, when the href may have been intended as a selector (e.g. '#container')
|
107
107
|
return $(this).attr('href');
|
108
108
|
},
|
109
109
|
title: function() {
|
@@ -145,7 +145,7 @@ var hljs=new function(){function l(o){return o.replace(/&/gm,"&").replace(/<
|
|
145
145
|
$prev,
|
146
146
|
$close,
|
147
147
|
$groupControls,
|
148
|
-
$events = $('<a/>'), // $({}) would be
|
148
|
+
$events = $('<a/>'), // $({}) would be preferred, but there is an issue with jQuery 1.4.2
|
149
149
|
|
150
150
|
// Variables for cached values or use across multiple functions
|
151
151
|
settings,
|
@@ -57,7 +57,8 @@ class HashRedisStoreTest < Minitest::Test
|
|
57
57
|
"first_updated_at" => yesterday.to_i,
|
58
58
|
"last_updated_at" => yesterday.to_i,
|
59
59
|
"file_hash" => "abcd",
|
60
|
-
"data" => [0, 1, 2]
|
60
|
+
"data" => [0, 1, 2],
|
61
|
+
"timedata" => [nil, Time.at(yesterday.to_i), Time.at(yesterday.to_i)]
|
61
62
|
},
|
62
63
|
@store.coverage["./dog.rb"]
|
63
64
|
)
|
@@ -65,15 +66,12 @@ class HashRedisStoreTest < Minitest::Test
|
|
65
66
|
@store.save_report(
|
66
67
|
"app_path/dog.rb" => [1, 1, 0]
|
67
68
|
)
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
},
|
75
|
-
@store.coverage["./dog.rb"]
|
76
|
-
)
|
69
|
+
|
70
|
+
assert_equal("abcd", @store.coverage["./dog.rb"]["file_hash"])
|
71
|
+
assert_equal(today.to_i, @store.coverage["./dog.rb"]["last_updated_at"])
|
72
|
+
assert_equal(yesterday.to_i, @store.coverage["./dog.rb"]["first_updated_at"])
|
73
|
+
assert_equal([1, 2, 2], @store.coverage["./dog.rb"]["data"])
|
74
|
+
assert_equal([Time.at(today.to_i), Time.at(today.to_i), Time.at(yesterday.to_i)], @store.coverage["./dog.rb"]["timedata"])
|
77
75
|
end
|
78
76
|
|
79
77
|
def test_ttl_set
|
@@ -109,7 +107,8 @@ class HashRedisStoreTest < Minitest::Test
|
|
109
107
|
"first_updated_at" => current_time.to_i,
|
110
108
|
"last_updated_at" => current_time.to_i,
|
111
109
|
"file_hash" => "abcd",
|
112
|
-
"data" => [0, nil, 1, 2]
|
110
|
+
"data" => [0, nil, 1, 2],
|
111
|
+
"timedata" => [nil, nil, Time.at(current_time.to_i), Time.at(current_time.to_i)]
|
113
112
|
}, @store.coverage["./dog.rb"]
|
114
113
|
)
|
115
114
|
assert_equal [1, 2, 0, 1, 5], @store.coverage["./cat.rb"]["data"]
|
@@ -213,7 +212,8 @@ class HashRedisStoreTest < Minitest::Test
|
|
213
212
|
"first_updated_at" => yesterday.to_i,
|
214
213
|
"last_updated_at" => yesterday.to_i,
|
215
214
|
"file_hash" => "abcd",
|
216
|
-
"data" => [0, 1, 2]
|
215
|
+
"data" => [0, 1, 2],
|
216
|
+
"timedata" => [nil, Time.at(yesterday.to_i), Time.at(yesterday.to_i)]
|
217
217
|
},
|
218
218
|
@store.coverage["./dog.rb"]
|
219
219
|
)
|
@@ -225,7 +225,8 @@ class HashRedisStoreTest < Minitest::Test
|
|
225
225
|
"first_updated_at" => yesterday.to_i,
|
226
226
|
"last_updated_at" => yesterday.to_i,
|
227
227
|
"file_hash" => "abcd",
|
228
|
-
"data" => [0, 1, 2]
|
228
|
+
"data" => [0, 1, 2],
|
229
|
+
"timedata" => [nil, Time.at(yesterday.to_i), Time.at(yesterday.to_i)]
|
229
230
|
},
|
230
231
|
@store.coverage["./dog.rb"]
|
231
232
|
)
|
@@ -235,9 +236,58 @@ class HashRedisStoreTest < Minitest::Test
|
|
235
236
|
"first_updated_at" => yesterday.to_i,
|
236
237
|
"last_updated_at" => yesterday.to_i,
|
237
238
|
"file_hash" => "abcd",
|
238
|
-
"data" => [0, 2, 4]
|
239
|
+
"data" => [0, 2, 4],
|
240
|
+
"timedata" => [nil, Time.at(yesterday.to_i), Time.at(yesterday.to_i)]
|
239
241
|
},
|
240
242
|
@store.coverage["./dog.rb"]
|
241
243
|
)
|
242
244
|
end
|
245
|
+
|
246
|
+
def test_split_coverage
|
247
|
+
@store = Coverband::Adapters::HashRedisStore.new(
|
248
|
+
@redis,
|
249
|
+
redis_namespace: "coverband_test",
|
250
|
+
relative_file_converter: MockRelativeFileConverter
|
251
|
+
)
|
252
|
+
|
253
|
+
mock_file_hash
|
254
|
+
yesterday = DateTime.now.prev_day.to_time
|
255
|
+
mock_time(yesterday)
|
256
|
+
|
257
|
+
@store.type = :eager_loading
|
258
|
+
data = {
|
259
|
+
"app_path/dog.rb" => [0, nil, 1]
|
260
|
+
}
|
261
|
+
@store.save_report(data)
|
262
|
+
|
263
|
+
@store.type = :runtime
|
264
|
+
@store.save_report(
|
265
|
+
"app_path/dog.rb" => [0, 1, 2]
|
266
|
+
)
|
267
|
+
redis_pipelined = Spy.on(@redis, :pipelined).and_call_through
|
268
|
+
assert_equal(
|
269
|
+
{
|
270
|
+
runtime: {
|
271
|
+
"./dog.rb" => {
|
272
|
+
"first_updated_at" => yesterday.to_i,
|
273
|
+
"last_updated_at" => yesterday.to_i,
|
274
|
+
"file_hash" => "abcd",
|
275
|
+
"data" => [0, 1, 2],
|
276
|
+
"timedata" => [nil, Time.at(yesterday.to_i), Time.at(yesterday.to_i)]
|
277
|
+
}
|
278
|
+
},
|
279
|
+
eager_loading: {
|
280
|
+
"./dog.rb" => {
|
281
|
+
"first_updated_at" => yesterday.to_i,
|
282
|
+
"last_updated_at" => nil,
|
283
|
+
"file_hash" => "abcd",
|
284
|
+
"data" => [0, nil, 1],
|
285
|
+
"timedata" => [nil, nil, Time.at(yesterday.to_i)]
|
286
|
+
}
|
287
|
+
}
|
288
|
+
},
|
289
|
+
@store.split_coverage([Coverband::RUNTIME_TYPE, Coverband::EAGER_TYPE], {}, {page: 1})
|
290
|
+
)
|
291
|
+
assert_equal 2, redis_pipelined.calls.count
|
292
|
+
end
|
243
293
|
end
|
@@ -33,14 +33,32 @@ class RouterTrackerTest < Minitest::Test
|
|
33
33
|
tracker = Coverband::Collectors::RouteTracker.new(store: store, roots: "dir")
|
34
34
|
|
35
35
|
payload = {
|
36
|
-
request: Payload.new("path", "GET")
|
36
|
+
request: Payload.new("path", "GET"),
|
37
|
+
status: 302,
|
38
|
+
location: "https://coverband.dev/"
|
37
39
|
}
|
38
40
|
tracker.track_key(payload)
|
39
41
|
tracker.save_report
|
40
42
|
assert_equal [route_hash], tracker.logged_keys
|
41
43
|
end
|
42
44
|
|
43
|
-
test "track
|
45
|
+
test "track redirect routes when track_redirect_routes is false" do
|
46
|
+
Coverband.configuration.track_redirect_routes = false
|
47
|
+
|
48
|
+
store = fake_store
|
49
|
+
tracker = Coverband::Collectors::RouteTracker.new(store: store, roots: "dir")
|
50
|
+
|
51
|
+
payload = {
|
52
|
+
request: Payload.new("path", "GET"),
|
53
|
+
status: 302,
|
54
|
+
location: "https://coverband.dev/"
|
55
|
+
}
|
56
|
+
tracker.track_key(payload)
|
57
|
+
tracker.save_report
|
58
|
+
assert_equal [], tracker.logged_keys
|
59
|
+
end
|
60
|
+
|
61
|
+
test "track controller routes in Rails < 6.1" do
|
44
62
|
store = fake_store
|
45
63
|
route_hash = {controller: "some/controller", action: "index", url_path: nil, verb: "GET"}
|
46
64
|
store.raw_store.expects(:hset).with(tracker_key, route_hash.to_s, anything)
|
@@ -57,6 +75,27 @@ class RouterTrackerTest < Minitest::Test
|
|
57
75
|
assert_equal [route_hash], tracker.logged_keys
|
58
76
|
end
|
59
77
|
|
78
|
+
test "track controller routes in Rails >= 6.1" do
|
79
|
+
store = fake_store
|
80
|
+
route_hash = {controller: "some/controller", action: "index", url_path: nil, verb: "GET"}
|
81
|
+
store.raw_store.expects(:hset).with(tracker_key, route_hash.to_s, anything)
|
82
|
+
tracker = Coverband::Collectors::RouteTracker.new(store: store, roots: "dir")
|
83
|
+
payload = {
|
84
|
+
params: {
|
85
|
+
"controller" => "some/controller",
|
86
|
+
"action" => "index"
|
87
|
+
},
|
88
|
+
controller: "SomeController",
|
89
|
+
action: "index",
|
90
|
+
path: "path",
|
91
|
+
method: "GET",
|
92
|
+
request: Payload.new("path", "GET")
|
93
|
+
}
|
94
|
+
tracker.track_key(payload)
|
95
|
+
tracker.save_report
|
96
|
+
assert_equal [route_hash], tracker.logged_keys
|
97
|
+
end
|
98
|
+
|
60
99
|
test "report used routes" do
|
61
100
|
store = fake_store
|
62
101
|
route_hash = {controller: "some/controller", action: "index", url_path: nil, verb: "GET"}
|
data/test/test_helper.rb
CHANGED
data/views/source_file.erb
CHANGED
@@ -45,6 +45,7 @@
|
|
45
45
|
runtime:
|
46
46
|
<%= result.file_with_type(source_file, Coverband::RUNTIME_TYPE)&.line_coverage(index) || 0 %>
|
47
47
|
all: <%= line.coverage %>
|
48
|
+
last posted: <%= timeago(result.file_with_type(source_file, Coverband::RUNTIME_TYPE)&.line_coverage_posted(index), "-") %>
|
48
49
|
</span><% end %>
|
49
50
|
<% if line.skipped? %><span class="hits">skipped</span><% end %>
|
50
51
|
<code class="ruby"><%= CGI.escapeHTML(line.src.chomp) %></code>
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: coverband
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 6.
|
4
|
+
version: 6.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dan Mayer
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2024-04-
|
12
|
+
date: 2024-04-23 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: benchmark-ips
|
@@ -67,6 +67,20 @@ dependencies:
|
|
67
67
|
- - ">="
|
68
68
|
- !ruby/object:Gem::Version
|
69
69
|
version: '0'
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: spy
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
70
84
|
- !ruby/object:Gem::Dependency
|
71
85
|
name: minitest
|
72
86
|
requirement: !ruby/object:Gem::Requirement
|
@@ -183,30 +197,30 @@ dependencies:
|
|
183
197
|
name: standard
|
184
198
|
requirement: !ruby/object:Gem::Requirement
|
185
199
|
requirements:
|
186
|
-
- -
|
200
|
+
- - '='
|
187
201
|
- !ruby/object:Gem::Version
|
188
202
|
version: 1.34.0
|
189
203
|
type: :development
|
190
204
|
prerelease: false
|
191
205
|
version_requirements: !ruby/object:Gem::Requirement
|
192
206
|
requirements:
|
193
|
-
- -
|
207
|
+
- - '='
|
194
208
|
- !ruby/object:Gem::Version
|
195
209
|
version: 1.34.0
|
196
210
|
- !ruby/object:Gem::Dependency
|
197
|
-
name:
|
211
|
+
name: rubocop
|
198
212
|
requirement: !ruby/object:Gem::Requirement
|
199
213
|
requirements:
|
200
|
-
- -
|
214
|
+
- - '='
|
201
215
|
- !ruby/object:Gem::Version
|
202
|
-
version:
|
216
|
+
version: 1.60.0
|
203
217
|
type: :development
|
204
218
|
prerelease: false
|
205
219
|
version_requirements: !ruby/object:Gem::Requirement
|
206
220
|
requirements:
|
207
|
-
- -
|
221
|
+
- - '='
|
208
222
|
- !ruby/object:Gem::Version
|
209
|
-
version:
|
223
|
+
version: 1.60.0
|
210
224
|
- !ruby/object:Gem::Dependency
|
211
225
|
name: coveralls
|
212
226
|
requirement: !ruby/object:Gem::Requirement
|
@@ -284,6 +298,7 @@ files:
|
|
284
298
|
- lib/coverband/adapters/base.rb
|
285
299
|
- lib/coverband/adapters/file_store.rb
|
286
300
|
- lib/coverband/adapters/hash_redis_store.rb
|
301
|
+
- lib/coverband/adapters/memcached_store.rb
|
287
302
|
- lib/coverband/adapters/null_store.rb
|
288
303
|
- lib/coverband/adapters/redis_store.rb
|
289
304
|
- lib/coverband/adapters/stdout_store.rb
|
@@ -509,9 +524,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
509
524
|
version: '2.7'
|
510
525
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
511
526
|
requirements:
|
512
|
-
- - "
|
527
|
+
- - ">="
|
513
528
|
- !ruby/object:Gem::Version
|
514
|
-
version:
|
529
|
+
version: '0'
|
515
530
|
requirements: []
|
516
531
|
rubygems_version: 3.4.10
|
517
532
|
signing_key:
|