coverband 5.2.4 → 5.2.5.rc.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/main.yml +6 -1
  3. data/Gemfile +2 -10
  4. data/{Gemfile.rails4 → Gemfile.rails7} +2 -1
  5. data/changes.md +8 -0
  6. data/coverband.gemspec +3 -3
  7. data/diagram.svg +1 -1
  8. data/lib/coverband/adapters/hash_redis_store.rb +1 -1
  9. data/lib/coverband/adapters/redis_store.rb +0 -2
  10. data/lib/coverband/collectors/coverage.rb +7 -1
  11. data/lib/coverband/collectors/route_tracker.rb +175 -0
  12. data/lib/coverband/configuration.rb +4 -1
  13. data/lib/coverband/integrations/background.rb +1 -0
  14. data/lib/coverband/reporters/web.rb +41 -4
  15. data/lib/coverband/utils/html_formatter.rb +8 -0
  16. data/lib/coverband/utils/rails6_ext.rb +58 -0
  17. data/lib/coverband/utils/railtie.rb +20 -0
  18. data/lib/coverband/version.rb +1 -1
  19. data/lib/coverband.rb +1 -0
  20. data/test/benchmarks/benchmark.rake +0 -1
  21. data/test/coverband/collectors/coverage_test.rb +20 -0
  22. data/test/coverband/collectors/route_tracker_test.rb +125 -0
  23. data/test/coverband/collectors/view_tracker_test.rb +1 -1
  24. data/test/coverband/reporters/web_test.rb +1 -1
  25. data/test/forked/rails_full_stack_views_test.rb +5 -0
  26. data/test/forked/rails_route_tracker_stack_test.rb +45 -0
  27. data/test/forked/rails_view_tracker_stack_test.rb +1 -1
  28. data/test/rails5_dummy/config/coverband.rb +1 -2
  29. data/test/rails7_dummy/Rakefile +6 -0
  30. data/test/rails7_dummy/app/controllers/dummy_controller.rb +5 -0
  31. data/test/rails7_dummy/app/controllers/dummy_view_controller.rb +16 -0
  32. data/test/rails7_dummy/app/views/dummy_view/show.html.erb +5 -0
  33. data/test/rails7_dummy/app/views/dummy_view/show_haml.html.haml +4 -0
  34. data/test/rails7_dummy/app/views/dummy_view/show_slim.html.slim +4 -0
  35. data/test/rails7_dummy/config/application.rb +15 -0
  36. data/test/rails7_dummy/config/boot.rb +3 -0
  37. data/test/rails7_dummy/config/coverband.rb +3 -0
  38. data/test/rails7_dummy/config/coverband_missing_redis.rb +3 -0
  39. data/test/rails7_dummy/config/environment.rb +5 -0
  40. data/test/rails7_dummy/config/routes.rb +7 -0
  41. data/test/rails7_dummy/config/secrets.yml +3 -0
  42. data/test/rails7_dummy/config.ru +4 -0
  43. data/test/rails7_dummy/tmp/.keep +0 -0
  44. data/test/test_helper.rb +14 -5
  45. data/views/nav.erb +7 -2
  46. data/views/route_tracker.erb +49 -0
  47. data/views/view_tracker.erb +1 -1
  48. metadata +43 -7
  49. data/Gemfile.rails6 +0 -9
@@ -119,7 +119,7 @@ module Coverband
119
119
 
120
120
  data = coverage_data_from_redis(data_from_redis)
121
121
  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].blank? ? nil : hash[file][LAST_UPDATED_KEY].to_i
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
123
123
  hash[file].merge!(LAST_UPDATED_KEY => hash[file][LAST_UPDATED_KEY], FIRST_UPDATED_KEY => hash[file][FIRST_UPDATED_KEY].to_i)
124
124
  end
125
125
 
@@ -31,8 +31,6 @@ module Coverband
31
31
  Coverband::TYPES.each do |type|
32
32
  @redis.del(type_base_key(type))
33
33
  end
34
- # temporarily clear the old namespace of coverband_3_2
35
- @redis.del(type_base_key(nil))
36
34
  end
37
35
 
38
36
  def clear_file!(filename)
@@ -99,7 +99,13 @@ module Coverband
99
99
  puts "Coverband: detected SimpleCov in test Env, allowing it to start Coverage"
100
100
  puts "Coverband: to ensure no error logs or missing Coverage call `SimpleCov.start` prior to requiring Coverband"
101
101
  else
102
- if Coverage.ruby_version_greater_than_or_equal_to?("2.6.0")
102
+ if ::Coverage.respond_to?(:state)
103
+ if ::Coverage.state == :idle
104
+ ::Coverage.start(oneshot_lines: Coverband.configuration.use_oneshot_lines_coverage)
105
+ elsif ::Coverage.state == :suspended
106
+ ::Coverage.resume
107
+ end
108
+ elsif Coverage.ruby_version_greater_than_or_equal_to?("2.6.0")
103
109
  ::Coverage.start(oneshot_lines: Coverband.configuration.use_oneshot_lines_coverage) unless ::Coverage.running?
104
110
  elsif Coverage.ruby_version_greater_than_or_equal_to?("2.5.0")
105
111
  ::Coverage.start unless ::Coverage.running?
@@ -0,0 +1,175 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+ require "singleton"
5
+
6
+ module Coverband
7
+ module Collectors
8
+ ###
9
+ # This class tracks route usage via ActiveSupport::Notifications
10
+ ###
11
+ class RouteTracker
12
+ attr_accessor :target
13
+ attr_reader :logger, :store, :ignore_patterns
14
+
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
+ }.to_s
31
+ end
32
+ else
33
+ []
34
+ end
35
+ end
36
+
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
49
+ end
50
+
51
+ ###
52
+ # This method is called on every routing call, so we try to reduce method calls
53
+ # and ensure high performance
54
+ ###
55
+ def track_routes(_name, _start, _finish, _id, payload)
56
+ route = if payload[:request]
57
+ {
58
+ controller: nil,
59
+ action: nil,
60
+ url_path: payload[:request].path,
61
+ verb: payload[:request].method
62
+ }
63
+ else
64
+ {
65
+ controller: payload[:controller],
66
+ action: payload[:action],
67
+ url_path: payload[:path],
68
+ verb: payload[:method]
69
+ }
70
+ end
71
+ if route
72
+ if newly_seen_route?(route)
73
+ @logged_routes << route
74
+ @routes_to_record << route if track_route?(route)
75
+ end
76
+ end
77
+ end
78
+
79
+ def used_routes
80
+ redis_store.hgetall(tracker_key)
81
+ end
82
+
83
+ def all_routes
84
+ all_routes = []
85
+ target.each do |route|
86
+ all_routes << route
87
+ end
88
+ all_routes.uniq
89
+ end
90
+
91
+ def unused_routes(used_routes = nil)
92
+ recently_used_routes = (used_routes || self.used_routes).keys
93
+ all_routes - recently_used_routes
94
+ end
95
+
96
+ def as_json
97
+ used_routes = self.used_routes
98
+ {
99
+ unused_routes: unused_routes(used_routes),
100
+ used_routes: used_routes
101
+ }.to_json
102
+ end
103
+
104
+ def tracking_since
105
+ if (tracking_time = redis_store.get(tracker_time_key))
106
+ Time.at(tracking_time.to_i).iso8601
107
+ else
108
+ "N/A"
109
+ end
110
+ end
111
+
112
+ def reset_recordings
113
+ redis_store.del(tracker_key)
114
+ redis_store.del(tracker_time_key)
115
+ end
116
+
117
+ def clear_route!(route)
118
+ return unless route
119
+
120
+ redis_store.hdel(tracker_key, route)
121
+ @logged_routes.delete(route)
122
+ end
123
+
124
+ def report_routes_tracked
125
+ redis_store.set(tracker_time_key, Time.now.to_i) unless @one_time_timestamp || tracker_time_key_exists?
126
+ @one_time_timestamp = true
127
+ reported_time = Time.now.to_i
128
+ @routes_to_record.to_a.each do |route|
129
+ redis_store.hset(tracker_key, route.to_s, reported_time)
130
+ end
131
+ @routes_to_record.clear
132
+ rescue => e
133
+ # we don't want to raise errors if Coverband can't reach redis.
134
+ # This is a nice to have not a bring the system down
135
+ logger&.error "Coverband: route_tracker failed to store, error #{e.class.name} info #{e.message}"
136
+ end
137
+
138
+ def self.supported_version?
139
+ defined?(Rails) && defined?(Rails::VERSION) && Rails::VERSION::STRING.split(".").first.to_i >= 4
140
+ end
141
+
142
+ protected
143
+
144
+ def newly_seen_route?(route)
145
+ !@logged_routes.include?(route)
146
+ end
147
+
148
+ def track_route?(route, options = {})
149
+ @ignore_patterns.none? { |pattern| route.to_s.include?(pattern) }
150
+ end
151
+
152
+ private
153
+
154
+ def redis_store
155
+ store.raw_store
156
+ end
157
+
158
+ def tracker_time_key_exists?
159
+ if defined?(redis_store.exists?)
160
+ redis_store.exists?(tracker_time_key)
161
+ else
162
+ redis_store.exists(tracker_time_key)
163
+ end
164
+ end
165
+
166
+ def tracker_key
167
+ "route_tracker_2"
168
+ end
169
+
170
+ def tracker_time_key
171
+ "route_tracker_time"
172
+ end
173
+ end
174
+ end
175
+ end
@@ -8,7 +8,8 @@ module Coverband
8
8
  :background_reporting_enabled,
9
9
  :test_env, :web_enable_clear, :gem_details, :web_debug, :report_on_exit,
10
10
  :simulate_oneshot_lines_coverage,
11
- :view_tracker, :defer_eager_loading_data
11
+ :view_tracker, :defer_eager_loading_data,
12
+ :track_routes, :route_tracker
12
13
  attr_writer :logger, :s3_region, :s3_bucket, :s3_access_key_id,
13
14
  :s3_secret_access_key, :password, :api_key, :service_url, :coverband_timeout, :service_dev_mode,
14
15
  :service_test_mode, :process_type, :track_views, :redis_url,
@@ -67,6 +68,8 @@ module Coverband
67
68
  @web_enable_clear = false
68
69
  @track_views = true
69
70
  @view_tracker = nil
71
+ @track_routes = false
72
+ @route_tracker = nil
70
73
  @web_debug = false
71
74
  @report_on_exit = true
72
75
  @use_oneshot_lines_coverage = ENV["ONESHOT"] || false
@@ -39,6 +39,7 @@ module Coverband
39
39
  sleep(sleep_seconds.to_i) if Coverband.configuration.defer_eager_loading_data?
40
40
  Coverband.report_coverage
41
41
  Coverband.configuration.view_tracker&.report_views_tracked
42
+ Coverband.configuration.route_tracker&.report_routes_tracked
42
43
  if Coverband.configuration.verbose
43
44
  logger.debug("Coverband: background reporting coverage (#{Coverband.configuration.store.type}). Sleeping #{sleep_seconds}s")
44
45
  end
@@ -38,6 +38,10 @@ module Coverband
38
38
  request_path_info = request.path_info == "" ? "/" : request.path_info
39
39
  if request.post?
40
40
  case request_path_info
41
+ when %r{\/clear_route_tracking_route}
42
+ clear_route_tracking_route
43
+ when %r{\/clear_route_tracking}
44
+ clear_route_tracking
41
45
  when %r{\/clear_view_tracking_file}
42
46
  clear_view_tracking_file
43
47
  when %r{\/clear_view_tracking}
@@ -59,6 +63,8 @@ module Coverband
59
63
  [200, {"Content-Type" => "text/json"}, [view_tracker_data]]
60
64
  when %r{\/view_tracker}
61
65
  [200, {"Content-Type" => "text/html"}, [view_tracker]]
66
+ when %r{\/route_tracker}
67
+ [200, {"Content-Type" => "text/html"}, [route_tracker]]
62
68
  when %r{\/enriched_debug_data}
63
69
  [200, {"Content-Type" => "text/json"}, [enriched_debug_data]]
64
70
  when %r{\/debug_data}
@@ -95,6 +101,14 @@ module Coverband
95
101
  base_path: base_path).format_view_tracker!
96
102
  end
97
103
 
104
+ def route_tracker
105
+ notice = "<strong>Notice:</strong> #{Rack::Utils.escape_html(request.params["notice"])}<br/>"
106
+ notice = request.params["notice"] ? notice : ""
107
+ Coverband::Utils::HTMLFormatter.new(nil,
108
+ notice: notice,
109
+ base_path: base_path).format_route_tracker!
110
+ end
111
+
98
112
  def view_tracker_data
99
113
  Coverband::Collectors::ViewTracker.new(store: Coverband.configuration.store).as_json
100
114
  end
@@ -126,7 +140,7 @@ module Coverband
126
140
  else
127
141
  notice = "web_enable_clear isn't enabled in your configuration"
128
142
  end
129
- [301, {"Location" => "#{base_path}?notice=#{notice}"}, []]
143
+ [302, {"Location" => "#{base_path}?notice=#{notice}"}, []]
130
144
  end
131
145
 
132
146
  def clear_file
@@ -137,7 +151,7 @@ module Coverband
137
151
  else
138
152
  notice = "web_enable_clear isn't enabled in your configuration"
139
153
  end
140
- [301, {"Location" => "#{base_path}?notice=#{notice}"}, []]
154
+ [302, {"Location" => "#{base_path}?notice=#{notice}"}, []]
141
155
  end
142
156
 
143
157
  def clear_view_tracking
@@ -148,7 +162,7 @@ module Coverband
148
162
  else
149
163
  notice = "web_enable_clear isn't enabled in your configuration"
150
164
  end
151
- [301, {"Location" => "#{base_path}/view_tracker?notice=#{notice}"}, []]
165
+ [302, {"Location" => "#{base_path}/view_tracker?notice=#{notice}"}, []]
152
166
  end
153
167
 
154
168
  def clear_view_tracking_file
@@ -160,7 +174,30 @@ module Coverband
160
174
  else
161
175
  notice = "web_enable_clear isn't enabled in your configuration"
162
176
  end
163
- [301, {"Location" => "#{base_path}/view_tracker?notice=#{notice}"}, []]
177
+ [302, {"Location" => "#{base_path}/view_tracker?notice=#{notice}"}, []]
178
+ end
179
+
180
+ def clear_route_tracking
181
+ if Coverband.configuration.web_enable_clear
182
+ tracker = Coverband::Collectors::RouteTracker.new(store: Coverband.configuration.store)
183
+ tracker.reset_recordings
184
+ notice = "route tracking reset"
185
+ else
186
+ notice = "web_enable_clear isn't enabled in your configuration"
187
+ end
188
+ [302, {"Location" => "#{base_path}/route_tracker?notice=#{notice}"}, []]
189
+ end
190
+
191
+ def clear_route_tracking_route
192
+ if Coverband.configuration.web_enable_clear
193
+ tracker = Coverband::Collectors::RouteTracker.new(store: Coverband.configuration.store)
194
+ route = request.params["route"]
195
+ tracker.clear_route!(route)
196
+ notice = "coverage for route #{route} cleared"
197
+ else
198
+ notice = "web_enable_clear isn't enabled in your configuration"
199
+ end
200
+ [302, {"Location" => "#{base_path}/route_tracker?notice=#{notice}"}, []]
164
201
  end
165
202
 
166
203
  private
@@ -37,6 +37,10 @@ module Coverband
37
37
  format_view_tracker
38
38
  end
39
39
 
40
+ def format_route_tracker!
41
+ format_route_tracker
42
+ end
43
+
40
44
  def format_source_file!(filename)
41
45
  source_file = @coverage_result.file_from_path_with_type(filename)
42
46
 
@@ -57,6 +61,10 @@ module Coverband
57
61
  template("view_tracker").result(binding)
58
62
  end
59
63
 
64
+ def format_route_tracker
65
+ template("route_tracker").result(binding)
66
+ end
67
+
60
68
  def format(result)
61
69
  Dir[File.join(File.dirname(__FILE__), "../../../public/*")].each do |path|
62
70
  FileUtils.cp_r(path, asset_output_path)
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ ###
4
+ # This backports routing redirect active notification events to Rails 6
5
+ #
6
+ # * reproducing this event: https://github.com/rails/rails/pull/43755/files
7
+ # * and pulls in the later: https://github.com/rails/rails/commit/40dc22f715ede12ab9b7e06d59fae185da2c38c7
8
+ # * using alias method although prepend might be an interesting alternative
9
+ # * this doesn't backport the built in listener for the event (ActionDispatch::LogSubscriber) as logging isn't needed
10
+ ###
11
+ require "action_dispatch/routing/redirection"
12
+
13
+ module ActionDispatch
14
+ module Routing
15
+ class Redirect < Endpoint
16
+ def call(env)
17
+ ActiveSupport::Notifications.instrument("redirect.action_dispatch") do |payload|
18
+ request = Request.new(env)
19
+ response = build_response(request)
20
+
21
+ payload[:status] = @status
22
+ payload[:location] = response.headers["Location"]
23
+ payload[:request] = request
24
+
25
+ response.to_a
26
+ end
27
+ end
28
+
29
+ def build_response(req)
30
+ uri = URI.parse(path(req.path_parameters, req))
31
+
32
+ unless uri.host
33
+ if relative_path?(uri.path)
34
+ uri.path = "#{req.script_name}/#{uri.path}"
35
+ elsif uri.path.empty?
36
+ uri.path = req.script_name.empty? ? "/" : req.script_name
37
+ end
38
+ end
39
+
40
+ uri.scheme ||= req.scheme
41
+ uri.host ||= req.host
42
+ uri.port ||= req.port unless req.standard_port?
43
+
44
+ req.commit_flash
45
+
46
+ body = ""
47
+
48
+ headers = {
49
+ "Location" => uri.to_s,
50
+ "Content-Type" => "text/html",
51
+ "Content-Length" => body.length.to_s
52
+ }
53
+
54
+ ActionDispatch::Response.new(status, headers, body)
55
+ end
56
+ end
57
+ end
58
+ end
@@ -28,6 +28,26 @@ module Coverband
28
28
  end
29
29
 
30
30
  begin
31
+ if Coverband.configuration.track_routes
32
+ if Gem::Version.new(Rails.version) >= Gem::Version.new("6.0.0") && Gem::Version.new(Rails.version) < Gem::Version.new("7.1.0")
33
+ require_relative "rails6_ext"
34
+ end
35
+
36
+ Coverband.configuration.route_tracker = Coverband::Collectors::RouteTracker.new
37
+
38
+ ActiveSupport::Notifications.subscribe("start_processing.action_controller") do |name, start, finish, id, payload|
39
+ Coverband.configuration.route_tracker.track_routes(name, start, finish, id, payload)
40
+ end
41
+
42
+ # 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
43
+ # https://github.com/rails/rails/pull/43755
44
+ # Automatic tracking of redirects isn't avaible before Rails 7.1.0 (currently tested against the 7.1.0.alpha)
45
+ # We could consider back porting or patching a solution that works on previous Rails versions
46
+ ActiveSupport::Notifications.subscribe("redirect.action_dispatch") do |name, start, finish, id, payload|
47
+ Coverband.configuration.route_tracker.track_routes(name, start, finish, id, payload)
48
+ end
49
+ end
50
+
31
51
  if Coverband.configuration.track_views
32
52
  COVERBAND_VIEW_TRACKER = if Coverband.coverband_service?
33
53
  Coverband::Collectors::ViewTrackerService.new
@@ -5,5 +5,5 @@
5
5
  # use format "4.2.1.rc.1" ~> 4.2.1.rc to prerelease versions like v4.2.1.rc.2 and v4.2.1.rc.3
6
6
  ###
7
7
  module Coverband
8
- VERSION = "5.2.4"
8
+ VERSION = "5.2.5.rc.2"
9
9
  end
data/lib/coverband.rb CHANGED
@@ -18,6 +18,7 @@ require "coverband/utils/file_hasher"
18
18
  require "coverband/collectors/coverage"
19
19
  require "coverband/collectors/view_tracker"
20
20
  require "coverband/collectors/view_tracker_service"
21
+ require "coverband/collectors/route_tracker"
21
22
  require "coverband/reporters/base"
22
23
  require "coverband/reporters/console_report"
23
24
  require "coverband/integrations/background"
@@ -250,7 +250,6 @@ namespace :benchmarks do
250
250
 
251
251
  desc "checks memory of collector"
252
252
  task memory_check: [:setup] do
253
- # require 'pry-byebug'
254
253
  require "objspace"
255
254
  puts "memory load check"
256
255
  puts(ObjectSpace.memsize_of_all / 2**20)
@@ -77,8 +77,25 @@ class CollectorsCoverageTest < Minitest::Test
77
77
  assert_match %r{Oh no}, error.message
78
78
  end
79
79
 
80
+ test "using coverage state idle with ruby >= 3.1.0" do
81
+ return unless Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.1.0")
82
+
83
+ ::Coverage.expects(:state).returns(:idle)
84
+ ::Coverage.expects(:start).with(oneshot_lines: false)
85
+ Coverband::Collectors::Coverage.send(:new)
86
+ end
87
+
88
+ test "using coverage state suspended with ruby >= 3.1.0" do
89
+ return unless Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.1.0")
90
+
91
+ ::Coverage.expects(:state).returns(:suspended).at_least_once
92
+ ::Coverage.expects(:resume)
93
+ Coverband::Collectors::Coverage.send(:new)
94
+ end
95
+
80
96
  test "one shot line coverage disabled for ruby >= 2.6" do
81
97
  return unless Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.5.0")
98
+ ::Coverage.expects(:respond_to?).returns(false)
82
99
 
83
100
  Coverband::Collectors::Coverage.expects(:ruby_version_greater_than_or_equal_to?).with("2.6.0").returns(true)
84
101
  ::Coverage.expects(:running?).returns(false)
@@ -88,6 +105,7 @@ class CollectorsCoverageTest < Minitest::Test
88
105
 
89
106
  test "one shot line coverage enabled for ruby >= 2.6" do
90
107
  return unless Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.5.0")
108
+ ::Coverage.expects(:respond_to?).returns(false)
91
109
 
92
110
  Coverband.configuration.expects(:use_oneshot_lines_coverage).returns(true)
93
111
  Coverband::Collectors::Coverage.expects(:ruby_version_greater_than_or_equal_to?).with("2.6.0").returns(true)
@@ -98,6 +116,7 @@ class CollectorsCoverageTest < Minitest::Test
98
116
 
99
117
  test "one shot line coverage for ruby >= 2.6 when already running" do
100
118
  return unless Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.5.0")
119
+ ::Coverage.expects(:respond_to?).returns(false)
101
120
 
102
121
  Coverband::Collectors::Coverage.expects(:ruby_version_greater_than_or_equal_to?).with("2.6.0").returns(true)
103
122
  ::Coverage.expects(:running?).returns(true)
@@ -107,6 +126,7 @@ class CollectorsCoverageTest < Minitest::Test
107
126
 
108
127
  test "no one shot line coverage for ruby < 2.6" do
109
128
  return unless Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.5.0")
129
+ ::Coverage.expects(:respond_to?).returns(false)
110
130
 
111
131
  Coverband::Collectors::Coverage.expects(:ruby_version_greater_than_or_equal_to?).with("2.6.0").returns(false)
112
132
  Coverband::Collectors::Coverage.expects(:ruby_version_greater_than_or_equal_to?).with("2.5.0").returns(true)
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ require File.expand_path("../../test_helper", File.dirname(__FILE__))
4
+ require "ostruct"
5
+
6
+ class RouterTrackerTest < Minitest::Test
7
+ # NOTE: using struct vs open struct as open struct has a special keyword method that overshadows the method value on Ruby 2.x
8
+ Payload = Struct.new(:path, :method)
9
+
10
+ def tracker_key
11
+ Coverband::Collectors::RouteTracker.expects(:supported_version?).at_least_once.returns(true)
12
+ Coverband::Collectors::RouteTracker.new.send(:tracker_key)
13
+ end
14
+
15
+ def setup
16
+ super
17
+ fake_store.raw_store.del(tracker_key)
18
+ end
19
+
20
+ test "init correctly" do
21
+ Coverband::Collectors::RouteTracker.expects(:supported_version?).returns(true)
22
+ tracker = Coverband::Collectors::RouteTracker.new(store: fake_store, roots: "dir")
23
+ assert_equal nil, tracker.target.first
24
+ assert !tracker.store.nil?
25
+ assert_equal [], tracker.target
26
+ assert_equal [], tracker.logged_routes
27
+ end
28
+
29
+ test "track redirect routes" do
30
+ store = fake_store
31
+ route_hash = {controller: nil, action: nil, url_path: "path", verb: "GET"}
32
+ store.raw_store.expects(:hset).with(tracker_key, route_hash.to_s, anything)
33
+ tracker = Coverband::Collectors::RouteTracker.new(store: store, roots: "dir")
34
+
35
+ payload = {
36
+ request: Payload.new("path", "GET")
37
+ }
38
+ tracker.track_routes("name", "start", "finish", "id", payload)
39
+ tracker.report_routes_tracked
40
+ assert_equal [route_hash], tracker.logged_routes
41
+ end
42
+
43
+ test "track controller routes" do
44
+ store = fake_store
45
+ route_hash = {controller: "SomeController", action: "index", url_path: "path", verb: "GET"}
46
+ store.raw_store.expects(:hset).with(tracker_key, route_hash.to_s, anything)
47
+ tracker = Coverband::Collectors::RouteTracker.new(store: store, roots: "dir")
48
+ payload = {
49
+ controller: "SomeController",
50
+ action: "index",
51
+ path: "path",
52
+ method: "GET"
53
+ }
54
+ tracker.track_routes("name", "start", "finish", "id", payload)
55
+ tracker.report_routes_tracked
56
+ assert_equal [route_hash], tracker.logged_routes
57
+ end
58
+
59
+ test "report used routes" do
60
+ store = fake_store
61
+ route_hash = {controller: "SomeController", action: "index", url_path: "path", verb: "GET"}
62
+ tracker = Coverband::Collectors::RouteTracker.new(store: store, roots: "dir")
63
+ payload = {
64
+ controller: "SomeController",
65
+ action: "index",
66
+ path: "path",
67
+ method: "GET"
68
+ }
69
+ tracker.track_routes("name", "start", "finish", "id", payload)
70
+ tracker.report_routes_tracked
71
+ assert_equal [route_hash.to_s], tracker.used_routes.keys
72
+ end
73
+
74
+ test "report unused routes" do
75
+ store = fake_store
76
+ tracker = Coverband::Collectors::RouteTracker.new(store: store, roots: "dir")
77
+ payload = {
78
+ controller: "SomeController",
79
+ action: "index",
80
+ path: "path",
81
+ method: "GET"
82
+ }
83
+ tracker.track_routes("name", "start", "finish", "id", payload)
84
+ tracker.report_routes_tracked
85
+ assert_equal [], tracker.unused_routes
86
+ end
87
+
88
+ test "reset store" do
89
+ store = fake_store
90
+ payload = {
91
+ controller: "SomeController",
92
+ action: "index",
93
+ path: "path",
94
+ method: "GET"
95
+ }
96
+ store.raw_store.expects(:del).with(tracker_key)
97
+ store.raw_store.expects(:del).with("route_tracker_time")
98
+ tracker = Coverband::Collectors::RouteTracker.new(store: store, roots: "dir")
99
+ tracker.track_routes("name", "start", "finish", "id", payload)
100
+ tracker.reset_recordings
101
+ end
102
+
103
+ test "clear_file" do
104
+ store = fake_store
105
+ route_hash = {controller: "SomeController", action: "index", url_path: "path", verb: "GET"}
106
+ tracker = Coverband::Collectors::RouteTracker.new(store: store, roots: "dir")
107
+ payload = {
108
+ controller: "SomeController",
109
+ action: "index",
110
+ path: "path",
111
+ method: "GET"
112
+ }
113
+ tracker.track_routes("name", "start", "finish", "id", payload)
114
+ tracker.report_routes_tracked
115
+ assert_equal [route_hash.to_s], tracker.used_routes.keys
116
+ tracker.clear_route!(route_hash.to_s)
117
+ assert_equal [], tracker.store.raw_store.hgetall(tracker_key).keys
118
+ end
119
+
120
+ protected
121
+
122
+ def fake_store
123
+ @fake_store ||= Coverband::Adapters::RedisStore.new(Coverband::Test.redis, redis_namespace: "coverband_test")
124
+ end
125
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  require File.expand_path("../../test_helper", File.dirname(__FILE__))
4
4
 
5
- class ReporterTest < Minitest::Test
5
+ class ViewTrackerTest < Minitest::Test
6
6
  def tracker_key
7
7
  "render_tracker_2"
8
8
  end
@@ -39,7 +39,7 @@ if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.2.0")
39
39
 
40
40
  test "clears coverband" do
41
41
  post "/clear"
42
- assert_equal 301, last_response.status
42
+ assert_equal 302, last_response.status
43
43
  end
44
44
  end
45
45
  end
@@ -23,6 +23,7 @@ class RailsFullStackTest < Minitest::Test
23
23
  assert_content("I am no dummy view tracker text")
24
24
  Coverband.report_coverage
25
25
  Coverband.configuration.view_tracker&.report_views_tracked
26
+ Coverband.configuration.route_tracker&.report_routes_tracked
26
27
  visit "/coverage/view_tracker"
27
28
  assert_content("Used Views: (1)")
28
29
  assert_content("Unused Views: (2)")
@@ -30,6 +31,10 @@ class RailsFullStackTest < Minitest::Test
30
31
  assert_selector("li.unused-views", text: "dummy_view/show_haml.html.haml")
31
32
  assert_selector("li.unused-views", text: "dummy_view/show_slim.html.slim")
32
33
 
34
+ visit "/coverage/route_tracker"
35
+ assert_content("Used Routes: (1)")
36
+ assert_content("Unused Routes: (5)")
37
+
33
38
  visit "/dummy_view/show_haml"
34
39
  assert_content("I am haml text")
35
40
  Coverband.report_coverage