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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7076b5973080adc6d9fc4ddeadf8baa92d19e04f0d8f2e1ef961ac3075737448
4
- data.tar.gz: aca8e906625af383fbae32139836e8c93113d89e9b17ca323708aed3b5b46ee7
3
+ metadata.gz: 21e62f4add1bcd44cacd630e37e291a38ecfb924d8fa48698ecbe4ba69a99471
4
+ data.tar.gz: 4cc4dfd1289bdf13f77c78a7deb399c18075ec53a835abb45fa11011d0815bd4
5
5
  SHA512:
6
- metadata.gz: 302bf319119b5c1c3911da3591db34170717a53b9d9af32aee1f04b87171115daa6f97502c1e2d2d06180c7ba9c24ca675e5b09c6f19473bffb98838c5765f6f
7
- data.tar.gz: 0a65eb875a43605f1ef355b6bc268728763e8c6a64e1a5566388fbbd685714e8db6ef48e38849a4d1de5b5b34e984782512b9ffe9a37446b3549f82fa586701a
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.
@@ -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.2.0
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/rails-now'
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", "~> 1.34.0"
48
- spec.add_development_dependency "standardrb"
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 = "coverband_hash_3_3"
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, "v2"].compact.join(".")
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 => error
120
- Coverband.configuration.logger.info "Redis is not available (#{error}), Coverband not configured"
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 { |(file, data)|
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
- # TODO: refactor this and the method below and consider removing all the cached results stuff
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
- files_set(local_type).each_slice(page_size).to_a[opts[:page] - 1] || {}
181
+ raise "call coverage_for_types with paging"
181
182
  elsif opts[:filename]
182
- # TODO: this probably needs to be an exact match of the parsed cache key section
183
- files_set(local_type).select{ |cache_key| cache_key.match(short_name(opts[:filename])) } || {}
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
- # use batches with a sleep in between to avoid overloading redis
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
- # TODO: fix this before shipping main line release
204
- # def split_coverage(types, coverage_cache, options = {})
205
- # if types.is_a?(Array)
206
- # coverage_for_types(types, options)
207
- # else
208
- # super
209
- # end
210
- # end
211
-
212
- # NOTE: when using paging we need to ensure we have the same set of files per page in runtime and eager
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
- local_type = Coverband::RUNTIME_TYPE
218
+ def coverage_for_types(_types, opts = {})
219
+ page_size = opts[:page_size] || 250
218
220
  hash_data = {}
219
221
 
220
- runtime_file_set = if opts[:page]
221
- files_set(local_type).each_slice(page_size).to_a[opts[:page] - 1] || {}
222
- elsif opts[:filename]
223
- # TODO: this probably needs to be an exact match of the parsed cache key section
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
- # TODO: debug the set isn't just paths it has other key details including coverage type so below probalby fails
239
- # match is a hack that will work a sometimes... fix this but it will prove out if this solves the perf issue
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 { |eager_key, val| runtime_file_set.any?{ |runtime_key|
242
- (eager_key.match(/\.\.(.*).rb/) && eager_key.match(/\.\.(.*).rb/)[0]==runtime_key.match(/\.\.(.*).rb/)[0]) }
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
- hash[file] = data_from_redis.select { |meta_data_key, _value| META_DATA_KEYS.include?(meta_data_key) }.merge!("data" => data)
291
- hash[file][LAST_UPDATED_KEY] = (hash[file][LAST_UPDATED_KEY].nil? || hash[file][LAST_UPDATED_KEY] == "") ? nil : hash[file][LAST_UPDATED_KEY].to_i
292
- hash[file].merge!(LAST_UPDATED_KEY => hash[file][LAST_UPDATED_KEY], FIRST_UPDATED_KEY => hash[file][FIRST_UPDATED_KEY].to_i)
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({}) { |(coverage, index), hash|
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[:request]
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
- if route
44
- if newly_seen_key?(route)
45
- @logged_keys << route
46
- @keys_to_record << route if track_key?(route)
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 => error
135
- Coverband.configuration.logger.info "Redis is not available (#{error}), Coverband not configured"
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
- raise "invalid configuration: unclear default store coverband expects either api_key or redis_url" if ENV["COVERBAND_REDIS_URL"]
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
- raise "invalid configuration: only coverband service expects an API Key" if api_key && store.class.to_s != "Coverband::Adapters::WebServiceStore"
183
- raise "invalid configuration: coverband service shouldn't have redis url set" if ENV["COVERBAND_REDIS_URL"] && store.instance_of?(::Coverband::Adapters::WebServiceStore)
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
- raise(StandardError, "One shot line coverage is only available in ruby >= 2.6") unless one_shot_coverage_implemented_in_ruby_version? || !value
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] ? 'strong red' : 'strong'
46
- data_loader_url="#{base_path}load_file_details?filename=#{data[:filename]}"
47
- # class=\"src_link cboxElement\
48
- link = "<a href=\"##{data[:hash]}\" class=\"cboxElement\" title=\"#{key}\" data-loader-url=\"#{data_loader_url}\" onclick=\"src_link_click(this)\">#{key}</a>"
49
- # Started GET "/config/coverage/load_file_details?filename=/home/danmayer/projects/coverband_rails_example/app/jobs/application_job.rb" for ::1 at 2024-03-05 16:02:33 -0700
50
- # class="<%= coverage_css_class(source_file.covered_percent) %> strong"
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])}\">&nbsp;</span>"
62
+ runtime_percentage = "#{data[:runtime_percentage]}<span class=\"#{coverage_css_class(data[:runtime_percentage])}\">&nbsp;</span>"
51
63
  row_data << [
52
64
  link,
53
- data[:covered_percent].to_s,
54
- data[:runtime_percentage].to_s,
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.file_count(:runtime)
75
+ filesreported = store.cached_file_count
64
76
  data["iTotalRecords"] = filesreported
65
77
  data["iTotalDisplayRecords"] = filesreported
66
78
  data["aaData"] = row_data