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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +10 -0
  3. data/.github/workflows/main.yml +2 -3
  4. data/.standard.yml +1 -1
  5. data/Gemfile +1 -1
  6. data/LICENSE +1 -1
  7. data/README.md +24 -10
  8. data/Rakefile +1 -1
  9. data/changes.md +12 -1
  10. data/coverband.gemspec +3 -3
  11. data/lib/coverband/adapters/base.rb +10 -10
  12. data/lib/coverband/adapters/file_store.rb +1 -1
  13. data/lib/coverband/adapters/hash_redis_store.rb +160 -10
  14. data/lib/coverband/adapters/null_store.rb +1 -1
  15. data/lib/coverband/adapters/stdout_store.rb +1 -1
  16. data/lib/coverband/adapters/web_service_store.rb +5 -7
  17. data/lib/coverband/collectors/coverage.rb +1 -1
  18. data/lib/coverband/collectors/route_tracker.rb +1 -1
  19. data/lib/coverband/collectors/translation_tracker.rb +2 -2
  20. data/lib/coverband/collectors/view_tracker.rb +1 -1
  21. data/lib/coverband/configuration.rb +12 -6
  22. data/lib/coverband/integrations/rack_server_check.rb +1 -3
  23. data/lib/coverband/reporters/base.rb +7 -7
  24. data/lib/coverband/reporters/html_report.rb +12 -4
  25. data/lib/coverband/reporters/json_report.rb +42 -4
  26. data/lib/coverband/reporters/web.rb +16 -2
  27. data/lib/coverband/reporters/web_pager.rb +28 -0
  28. data/lib/coverband/utils/absolute_file_converter.rb +19 -19
  29. data/lib/coverband/utils/file_hasher.rb +3 -3
  30. data/lib/coverband/utils/html_formatter.rb +4 -4
  31. data/lib/coverband/utils/method_definition_scanner.rb +1 -1
  32. data/lib/coverband/utils/railtie.rb +4 -6
  33. data/lib/coverband/utils/relative_file_converter.rb +7 -7
  34. data/lib/coverband/utils/results.rb +14 -14
  35. data/lib/coverband/utils/source_file.rb +2 -2
  36. data/lib/coverband/utils/tasks.rb +1 -1
  37. data/lib/coverband/version.rb +1 -1
  38. data/lib/coverband.rb +19 -3
  39. data/public/application.js +35 -0
  40. data/public/dependencies.js +1 -1
  41. data/roadmap.md +1 -1
  42. data/test/benchmarks/benchmark.rake +5 -5
  43. data/test/coverband/adapters/file_store_test.rb +1 -1
  44. data/test/coverband/adapters/hash_redis_store_test.rb +48 -0
  45. data/test/coverband/collectors/coverage_test.rb +1 -1
  46. data/test/coverband/reporters/json_test.rb +1 -1
  47. data/test/coverband/utils/source_file_test.rb +11 -11
  48. data/test/fixtures/casting_invitor.rb +1 -1
  49. data/test/fixtures/sample.rb +2 -2
  50. data/test/fixtures/skipped_and_executed.rb +1 -1
  51. data/test/forked/rails_rake_full_stack_test.rb +1 -1
  52. data/test/integration/full_stack_send_deferred_eager_test.rb +52 -0
  53. data/test/test_helper.rb +3 -5
  54. data/test/unique_files.rb +1 -1
  55. data/views/file_list.erb +38 -32
  56. data/views/layout.erb +3 -3
  57. metadata +17 -139
  58. data/LICENSE.txt +0 -22
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 036f9cdceb426dd675d4e6d78af901a6618dee5062fc5572b47ce90518ffbb1f
4
- data.tar.gz: d1d1d8f6c1cf4123d34ae630cd92767d673e1e04e4ef0f18660327c481b309f0
3
+ metadata.gz: f25f2799e62201b31472588ed6ee3d00697b6ab8eba5beb04c3a76cb7e52d962
4
+ data.tar.gz: 599a0d1a4ef26b9b353d4ae341db2287f15b5119b9d9d3ed3d2b0e796c6e1fae
5
5
  SHA512:
6
- metadata.gz: a10c5a56f2ef1cb15e42f5f52f4636432fa0bb779276e44dd719b06474d51da7529b03334d8d0a383fe25b38af932bc0d32c67e3c527aaa568bd9ab35466eb9e
7
- data.tar.gz: 9be31a11af5c7d490e2f965a8e91f1fc916668efa3ec384afc88f78225a880056d3b3ae5dc4e1193be87e63a0d2cb07f35c72c4268018cf01e357c7d9835d3ae
6
+ metadata.gz: b58f4486c028a9ed4f2e3444ed164a43b620b4cc213f91e9b7aef93c2ef1c394840aade62466b216f29ade57851314989e0ed3f3bca2a7f692deddd106efe7a3
7
+ data.tar.gz: 6569ffaeb15feb24b4eb40d5e0403bcf4fcc6fc381a7fd30e64a9e6a7fee06498e05ffe2832c7a99545c51a8b25a12cf6264daf672ef82485ccf10e03a3895a3
@@ -0,0 +1,10 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "bundler"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "weekly"
7
+ - package-ecosystem: "github-actions"
8
+ directory: "/"
9
+ schedule:
10
+ interval: "weekly"
@@ -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@v3
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
@@ -1,4 +1,4 @@
1
- ruby_version: 2.3
1
+ ruby_version: 2.7
2
2
  fix: false # default: false
3
3
  parallel: true # default: false
4
4
  format: progress # default: Standard::Formatter
data/Gemfile CHANGED
@@ -4,7 +4,7 @@ source "https://rubygems.org"
4
4
 
5
5
  # Specify your gem's dependencies in coverband.gemspec
6
6
  gemspec
7
- gem "rails", "~>6"
7
+ gem "rails", "~>7"
8
8
  gem "haml"
9
9
  gem "slim"
10
10
  gem "webrick"
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2010-2018 Dan Mayer
3
+ Copyright (c) 2010 Dan Mayer
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy of
6
6
  this software and associated documentation files (the "Software"), to deal in
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
- > The web index is available on the [Coverband Demo site](https://coverband-demo.herokuapp.com/coverage?#_Coverage).
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 opt-in to this feature... enable the feature in your Coverband config.
219
+ This feature is enabled by default. To stop this feature, disable the feature in your Coverband config.
225
220
 
226
- `config.track_views = true`
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). 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.
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 license.txt for copying permission.
466
+ See the file [LICENSE](LICENSE) for copying permission.
data/Rakefile CHANGED
@@ -8,7 +8,7 @@ RuboCop::RakeTask.new
8
8
 
9
9
  task default: %i[test]
10
10
 
11
- task 'test:all': %i[rubocop test forked_tests benchmarks:memory benchmarks]
11
+ task "test:all": %i[rubocop test forked_tests benchmarks:memory benchmarks]
12
12
 
13
13
  task :test
14
14
  require "rake/testtask"
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 consistant on the data download and html view
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.3"
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", "= 0.2.5"
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 coveband_3_2
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
@@ -52,7 +52,7 @@ module Coverband
52
52
  raise NotImplementedError, "FileStore doesn't support migrations"
53
53
  end
54
54
 
55
- def coverage(_local_type = nil)
55
+ def coverage(_local_type = nil, opts = {})
56
56
  if merge_mode
57
57
  data = {}
58
58
  Dir[path.sub(/\.\d+/, ".*")].each do |path|
@@ -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
- def coverage(local_type = nil)
90
- files_set = files_set(local_type)
91
- @redis.pipelined { |pipeline|
92
- files_set.each do |key|
93
- pipeline.hgetall(key)
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
- }.each_with_object({}) do |data_from_redis, hash|
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.nil? ? nil : line_coverage.to_i
280
+ line_coverage&.to_i
131
281
  end
132
282
  end
133
283
 
@@ -26,7 +26,7 @@ module Coverband
26
26
  raise NotImplementedError, "NullStore doesn't support migrations"
27
27
  end
28
28
 
29
- def coverage(_local_type = nil)
29
+ def coverage(_local_type = nil, opts = {})
30
30
  {}
31
31
  end
32
32
 
@@ -25,7 +25,7 @@ module Coverband
25
25
  raise NotImplementedError, "StdoutStore doesn't support migrations"
26
26
  end
27
27
 
28
- def coverage(_local_type = nil)
28
+ def coverage(_local_type = nil, opts = {})
29
29
  {}
30
30
  end
31
31
 
@@ -42,7 +42,7 @@ module Coverband
42
42
 
43
43
  ###
44
44
  # Fetch coverband coverage via the API
45
- # This would allow one to expore from the service and move back to the open source
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
- begin
102
- report_body = @failed_coverage_reports.pop
103
- send_report_body(report_body)
104
- rescue
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 avaible before Rails 7.1.0 (currently tested against the 7.1.0.alpha)
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}".to_sym] = h_v
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 = (used_keys || used_keys).keys
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) } }