coverband 6.0.1 → 6.0.3.rc.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/dependabot.yml +10 -0
- data/.github/workflows/main.yml +2 -3
- data/.standard.yml +1 -1
- data/Gemfile +1 -1
- data/LICENSE +1 -1
- data/README.md +24 -10
- data/Rakefile +1 -1
- data/changes.md +12 -1
- data/coverband.gemspec +3 -3
- data/lib/coverband/adapters/base.rb +10 -10
- data/lib/coverband/adapters/file_store.rb +1 -1
- data/lib/coverband/adapters/hash_redis_store.rb +160 -10
- data/lib/coverband/adapters/null_store.rb +1 -1
- data/lib/coverband/adapters/stdout_store.rb +1 -1
- data/lib/coverband/adapters/web_service_store.rb +5 -7
- data/lib/coverband/collectors/coverage.rb +1 -1
- data/lib/coverband/collectors/route_tracker.rb +1 -1
- data/lib/coverband/collectors/translation_tracker.rb +2 -2
- data/lib/coverband/collectors/view_tracker.rb +1 -1
- data/lib/coverband/configuration.rb +12 -6
- data/lib/coverband/integrations/rack_server_check.rb +1 -3
- data/lib/coverband/reporters/base.rb +7 -7
- data/lib/coverband/reporters/html_report.rb +12 -4
- data/lib/coverband/reporters/json_report.rb +42 -4
- data/lib/coverband/reporters/web.rb +16 -2
- data/lib/coverband/reporters/web_pager.rb +28 -0
- data/lib/coverband/utils/absolute_file_converter.rb +19 -19
- data/lib/coverband/utils/file_hasher.rb +3 -3
- data/lib/coverband/utils/html_formatter.rb +4 -4
- data/lib/coverband/utils/method_definition_scanner.rb +1 -1
- data/lib/coverband/utils/railtie.rb +4 -6
- data/lib/coverband/utils/relative_file_converter.rb +7 -7
- data/lib/coverband/utils/results.rb +14 -14
- data/lib/coverband/utils/source_file.rb +2 -2
- data/lib/coverband/utils/tasks.rb +1 -1
- data/lib/coverband/version.rb +1 -1
- data/lib/coverband.rb +19 -3
- data/public/application.js +35 -0
- data/public/dependencies.js +1 -1
- data/roadmap.md +1 -1
- data/test/benchmarks/benchmark.rake +5 -5
- data/test/coverband/adapters/file_store_test.rb +1 -1
- data/test/coverband/adapters/hash_redis_store_test.rb +48 -0
- data/test/coverband/collectors/coverage_test.rb +1 -1
- data/test/coverband/reporters/json_test.rb +1 -1
- data/test/coverband/utils/source_file_test.rb +11 -11
- data/test/fixtures/casting_invitor.rb +1 -1
- data/test/fixtures/sample.rb +2 -2
- data/test/fixtures/skipped_and_executed.rb +1 -1
- data/test/forked/rails_rake_full_stack_test.rb +1 -1
- data/test/integration/full_stack_send_deferred_eager_test.rb +52 -0
- data/test/test_helper.rb +3 -5
- data/test/unique_files.rb +1 -1
- data/views/file_list.erb +38 -32
- data/views/layout.erb +3 -3
- metadata +17 -139
- data/LICENSE.txt +0 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f25f2799e62201b31472588ed6ee3d00697b6ab8eba5beb04c3a76cb7e52d962
|
4
|
+
data.tar.gz: 599a0d1a4ef26b9b353d4ae341db2287f15b5119b9d9d3ed3d2b0e796c6e1fae
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b58f4486c028a9ed4f2e3444ed164a43b620b4cc213f91e9b7aef93c2ef1c394840aade62466b216f29ade57851314989e0ed3f3bca2a7f692deddd106efe7a3
|
7
|
+
data.tar.gz: 6569ffaeb15feb24b4eb40d5e0403bcf4fcc6fc381a7fd30e64a9e6a7fee06498e05ffe2832c7a99545c51a8b25a12cf6264daf672ef82485ccf10e03a3895a3
|
data/.github/workflows/main.yml
CHANGED
@@ -20,13 +20,12 @@ jobs:
|
|
20
20
|
# truffleruby-head,
|
21
21
|
# removing jruby again to flaky
|
22
22
|
gemfile: [ Gemfile.rails6.0, Gemfile.rails6.1, Gemfile.rails7.0, Gemfile.rails7.1 ]
|
23
|
-
# ruby: [2.3, 2.4, 2.5, 2.6, 2.7, "3.0", "3.1", jruby]
|
24
23
|
# need to add support for multiple gemfiles
|
25
|
-
ruby: ["2.7", "3.0", "3.1", "3.2"]
|
24
|
+
ruby: ["2.7", "3.0", "3.1", "3.2", "3.3"]
|
26
25
|
redis-version: [4, 5, 6, 7]
|
27
26
|
runs-on: ${{ matrix.os }}-latest
|
28
27
|
steps:
|
29
|
-
- uses: actions/checkout@
|
28
|
+
- uses: actions/checkout@v4
|
30
29
|
- uses: supercharge/redis-github-action@1.2.0
|
31
30
|
with:
|
32
31
|
redis-version: ${{ matrix.redis-version }}
|
data/.standard.yml
CHANGED
data/Gemfile
CHANGED
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -79,7 +79,7 @@ run ActionController::Dispatcher.new
|
|
79
79
|
|
80
80
|
![image](https://raw.github.com/danmayer/coverband/master/docs/coverband_web_ui.png)
|
81
81
|
|
82
|
-
>
|
82
|
+
> You can check it out locally by running the [Coverband Demo App](https://github.com/danmayer/coverband_rails_example).
|
83
83
|
|
84
84
|
- View overall coverage information
|
85
85
|
|
@@ -179,11 +179,6 @@ Coverband.configure do |config|
|
|
179
179
|
# default false. button at the top of the web interface which clears all data
|
180
180
|
config.web_enable_clear = true
|
181
181
|
|
182
|
-
# default false. Experimental support for tracking view layer tracking.
|
183
|
-
# Does not track line-level usage, only indicates if an entire file
|
184
|
-
# is used or not.
|
185
|
-
config.track_views = true
|
186
|
-
|
187
182
|
# default false. Experimental support for routes usage tracking.
|
188
183
|
config.track_routes = true
|
189
184
|
end
|
@@ -221,9 +216,9 @@ config.ignore += ['config/application.rb',
|
|
221
216
|
|
222
217
|
Coverband allows an optional feature to track all view files that are used by an application.
|
223
218
|
|
224
|
-
To
|
219
|
+
This feature is enabled by default. To stop this feature, disable the feature in your Coverband config.
|
225
220
|
|
226
|
-
`config.track_views =
|
221
|
+
`config.track_views = false`
|
227
222
|
|
228
223
|
![image](https://raw.github.com/danmayer/coverband/master/docs/coverband_view_tracker.png)
|
229
224
|
|
@@ -252,12 +247,25 @@ end
|
|
252
247
|
|
253
248
|
### Avoiding Cache Stampede
|
254
249
|
|
255
|
-
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 due to a concept called [cache stampede](https://en.wikipedia.org/wiki/Cache_stampede).
|
250
|
+
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 due to a concept called [cache stampede](https://en.wikipedia.org/wiki/Cache_stampede).
|
251
|
+
|
252
|
+
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 number 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. Note, the default wiggle is set to 30 seconds.
|
256
253
|
|
257
254
|
Add a wiggle (in seconds) to the background thread to avoid all your servers reporting at the same time:
|
258
255
|
|
259
256
|
`config.reporting_wiggle = 30`
|
260
257
|
|
258
|
+
Another way to avoid cache stampede is to omit some reporting on starting servers. Coverband stores the results of eager_loading to Redis at server startup. The eager_loading results are the same for all servers, so there is no need to save all results. By configuring the eager_loading results of some servers to be stored in Redis, we can reduce the load on Redis during deployment.
|
259
|
+
|
260
|
+
```ruby
|
261
|
+
# To omit reporting on starting servers, need to defer saving eager_loading data
|
262
|
+
config.defer_eager_loading_data = true
|
263
|
+
# Store eager_loading data on 5% of servers
|
264
|
+
config.send_deferred_eager_loading_data = rand(100) < 5
|
265
|
+
# Store eager_loading data on servers with the environment variable
|
266
|
+
config.send_deferred_eager_loading_data = ENV.fetch('ENABLE_EAGER_LOADING_COVERAGE', false)
|
267
|
+
```
|
268
|
+
|
261
269
|
### Redis Hash Store
|
262
270
|
|
263
271
|
Coverband on very high volume sites with many server processes reporting can have a race condition which can cause hit counts to be inaccurate. 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 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 frequency.
|
@@ -267,6 +275,11 @@ Coverband on very high volume sites with many server processes reporting can hav
|
|
267
275
|
|
268
276
|
See more discussion [here](https://github.com/danmayer/coverband/issues/384).
|
269
277
|
|
278
|
+
Please note that with the Redis Hash Store, everytime you load the full report, Coverband will execute `HGETALL` queries in your Redis server twice for every file in the project (once for runtime coverage and once for eager loading coverage). This shouldn't have a big impact in small to medium projects, but can be quite a hassle if your project has a few thousand files.
|
279
|
+
To help reduce the extra redis load when getting the coverage report, you can enable `get_coverage_cache` (but note that when doing that, you will always get a previous version of the report, while a cache is re-populated with a newer version).
|
280
|
+
|
281
|
+
- Use Hash Redis Store with _get coverage cache_: `config.store = Coverband::Adapters::HashRedisStore.new(redis, get_coverage_cache: true)`
|
282
|
+
|
270
283
|
### Clear Coverage
|
271
284
|
|
272
285
|
Now that Coverband uses MD5 hashes there should be no reason to manually clear coverage unless one is testing, changing versions, or possibly debugging Coverband itself.
|
@@ -425,6 +438,7 @@ If you submit a change please make sure the tests and benchmarks are passing.
|
|
425
438
|
- **Coverband, [Elastic APM](https://github.com/elastic/apm-agent-ruby) and resque**
|
426
439
|
- In an environment that uses the Elastic APM ruby agent, resque jobs will fail with `Transactions may not be nested. Already inside #<ElasticAPM::Transaction>` if the `elastic-apm` gem is loaded _before_ the `coverband` gem
|
427
440
|
- Put `coverage` ahead of `elastic-apm` in your Gemfile
|
441
|
+
- **Bootsnap**, The methods used by [bootsnap do not support having coverage enabled](https://github.com/Shopify/bootsnap/blob/main/lib/bootsnap/compile_cache/iseq.rb#L87), so you can either have Coverband or bootsnap, but not both.
|
428
442
|
|
429
443
|
### Debugging Redis Store
|
430
444
|
|
@@ -449,4 +463,4 @@ The Coverband logo was created by [Dave Woodall](http://davewoodall.com). Thanks
|
|
449
463
|
# License
|
450
464
|
|
451
465
|
This is a MIT License project...
|
452
|
-
See the file
|
466
|
+
See the file [LICENSE](LICENSE) for copying permission.
|
data/Rakefile
CHANGED
data/changes.md
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
### Coverband 6.0.2
|
2
|
+
|
3
|
+
* thanks makicamel for improved deferred eager loading
|
4
|
+
* thanks Drowze for two performance improvements to reporting coverage
|
5
|
+
|
6
|
+
|
7
|
+
### Coverband 6.0.1
|
8
|
+
|
9
|
+
* [fix on reload routes](https://github.com/danmayer/coverband/commit/f7a81c9499c01a7c027e5f8bc127815bf29a5cb7)
|
10
|
+
|
11
|
+
|
1
12
|
### Coverband 6.0.0
|
2
13
|
|
3
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__
|
@@ -124,7 +135,7 @@ __NOTE: the current RCs include below, but this might turn into coverband 6.0__
|
|
124
135
|
- added support to download coverage and view data in JSON format
|
125
136
|
- documentation about working with environment variables
|
126
137
|
- add cache wiggle to avoid Redis CPU spikes (cache stampede on Redis server)
|
127
|
-
- make the nocov
|
138
|
+
- make the nocov consistent on the data download and html view
|
128
139
|
- small performance improvements
|
129
140
|
|
130
141
|
### Coverband 4.2.3
|
data/coverband.gemspec
CHANGED
@@ -18,10 +18,9 @@ Gem::Specification.new do |spec|
|
|
18
18
|
|
19
19
|
spec.files = `git ls-files`.split("\n").reject { |f| f.start_with?("docs") }
|
20
20
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
21
|
-
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
22
21
|
spec.require_paths = %w[lib]
|
23
22
|
|
24
|
-
spec.required_ruby_version = ">= 2.
|
23
|
+
spec.required_ruby_version = ">= 2.7"
|
25
24
|
|
26
25
|
spec.metadata = {
|
27
26
|
"homepage_uri" => "https://github.com/danmayer/coverband",
|
@@ -40,11 +39,12 @@ Gem::Specification.new do |spec|
|
|
40
39
|
spec.add_development_dependency "minitest-fork_executor"
|
41
40
|
spec.add_development_dependency "minitest-stub-const"
|
42
41
|
spec.add_development_dependency "mocha", "~> 1.7.0"
|
42
|
+
# spec.add_development_dependency "spy"
|
43
43
|
spec.add_development_dependency "rack"
|
44
44
|
spec.add_development_dependency "rack-test"
|
45
45
|
spec.add_development_dependency "rake"
|
46
46
|
spec.add_development_dependency "resque"
|
47
|
-
spec.add_development_dependency "standard", "
|
47
|
+
spec.add_development_dependency "standard", "~> 1.34.0"
|
48
48
|
spec.add_development_dependency "standardrb"
|
49
49
|
|
50
50
|
spec.add_development_dependency "coveralls"
|
@@ -35,7 +35,7 @@ module Coverband
|
|
35
35
|
raise ABSTRACT_KEY
|
36
36
|
end
|
37
37
|
|
38
|
-
def coverage(_local_type = nil)
|
38
|
+
def coverage(_local_type = nil, opts = {})
|
39
39
|
raise ABSTRACT_KEY
|
40
40
|
end
|
41
41
|
|
@@ -51,9 +51,9 @@ module Coverband
|
|
51
51
|
raise "abstract"
|
52
52
|
end
|
53
53
|
|
54
|
-
def get_coverage_report
|
54
|
+
def get_coverage_report(options = {})
|
55
55
|
coverage_cache = {}
|
56
|
-
data = Coverband.configuration.store.split_coverage(Coverband::TYPES, coverage_cache)
|
56
|
+
data = Coverband.configuration.store.split_coverage(Coverband::TYPES, coverage_cache, options)
|
57
57
|
data.merge(Coverband::MERGED_TYPE => Coverband.configuration.store.merged_coverage(Coverband::TYPES, coverage_cache))
|
58
58
|
end
|
59
59
|
|
@@ -67,12 +67,12 @@ module Coverband
|
|
67
67
|
|
68
68
|
protected
|
69
69
|
|
70
|
-
def split_coverage(types, coverage_cache)
|
70
|
+
def split_coverage(types, coverage_cache, options = {})
|
71
71
|
types.reduce({}) do |data, type|
|
72
72
|
if type == Coverband::RUNTIME_TYPE && Coverband.configuration.simulate_oneshot_lines_coverage
|
73
73
|
data.update(type => coverage_cache[type] ||= simulated_runtime_coverage)
|
74
74
|
else
|
75
|
-
data.update(type => coverage_cache[type] ||= coverage(type))
|
75
|
+
data.update(type => coverage_cache[type] ||= coverage(type, options))
|
76
76
|
end
|
77
77
|
end
|
78
78
|
end
|
@@ -100,7 +100,7 @@ module Coverband
|
|
100
100
|
def expand_report(report)
|
101
101
|
expanded = {}
|
102
102
|
report_time = Time.now.to_i
|
103
|
-
updated_time = type == Coverband::EAGER_TYPE ? nil : report_time
|
103
|
+
updated_time = (type == Coverband::EAGER_TYPE) ? nil : report_time
|
104
104
|
report.each_pair do |key, line_data|
|
105
105
|
extended_data = {
|
106
106
|
FIRST_UPDATED_KEY => report_time,
|
@@ -115,7 +115,7 @@ module Coverband
|
|
115
115
|
|
116
116
|
def merge_reports(new_report, old_report, options = {})
|
117
117
|
# transparently update from RUNTIME_TYPE = nil to RUNTIME_TYPE = :runtime
|
118
|
-
# transparent update for format
|
118
|
+
# transparent update for format coverband_3_2
|
119
119
|
old_report = coverage(nil, override_type: nil) if old_report.nil? && type == Coverband::RUNTIME_TYPE
|
120
120
|
new_report = expand_report(new_report) unless options[:skip_expansion]
|
121
121
|
keys = (new_report.keys + old_report.keys).uniq
|
@@ -145,11 +145,11 @@ module Coverband
|
|
145
145
|
# TODO: This should only be 2 cases get our dup / not dups aligned
|
146
146
|
def array_add(latest, original)
|
147
147
|
if Coverband.configuration.use_oneshot_lines_coverage
|
148
|
-
latest.map!.with_index { |v, i| (v + original[i] >= 1 ? 1 : 0) if v && original[i] }
|
148
|
+
latest.map!.with_index { |v, i| ((v + original[i] >= 1) ? 1 : 0) if v && original[i] }
|
149
149
|
elsif Coverband.configuration.simulate_oneshot_lines_coverage
|
150
|
-
latest.map.with_index { |v, i| (v + original[i] >= 1 ? 1 : 0) if v && original[i] }
|
150
|
+
latest.map.with_index { |v, i| ((v + original[i] >= 1) ? 1 : 0) if v && original[i] }
|
151
151
|
else
|
152
|
-
latest.map.with_index { |v, i| v && original[i] ? v + original[i] : nil }
|
152
|
+
latest.map.with_index { |v, i| (v && original[i]) ? v + original[i] : nil }
|
153
153
|
end
|
154
154
|
end
|
155
155
|
end
|
@@ -5,6 +5,92 @@ require "securerandom"
|
|
5
5
|
module Coverband
|
6
6
|
module Adapters
|
7
7
|
class HashRedisStore < Base
|
8
|
+
class GetCoverageNullCacheStore
|
9
|
+
def self.clear!(*_local_types)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.fetch(_local_type)
|
13
|
+
yield(0)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class GetCoverageRedisCacheStore
|
18
|
+
LOCK_LIMIT = 60 * 30 # 30 minutes
|
19
|
+
|
20
|
+
def initialize(redis, key_prefix)
|
21
|
+
@redis = redis
|
22
|
+
@key_prefix = [key_prefix, "get-coverage"].join(".")
|
23
|
+
end
|
24
|
+
|
25
|
+
def fetch(local_type)
|
26
|
+
cached_result = get(local_type)
|
27
|
+
|
28
|
+
# if no cache available, block the call and populate the cache
|
29
|
+
# if cache is available, return it and start re-populating it (with a lock)
|
30
|
+
if cached_result.nil?
|
31
|
+
value = yield(0)
|
32
|
+
result = set(local_type, JSON.generate(value))
|
33
|
+
value
|
34
|
+
else
|
35
|
+
if lock!(local_type)
|
36
|
+
Thread.new do
|
37
|
+
result = yield(deferred_time)
|
38
|
+
set(local_type, JSON.generate(result))
|
39
|
+
ensure
|
40
|
+
unlock!(local_type)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
JSON.parse(cached_result)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def clear!(local_types = Coverband::TYPES)
|
48
|
+
Array(local_types).each do |local_type|
|
49
|
+
del(local_type)
|
50
|
+
unlock!(local_type)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
protected
|
55
|
+
|
56
|
+
def split_coverage(types, coverage_cache, options = {})
|
57
|
+
if types.is_a?(Array)
|
58
|
+
coverage_for_types(types, options)
|
59
|
+
else
|
60
|
+
super
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
# sleep in between to avoid holding other redis commands..
|
67
|
+
# with a small random offset so runtime and eager types can be processed "at the same time"
|
68
|
+
def deferred_time
|
69
|
+
rand(2.0..3.0)
|
70
|
+
end
|
71
|
+
|
72
|
+
def del(local_type)
|
73
|
+
@redis.del("#{@key_prefix}.cache.#{local_type}")
|
74
|
+
end
|
75
|
+
|
76
|
+
def get(local_type)
|
77
|
+
@redis.get("#{@key_prefix}.cache.#{local_type}")
|
78
|
+
end
|
79
|
+
|
80
|
+
def set(local_type, value)
|
81
|
+
@redis.set("#{@key_prefix}.cache.#{local_type}", value)
|
82
|
+
end
|
83
|
+
|
84
|
+
# lock for at most 60 minutes
|
85
|
+
def lock!(local_type)
|
86
|
+
@redis.set("#{@key_prefix}.lock.#{local_type}", "1", nx: true, ex: LOCK_LIMIT)
|
87
|
+
end
|
88
|
+
|
89
|
+
def unlock!(local_type)
|
90
|
+
@redis.del("#{@key_prefix}.lock.#{local_type}")
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
8
94
|
FILE_KEY = "file"
|
9
95
|
FILE_LENGTH_KEY = "file_length"
|
10
96
|
META_DATA_KEYS = [DATA_KEY, FIRST_UPDATED_KEY, LAST_UPDATED_KEY, FILE_HASH].freeze
|
@@ -17,7 +103,7 @@ module Coverband
|
|
17
103
|
|
18
104
|
JSON_PAYLOAD_EXPIRATION = 5 * 60
|
19
105
|
|
20
|
-
attr_reader :redis_namespace
|
106
|
+
attr_reader :redis_namespace, :get_coverage_cache
|
21
107
|
|
22
108
|
def initialize(redis, opts = {})
|
23
109
|
super()
|
@@ -29,6 +115,13 @@ module Coverband
|
|
29
115
|
|
30
116
|
@ttl = opts[:ttl]
|
31
117
|
@relative_file_converter = opts[:relative_file_converter] || Utils::RelativeFileConverter
|
118
|
+
|
119
|
+
@get_coverage_cache = if opts[:get_coverage_cache]
|
120
|
+
key_prefix = [REDIS_STORAGE_FORMAT_VERSION, @redis_namespace, "v2"].compact.join(".")
|
121
|
+
GetCoverageRedisCacheStore.new(redis, key_prefix)
|
122
|
+
else
|
123
|
+
GetCoverageNullCacheStore
|
124
|
+
end
|
32
125
|
end
|
33
126
|
|
34
127
|
def supported?
|
@@ -45,6 +138,8 @@ module Coverband
|
|
45
138
|
file_keys = files_set
|
46
139
|
@redis.del(*file_keys) if file_keys.any?
|
47
140
|
@redis.del(files_key)
|
141
|
+
@redis.del(files_key(type))
|
142
|
+
@get_coverage_cache.clear!(type)
|
48
143
|
end
|
49
144
|
self.type = old_type
|
50
145
|
end
|
@@ -54,13 +149,14 @@ module Coverband
|
|
54
149
|
relative_path_file = @relative_file_converter.convert(file)
|
55
150
|
Coverband::TYPES.each do |type|
|
56
151
|
@redis.del(key(relative_path_file, type, file_hash: file_hash))
|
152
|
+
@get_coverage_cache.clear!(type)
|
57
153
|
end
|
58
154
|
@redis.srem(files_key, relative_path_file)
|
59
155
|
end
|
60
156
|
|
61
157
|
def save_report(report)
|
62
158
|
report_time = Time.now.to_i
|
63
|
-
updated_time = type == Coverband::EAGER_TYPE ? nil : report_time
|
159
|
+
updated_time = (type == Coverband::EAGER_TYPE) ? nil : report_time
|
64
160
|
keys = []
|
65
161
|
report.each_slice(@save_report_batch_size) do |slice|
|
66
162
|
files_data = slice.map { |(file, data)|
|
@@ -86,17 +182,71 @@ module Coverband
|
|
86
182
|
@redis.sadd(files_key, keys) if keys.any?
|
87
183
|
end
|
88
184
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
185
|
+
# TODO: refactor this and the method below and consider removing all the cached results stuff
|
186
|
+
def coverage(local_type = nil, opts = {})
|
187
|
+
page_size = opts[:page_size] || 250
|
188
|
+
cached_results = @get_coverage_cache.fetch(local_type || type) do |sleep_time|
|
189
|
+
files_set = if opts[:page]
|
190
|
+
files_set(local_type).each_slice(page_size).to_a[opts[:page] - 1] || {}
|
191
|
+
elsif opts[:filename]
|
192
|
+
files_set(local_type).select { |filepath| filepath == opts[:filename] } || {}
|
193
|
+
else
|
194
|
+
files_set(local_type)
|
94
195
|
end
|
95
|
-
|
196
|
+
# use batches with a sleep in between to avoid overloading redis
|
197
|
+
files_set.each_slice(page_size).flat_map do |key_batch|
|
198
|
+
sleep sleep_time
|
199
|
+
@redis.pipelined do |pipeline|
|
200
|
+
key_batch.each do |key|
|
201
|
+
pipeline.hgetall(key)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
cached_results.each_with_object({}) do |data_from_redis, hash|
|
96
208
|
add_coverage_for_file(data_from_redis, hash)
|
97
209
|
end
|
98
210
|
end
|
99
211
|
|
212
|
+
# NOTE: when using paging we need to ensure we have the same set of files per page in runtime and eager
|
213
|
+
def coverage_for_types(types, opts = {})
|
214
|
+
page_size = opts[:page_size] || 250
|
215
|
+
|
216
|
+
local_type = Coverband::RUNTIME_TYPE
|
217
|
+
hash_data = {}
|
218
|
+
|
219
|
+
runtime_file_set = if opts[:page]
|
220
|
+
files_set(local_type).each_slice(page_size).to_a[opts[:page] - 1] || {}
|
221
|
+
elsif opts[:filename]
|
222
|
+
files_set(local_type).select { |filepath| filepath == opts[:filename] } || {}
|
223
|
+
else
|
224
|
+
files_set(local_type)
|
225
|
+
end
|
226
|
+
hash_data[Coverband::RUNTIME_TYPE] = runtime_file_set.each_slice(page_size).flat_map do |key_batch|
|
227
|
+
@redis.pipelined do |pipeline|
|
228
|
+
key_batch.each do |key|
|
229
|
+
pipeline.hgetall(key)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
matched_file_set = files_set(Coverband::EAGER_TYPE)
|
235
|
+
.select { |filepath| runtime_file_set.include?(filepath) } || {}
|
236
|
+
hash_data[Coverband::EAGER_TYPE] = matched_file_set.each_slice(page_size).flat_map do |key_batch|
|
237
|
+
@redis.pipelined do |pipeline|
|
238
|
+
key_batch.each do |key|
|
239
|
+
pipeline.hgetall(key)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
hash_data
|
244
|
+
end
|
245
|
+
|
246
|
+
def file_count(local_type = nil)
|
247
|
+
files_set(local_type).count { |filename| !Coverband.configuration.ignore.any? { |i| filename.match(i) } }
|
248
|
+
end
|
249
|
+
|
100
250
|
def raw_store
|
101
251
|
@redis
|
102
252
|
end
|
@@ -119,7 +269,7 @@ module Coverband
|
|
119
269
|
|
120
270
|
data = coverage_data_from_redis(data_from_redis)
|
121
271
|
hash[file] = data_from_redis.select { |meta_data_key, _value| META_DATA_KEYS.include?(meta_data_key) }.merge!("data" => data)
|
122
|
-
hash[file][LAST_UPDATED_KEY] = hash[file][LAST_UPDATED_KEY].nil? || hash[file][LAST_UPDATED_KEY] == "" ? nil : hash[file][LAST_UPDATED_KEY].to_i
|
272
|
+
hash[file][LAST_UPDATED_KEY] = (hash[file][LAST_UPDATED_KEY].nil? || hash[file][LAST_UPDATED_KEY] == "") ? nil : hash[file][LAST_UPDATED_KEY].to_i
|
123
273
|
hash[file].merge!(LAST_UPDATED_KEY => hash[file][LAST_UPDATED_KEY], FIRST_UPDATED_KEY => hash[file][FIRST_UPDATED_KEY].to_i)
|
124
274
|
end
|
125
275
|
|
@@ -127,7 +277,7 @@ module Coverband
|
|
127
277
|
max = data_from_redis[FILE_LENGTH_KEY].to_i - 1
|
128
278
|
Array.new(max + 1) do |index|
|
129
279
|
line_coverage = data_from_redis[index.to_s]
|
130
|
-
line_coverage
|
280
|
+
line_coverage&.to_i
|
131
281
|
end
|
132
282
|
end
|
133
283
|
|
@@ -42,7 +42,7 @@ module Coverband
|
|
42
42
|
|
43
43
|
###
|
44
44
|
# Fetch coverband coverage via the API
|
45
|
-
# This would allow one to
|
45
|
+
# This would allow one to explore from the service and move back to the open source
|
46
46
|
# without having to reset coverage
|
47
47
|
###
|
48
48
|
def coverage(local_type = nil, opts = {})
|
@@ -98,12 +98,10 @@ module Coverband
|
|
98
98
|
def retry_failed_reports
|
99
99
|
retries = []
|
100
100
|
@failed_coverage_reports.any? do
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
retries << report_body
|
106
|
-
end
|
101
|
+
report_body = @failed_coverage_reports.pop
|
102
|
+
send_report_body(report_body)
|
103
|
+
rescue
|
104
|
+
retries << report_body
|
107
105
|
end
|
108
106
|
retries.each do |report_body|
|
109
107
|
add_retry_message(report_body)
|
@@ -64,7 +64,7 @@ module Coverband
|
|
64
64
|
else
|
65
65
|
if @deferred_eager_loading_data && Coverband.configuration.defer_eager_loading_data?
|
66
66
|
toggle_eager_loading do
|
67
|
-
@store.save_report(@deferred_eager_loading_data)
|
67
|
+
@store.save_report(@deferred_eager_loading_data) if Coverband.configuration.send_deferred_eager_loading_data?
|
68
68
|
@deferred_eager_loading_data = nil
|
69
69
|
end
|
70
70
|
end
|
@@ -65,7 +65,7 @@ module Coverband
|
|
65
65
|
|
66
66
|
# NOTE: This event was instrumented in Aug 10th 2022, but didn't make the 7.0.4 release and should be in the next release
|
67
67
|
# https://github.com/rails/rails/pull/43755
|
68
|
-
# Automatic tracking of redirects isn't
|
68
|
+
# Automatic tracking of redirects isn't available before Rails 7.1.0 (currently tested against the 7.1.0.alpha)
|
69
69
|
# We could consider back porting or patching a solution that works on previous Rails versions
|
70
70
|
ActiveSupport::Notifications.subscribe("redirect.action_dispatch") do |name, start, finish, id, payload|
|
71
71
|
Coverband.configuration.route_tracker.track_key(payload)
|
@@ -36,7 +36,7 @@ module Coverband
|
|
36
36
|
app_translation_keys = []
|
37
37
|
app_translation_files = ::I18n.load_path.select { |f| f.match(/config\/locales/) }
|
38
38
|
app_translation_files.each do |file|
|
39
|
-
app_translation_keys += flatten_hash(YAML.load_file(file)).keys
|
39
|
+
app_translation_keys += flatten_hash(YAML.load_file(file, aliases: true)).keys
|
40
40
|
end
|
41
41
|
app_translation_keys.uniq
|
42
42
|
else
|
@@ -48,7 +48,7 @@ module Coverband
|
|
48
48
|
hash.each_with_object({}) do |(k, v), h|
|
49
49
|
if v.is_a? Hash
|
50
50
|
flatten_hash(v).map do |h_k, h_v|
|
51
|
-
h["#{k}.#{h_k}"
|
51
|
+
h[:"#{k}.#{h_k}"] = h_v
|
52
52
|
end
|
53
53
|
else
|
54
54
|
h[k] = v
|
@@ -81,7 +81,7 @@ module Coverband
|
|
81
81
|
end
|
82
82
|
|
83
83
|
def unused_keys(used_views = nil)
|
84
|
-
recently_used_views =
|
84
|
+
recently_used_views = used_keys.keys
|
85
85
|
unused_views = all_keys - recently_used_views
|
86
86
|
# since layouts don't include format we count them used if they match with ANY formats
|
87
87
|
unused_views.reject { |view| view.match(/\/layouts\//) && recently_used_views.any? { |used_view| view.include?(used_view) } }
|