coverband 4.2.3 → 4.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +5 -6
  3. data/.travis.yml +6 -0
  4. data/README.md +40 -20
  5. data/changes.md +16 -9
  6. data/coverband.gemspec +1 -0
  7. data/lib/coverband/adapters/base.rb +1 -1
  8. data/lib/coverband/adapters/hash_redis_store.rb +49 -42
  9. data/lib/coverband/adapters/redis_store.rb +4 -2
  10. data/lib/coverband/collectors/coverage.rb +1 -1
  11. data/lib/coverband/collectors/view_tracker.rb +14 -5
  12. data/lib/coverband/configuration.rb +18 -5
  13. data/lib/coverband/integrations/background.rb +3 -0
  14. data/lib/coverband/reporters/html_report.rb +10 -0
  15. data/lib/coverband/reporters/web.rb +16 -0
  16. data/lib/coverband/utils/html_formatter.rb +8 -0
  17. data/lib/coverband/utils/railtie.rb +16 -9
  18. data/lib/coverband/utils/s3_report.rb +0 -1
  19. data/lib/coverband/version.rb +1 -1
  20. data/lib/coverband.rb +12 -7
  21. data/lua/install.sh +15 -0
  22. data/lua/lib/persist-coverage.lua +28 -0
  23. data/lua/test/bootstrap.lua +5 -0
  24. data/lua/test/harness.lua +19 -0
  25. data/lua/test/redis-call.lua +55 -0
  26. data/lua/test/test-persist-coverage.lua +132 -0
  27. data/test/coverband/adapters/hash_redis_store_test.rb +4 -3
  28. data/test/coverband/adapters/redis_store_test.rb +10 -5
  29. data/test/coverband/collectors/coverage_test.rb +10 -4
  30. data/test/coverband/collectors/view_tracker_test.rb +38 -18
  31. data/test/coverband/configuration_test.rb +10 -10
  32. data/test/coverband/integrations/background_test.rb +10 -0
  33. data/test/coverband/integrations/resque_worker_test.rb +0 -1
  34. data/test/coverband/reporters/base_test.rb +6 -5
  35. data/test/coverband/reporters/console_test.rb +2 -5
  36. data/test/coverband/reporters/html_test.rb +1 -3
  37. data/test/coverband/utils/html_formatter_test.rb +1 -3
  38. data/test/forked/rails_rake_full_stack_test.rb +10 -0
  39. data/test/integration/full_stack_test.rb +1 -3
  40. data/test/rails4_dummy/config/coverband.rb +3 -13
  41. data/test/rails4_dummy/config/coverband_missing_redis.rb +3 -0
  42. data/test/rails5_dummy/config/coverband.rb +4 -2
  43. data/test/rails5_dummy/config/coverband_missing_redis.rb +15 -0
  44. data/test/test_helper.rb +8 -4
  45. data/views/data.erb +1 -0
  46. data/views/nav.erb +1 -0
  47. data/views/view_tracker.erb +5 -2
  48. metadata +27 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0f5ee08a6ec38b97538430db828645f0aab7736c8c51946fea8c44f871c6a4a9
4
- data.tar.gz: 733933d77d75e9e50c09a282f87da2b7cd3b09db9e24679b0267f384ba034c0f
3
+ metadata.gz: 1c094b4a6ef2b76f771cb96aa1fbdc246dc8beea74033ffca4cb466ef96c0a08
4
+ data.tar.gz: 1162d9244c0e1a1b28c0fa051d7cd53d29b6a4fdfc50c1fadb7d6793229aaf8b
5
5
  SHA512:
6
- metadata.gz: 68117ba4c4d9dbc9370187e2a31f3b916a0368cb46b915eb7f3e189f1c7bb4f5699d972e176d9a138d999ca59a50e2817658767ccb803f2726dd3fd3f8ff31b4
7
- data.tar.gz: bffb9453d31c0c93ec6706a2c426a6835b713dfc1000c50e030c252a25bd7abdedd95f41622dfc309420aa27276b19afb44cec81a5e2ec20abc35b1768f71541
6
+ metadata.gz: d9eba70b91d87992d1e450490f7fd4545cccb5092bf093c424c51b964cb4b31ad5c8a1d49d1cc774ce6470c6466b7d3b7a7a1df92ecbc4e574a203467f621b55
7
+ data.tar.gz: 815c462d38f7a27b26f5d209044c53b2e0058c4ea2534db3f6663a267ad0cc435bd6c895128c5ea8f85c0aec61e4e31d9f61c426526b71423e24c941852f74be
data/.rubocop.yml CHANGED
@@ -1,17 +1,18 @@
1
1
  AllCops:
2
2
  TargetRubyVersion: 2.4
3
3
  Exclude:
4
+ - vendor/bundle/**/*
4
5
  - docs/**/*
5
6
  - test/rails4_dummy/**/*
6
7
  - test/rails5_dummy/**/*
7
8
  - test/fixtures/**/*
8
9
  # Get the code passing first then we will enable for tests
9
- - test/**/*
10
+ - test/**/*
10
11
  Documentation:
11
12
  Enabled: false
12
13
  Metrics/MethodLength:
13
14
  Enabled: false
14
- Metrics/LineLength:
15
+ Layout/LineLength:
15
16
  Max: 160
16
17
  Metrics/BlockNesting:
17
18
  Max: 5
@@ -31,6 +32,8 @@ Metrics/ParameterLists:
31
32
  Max: 10
32
33
  Naming/AccessorMethodName:
33
34
  Enabled: false
35
+ Naming/RescuedExceptionsVariableName:
36
+ Enabled: false
34
37
  Naming/PredicateName:
35
38
  Enabled: true
36
39
  Style/TernaryParentheses:
@@ -59,10 +62,6 @@ Style/SymbolProc:
59
62
  Enabled: false
60
63
  Style/RegexpLiteral:
61
64
  Enabled: false
62
- Performance/Casecmp:
63
- Enabled: false
64
- Performance/RegexpMatch:
65
- Enabled: false
66
65
  Layout/MultilineMethodCallIndentation:
67
66
  Enabled: true
68
67
  Layout/MultilineOperationIndentation:
data/.travis.yml CHANGED
@@ -4,12 +4,18 @@ rvm:
4
4
  - "2.4"
5
5
  - "2.5"
6
6
  - "2.6.1"
7
+ cache:
8
+ bundler: true
9
+ directories:
10
+ - $HOME/lua51
7
11
  gemfile:
8
12
  - Gemfile
9
13
  - Gemfile.rails4
10
14
  services:
11
15
  - redis-server
12
16
  script:
17
+ - sudo ./lua/install.sh
18
+ - $HOME/lua51/bin/busted lua/test/*
13
19
  - bundle exec rake rubocop
14
20
  - COVERBAND_HASH_REDIS_STORE=t bundle exec rake
15
21
  - COVERBAND_HASH_REDIS_STORE=t bundle exec rake forked_tests
data/README.md CHANGED
@@ -67,7 +67,7 @@ See [changelog](https://github.com/danmayer/coverband/blob/master/changes.md).
67
67
 
68
68
  ## Rails
69
69
 
70
- The Railtie integration means you shouldn't need to do anything anything else other than ensure coverband is required after rails within your Gemfile. The only exception to this is gem tracking of `Bundle.require` which depends on requiring coverband within the application.rb. See [Collecting Gem / Library Usage](https://github.com/danmayer/coverband#collecting-gem--library-usage).
70
+ The Railtie integration means you shouldn't need to do anything else other than ensure coverband is required after rails within your Gemfile. The only exception to this is gem tracking of `Bundle.require` which depends on requiring coverband within the application.rb. See [Collecting Gem / Library Usage](https://github.com/danmayer/coverband#collecting-gem--library-usage).
71
71
 
72
72
  ## Sinatra
73
73
 
@@ -114,7 +114,7 @@ The web endpoint is a barebones endpoint that you can either expose direct (afte
114
114
  - **force coverage collection:** This triggers coverage collection on the current webserver process
115
115
  - **clear coverage report:** This will clear the coverage data. This wipes out all collected data (**dangerous**)
116
116
  - View individual file details
117
- - **clear individual file coverage:** This will clear the details of the file you are looking at. This is helpful if you don't want to loose all Coverage data but made a change that you expect would impact a particular file.
117
+ - **clear individual file coverage:** This will clear the details of the file you are looking at. This is helpful if you don't want to lose all Coverage data but made a change that you expect would impact a particular file.
118
118
 
119
119
  ### Rake Tasks
120
120
 
@@ -169,9 +169,29 @@ Coverband.configure do |config|
169
169
  # and is safe to use if one is investigating issues in production, but it will slightly
170
170
  # hit perf.
171
171
  config.verbose = false
172
+
173
+ # default false. button at the top of the web interface which clears all data
174
+ config.web_enable_clear = true
175
+
176
+ # default false. Experimental support for tracking view layer tracking.
177
+ # Does not track line-level usage, only indicates if an entire file
178
+ # is used or not.
179
+ config.track_views = true
172
180
  end
173
181
  ```
174
182
 
183
+ ### Working with environment variables
184
+
185
+ Do you use figaro, mc-settings, dotenv or something else to inject environment variables into your app? If so ensure you have that done BEFORE coverband is required.
186
+
187
+ For example if you use dotenv, you need to do this, see https://github.com/bkeepers/dotenv#note-on-load-order
188
+
189
+ ```
190
+ gem 'dotenv-rails', require: 'dotenv/rails-now'
191
+ gem 'coverband'
192
+ gem 'other-gem-that-requires-env-variables'
193
+ ```
194
+
175
195
  ### Ignoring Files
176
196
 
177
197
  Sometimes you have files that are known to be valuable perhaps in other environments or something that is just run very infrequently. Opposed to having to mentally filter them out of the report, you can just have them ignored in the Coverband reporting by using `config.ignore` as shown below. Ignore takes a string but can also match with regex rules see how below ignores all rake tasks as an example.
@@ -185,6 +205,7 @@ config.ignore += ['config/application.rb',
185
205
  'config/environments/*',
186
206
  'lib/tasks/*']
187
207
  ```
208
+
188
209
  ### View Tracking
189
210
 
190
211
  Coverband allows an optional feature to track all view files that are used by an application.
@@ -216,6 +237,14 @@ ENV['AWS_ACCESS_KEY_ID']
216
237
  ENV['AWS_SECRET_ACCESS_KEY']
217
238
  ```
218
239
 
240
+ ### Avoiding Cache Stampede
241
+
242
+ If you have many servers and they all hit Redis at the same time you can see spikes in your Redis CPU, and memory. This is do to a concept called [cache stampede](https://en.wikipedia.org/wiki/Cache_stampede). It is better to spread out the reporting across your servers. A simple way to do this is to add a random wiggle on your background reporting. This configuration option allows a wiggle. The right amount of wiggle depends on the numbers of servers you have and how willing you are to have delays in your coverage reporting. I would recommend at least 1 second per server.
243
+
244
+ Add a wiggle (in seconds) to the background thread to avoid all your servers reporting at the same time:
245
+
246
+ `config.reporting_wiggle = 30`
247
+
219
248
  ### Redis Hash Store
220
249
 
221
250
  Coverband on very high volume sites with many server processes reporting can have a race condition. To resolve the race condition and reduce Ruby memory overhead we have introduced a new Redis storage option. This moves the some of the work from the Ruby processes to Redis. It is worth noting because of this, it has a larger demands on the Redis server. So adjust your Redis instance accordingly. To help reduce the extra redis load you can also change the background reporting time period.
@@ -258,22 +287,6 @@ rake coverband:clear # reset coverband coverage data
258
287
  rake coverband:coverage # report runtime coverband code coverage
259
288
  ```
260
289
 
261
- ### Forcing Coverband to Track Coverage on files loaded during boot `safe_reload_files`
262
-
263
- Coverband will report code usage for anything `required` or `loaded` after calling `Coverband.start` which happens automatically when coverband is required. This means some of the files loaded before coverband such as the Rails application.rb will be reported as having no coverage.
264
-
265
- The `safe_reload_files` reload option in the configuration options can help to ensure you can track any files regardless of loading before Coverband. For example if I wanted to show the coverage of `config/coverband.rb`, which has to be loaded before calling `Coverband.start`, I could do that by adding that path to the `safe_reload_files` option.
266
-
267
- ```
268
- Coverband.configure do |config|
269
- # ... a bunch of other options
270
- # using the new safe reload to enforce files loaded
271
- config.safe_reload_files = ['config/coverband.rb']
272
- end
273
- ```
274
-
275
- By adding any files above you will get reporting on those files as part of your coverage runtime report. The files are reloaded when Coverband first starts, you can also trigger a reload via the web interface.
276
-
277
290
  ### Collecting Gem / Library Usage
278
291
 
279
292
  __WARNING:__ Gem Tracking is still in experimental stages and not recommended for production. We have some performance issues when view reports on large applications. Gem tracing also during background thread data collection has HIGH memory requirements, during report merging (seemingly around 128mb of extra memory, which is crazy). We recommend deploying WITHOUT `track_gems` first and only enabling it after confirming that Coverband is working and performing well.
@@ -338,6 +351,12 @@ If you are trying to debug locally wondering what code is being run during a req
338
351
  - Coverband 3.0.X+ requires Ruby 2.3+
339
352
  - Coverband currently requires Redis for production usage
340
353
 
354
+ ### Ruby and Rails Version Support
355
+
356
+ We will match Heroku & Ruby's support lifetime, supporting the last 3 major Ruby releases. For details see [supported runtimes](https://devcenter.heroku.com/articles/ruby-support#supported-runtimes).
357
+
358
+ For Rails, we will follow the policy of the [Rails team maintenance policy](https://guides.rubyonrails.org/maintenance_policy.html). We officially support the last two major release versions, while providing minimal support (major bugs / security fixes) for an additional version. This means at the moment we primaryly target Rails 6.x, 5.x, and will try to keep current functionality working for Rails 4.x but may release new features that do not work on that target.
359
+
341
360
  # Contributing To Coverband
342
361
 
343
362
  If you are working on adding features, PRs, or bugfixes to Coverband this section should help get you going.
@@ -363,9 +382,10 @@ If you submit a change please make sure the tests and benchmarks are passing.
363
382
 
364
383
  ### Known Issues
365
384
 
366
- - **total fail** on front end code, because of the precompiled template step basically coverage doesn't work well for `erb`, `slim`, and the like.
385
+ - **total fail** on front end code, for line for line coverage, because of the precompiled template step basically coverage doesn't work well for `erb`, `slim`, and the like.
367
386
  - related it will try to report something, but the line numbers reported for `ERB` files are often off and aren't considered useful. I recommend filtering out .erb using the `config.ignore` option. The default configuration excludes these files
368
- - coverage doesn't show for Rails `config/application.rb` or `config/boot.rb` as they get loaded when loading the Rake environment prior to starting the `Coverage` library. See [reload files section](#forcing-coverband-to-track-coverage-on-files-loaded-during-boot-safe_reload_files).
387
+ - **NOTE:** We now have file level coverage for view files, but don't support line level detail
388
+ - The view file detection doesn't workf or mailers at the moment only for web related views / JSON templates. This is due to how Rails active mailer notifications work.
369
389
 
370
390
  ### Debugging Redis Store
371
391
 
data/changes.md CHANGED
@@ -76,19 +76,26 @@ Feature Ideas:
76
76
 
77
77
  # Alpha / Beta / Release Candidates
78
78
 
79
- ### Coverband 4.2.2
79
+ ### Coverband 4.2.5
80
80
 
81
- - new experimental hash redis store for high volume collection
82
- (hundreds of clients), thanks @kbaum
83
- - view_tracker supports tracking view layer files like `.html.erb`
84
- - documentation improvements, thanks @brossetti1, @jjb, @kbaum
85
- - we now have discordapp for discussions, thanks @kbaum,
86
- https://discordapp.com/channels/609509533999562753/609509533999562756
87
- - perf fix on Rails initialization, thanks @skangg
88
- - simplified logging
81
+ - ?
89
82
 
90
83
  # Released
91
84
 
85
+ ### Coverband 4.2.4
86
+
87
+ - fixes related to startup without Redis, skipping Coverband on common rake tasks (assets:precompile), etc
88
+ - avoid calls to redis if coverage not updated
89
+ - split out lua work and added testing around lua scripts use for the hash redis store
90
+ - improvements to hash redis store resulting in 25% faster perf
91
+ - avoid duplicate hash calculations
92
+ - debug data now includes md5_hashes
93
+ - added support to download coverage and view data in JSON format
94
+ - documentation about working with environment variables
95
+ - add cache wiggle to avoid Redis CPU spikes (cache stampede on Redis server)
96
+ - make the nocov consistant on the data download and html view
97
+ - small performance improvements
98
+
92
99
  ### Coverband 4.2.3
93
100
 
94
101
  - readme fixes
data/coverband.gemspec CHANGED
@@ -35,6 +35,7 @@ Gem::Specification.new do |spec|
35
35
  spec.add_development_dependency 'rake'
36
36
  spec.add_development_dependency 'resque'
37
37
  spec.add_development_dependency 'rubocop'
38
+ spec.add_development_dependency 'rubocop-performance'
38
39
 
39
40
  spec.add_development_dependency 'coveralls'
40
41
  # add when debugging
@@ -40,7 +40,7 @@ module Coverband
40
40
  end
41
41
 
42
42
  def size_in_mib
43
- format('%.2f', (size.to_f / 2**20))
43
+ format('%<size>.2f', size: (size.to_f / 2**20))
44
44
  end
45
45
 
46
46
  def save_report(_report)
@@ -1,11 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'securerandom'
4
+
3
5
  module Coverband
4
6
  module Adapters
5
7
  class HashRedisStore < Base
6
8
  FILE_KEY = 'file'
7
9
  FILE_LENGTH_KEY = 'file_length'
8
- META_DATA_KEYS = [DATA_KEY, FIRST_UPDATED_KEY, LAST_UPDATED_KEY].freeze
10
+ META_DATA_KEYS = [DATA_KEY, FIRST_UPDATED_KEY, LAST_UPDATED_KEY, FILE_HASH].freeze
9
11
  ###
10
12
  # This key isn't related to the coverband version, but to the interal format
11
13
  # used to store data to redis. It is changed only when breaking changes to our
@@ -13,16 +15,19 @@ module Coverband
13
15
  ###
14
16
  REDIS_STORAGE_FORMAT_VERSION = 'coverband_hash_3_3'
15
17
 
18
+ JSON_PAYLOAD_EXPIRATION = 5 * 60
19
+
16
20
  attr_reader :redis_namespace
17
21
 
18
22
  def initialize(redis, opts = {})
19
23
  super()
20
24
  @redis_namespace = opts[:redis_namespace]
25
+ @save_report_batch_size = opts[:save_report_batch_size] || 100
21
26
  @format_version = REDIS_STORAGE_FORMAT_VERSION
22
27
  @redis = redis
23
28
  raise 'HashRedisStore requires redis >= 2.6.0' unless supported?
24
29
 
25
- @ttl = opts[:ttl] || -1
30
+ @ttl = opts[:ttl]
26
31
  @relative_file_converter = opts[:relative_file_converter] || Utils::RelativeFileConverter
27
32
  end
28
33
 
@@ -53,13 +58,14 @@ module Coverband
53
58
  def save_report(report)
54
59
  report_time = Time.now.to_i
55
60
  updated_time = type == Coverband::EAGER_TYPE ? nil : report_time
56
- script_id = hash_incr_script
57
- @redis.pipelined do
58
- keys = report.map do |file, data|
61
+ keys = []
62
+ report.each_slice(@save_report_batch_size) do |slice|
63
+ files_data = slice.map do |(file, data)|
59
64
  relative_file = @relative_file_converter.convert(file)
60
65
  file_hash = file_hash(relative_file)
61
66
  key = key(relative_file, file_hash: file_hash)
62
- script_input = save_report_script_input(
67
+ keys << key
68
+ script_input(
63
69
  key: key,
64
70
  file: relative_file,
65
71
  file_hash: file_hash,
@@ -67,16 +73,24 @@ module Coverband
67
73
  report_time: report_time,
68
74
  updated_time: updated_time
69
75
  )
70
- @redis.evalsha(script_id, script_input[:keys], script_input[:args])
71
- key
72
76
  end
73
- @redis.sadd(files_key, keys) if keys.any?
77
+ next unless files_data.any?
78
+
79
+ arguments_key = [@redis_namespace, SecureRandom.uuid].compact.join('.')
80
+ @redis.set(arguments_key, { ttl: @ttl, files_data: files_data }.to_json, ex: JSON_PAYLOAD_EXPIRATION)
81
+ @redis.evalsha(hash_incr_script, [arguments_key])
74
82
  end
83
+ @redis.sadd(files_key, keys) if keys.any?
75
84
  end
76
85
 
77
86
  def coverage(local_type = nil)
78
- files_set(local_type).each_with_object({}) do |key, hash|
79
- add_coverage_for_file(key, hash)
87
+ files_set = files_set(local_type)
88
+ @redis.pipelined do
89
+ files_set.map do |key|
90
+ @redis.hgetall(key)
91
+ end
92
+ end.each_with_object({}) do |data_from_redis, hash|
93
+ add_coverage_for_file(data_from_redis, hash)
80
94
  end
81
95
  end
82
96
 
@@ -94,9 +108,7 @@ module Coverband
94
108
 
95
109
  private
96
110
 
97
- def add_coverage_for_file(key, hash)
98
- data_from_redis = @redis.hgetall(key)
99
-
111
+ def add_coverage_for_file(data_from_redis, hash)
100
112
  return if data_from_redis.empty?
101
113
 
102
114
  file = data_from_redis[FILE_KEY]
@@ -116,38 +128,33 @@ module Coverband
116
128
  end
117
129
  end
118
130
 
119
- def save_report_script_input(key:, file:, file_hash:, data:, report_time:, updated_time:)
120
- data.each_with_index
121
- .each_with_object(keys: [key], args: [report_time, updated_time, file, file_hash, @ttl, data.length]) do |(coverage, index), hash|
122
- if coverage
123
- hash[:keys] << index
124
- hash[:args] << coverage
125
- end
131
+ def script_input(key:, file:, file_hash:, data:, report_time:, updated_time:)
132
+ coverage_data = data.each_with_index.each_with_object({}) do |(coverage, index), hash|
133
+ hash[index] = coverage if coverage
126
134
  end
135
+ meta = {
136
+ first_updated_at: report_time,
137
+ file: file,
138
+ file_hash: file_hash,
139
+ file_length: data.length,
140
+ hash_key: key
141
+ }
142
+ meta[:last_updated_at] = updated_time if updated_time
143
+ {
144
+ hash_key: key,
145
+ meta: meta,
146
+ coverage: coverage_data
147
+ }
127
148
  end
128
149
 
129
150
  def hash_incr_script
130
- @hash_incr_script ||= @redis.script(:load, <<~LUA)
131
- local first_updated_at = table.remove(ARGV, 1)
132
- local last_updated_at = table.remove(ARGV, 1)
133
- local file = table.remove(ARGV, 1)
134
- local file_hash = table.remove(ARGV, 1)
135
- local ttl = table.remove(ARGV, 1)
136
- local file_length = table.remove(ARGV, 1)
137
- local hash_key = table.remove(KEYS, 1)
138
- redis.call('HMSET', hash_key, '#{LAST_UPDATED_KEY}', last_updated_at, '#{FILE_KEY}', file, '#{FILE_HASH}', file_hash, '#{FILE_LENGTH_KEY}', file_length)
139
- redis.call('HSETNX', hash_key, '#{FIRST_UPDATED_KEY}', first_updated_at)
140
- for i, key in ipairs(KEYS) do
141
- if ARGV[i] == '-1' then
142
- redis.call("HSET", hash_key, key, ARGV[i])
143
- else
144
- redis.call("HINCRBY", hash_key, key, ARGV[i])
145
- end
146
- end
147
- if ttl ~= '-1' then
148
- redis.call("EXPIRE", hash_key, ttl)
149
- end
150
- LUA
151
+ @hash_incr_script ||= @redis.script(:load, lua_script_content)
152
+ end
153
+
154
+ def lua_script_content
155
+ File.read(File.join(
156
+ File.dirname(__FILE__), '../../../lua/lib/persist-coverage.lua'
157
+ ))
151
158
  end
152
159
 
153
160
  def values_from_redis(local_type, files)
@@ -77,7 +77,9 @@ module Coverband
77
77
  def coverage(local_type = nil, opts = {})
78
78
  local_type ||= opts.key?(:override_type) ? opts[:override_type] : type
79
79
  data = redis.get type_base_key(local_type)
80
- data ? JSON.parse(data) : {}
80
+ data = data ? JSON.parse(data) : {}
81
+ data.delete_if { |file_path, file_data| file_hash(file_path) != file_data['file_hash'] } unless opts[:skip_hash_check]
82
+ data
81
83
  end
82
84
 
83
85
  # Note: This could lead to slight race on redis
@@ -86,7 +88,7 @@ module Coverband
86
88
  # and the tradeoff has always been acceptable
87
89
  def save_report(report)
88
90
  data = report.dup
89
- data = merge_reports(data, coverage)
91
+ data = merge_reports(data, coverage(nil, skip_hash_check: true))
90
92
  save_coverage(data)
91
93
  end
92
94
 
@@ -71,7 +71,7 @@ module Coverband
71
71
  ###
72
72
  def track_file?(file)
73
73
  @ignore_patterns.none? do |pattern|
74
- file.include?(pattern)
74
+ file.match(pattern)
75
75
  end && (file.start_with?(@project_directory) ||
76
76
  (@track_gems &&
77
77
  Coverband.configuration.gem_paths.any? { |path| file.start_with?(path) }))
@@ -29,10 +29,10 @@ module Coverband
29
29
 
30
30
  @roots = options.fetch(:roots) { Coverband.configuration.all_root_patterns }
31
31
  @roots = @roots.split(',') if @roots.is_a?(String)
32
+ @one_time_timestamp = false
32
33
 
33
34
  @logged_views = []
34
35
  @views_to_record = []
35
- redis_store.set(tracker_time_key, Time.now.to_i) unless redis_store.exists(tracker_time_key)
36
36
  end
37
37
 
38
38
  ###
@@ -79,6 +79,13 @@ module Coverband
79
79
  all_views.reject { |view| view.match(/\/layouts\//) && recently_used_views.any? { |used_view| view.include?(used_view) } }
80
80
  end
81
81
 
82
+ def as_json
83
+ {
84
+ unused_views: unused_views,
85
+ used_views: used_views
86
+ }.to_json
87
+ end
88
+
82
89
  def tracking_since
83
90
  if (tracking_time = redis_store.get(tracker_time_key))
84
91
  Time.at(tracking_time.to_i).iso8601
@@ -100,11 +107,9 @@ module Coverband
100
107
  logged_views.delete(filename)
101
108
  end
102
109
 
103
- def self.supported_version?
104
- defined?(Rails) && defined?(Rails::VERSION) && Rails::VERSION::STRING.split('.').first.to_i >= 4
105
- end
106
-
107
110
  def report_views_tracked
111
+ redis_store.set(tracker_time_key, Time.now.to_i) unless @one_time_timestamp || redis_store.exists(tracker_time_key)
112
+ @one_time_timestamp = true
108
113
  reported_time = Time.now.to_i
109
114
  views_to_record.each do |file|
110
115
  redis_store.hset(tracker_key, file, reported_time)
@@ -116,6 +121,10 @@ module Coverband
116
121
  logger&.error "Coverband: view_tracker failed to store, error #{e.class.name}"
117
122
  end
118
123
 
124
+ def self.supported_version?
125
+ defined?(Rails) && defined?(Rails::VERSION) && Rails::VERSION::STRING.split('.').first.to_i >= 4
126
+ end
127
+
119
128
  protected
120
129
 
121
130
  def newly_seen_file?(file)
@@ -8,9 +8,11 @@ module Coverband
8
8
  :background_reporting_enabled,
9
9
  :background_reporting_sleep_seconds, :test_env,
10
10
  :web_enable_clear, :gem_details, :web_debug, :report_on_exit,
11
- :simulate_oneshot_lines_coverage, :track_views, :view_tracker
11
+ :simulate_oneshot_lines_coverage, :track_views, :view_tracker,
12
+ :reporting_wiggle
12
13
 
13
- attr_writer :logger, :s3_region, :s3_bucket, :s3_access_key_id, :s3_secret_access_key, :password
14
+ attr_writer :logger, :s3_region, :s3_bucket, :s3_access_key_id,
15
+ :s3_secret_access_key, :password
14
16
  attr_reader :track_gems, :ignore, :use_oneshot_lines_coverage
15
17
 
16
18
  #####
@@ -23,12 +25,22 @@ module Coverband
23
25
  IGNORE_TASKS = ['coverband:clear',
24
26
  'coverband:coverage',
25
27
  'coverband:coverage_server',
26
- 'coverband:migrate']
28
+ 'coverband:migrate',
29
+ 'assets:precompile',
30
+ 'db:version',
31
+ 'db:create',
32
+ 'db:drop',
33
+ 'db:seed',
34
+ 'db:setup',
35
+ 'db:test:prepare',
36
+ 'db:structure:dump',
37
+ 'db:structure:load',
38
+ 'db:version']
27
39
 
28
40
  # Heroku when building assets runs code from a dynamic directory
29
41
  # /tmp was added to avoid coverage from /tmp/build directories during
30
42
  # heroku asset compilation
31
- IGNORE_DEFAULTS = %w[vendor .erb$ .slim$ /tmp internal:prelude schema.rb]
43
+ IGNORE_DEFAULTS = %w[vendor/ .erb$ .slim$ /tmp internal:prelude schema.rb]
32
44
 
33
45
  # Add in missing files which were never loaded
34
46
  # we need to know what all paths to check for unloaded files
@@ -72,6 +84,7 @@ module Coverband
72
84
  @s3_secret_access_key = nil
73
85
  @redis_namespace = nil
74
86
  @redis_ttl = 2_592_000 # in seconds. Default is 30 days.
87
+ @reporting_wiggle = nil
75
88
  end
76
89
 
77
90
  def logger
@@ -144,7 +157,7 @@ module Coverband
144
157
 
145
158
  # by default we ignore vendor where many deployments put gems
146
159
  # we will remove this default if track_gems is set
147
- @ignore.delete('vendor')
160
+ @ignore.delete('vendor/')
148
161
  # while we want to allow vendored gems we don't want to track vendored ruby STDLIB
149
162
  @ignore << 'vendor/ruby-*' unless @ignore.include?('vendor/ruby-*')
150
163
  add_group('App', root)
@@ -33,6 +33,9 @@ module Coverband
33
33
  loop do
34
34
  Coverband.report_coverage
35
35
  Coverband.configuration.view_tracker&.report_views_tracked
36
+ if Coverband.configuration.reporting_wiggle
37
+ sleep_seconds = Coverband.configuration.background_reporting_sleep_seconds + rand(Coverband.configuration.reporting_wiggle.to_i)
38
+ end
36
39
  if Coverband.configuration.verbose
37
40
  logger.debug("Coverband: background reporting coverage (#{Coverband.configuration.store.type}). Sleeping #{sleep_seconds}s")
38
41
  end
@@ -32,6 +32,10 @@ module Coverband
32
32
  end
33
33
  end
34
34
 
35
+ def report_data
36
+ report_dynamic_data
37
+ end
38
+
35
39
  private
36
40
 
37
41
  def static?
@@ -54,6 +58,12 @@ module Coverband
54
58
  base_path: base_path,
55
59
  notice: notice).format_dynamic_html!
56
60
  end
61
+
62
+ def report_dynamic_data
63
+ Coverband::Utils::HTMLFormatter.new(filtered_report_files,
64
+ base_path: base_path,
65
+ notice: notice).format_dynamic_data!
66
+ end
57
67
  end
58
68
  end
59
69
  end
@@ -51,8 +51,12 @@ module Coverband
51
51
  @static.call(env)
52
52
  when %r{\/settings}
53
53
  [200, { 'Content-Type' => 'text/html' }, [settings]]
54
+ when %r{\/view_tracker_data}
55
+ [200, { 'Content-Type' => 'text/json' }, [view_tracker_data]]
54
56
  when %r{\/view_tracker}
55
57
  [200, { 'Content-Type' => 'text/html' }, [view_tracker]]
58
+ when %r{\/enriched_debug_data}
59
+ [200, { 'Content-Type' => 'text/json' }, [enriched_debug_data]]
56
60
  when %r{\/debug_data}
57
61
  [200, { 'Content-Type' => 'text/json' }, [debug_data]]
58
62
  when %r{\/load_file_details}
@@ -87,10 +91,22 @@ module Coverband
87
91
  base_path: base_path).format_view_tracker!
88
92
  end
89
93
 
94
+ def view_tracker_data
95
+ Coverband::Collectors::ViewTracker.new(store: Coverband.configuration.store).as_json
96
+ end
97
+
90
98
  def debug_data
91
99
  Coverband.configuration.store.get_coverage_report.to_json
92
100
  end
93
101
 
102
+ def enriched_debug_data
103
+ Coverband::Reporters::HTMLReport.new(Coverband.configuration.store,
104
+ static: false,
105
+ base_path: base_path,
106
+ notice: '',
107
+ open_report: false).report_data
108
+ end
109
+
94
110
  def load_file_details
95
111
  filename = request.params['filename']
96
112
  Coverband::Reporters::HTMLReport.new(Coverband.configuration.store,
@@ -29,6 +29,10 @@ module Coverband
29
29
  format_html(@coverage_result)
30
30
  end
31
31
 
32
+ def format_dynamic_data!
33
+ format_data(@coverage_result)
34
+ end
35
+
32
36
  def format_settings!
33
37
  format_settings
34
38
  end
@@ -71,6 +75,10 @@ module Coverband
71
75
  template('layout').result(binding)
72
76
  end
73
77
 
78
+ def format_data(result)
79
+ template('data').result(binding)
80
+ end
81
+
74
82
  # Returns the an erb instance for the template of given name
75
83
  def template(name)
76
84
  ERB.new(File.read(File.join(File.dirname(__FILE__), '../../../views/', "#{name}.erb")))