coverband 5.2.5 → 5.2.6.rc.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -0
- data/changes.md +4 -0
- data/diagram.svg +1 -1
- data/lib/coverband/at_exit.rb +2 -1
- data/lib/coverband/collectors/abstract_tracker.rb +164 -0
- data/lib/coverband/collectors/route_tracker.rb +35 -114
- data/lib/coverband/collectors/translation_tracker.rb +60 -0
- data/lib/coverband/collectors/view_tracker.rb +29 -96
- data/lib/coverband/collectors/view_tracker_service.rb +1 -1
- data/lib/coverband/configuration.rb +32 -1
- data/lib/coverband/integrations/background.rb +1 -2
- data/lib/coverband/reporters/web.rb +54 -82
- data/lib/coverband/utils/html_formatter.rb +7 -18
- data/lib/coverband/utils/railtie.rb +1 -38
- data/lib/coverband/version.rb +1 -1
- data/lib/coverband.rb +2 -0
- data/test/coverband/collectors/route_tracker_test.rb +22 -22
- data/test/coverband/collectors/translation_tracker_test.rb +91 -0
- data/test/coverband/collectors/view_tracker_test.rb +29 -29
- data/test/forked/rails_full_stack_views_test.rb +13 -13
- data/test/forked/rails_route_tracker_stack_test.rb +1 -1
- data/test/forked/rails_view_tracker_stack_test.rb +1 -1
- data/views/{route_tracker.erb → abstract_tracker.erb} +12 -13
- data/views/nav.erb +10 -5
- metadata +9 -6
- data/views/view_tracker.erb +0 -52
data/lib/coverband/at_exit.rb
CHANGED
@@ -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&.
|
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
|
-
|
13
|
-
|
11
|
+
class RouteTracker < AbstractTracker
|
12
|
+
REPORT_ROUTE = "routes_tracker"
|
13
|
+
TITLE = "Routes"
|
14
14
|
|
15
15
|
def initialize(options = {})
|
16
|
-
|
17
|
-
|
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
|
-
|
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
|
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
|
73
|
-
@
|
74
|
-
@
|
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
|
80
|
-
|
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
|
88
|
-
recently_used_routes = (
|
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
|
-
|
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
|
102
|
-
|
103
|
-
|
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
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
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
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
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
|
-
|
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
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "set"
|
4
|
+
require "singleton"
|
5
|
+
|
6
|
+
module Coverband
|
7
|
+
module Collectors
|
8
|
+
module I18n
|
9
|
+
module KeyRegistry
|
10
|
+
def lookup(locale, key, scope = [], options = {})
|
11
|
+
separator = options[:separator] || ::I18n.default_separator
|
12
|
+
flat_key = ::I18n.normalize_keys(locale, key, scope, separator).join(separator)
|
13
|
+
Coverband.configuration.translations_tracker.track_key(flat_key)
|
14
|
+
|
15
|
+
super
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
###
|
21
|
+
# This class tracks translation usage via I18n::Backend
|
22
|
+
###
|
23
|
+
class TranslationTracker < AbstractTracker
|
24
|
+
REPORT_ROUTE = "translations_tracker"
|
25
|
+
TITLE = "Translations"
|
26
|
+
|
27
|
+
def railtie!
|
28
|
+
# plugin to i18n
|
29
|
+
::I18n::Backend::Simple.send :include, ::Coverband::Collectors::I18n::KeyRegistry
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
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
|
40
|
+
end
|
41
|
+
app_translation_keys.uniq
|
42
|
+
else
|
43
|
+
[]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def flatten_hash(hash)
|
48
|
+
hash.each_with_object({}) do |(k, v), h|
|
49
|
+
if v.is_a? Hash
|
50
|
+
flatten_hash(v).map do |h_k, h_v|
|
51
|
+
h["#{k}.#{h_k}".to_sym] = h_v
|
52
|
+
end
|
53
|
+
else
|
54
|
+
h[k] = v
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -12,45 +12,35 @@ module Coverband
|
|
12
12
|
# but am now rolling into Coverband
|
13
13
|
# https://github.com/livingsocial/flatfoot
|
14
14
|
###
|
15
|
-
class ViewTracker
|
16
|
-
|
17
|
-
attr_reader :logger, :roots, :store, :ignore_patterns
|
15
|
+
class ViewTracker < AbstractTracker
|
16
|
+
attr_reader :roots
|
18
17
|
|
19
|
-
|
20
|
-
|
21
|
-
raise "Coverband: view tracker initialized before configuration!" if !Coverband.configured? && ENV["COVERBAND_TEST"] == "test"
|
18
|
+
REPORT_ROUTE = "views_tracker"
|
19
|
+
TITLE = "Views"
|
22
20
|
|
21
|
+
def initialize(options = {})
|
23
22
|
@project_directory = File.expand_path(Coverband.configuration.root)
|
24
|
-
@ignore_patterns = Coverband.configuration.ignore
|
25
|
-
@store = options.fetch(:store) { Coverband.configuration.store }
|
26
|
-
@logger = options.fetch(:logger) { Coverband.configuration.logger }
|
27
|
-
@target = options.fetch(:target) { Dir.glob("#{@project_directory}/app/views/**/*.html.{erb,haml,slim}") }
|
28
|
-
|
29
23
|
@roots = options.fetch(:roots) { Coverband.configuration.all_root_patterns }
|
30
24
|
@roots = @roots.split(",") if @roots.is_a?(String)
|
31
|
-
@one_time_timestamp = false
|
32
|
-
|
33
|
-
@logged_views = Set.new
|
34
|
-
@views_to_record = Set.new
|
35
|
-
end
|
36
25
|
|
37
|
-
|
38
|
-
@logged_views.to_a
|
26
|
+
super
|
39
27
|
end
|
40
28
|
|
41
|
-
def
|
42
|
-
|
29
|
+
def railtie!
|
30
|
+
ActiveSupport::Notifications.subscribe(/render_(template|partial|collection).action_view/) do |name, start, finish, id, payload|
|
31
|
+
Coverband.configuration.view_tracker.track_key(payload) unless name.include?("!")
|
32
|
+
end
|
43
33
|
end
|
44
34
|
|
45
35
|
###
|
46
36
|
# This method is called on every render call, so we try to reduce method calls
|
47
37
|
# and ensure high performance
|
48
38
|
###
|
49
|
-
def
|
39
|
+
def track_key(payload)
|
50
40
|
if (file = payload[:identifier])
|
51
|
-
if
|
52
|
-
@
|
53
|
-
@
|
41
|
+
if newly_seen_key?(file)
|
42
|
+
@logged_keys << file
|
43
|
+
@keys_to_record << file if track_file?(file)
|
54
44
|
end
|
55
45
|
end
|
56
46
|
|
@@ -61,13 +51,13 @@ module Coverband
|
|
61
51
|
# http://edgeguides.rubyonrails.org/active_support_instrumentation.html#render_partial-action_view
|
62
52
|
###
|
63
53
|
return unless (layout_file = payload[:layout])
|
64
|
-
return unless
|
54
|
+
return unless newly_seen_key?(layout_file)
|
65
55
|
|
66
|
-
@
|
67
|
-
@
|
56
|
+
@logged_keys << layout_file
|
57
|
+
@keys_to_record << layout_file if track_file?(layout_file, layout: true)
|
68
58
|
end
|
69
59
|
|
70
|
-
def
|
60
|
+
def used_keys
|
71
61
|
views = redis_store.hgetall(tracker_key)
|
72
62
|
normalized_views = {}
|
73
63
|
views.each_pair do |view, time|
|
@@ -79,7 +69,7 @@ module Coverband
|
|
79
69
|
normalized_views
|
80
70
|
end
|
81
71
|
|
82
|
-
def
|
72
|
+
def all_keys
|
83
73
|
all_views = []
|
84
74
|
target.each do |view|
|
85
75
|
roots.each do |root|
|
@@ -90,92 +80,35 @@ module Coverband
|
|
90
80
|
all_views.uniq
|
91
81
|
end
|
92
82
|
|
93
|
-
def
|
94
|
-
recently_used_views = (
|
95
|
-
unused_views =
|
83
|
+
def unused_keys(used_views = nil)
|
84
|
+
recently_used_views = (used_keys || used_keys).keys
|
85
|
+
unused_views = all_keys - recently_used_views
|
96
86
|
# since layouts don't include format we count them used if they match with ANY formats
|
97
87
|
unused_views.reject { |view| view.match(/\/layouts\//) && recently_used_views.any? { |used_view| view.include?(used_view) } }
|
98
88
|
end
|
99
89
|
|
100
|
-
def
|
101
|
-
used_views = self.used_views
|
102
|
-
{
|
103
|
-
unused_views: unused_views(used_views),
|
104
|
-
used_views: used_views
|
105
|
-
}.to_json
|
106
|
-
end
|
107
|
-
|
108
|
-
def tracking_since
|
109
|
-
if (tracking_time = redis_store.get(tracker_time_key))
|
110
|
-
Time.at(tracking_time.to_i).iso8601
|
111
|
-
else
|
112
|
-
"N/A"
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
def reset_recordings
|
117
|
-
redis_store.del(tracker_key)
|
118
|
-
redis_store.del(tracker_time_key)
|
119
|
-
end
|
120
|
-
|
121
|
-
def clear_file!(filename)
|
90
|
+
def clear_key!(filename)
|
122
91
|
return unless filename
|
123
92
|
|
124
93
|
filename = "#{@project_directory}/#{filename}"
|
125
94
|
redis_store.hdel(tracker_key, filename)
|
126
|
-
@
|
127
|
-
end
|
128
|
-
|
129
|
-
def report_views_tracked
|
130
|
-
redis_store.set(tracker_time_key, Time.now.to_i) unless @one_time_timestamp || tracker_time_key_exists?
|
131
|
-
@one_time_timestamp = true
|
132
|
-
reported_time = Time.now.to_i
|
133
|
-
@views_to_record.to_a.each do |file|
|
134
|
-
redis_store.hset(tracker_key, file, reported_time)
|
135
|
-
end
|
136
|
-
@views_to_record.clear
|
137
|
-
rescue => e
|
138
|
-
# we don't want to raise errors if Coverband can't reach redis.
|
139
|
-
# This is a nice to have not a bring the system down
|
140
|
-
logger&.error "Coverband: view_tracker failed to store, error #{e.class.name}"
|
141
|
-
end
|
142
|
-
|
143
|
-
def self.supported_version?
|
144
|
-
defined?(Rails) && defined?(Rails::VERSION) && Rails::VERSION::STRING.split(".").first.to_i >= 4
|
95
|
+
@logged_keys.delete(filename)
|
145
96
|
end
|
146
97
|
|
147
|
-
|
148
|
-
|
149
|
-
def newly_seen_file?(file)
|
150
|
-
!@logged_views.include?(file)
|
151
|
-
end
|
98
|
+
private
|
152
99
|
|
153
100
|
def track_file?(file, options = {})
|
154
101
|
(file.start_with?(@project_directory) || options[:layout]) &&
|
155
102
|
@ignore_patterns.none? { |pattern| file.include?(pattern) }
|
156
103
|
end
|
157
104
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
store.raw_store
|
162
|
-
end
|
163
|
-
|
164
|
-
def tracker_time_key_exists?
|
165
|
-
if defined?(redis_store.exists?)
|
166
|
-
redis_store.exists?(tracker_time_key)
|
105
|
+
def concrete_target
|
106
|
+
if defined?(Rails.application)
|
107
|
+
Dir.glob("#{@project_directory}/app/views/**/*.html.{erb,haml,slim}")
|
167
108
|
else
|
168
|
-
|
109
|
+
[]
|
169
110
|
end
|
170
111
|
end
|
171
|
-
|
172
|
-
def tracker_key
|
173
|
-
"render_tracker_2"
|
174
|
-
end
|
175
|
-
|
176
|
-
def tracker_time_key
|
177
|
-
"render_tracker_time"
|
178
|
-
end
|
179
112
|
end
|
180
113
|
end
|
181
114
|
end
|
@@ -6,7 +6,7 @@ module Coverband
|
|
6
6
|
# This class extends view tracker to support web service reporting
|
7
7
|
###
|
8
8
|
class ViewTrackerService < ViewTracker
|
9
|
-
def
|
9
|
+
def save_report
|
10
10
|
reported_time = Time.now.to_i
|
11
11
|
if @views_to_record.any?
|
12
12
|
relative_views = @views_to_record.map! do |view|
|
@@ -9,7 +9,9 @@ module Coverband
|
|
9
9
|
:test_env, :web_enable_clear, :gem_details, :web_debug, :report_on_exit,
|
10
10
|
:simulate_oneshot_lines_coverage,
|
11
11
|
:view_tracker, :defer_eager_loading_data,
|
12
|
-
:track_routes, :route_tracker
|
12
|
+
:track_routes, :route_tracker,
|
13
|
+
:track_translations, :translations_tracker,
|
14
|
+
:trackers
|
13
15
|
attr_writer :logger, :s3_region, :s3_bucket, :s3_access_key_id,
|
14
16
|
:s3_secret_access_key, :password, :api_key, :service_url, :coverband_timeout, :service_dev_mode,
|
15
17
|
:service_test_mode, :process_type, :track_views, :redis_url,
|
@@ -70,6 +72,8 @@ module Coverband
|
|
70
72
|
@view_tracker = nil
|
71
73
|
@track_routes = false
|
72
74
|
@route_tracker = nil
|
75
|
+
@track_translations = false
|
76
|
+
@translations_tracker = nil
|
73
77
|
@web_debug = false
|
74
78
|
@report_on_exit = true
|
75
79
|
@use_oneshot_lines_coverage = ENV["ONESHOT"] || false
|
@@ -92,6 +96,8 @@ module Coverband
|
|
92
96
|
@redis_ttl = 2_592_000 # in seconds. Default is 30 days.
|
93
97
|
@reporting_wiggle = nil
|
94
98
|
|
99
|
+
@trackers = []
|
100
|
+
|
95
101
|
# TODO: these are deprecated
|
96
102
|
@s3_region = nil
|
97
103
|
@s3_bucket = nil
|
@@ -101,6 +107,31 @@ module Coverband
|
|
101
107
|
@gem_details = false
|
102
108
|
end
|
103
109
|
|
110
|
+
def railtie!
|
111
|
+
if Coverband.configuration.track_routes
|
112
|
+
Coverband.configuration.route_tracker = Coverband::Collectors::RouteTracker.new
|
113
|
+
trackers << Coverband.configuration.route_tracker
|
114
|
+
end
|
115
|
+
|
116
|
+
if Coverband.configuration.track_translations
|
117
|
+
Coverband.configuration.translations_tracker = Coverband::Collectors::TranslationTracker.new
|
118
|
+
trackers << Coverband.configuration.translations_tracker
|
119
|
+
end
|
120
|
+
|
121
|
+
if Coverband.configuration.track_views
|
122
|
+
Coverband.configuration.view_tracker = if Coverband.coverband_service?
|
123
|
+
Coverband::Collectors::ViewTrackerService.new
|
124
|
+
else
|
125
|
+
Coverband::Collectors::ViewTracker.new
|
126
|
+
end
|
127
|
+
trackers << Coverband.configuration.view_tracker
|
128
|
+
end
|
129
|
+
trackers.each { |tracker| tracker.railtie! }
|
130
|
+
rescue Redis::CannotConnectError => error
|
131
|
+
Coverband.configuration.logger.info "Redis is not available (#{error}), Coverband not configured"
|
132
|
+
Coverband.configuration.logger.info "If this is a setup task like assets:precompile feel free to ignore"
|
133
|
+
end
|
134
|
+
|
104
135
|
def logger
|
105
136
|
@logger ||= if defined?(Rails.logger) && Rails.logger
|
106
137
|
Rails.logger
|