coverband 6.0.3.rc.3 → 6.1.0
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 +18 -4
- data/coverband.gemspec +6 -4
- data/lib/coverband/adapters/hash_redis_store.rb +62 -47
- data/lib/coverband/adapters/memcached_store.rb +89 -0
- data/lib/coverband/collectors/route_tracker.rb +9 -6
- data/lib/coverband/configuration.rb +31 -10
- data/lib/coverband/reporters/json_report.rb +21 -9
- data/lib/coverband/reporters/web.rb +12 -4
- data/lib/coverband/utils/html_formatter.rb +5 -11
- data/lib/coverband/utils/results.rb +2 -0
- data/lib/coverband/utils/source_file.rb +17 -2
- data/lib/coverband/version.rb +1 -1
- data/lib/coverband.rb +2 -18
- data/lua/lib/persist-coverage.lua +3 -0
- data/public/application.css +24 -1
- data/public/application.js +73 -87
- data/public/dependencies.js +3 -3
- data/test/coverband/adapters/hash_redis_store_test.rb +16 -14
- data/test/coverband/collectors/route_tracker_test.rb +41 -2
- data/test/coverband/reporters/json_test.rb +1 -1
- data/test/coverband/utils/results_test.rb +17 -2
- data/views/source_file.erb +1 -0
- metadata +12 -13
- data/lib/coverband/reporters/web_pager.rb +0 -28
- data/views/source_file_loader.erb +0 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 21e62f4add1bcd44cacd630e37e291a38ecfb924d8fa48698ecbe4ba69a99471
|
4
|
+
data.tar.gz: 4cc4dfd1289bdf13f77c78a7deb399c18075ec53a835abb45fa11011d0815bd4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2bcf294b5d07fc508b1468b0e3a2d993d15b1dae067bd147ca1d55ebc444cf6f7157fbd6e5afee205a7fa9e3648bee9fdab66cec6573ff31605c417346c858a3
|
7
|
+
data.tar.gz: 282523aea85ea90943c6a8bbf595a3c135f7a95534ac2046ccfeb379cea252ad4889b3e31799de8917b3b989a9492f55fee032f302f8aa8a8c619e870941c79c
|
@@ -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,21 @@
|
|
1
|
+
### Coverband 6.1.0
|
2
|
+
|
3
|
+
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.
|
4
|
+
|
5
|
+
* Thanks to @FeLvi-zzz for the last time accessed support for the Hash Redis Store
|
6
|
+
* Thanks to @alpaca-tc for the improvements on the route tracker
|
7
|
+
* Thanks to @ydah for typo fixes, doc updates, adding ruby 3.3 to build matrix, improvements on CI, standardrb fixes
|
8
|
+
* Thanks to @trivett, @khaled-badenjki, @IsabelleLePivain for improved docs
|
9
|
+
* Thanks to @prastamaha for the memcached adapter
|
10
|
+
* Thanks to @ursm for a yaml fix
|
11
|
+
* Thanks to @Drowze for a layered cache approach for perf improvements
|
12
|
+
* Thanks to @vs37559 for a sinatra pandrino fix
|
13
|
+
* This release addresses large projects and adds in paged reporting
|
14
|
+
* to ensure even on projects with 10K+ files it can load on heroku under the 30s timeout
|
15
|
+
* only supports HashRedis store
|
16
|
+
* faster UI for web UI in general which should be noticable on non paged reports
|
17
|
+
* reduce redis calls
|
18
|
+
|
1
19
|
### Coverband 6.0.2
|
2
20
|
|
3
21
|
* thanks makicamel for improved deferred eager loading
|
@@ -11,16 +29,12 @@
|
|
11
29
|
|
12
30
|
### Coverband 6.0.0
|
13
31
|
|
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
32
|
* The 6.0.0 release is all that was the 6 different RC releases of 5.2.6
|
17
33
|
* Added Rails test matrix to github actions to test on all the supported versions
|
18
34
|
- Rails: 6.0.x, 6.1.x, 7.0.x, 7.1.x
|
19
35
|
|
20
36
|
### Coverband 5.2.6
|
21
37
|
|
22
|
-
__NOTE: the current RCs include below, but this might turn into coverband 6.0__
|
23
|
-
|
24
38
|
- add support for translation keys
|
25
39
|
- refactor non Coverage.so based trackers
|
26
40
|
- adds CSP report support (thanks @jwg2s)
|
data/coverband.gemspec
CHANGED
@@ -34,18 +34,20 @@ Gem::Specification.new do |spec|
|
|
34
34
|
spec.add_development_dependency "capybara"
|
35
35
|
spec.add_development_dependency "m"
|
36
36
|
spec.add_development_dependency "memory_profiler"
|
37
|
-
# breaking change in minitest and mocha
|
37
|
+
# breaking change in minitest and mocha...
|
38
|
+
# note: we are also adding 'spy' as mocha doesn't want us to spy on redis calls...
|
39
|
+
# ^^^ probably need a large test cleanup refactor
|
38
40
|
spec.add_development_dependency "minitest", "= 5.18.1"
|
39
41
|
spec.add_development_dependency "minitest-fork_executor"
|
40
42
|
spec.add_development_dependency "minitest-stub-const"
|
41
43
|
spec.add_development_dependency "mocha", "~> 1.7.0"
|
42
|
-
# spec.add_development_dependency "spy"
|
43
44
|
spec.add_development_dependency "rack"
|
44
45
|
spec.add_development_dependency "rack-test"
|
45
46
|
spec.add_development_dependency "rake"
|
46
47
|
spec.add_development_dependency "resque"
|
47
|
-
spec.add_development_dependency "standard", "
|
48
|
-
|
48
|
+
spec.add_development_dependency "standard", "= 1.34.0"
|
49
|
+
# breaking changes in various rubocop versions
|
50
|
+
spec.add_development_dependency "rubocop", "= 1.60.0"
|
49
51
|
|
50
52
|
spec.add_development_dependency "coveralls"
|
51
53
|
# minitest-profile is not compatible with Rails 7.1.0 setup... dropping it for now
|
@@ -89,7 +89,7 @@ module Coverband
|
|
89
89
|
# used to store data to redis. It is changed only when breaking changes to our
|
90
90
|
# redis format are required.
|
91
91
|
###
|
92
|
-
REDIS_STORAGE_FORMAT_VERSION = "
|
92
|
+
REDIS_STORAGE_FORMAT_VERSION = "coverband_hash_4_0"
|
93
93
|
|
94
94
|
JSON_PAYLOAD_EXPIRATION = 5 * 60
|
95
95
|
|
@@ -107,7 +107,7 @@ module Coverband
|
|
107
107
|
@relative_file_converter = opts[:relative_file_converter] || Utils::RelativeFileConverter
|
108
108
|
|
109
109
|
@get_coverage_cache = if opts[:get_coverage_cache]
|
110
|
-
key_prefix = [REDIS_STORAGE_FORMAT_VERSION, @redis_namespace
|
110
|
+
key_prefix = [REDIS_STORAGE_FORMAT_VERSION, @redis_namespace].compact.join(".")
|
111
111
|
GetCoverageRedisCacheStore.new(redis, key_prefix)
|
112
112
|
else
|
113
113
|
GetCoverageNullCacheStore
|
@@ -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,29 +162,33 @@ 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
|
174
174
|
|
175
|
-
#
|
175
|
+
# NOTE: This method should be used for full coverage or filename coverage look ups
|
176
|
+
# When paging code should use coverage_for_types and pull eager and runtime together as matched pairs
|
176
177
|
def coverage(local_type = nil, opts = {})
|
177
178
|
page_size = opts[:page_size] || 250
|
178
179
|
cached_results = @get_coverage_cache.fetch(local_type || type) do |sleep_time|
|
179
180
|
files_set = if opts[:page]
|
180
|
-
|
181
|
+
raise "call coverage_for_types with paging"
|
181
182
|
elsif opts[:filename]
|
182
|
-
|
183
|
-
|
183
|
+
type_key_prefix = key_prefix(local_type)
|
184
|
+
# NOTE: a better way to extract filename from key would be better
|
185
|
+
files_set(local_type).select do |cache_key|
|
186
|
+
cache_key.sub(type_key_prefix, "").match(short_name(opts[:filename]))
|
187
|
+
end || {}
|
184
188
|
else
|
185
189
|
files_set(local_type)
|
186
190
|
end
|
187
|
-
#
|
191
|
+
# below uses batches with a sleep in between to avoid overloading redis
|
188
192
|
files_set.each_slice(page_size).flat_map do |key_batch|
|
189
193
|
sleep sleep_time
|
190
194
|
@redis.pipelined do |pipeline|
|
@@ -200,33 +204,25 @@ module Coverband
|
|
200
204
|
end
|
201
205
|
end
|
202
206
|
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
# TODO: This merge of eager and runtime isn't working fix later...
|
214
|
-
def coverage_for_types(types, opts = {})
|
215
|
-
page_size = opts[:page_size] || 250
|
207
|
+
def split_coverage(types, coverage_cache, options = {})
|
208
|
+
if types.is_a?(Array) && !options[:filename] && options[:page]
|
209
|
+
data = coverage_for_types(types, options)
|
210
|
+
coverage_cache[Coverband::RUNTIME_TYPE] = data[Coverband::RUNTIME_TYPE]
|
211
|
+
coverage_cache[Coverband::EAGER_TYPE] = data[Coverband::EAGER_TYPE]
|
212
|
+
data
|
213
|
+
else
|
214
|
+
super
|
215
|
+
end
|
216
|
+
end
|
216
217
|
|
217
|
-
|
218
|
+
def coverage_for_types(_types, opts = {})
|
219
|
+
page_size = opts[:page_size] || 250
|
218
220
|
hash_data = {}
|
219
221
|
|
220
|
-
runtime_file_set =
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
# match is a hack that will only kind of work
|
225
|
-
files_set(local_type).select{ |cache_key| cache_key.match(short_name(opts[:filename])) } || {}
|
226
|
-
else
|
227
|
-
files_set(local_type)
|
228
|
-
end
|
229
|
-
|
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] || []
|
225
|
+
|
230
226
|
hash_data[Coverband::RUNTIME_TYPE] = runtime_file_set.each_slice(page_size).flat_map do |key_batch|
|
231
227
|
@redis.pipelined do |pipeline|
|
232
228
|
key_batch.each do |key|
|
@@ -235,12 +231,14 @@ module Coverband
|
|
235
231
|
end
|
236
232
|
end
|
237
233
|
|
238
|
-
|
239
|
-
|
234
|
+
eager_key_pre = key_prefix(Coverband::EAGER_TYPE)
|
235
|
+
runtime_key_pre = key_prefix(Coverband::RUNTIME_TYPE)
|
240
236
|
matched_file_set = files_set(Coverband::EAGER_TYPE)
|
241
|
-
.select
|
242
|
-
|
243
|
-
|
237
|
+
.select do |eager_key, _val|
|
238
|
+
runtime_file_set.any? do |runtime_key|
|
239
|
+
(eager_key.sub(eager_key_pre, "") == runtime_key.sub(runtime_key_pre, ""))
|
240
|
+
end
|
241
|
+
end || []
|
244
242
|
hash_data[Coverband::EAGER_TYPE] = matched_file_set.each_slice(page_size).flat_map do |key_batch|
|
245
243
|
@redis.pipelined do |pipeline|
|
246
244
|
key_batch.each do |key|
|
@@ -259,13 +257,17 @@ module Coverband
|
|
259
257
|
|
260
258
|
def short_name(filename)
|
261
259
|
filename.sub(/^#{Coverband.configuration.root}/, ".")
|
262
|
-
.gsub(%r{
|
260
|
+
.gsub(%r{^\./}, "")
|
263
261
|
end
|
264
262
|
|
265
263
|
def file_count(local_type = nil)
|
266
264
|
files_set(local_type).count { |filename| !Coverband.configuration.ignore.any? { |i| filename.match(i) } }
|
267
265
|
end
|
268
266
|
|
267
|
+
def cached_file_count
|
268
|
+
@cached_file_count ||= file_count(Coverband::RUNTIME_TYPE)
|
269
|
+
end
|
270
|
+
|
269
271
|
def raw_store
|
270
272
|
@redis
|
271
273
|
end
|
@@ -287,9 +289,14 @@ module Coverband
|
|
287
289
|
return unless file_hash(file) == data_from_redis[FILE_HASH]
|
288
290
|
|
289
291
|
data = coverage_data_from_redis(data_from_redis)
|
290
|
-
|
291
|
-
hash[file]
|
292
|
-
|
292
|
+
timedata = coverage_time_data_from_redis(data_from_redis)
|
293
|
+
hash[file] = data_from_redis.select do |meta_data_key, _value|
|
294
|
+
META_DATA_KEYS.include?(meta_data_key)
|
295
|
+
end.merge!("data" => data, "timedata" => timedata)
|
296
|
+
hash[file][LAST_UPDATED_KEY] =
|
297
|
+
(hash[file][LAST_UPDATED_KEY].nil? || hash[file][LAST_UPDATED_KEY] == "") ? nil : hash[file][LAST_UPDATED_KEY].to_i
|
298
|
+
hash[file].merge!(LAST_UPDATED_KEY => hash[file][LAST_UPDATED_KEY],
|
299
|
+
FIRST_UPDATED_KEY => hash[file][FIRST_UPDATED_KEY].to_i)
|
293
300
|
end
|
294
301
|
|
295
302
|
def coverage_data_from_redis(data_from_redis)
|
@@ -300,10 +307,18 @@ module Coverband
|
|
300
307
|
end
|
301
308
|
end
|
302
309
|
|
310
|
+
def coverage_time_data_from_redis(data_from_redis)
|
311
|
+
max = data_from_redis[FILE_LENGTH_KEY].to_i - 1
|
312
|
+
Array.new(max + 1) do |index|
|
313
|
+
unixtime = data_from_redis["#{index}_last_posted"]
|
314
|
+
unixtime.nil? ? nil : Time.at(unixtime.to_i)
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
303
318
|
def script_input(key:, file:, file_hash:, data:, report_time:, updated_time:)
|
304
|
-
coverage_data = data.each_with_index.each_with_object({})
|
319
|
+
coverage_data = data.each_with_index.each_with_object({}) do |(coverage, index), hash|
|
305
320
|
hash[index] = coverage if coverage
|
306
|
-
|
321
|
+
end
|
307
322
|
meta = {
|
308
323
|
first_updated_at: report_time,
|
309
324
|
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,15 +12,15 @@ 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,
|
18
21
|
:service_test_mode, :process_type, :track_views, :redis_url,
|
19
22
|
:background_reporting_sleep_seconds, :reporting_wiggle,
|
20
|
-
:send_deferred_eager_loading_data
|
23
|
+
:send_deferred_eager_loading_data, :paged_reporting
|
21
24
|
|
22
25
|
attr_reader :track_gems, :ignore, :use_oneshot_lines_coverage
|
23
26
|
|
@@ -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
|
@@ -293,6 +310,10 @@ module Coverband
|
|
293
310
|
@send_deferred_eager_loading_data
|
294
311
|
end
|
295
312
|
|
313
|
+
def paged_reporting
|
314
|
+
!!@paged_reporting
|
315
|
+
end
|
316
|
+
|
296
317
|
def service_disabled_dev_test_env?
|
297
318
|
return false unless service?
|
298
319
|
|
@@ -317,7 +338,7 @@ module Coverband
|
|
317
338
|
end
|
318
339
|
|
319
340
|
def track_gems=(_value)
|
320
|
-
puts "gem tracking is deprecated, setting this will be ignored"
|
341
|
+
puts "gem tracking is deprecated, setting this will be ignored & eventually removed"
|
321
342
|
end
|
322
343
|
|
323
344
|
private
|
@@ -30,6 +30,18 @@ module Coverband
|
|
30
30
|
|
31
31
|
private
|
32
32
|
|
33
|
+
def coverage_css_class(covered_percent)
|
34
|
+
if covered_percent.nil?
|
35
|
+
""
|
36
|
+
elsif covered_percent > 90
|
37
|
+
"green"
|
38
|
+
elsif covered_percent > 80
|
39
|
+
"yellow"
|
40
|
+
else
|
41
|
+
"red"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
33
45
|
def report_as_json
|
34
46
|
result = Coverband::Utils::Results.new(filtered_report_files)
|
35
47
|
source_files = result.source_files
|
@@ -42,16 +54,16 @@ module Coverband
|
|
42
54
|
if as_report
|
43
55
|
row_data = []
|
44
56
|
data[:files].each_pair do |key, data|
|
45
|
-
source_class = data[:never_loaded] ?
|
46
|
-
data_loader_url="#{base_path}load_file_details?filename=#{data[:filename]}"
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
# class
|
57
|
+
source_class = data[:never_loaded] ? "strong red" : "strong"
|
58
|
+
data_loader_url = "#{base_path}load_file_details?filename=#{data[:filename]}"
|
59
|
+
link = "<a href=\"##{data[:hash]}\" class=\"src_link #{source_class} cboxElement\" title=\"#{key}\" data-loader-url=\"#{data_loader_url}\" onclick=\"src_link_click(this)\">#{key}</a>"
|
60
|
+
# Hack to ensure the sorting works on percentage columns, the span is hidden but colors the cell and the text is used for sorting
|
61
|
+
covered_percent = "#{data[:covered_percent]} <span class=\"#{coverage_css_class(data[:covered_percent])}\"> </span>"
|
62
|
+
runtime_percentage = "#{data[:runtime_percentage]}<span class=\"#{coverage_css_class(data[:runtime_percentage])}\"> </span>"
|
51
63
|
row_data << [
|
52
64
|
link,
|
53
|
-
|
54
|
-
|
65
|
+
covered_percent,
|
66
|
+
runtime_percentage,
|
55
67
|
data[:lines_of_code].to_s,
|
56
68
|
(data[:lines_covered] + data[:lines_missed]).to_s,
|
57
69
|
data[:lines_covered].to_s,
|
@@ -60,7 +72,7 @@ module Coverband
|
|
60
72
|
data[:covered_strength].to_s
|
61
73
|
]
|
62
74
|
end
|
63
|
-
filesreported = store.
|
75
|
+
filesreported = store.cached_file_count
|
64
76
|
data["iTotalRecords"] = filesreported
|
65
77
|
data["iTotalDisplayRecords"] = filesreported
|
66
78
|
data["aaData"] = row_data
|