coverband 5.2.6.rc.1 → 5.2.6.rc.3

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: fc0d04670278db743e5beef3f1494cf3534f162c5e6e285100ac42ad6f04f24b
4
- data.tar.gz: 6e091007de4ac8fa1f99dff488756e09a748ce6648b186b95d59b9f3b771548b
3
+ metadata.gz: '039bd98899c0794752cb3244029b3bce0d9d6ebda8ddf449423e94e44741acb5'
4
+ data.tar.gz: 5b19acc65170a2f2c6d66a750a7e958759e90caca808943426481828c26dedfa
5
5
  SHA512:
6
- metadata.gz: 2a1292ce42a916fdcc5bfc47cf800ee6ff68511c98a81a6321c65d602e6843bc3112c8c6347b7706595c4926c7ecad37678ca536e301a1af4b1234f2df6bb352
7
- data.tar.gz: e02d38262d17772614ef420c33bf5307d6616338b593e07b787e87fccf62b8ba56082a926a1b3bd3af5d2370e87c7c7fa9932bc624d3b840b7affd9d4df60ba7
6
+ metadata.gz: 51e2ef52b7bd14f677845d6d565872ab9a607990ff28e89ba020f5fe8f8a65d728400f241ea2d70647580f2387e2115f9d033e450704d821d9a1664be5457f76
7
+ data.tar.gz: 62dc029eb498529c31e81a297db3afb5a1d95a716d0802ed0b99128b8405f937b6d90c61eb3d0dc49df4f2faf9b9c2545ae5f54dbda9db2d04e3b6465001a75d
@@ -13,4 +13,5 @@ jobs:
13
13
  - name: Update diagram
14
14
  uses: githubocto/repo-visualizer@main
15
15
  with:
16
- excluded_paths: "ignore,.github"
16
+ excluded_paths: "ignore,.github"
17
+ branch: diagram
@@ -21,11 +21,11 @@ jobs:
21
21
  # removing jruby again to flaky
22
22
  # ruby: [2.3, 2.4, 2.5, 2.6, 2.7, "3.0", "3.1", jruby]
23
23
  # need to add support for multiple gemfiles
24
- ruby: ["2.7", "3.0", "3.1"]
24
+ ruby: ["2.7", "3.0", "3.1", "3.2"]
25
25
  redis-version: [4, 5, 6]
26
26
  runs-on: ${{ matrix.os }}-latest
27
27
  steps:
28
- - uses: actions/checkout@v2
28
+ - uses: actions/checkout@v3
29
29
  - uses: supercharge/redis-github-action@1.2.0
30
30
  - uses: ruby/setup-ruby@v1
31
31
  with:
data/README.md CHANGED
@@ -207,9 +207,9 @@ config.ignore += ['config/application.rb',
207
207
  'config/boot.rb',
208
208
  'config/puma.rb',
209
209
  'config/schedule.rb',
210
- 'bin/*',
211
- 'config/environments/*',
212
- 'lib/tasks/*']
210
+ 'bin/.*',
211
+ 'config/environments/.*',
212
+ 'lib/tasks/.*']
213
213
  ```
214
214
 
215
215
  **Ignoring Custom Gem Locations:** Note, if you have your gems in a custom location under your app folder you likely want to add them to `config.ignore`. For example, if you have your gems not in a default ignored location of `app/vendor` but have them in `app/gems` you would need to add `gems/*` to your ignore list.
@@ -437,7 +437,7 @@ What is the coverage data in Redis?
437
437
 
438
438
  A diagram of the code.
439
439
 
440
- ![Visualization of this repo](./diagram.svg)
440
+ ![Visualization of this repo](https://raw.githubusercontent.com/danmayer/coverband/diagram/diagram.svg)
441
441
 
442
442
  ## Logo
443
443
 
data/changes.md CHANGED
@@ -1,6 +1,10 @@
1
1
  ### Coverband 5.2.6
2
2
 
3
+ __NOTE: the current RCs include below, but this might turn into coverband 6.0__
4
+
3
5
  - add support for translation keys
6
+ - refactor non Coverage.so based trackers
7
+ - adds CSP report support (thanks @jwg2s)
4
8
 
5
9
  ### Coverband 5.2.5
6
10
 
@@ -22,7 +22,8 @@ module Coverband
22
22
  Coverband.report_coverage
23
23
  # to ensure we track mailer views we now need to report views tracking
24
24
  # at exit as well for rake tasks and background tasks that can trigger email
25
- Coverband.configuration.view_tracker&.report_views_tracked
25
+ Coverband.configuration.view_tracker&.save_report
26
+ Coverband.configuration.translations_tracker&.save_report
26
27
  end
27
28
  end
28
29
  end
@@ -0,0 +1,164 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+ require "singleton"
5
+
6
+ module Coverband
7
+ module Collectors
8
+ ###
9
+ # This abstract class makes it easy to track any used/unused with timestamp set of usage
10
+ ###
11
+ class AbstractTracker
12
+ REPORT_ROUTE = "/"
13
+ TITLE = "abstract"
14
+
15
+ attr_accessor :target
16
+ attr_reader :logger, :store, :ignore_patterns
17
+
18
+ def initialize(options = {})
19
+ raise NotImplementedError, "#{self.class.name} requires a newer version of Rails" unless self.class.supported_version?
20
+ raise "Coverband: #{self.class.name} initialized before configuration!" if !Coverband.configured? && ENV["COVERBAND_TEST"] == "test"
21
+
22
+ @ignore_patterns = Coverband.configuration.ignore
23
+ @store = options.fetch(:store) { Coverband.configuration.store }
24
+ @logger = options.fetch(:logger) { Coverband.configuration.logger }
25
+ @target = options.fetch(:target) do
26
+ concrete_target
27
+ end
28
+
29
+ @one_time_timestamp = false
30
+
31
+ @logged_keys = Set.new
32
+ @keys_to_record = Set.new
33
+ end
34
+
35
+ def logged_keys
36
+ @logged_keys.to_a
37
+ end
38
+
39
+ def keys_to_record
40
+ @keys_to_record.to_a
41
+ end
42
+
43
+ ###
44
+ # This method is called on every translation usage
45
+ ###
46
+ def track_key(key)
47
+ if key
48
+ if newly_seen_key?(key)
49
+ @logged_keys << key
50
+ @keys_to_record << key if track_key?(key)
51
+ end
52
+ end
53
+ end
54
+
55
+ def used_keys
56
+ redis_store.hgetall(tracker_key)
57
+ end
58
+
59
+ def all_keys
60
+ target.uniq
61
+ end
62
+
63
+ def unused_keys(used_keys = nil)
64
+ recently_used_keys = (used_keys || self.used_keys).keys
65
+ all_keys.reject { |k| recently_used_keys.include?(k.to_s) }
66
+ end
67
+
68
+ def as_json
69
+ used_keys = self.used_keys
70
+ {
71
+ unused_keys: unused_keys(used_keys),
72
+ used_keys: used_keys
73
+ }.to_json
74
+ end
75
+
76
+ def tracking_since
77
+ if (tracking_time = redis_store.get(tracker_time_key))
78
+ Time.at(tracking_time.to_i).iso8601
79
+ else
80
+ "N/A"
81
+ end
82
+ end
83
+
84
+ def reset_recordings
85
+ redis_store.del(tracker_key)
86
+ redis_store.del(tracker_time_key)
87
+ end
88
+
89
+ def clear_key!(key)
90
+ return unless key
91
+ puts "#{tracker_key} key #{key}"
92
+ redis_store.hdel(tracker_key, key)
93
+ @logged_keys.delete(key)
94
+ end
95
+
96
+ def save_report
97
+ redis_store.set(tracker_time_key, Time.now.to_i) unless @one_time_timestamp || tracker_time_key_exists?
98
+ @one_time_timestamp = true
99
+ reported_time = Time.now.to_i
100
+ @keys_to_record.to_a.each do |key|
101
+ redis_store.hset(tracker_key, key.to_s, reported_time)
102
+ end
103
+ @keys_to_record.clear
104
+ rescue => e
105
+ # we don't want to raise errors if Coverband can't reach redis.
106
+ # This is a nice to have not a bring the system down
107
+ logger&.error "Coverband: #{self.class.name} failed to store, error #{e.class.name} info #{e.message}"
108
+ end
109
+
110
+ # This is the basic rails version supported, if there is something more unique over ride in subclass
111
+ def self.supported_version?
112
+ defined?(Rails) && defined?(Rails::VERSION) && Rails::VERSION::STRING.split(".").first.to_i >= 5
113
+ end
114
+
115
+ def route
116
+ self.class::REPORT_ROUTE
117
+ end
118
+
119
+ def title
120
+ self.class::TITLE
121
+ end
122
+
123
+ protected
124
+
125
+ def newly_seen_key?(key)
126
+ !@logged_keys.include?(key)
127
+ end
128
+
129
+ def track_key?(key, options = {})
130
+ @ignore_patterns.none? { |pattern| key.to_s.include?(pattern) }
131
+ end
132
+
133
+ private
134
+
135
+ def concrete_target
136
+ raise "subclass must implement"
137
+ end
138
+
139
+ def redis_store
140
+ store.raw_store
141
+ end
142
+
143
+ def tracker_time_key_exists?
144
+ if defined?(redis_store.exists?)
145
+ redis_store.exists?(tracker_time_key)
146
+ else
147
+ redis_store.exists(tracker_time_key)
148
+ end
149
+ end
150
+
151
+ def tracker_key
152
+ "#{class_key}_tracker"
153
+ end
154
+
155
+ def tracker_time_key
156
+ "#{class_key}_tracker_time"
157
+ end
158
+
159
+ def class_key
160
+ @class_key ||= self.class.name.split("::").last
161
+ end
162
+ end
163
+ end
164
+ end
@@ -8,51 +8,23 @@ module Coverband
8
8
  ###
9
9
  # This class tracks route usage via ActiveSupport::Notifications
10
10
  ###
11
- class RouteTracker
12
- attr_accessor :target
13
- attr_reader :logger, :store, :ignore_patterns
11
+ class RouteTracker < AbstractTracker
12
+ REPORT_ROUTE = "routes_tracker"
13
+ TITLE = "Routes"
14
14
 
15
15
  def initialize(options = {})
16
- raise NotImplementedError, "Route Tracker requires Rails 4 or greater" unless self.class.supported_version?
17
- raise "Coverband: route tracker initialized before configuration!" if !Coverband.configured? && ENV["COVERBAND_TEST"] == "test"
18
-
19
- @ignore_patterns = Coverband.configuration.ignore
20
- @store = options.fetch(:store) { Coverband.configuration.store }
21
- @logger = options.fetch(:logger) { Coverband.configuration.logger }
22
- @target = options.fetch(:target) do
23
- if defined?(Rails.application)
24
- Rails.application.routes.routes.map do |route|
25
- {
26
- controller: route.defaults[:controller],
27
- action: route.defaults[:action],
28
- url_path: route.path.spec.to_s.gsub("(.:format)", ""),
29
- verb: route.verb
30
- }
31
- end
32
- else
33
- []
34
- end
16
+ if Rails&.respond_to?(:version) && Gem::Version.new(Rails.version) >= Gem::Version.new("6.0.0") && Gem::Version.new(Rails.version) < Gem::Version.new("7.1.0")
17
+ require_relative "../utils/rails6_ext"
35
18
  end
36
19
 
37
- @one_time_timestamp = false
38
-
39
- @logged_routes = Set.new
40
- @routes_to_record = Set.new
41
- end
42
-
43
- def logged_routes
44
- @logged_routes.to_a
45
- end
46
-
47
- def routes_to_record
48
- @routes_to_record.to_a
20
+ super
49
21
  end
50
22
 
51
23
  ###
52
24
  # This method is called on every routing call, so we try to reduce method calls
53
25
  # and ensure high performance
54
26
  ###
55
- def track_routes(_name, _start, _finish, _id, payload)
27
+ def track_key(payload)
56
28
  route = if payload[:request]
57
29
  {
58
30
  controller: nil,
@@ -69,104 +41,53 @@ module Coverband
69
41
  }
70
42
  end
71
43
  if route
72
- if newly_seen_route?(route)
73
- @logged_routes << route
74
- @routes_to_record << route if track_route?(route)
44
+ if newly_seen_key?(route)
45
+ @logged_keys << route
46
+ @keys_to_record << route if track_key?(route)
75
47
  end
76
48
  end
77
49
  end
78
50
 
79
- def used_routes
80
- redis_store.hgetall(tracker_key)
81
- end
82
-
83
- def all_routes
84
- target.uniq
51
+ def self.supported_version?
52
+ defined?(Rails) && defined?(Rails::VERSION) && Rails::VERSION::STRING.split(".").first.to_i >= 6
85
53
  end
86
54
 
87
- def unused_routes(used_routes = nil)
88
- recently_used_routes = (used_routes || self.used_routes).keys
55
+ def unused_keys(used_keys = nil)
56
+ recently_used_routes = (used_keys || self.used_keys).keys
89
57
  # NOTE: we match with or without path to handle paths with named params like `/user/:user_id` to used routes filling with all the variable named paths
90
- all_routes.reject { |r| recently_used_routes.include?(r.to_s) || recently_used_routes.include?(r.merge(url_path: nil).to_s) }
91
- end
92
-
93
- def as_json
94
- used_routes = self.used_routes
95
- {
96
- unused_routes: unused_routes(used_routes),
97
- used_routes: used_routes
98
- }.to_json
58
+ all_keys.reject { |r| recently_used_routes.include?(r.to_s) || recently_used_routes.include?(r.merge(url_path: nil).to_s) }
99
59
  end
100
60
 
101
- def tracking_since
102
- if (tracking_time = redis_store.get(tracker_time_key))
103
- Time.at(tracking_time.to_i).iso8601
104
- else
105
- "N/A"
61
+ def railtie!
62
+ ActiveSupport::Notifications.subscribe("start_processing.action_controller") do |name, start, finish, id, payload|
63
+ Coverband.configuration.route_tracker.track_key(payload)
106
64
  end
107
- end
108
-
109
- def reset_recordings
110
- redis_store.del(tracker_key)
111
- redis_store.del(tracker_time_key)
112
- end
113
-
114
- def clear_route!(route)
115
- return unless route
116
65
 
117
- redis_store.hdel(tracker_key, route)
118
- @logged_routes.delete(route)
119
- end
120
-
121
- def report_routes_tracked
122
- redis_store.set(tracker_time_key, Time.now.to_i) unless @one_time_timestamp || tracker_time_key_exists?
123
- @one_time_timestamp = true
124
- reported_time = Time.now.to_i
125
- @routes_to_record.to_a.each do |route|
126
- redis_store.hset(tracker_key, route.to_s, reported_time)
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
+ # 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)
69
+ # We could consider back porting or patching a solution that works on previous Rails versions
70
+ ActiveSupport::Notifications.subscribe("redirect.action_dispatch") do |name, start, finish, id, payload|
71
+ Coverband.configuration.route_tracker.track_key(payload)
127
72
  end
128
- @routes_to_record.clear
129
- rescue => e
130
- # we don't want to raise errors if Coverband can't reach redis.
131
- # This is a nice to have not a bring the system down
132
- logger&.error "Coverband: route_tracker failed to store, error #{e.class.name} info #{e.message}"
133
- end
134
-
135
- def self.supported_version?
136
- defined?(Rails) && defined?(Rails::VERSION) && Rails::VERSION::STRING.split(".").first.to_i >= 4
137
- end
138
-
139
- protected
140
-
141
- def newly_seen_route?(route)
142
- !@logged_routes.include?(route)
143
- end
144
-
145
- def track_route?(route, options = {})
146
- @ignore_patterns.none? { |pattern| route.to_s.include?(pattern) }
147
73
  end
148
74
 
149
75
  private
150
76
 
151
- def redis_store
152
- store.raw_store
153
- end
154
-
155
- def tracker_time_key_exists?
156
- if defined?(redis_store.exists?)
157
- redis_store.exists?(tracker_time_key)
77
+ def concrete_target
78
+ if defined?(Rails.application)
79
+ Rails.application.routes.routes.map do |route|
80
+ {
81
+ controller: route.defaults[:controller],
82
+ action: route.defaults[:action],
83
+ url_path: route.path.spec.to_s.gsub("(.:format)", ""),
84
+ verb: route.verb
85
+ }
86
+ end
158
87
  else
159
- redis_store.exists(tracker_time_key)
88
+ []
160
89
  end
161
90
  end
162
-
163
- def tracker_key
164
- "route_tracker_2"
165
- end
166
-
167
- def tracker_time_key
168
- "route_tracker_time"
169
- end
170
91
  end
171
92
  end
172
93
  end
@@ -20,129 +20,30 @@ module Coverband
20
20
  ###
21
21
  # This class tracks translation usage via I18n::Backend
22
22
  ###
23
- class TranslationTracker
24
- attr_accessor :target
25
- attr_reader :logger, :store, :ignore_patterns
23
+ class TranslationTracker < AbstractTracker
24
+ REPORT_ROUTE = "translations_tracker"
25
+ TITLE = "Translations"
26
26
 
27
- def initialize(options = {})
28
- raise NotImplementedError, "#{self.class.name} requires Rails 4 or greater" unless self.class.supported_version?
29
- raise "Coverband: #{self.class.name} initialized before configuration!" if !Coverband.configured? && ENV["COVERBAND_TEST"] == "test"
30
-
31
- @ignore_patterns = Coverband.configuration.ignore
32
- @store = options.fetch(:store) { Coverband.configuration.store }
33
- @logger = options.fetch(:logger) { Coverband.configuration.logger }
34
- @target = options.fetch(:target) do
35
- if defined?(Rails.application)
36
- # I18n.eager_load!
37
- # I18n.backend.send(:translations)
38
- app_translation_keys = []
39
- app_translation_files = ::I18n.load_path.select { |f| f.match(/config\/locales/) }
40
- app_translation_files.each do |file|
41
- app_translation_keys += flatten_hash(YAML.load_file(file)).keys
42
- end
43
- app_translation_keys.uniq
44
- else
45
- []
46
- end
47
- end
48
-
49
- @one_time_timestamp = false
50
-
51
- @logged_keys = Set.new
52
- @keys_to_record = Set.new
27
+ def railtie!
28
+ # plugin to i18n
29
+ ::I18n::Backend::Simple.send :include, ::Coverband::Collectors::I18n::KeyRegistry
53
30
  end
54
31
 
55
- def logged_keys
56
- @logged_keys.to_a
57
- end
58
-
59
- def keys_to_record
60
- @keys_to_record.to_a
61
- end
32
+ private
62
33
 
63
- ###
64
- # This method is called on every translation usage
65
- ###
66
- def track_key(key)
67
- if key
68
- if newly_seen_key?(key)
69
- @logged_keys << key
70
- @keys_to_record << key if track_key?(key)
34
+ def concrete_target
35
+ if defined?(Rails.application)
36
+ app_translation_keys = []
37
+ app_translation_files = ::I18n.load_path.select { |f| f.match(/config\/locales/) }
38
+ app_translation_files.each do |file|
39
+ app_translation_keys += flatten_hash(YAML.load_file(file)).keys
71
40
  end
72
- end
73
- end
74
-
75
- def used_keys
76
- redis_store.hgetall(tracker_key)
77
- end
78
-
79
- def all_keys
80
- target.uniq
81
- end
82
-
83
- def unused_keys(used_keys = nil)
84
- recently_used_keys = (used_keys || self.used_keys).keys
85
- all_keys.reject { |k| recently_used_keys.include?(k.to_s) }
86
- end
87
-
88
- def as_json
89
- used_keys = self.used_keys
90
- {
91
- unused_keys: unused_keys(used_keys),
92
- used_keys: used_keys
93
- }.to_json
94
- end
95
-
96
- def tracking_since
97
- if (tracking_time = redis_store.get(tracker_time_key))
98
- Time.at(tracking_time.to_i).iso8601
41
+ app_translation_keys.uniq
99
42
  else
100
- "N/A"
43
+ []
101
44
  end
102
45
  end
103
46
 
104
- def reset_recordings
105
- redis_store.del(tracker_key)
106
- redis_store.del(tracker_time_key)
107
- end
108
-
109
- def clear_key!(key)
110
- return unless key
111
-
112
- redis_store.hdel(tracker_key, key)
113
- @logged_keys.delete(key)
114
- end
115
-
116
- def save_report
117
- redis_store.set(tracker_time_key, Time.now.to_i) unless @one_time_timestamp || tracker_time_key_exists?
118
- @one_time_timestamp = true
119
- reported_time = Time.now.to_i
120
- @keys_to_record.to_a.each do |key|
121
- redis_store.hset(tracker_key, key.to_s, reported_time)
122
- end
123
- @keys_to_record.clear
124
- rescue => e
125
- # we don't want to raise errors if Coverband can't reach redis.
126
- # This is a nice to have not a bring the system down
127
- logger&.error "Coverband: #{self.class.name} failed to store, error #{e.class.name} info #{e.message}"
128
- end
129
-
130
- def self.supported_version?
131
- defined?(Rails) && defined?(Rails::VERSION) && Rails::VERSION::STRING.split(".").first.to_i >= 4
132
- end
133
-
134
- protected
135
-
136
- def newly_seen_key?(key)
137
- !@logged_keys.include?(key)
138
- end
139
-
140
- def track_key?(key, options = {})
141
- @ignore_patterns.none? { |pattern| key.to_s.include?(pattern) }
142
- end
143
-
144
- private
145
-
146
47
  def flatten_hash(hash)
147
48
  hash.each_with_object({}) do |(k, v), h|
148
49
  if v.is_a? Hash
@@ -154,30 +55,6 @@ module Coverband
154
55
  end
155
56
  end
156
57
  end
157
-
158
- def redis_store
159
- store.raw_store
160
- end
161
-
162
- def tracker_time_key_exists?
163
- if defined?(redis_store.exists?)
164
- redis_store.exists?(tracker_time_key)
165
- else
166
- redis_store.exists(tracker_time_key)
167
- end
168
- end
169
-
170
- def tracker_key
171
- "#{class_key}_tracker"
172
- end
173
-
174
- def tracker_time_key
175
- "#{class_key}_tracker_time"
176
- end
177
-
178
- def class_key
179
- @class_key ||= self.class.name.split("::").last
180
- end
181
58
  end
182
59
  end
183
60
  end