routes_coverage 0.4.0 → 0.5.0

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
- SHA1:
3
- metadata.gz: 899581bd48d52230827553a1a8f94428f7c224cc
4
- data.tar.gz: debceb6a0e49ef3d7b313c2733fc20ce3b08d428
2
+ SHA256:
3
+ metadata.gz: 576b62caebf21a9e1448f549a213eaf5770ca8486c1ad94183c1bd8617bb9916
4
+ data.tar.gz: 9a150fe8e215e92ba00dee273700ebe69f08baca5606cc7980b09fcc854c0969
5
5
  SHA512:
6
- metadata.gz: 452ab35f957e94c2122f89cb8c85599da67ea5ee8710c5090fe236d7b50a83d135221d42764fdb00d72309ebf85ea29b3e6047e30e1ef1b02dd4bc7e7ffd46f6
7
- data.tar.gz: d0288d1afe2888e8118d09f2758acf8847cab1567b88dad93d59cfd5a84423095f2a6a6af4f4dd2206dbbc33f50d1556b40667611e93c491d3f8313fe5e2c924
6
+ metadata.gz: 1695cd9694f9388c438814a990127af84930e39413694d15fec8a35bb5f12b1217c1c4a4e8d9fd3b1289a0f91543cc11d772142354481907508337c736f2217e
7
+ data.tar.gz: 4197958e82df3eceee17d222f441cbf7106d4de67d5fafb3bcdb4d16b0e59159c4678f2420c679483379aeb28a3304b246b77a8215026034aca936af7b4cb797
data/README.md CHANGED
@@ -41,6 +41,7 @@ RSpec.configure do |config|
41
41
  config.routes_coverage.exclude_namespaces << 'somenamespace' # excludes /somenamespace/*
42
42
 
43
43
  config.routes_coverage.groups["Some Route group title"] = %r{^/somespace/}
44
+ config.routes_coverage.groups["Subdomain"] = { constraints: { subdomain: 'some_subdomain' }, path: '/' }
44
45
  config.routes_coverage.groups["Admin"] = Regexp.union([
45
46
  %r{^/admin/},
46
47
  %r{^/secret_place/},
@@ -1,14 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RoutesCoverage
2
4
  module Adapters
3
5
  class AtExit
4
- def self.use coverer=nil
5
- #NB: at_exit order is important, for example minitest uses it to run, need to install our handler before it
6
+ def self.use(_coverer = nil)
7
+ # NB: at_exit order is important, for example minitest uses it to run, need to install our handler before it
6
8
 
7
9
  RoutesCoverage.reset!
8
10
  at_exit do
9
11
  next if RoutesCoverage.pid != Process.pid
12
+
10
13
  RoutesCoverage.perform_report
11
- exit
14
+ exit # rubocop:disable Rails/Exit
12
15
  end
13
16
  end
14
17
  end
@@ -1,12 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec/core'
4
+
1
5
  RSpec.configure do |config|
2
6
  config.add_setting :routes_coverage
3
7
  config.routes_coverage = RoutesCoverage.settings
8
+ end
4
9
 
5
- config.before(:suite) do
6
- RoutesCoverage.reset!
7
- end
10
+ module RoutesCoverage
11
+ module Adapters
12
+ class RSpec
13
+ def self.use
14
+ ::RSpec.configure do |config|
15
+ config.before(:suite) do
16
+ RoutesCoverage.reset!
17
+ end
8
18
 
9
- config.after(:suite) do
10
- RoutesCoverage.perform_report
19
+ config.after(:suite) do
20
+ RoutesCoverage.perform_report
21
+ end
22
+ end
23
+ end
24
+ end
11
25
  end
12
26
  end
@@ -1,11 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RoutesCoverage
2
4
  module Adapters
3
5
  class SimpleCov
4
6
  def self.use
5
7
  RoutesCoverage.reset!
6
- ::SimpleCov.at_exit{
8
+ prev_block = ::SimpleCov.at_exit
9
+ ::SimpleCov.at_exit do
7
10
  RoutesCoverage.perform_report
8
- }
11
+ prev_block.call
12
+ end
9
13
  end
10
14
  end
11
15
  end
@@ -1,15 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RoutesCoverage
2
4
  module Formatters
3
5
  class Base
4
- def initialize result, groups, settings
6
+ def initialize(result, groups, settings)
5
7
  @result = result
6
8
  @groups = groups
7
9
  @settings = settings
8
10
  end
9
11
 
10
- attr_reader :result
11
- attr_reader :groups
12
- attr_reader :settings
12
+ attr_reader :result, :groups, :settings
13
13
  end
14
14
  end
15
15
  end
@@ -1,11 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RoutesCoverage
2
4
  module Formatters
3
5
  class FullText < SummaryText
4
-
5
6
  class RouteFormatter
6
7
  attr_reader :buffer
7
8
 
8
- def initialize result=nil, _settings=nil, output_hits=false
9
+ def initialize(result = nil, _settings = nil, output_hits: false)
9
10
  @buffer = []
10
11
  @result = result
11
12
  @output_hits = output_hits
@@ -28,49 +29,47 @@ module RoutesCoverage
28
29
  @buffer << draw_header(routes)
29
30
  end
30
31
 
31
- def no_routes _routes_from_rails5=nil
32
+ def no_routes(_routes_from_rails5 = nil)
32
33
  @buffer << "\tNone"
33
34
  end
34
35
 
35
36
  private
36
- HEADER = ['Prefix', 'Verb', 'URI Pattern', 'Controller#Action']
37
+
38
+ HEADER = ['Prefix', 'Verb', 'URI Pattern', 'Controller#Action'].freeze
37
39
  def draw_section(routes)
38
40
  header_lengths = HEADER.map(&:length)
39
41
  name_width, verb_width, path_width, reqs_width = widths(routes).zip(header_lengths).map(&:max)
40
42
 
41
- hits = nil
42
43
  routes.map do |r|
43
- # puts "route is #{r.inspect}"
44
- if @output_hits
45
- # hits = " ?"
46
- if r[:original].respond_to?(:__getobj__)
47
- original_route = r[:original].__getobj__ # SimpleDelegator
48
- else
49
- original_route = r[:original]
50
- end
51
- hits = " #{@result.route_hit_counts[original_route]}"
52
- end
53
- "#{r[:name].rjust(name_width) if @output_prefix} #{r[:verb].ljust(verb_width)} #{r[:path].ljust(path_width)} #{r[:reqs].ljust(reqs_width)}#{hits}"
44
+ # unwrap SimpleDelegator
45
+ original_route = r[:original].respond_to?(:__getobj__) ? r[:original].__getobj__ : r[:original]
46
+
47
+ "#{r[:name].rjust(name_width) if @output_prefix} "\
48
+ "#{r[:verb].ljust(verb_width)} #{r[:path].ljust(path_width)} #{r[:reqs].ljust(reqs_width)}"\
49
+ "#{" #{@result.route_hit_counts[original_route]}" if @output_hits}"
54
50
  end
55
51
  end
56
52
 
57
53
  def draw_header(routes)
58
54
  name_width, verb_width, path_width, reqs_width = widths(routes)
59
55
 
60
- "#{"Prefix".rjust(name_width) if @output_prefix} #{"Verb".ljust(verb_width)} #{"URI Pattern".ljust(path_width)} #{"Controller#Action".ljust(reqs_width)}#{' Hits' if @output_hits}"
56
+ [
57
+ ('Prefix'.rjust(name_width) if @output_prefix),
58
+ 'Verb'.ljust(verb_width),
59
+ 'URI Pattern'.ljust(path_width),
60
+ 'Controller#Action'.ljust(reqs_width),
61
+ ('Hits' if @output_hits)
62
+ ].compact.join(' ')
61
63
  end
62
64
 
63
65
  def widths(routes)
64
- [routes.map { |r| r[:name].length }.max || 0,
65
- routes.map { |r| r[:verb].length }.max || 0,
66
- routes.map { |r| r[:path].length }.max || 0,
67
- routes.map { |r| r[:reqs].length }.max || 0,
68
- ]
66
+ %i[name verb path reqs].map do |key|
67
+ routes.map { |r| r[key].length }.max.to_i
68
+ end
69
69
  end
70
70
  end
71
71
 
72
-
73
- def routes_section formatter, title, routes
72
+ def routes_section(formatter, title, routes)
74
73
  formatter.buffer << title
75
74
 
76
75
  if routes.none?
@@ -83,24 +82,27 @@ module RoutesCoverage
83
82
  formatter.result
84
83
  end
85
84
 
86
- def hit_routes
87
- routes = result.hit_routes
85
+ def collect_routes(routes)
86
+ # rails 3
87
+ return Result::Inspector.new.collect_all_routes(routes) unless Result::Inspector::NEW_RAILS
88
+
89
+ Result::Inspector.new(routes).collect_all_routes
90
+ end
91
+
92
+ def hit_routes_details
88
93
  # engine routes now are in the same list
89
- if Result::Inspector::NEW_RAILS
90
- hit_routes = Result::Inspector.new(result.hit_routes).collect_all_routes
91
- pending_routes = Result::Inspector.new(result.pending_routes).collect_all_routes
92
- else
93
- #rails 3
94
- hit_routes = Result::Inspector.new.collect_all_routes(result.hit_routes)
95
- pending_routes = Result::Inspector.new.collect_all_routes(result.pending_routes)
96
- end
94
+ hit_routes = collect_routes(result.hit_routes)
95
+ pending_routes = collect_routes(result.pending_routes)
96
+
97
+ <<~TXT
98
+ #{routes_section(RouteFormatter.new(result, settings, output_hits: true), 'Covered routes:', hit_routes)}
97
99
 
98
- return routes_section(RouteFormatter.new(result, settings, true), "Covered routes:", hit_routes) + "\n\n" +
99
- routes_section(RouteFormatter.new(result, settings), "Pending routes:", pending_routes)
100
+ #{routes_section(RouteFormatter.new(result, settings), 'Pending routes:', pending_routes)}
101
+ TXT
100
102
  end
101
103
 
102
104
  def format
103
- "#{super}\n\n#{hit_routes}"
105
+ "#{super}\n\n#{hit_routes_details}"
104
106
  end
105
107
  end
106
108
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "erb"
2
4
  require "cgi"
3
5
  require "fileutils"
@@ -23,24 +25,25 @@ module RoutesCoverage
23
25
  ERB.new(File.read(File.join(File.dirname(__FILE__), "html_views", "#{name}.erb")))
24
26
  end
25
27
 
26
-
27
28
  def root(root = nil)
28
- #TODO: config for this
29
+ # TODO: config for this
29
30
  return SimpleCov.root if defined? SimpleCov
30
31
  return @root if defined?(@root) && root.nil?
32
+
31
33
  @root = File.expand_path(root || Dir.getwd)
32
34
  end
33
35
 
34
36
  def coverage_dir(dir = nil)
35
- #TODO: config for this
37
+ # TODO: config for this
36
38
  return SimpleCov.coverage_dir if defined? SimpleCov
37
39
  return @coverage_dir if defined?(@coverage_dir) && dir.nil?
38
- @coverage_path = nil # invalidate cache
40
+
41
+ @output_path = nil # invalidate cache
39
42
  @coverage_dir = (dir || "coverage")
40
43
  end
41
44
 
42
45
  def output_path
43
- @coverage_path ||= File.expand_path(coverage_dir, root).tap do |path|
46
+ @output_path ||= File.expand_path(coverage_dir, root).tap do |path|
44
47
  FileUtils.mkdir_p path
45
48
  end
46
49
  end
@@ -51,8 +54,8 @@ module RoutesCoverage
51
54
  @project_name ||= File.basename(root.split("/").last).capitalize.tr("_", " ")
52
55
  end
53
56
 
54
- def asset_content name
55
- File.read(File.expand_path("../../../compiled_assets/#{name}", File.dirname(__FILE__)))
57
+ def asset_content(name)
58
+ File.read(File.expand_path("../../../compiled_assets/#{name}", __dir__))
56
59
  end
57
60
 
58
61
  def style_asset_link
@@ -83,33 +86,23 @@ module RoutesCoverage
83
86
  end
84
87
  end
85
88
 
86
- def hits_css_class hits
87
- hits > 0 ? 'cov' : 'uncov'
89
+ def hits_css_class(hits)
90
+ hits.positive? ? 'cov' : 'uncov'
88
91
  end
89
92
 
90
93
  def timeago(time)
91
94
  "<abbr class=\"timeago\" title=\"#{time.iso8601}\">#{time.iso8601}</abbr>"
92
95
  end
93
96
 
94
-
95
97
  def all_result_groups
96
- return @all_result_groups if @all_result_groups
97
- @all_result_groups = [
98
- {
99
- id: 'all_routes',
100
- name: 'All Routes',
101
- result: result,
102
- }
103
- ]
104
- @all_result_groups += groups.map do |group_name, group_result|
105
- {
106
- id: group_name.gsub(/^[^a-zA-Z]+/, "").gsub(/[^a-zA-Z0-9\-\_]/, ""),
107
- name: group_name,
108
- result: group_result,
109
- }
110
- end
111
-
112
- @all_result_groups
98
+ @all_result_groups ||= [{ id: 'all_routes', name: 'All Routes', result: result }] +
99
+ groups.map do |group_name, group_result|
100
+ {
101
+ id: group_name.gsub(/^[^a-zA-Z]+/, "").gsub(/[^a-zA-Z0-9\-_]/, ""),
102
+ name: group_name,
103
+ result: group_result
104
+ }
105
+ end
113
106
  end
114
107
  end
115
108
  end
@@ -1,15 +1,18 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RoutesCoverage
2
4
  module Formatters
3
5
  class SummaryText < Base
4
- def hits_count result
5
- "#{result.hit_routes_count} of #{result.expected_routes_count}#{"(#{result.total_count} total)" if result.expected_routes_count != result.total_count} routes hit#{ " at #{result.avg_hits} hits average" if result.hit_routes_count > 0}"
6
+ def hits_count(result)
7
+ "#{result.hit_routes_count} of #{result.expected_routes_count}"\
8
+ "#{"(#{result.total_count} total)" if result.expected_routes_count != result.total_count}"\
9
+ " routes hit#{" at #{result.avg_hits} hits average" if result.hit_routes_count.positive?}"
6
10
  end
7
11
 
8
12
  def status
9
13
  return unless settings.minimum_coverage
10
- unless result.coverage_pass?
11
- "Coverage is too low"
12
- end
14
+
15
+ "Coverage is too low" unless result.coverage_pass?
13
16
  end
14
17
 
15
18
  def format
@@ -18,9 +21,9 @@ module RoutesCoverage
18
21
  ]
19
22
 
20
23
  if groups.any?
21
- buffer += groups.map{|group_name, group_result|
24
+ buffer += groups.map do |group_name, group_result|
22
25
  " #{group_name}: #{group_result.coverage}% (#{hits_count group_result})"
23
- }
26
+ end
24
27
  end
25
28
 
26
29
  buffer << status
@@ -1,26 +1,27 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RoutesCoverage
2
4
  class Middleware
3
- def initialize app
5
+ def initialize(app)
4
6
  @app = app
5
7
  end
6
8
 
7
- def call env
9
+ def call(original_env)
8
10
  # router changes env/request during recognition so need a copy:
9
- req = ::Rails.application.routes.request_class.new env.dup
10
- ::Rails.application.routes.router.recognize(req) do |route|
11
+ env = original_env.dup
12
+ req = ::Rails.application.routes.request_class.new env
13
+ ::Rails.application.routes.router.recognize(req) do |route, parameters5, parameters4|
14
+ parameters = parameters5 || parameters4
11
15
  dispatcher = route.app
12
16
  if dispatcher.respond_to?(:dispatcher?)
13
- unless dispatcher.matches?(req) # && dispatcher.dispatcher?
14
- dispatcher = nil
15
- end
17
+ req.path_parameters = parameters
18
+ dispatcher = nil unless dispatcher.matches?(req) # && dispatcher.dispatcher?
16
19
  else # rails < 4.2
17
20
  dispatcher = route.app
18
- while dispatcher.is_a?(ActionDispatch::Routing::Mapper::Constraints) do
19
- if dispatcher.matches?(env)
20
- dispatcher = dispatcher.app
21
- else
22
- dispatcher = nil
23
- end
21
+ req.env['action_dispatch.request.path_parameters'] =
22
+ (env['action_dispatch.request.path_parameters'] || {}).merge(parameters)
23
+ while dispatcher.is_a?(ActionDispatch::Routing::Mapper::Constraints)
24
+ dispatcher = (dispatcher.app if dispatcher.matches?(env))
24
25
  end
25
26
  end
26
27
  next unless dispatcher
@@ -29,8 +30,8 @@ module RoutesCoverage
29
30
  # there may be multiple matching routes - we should match only first
30
31
  break
31
32
  end
32
- #TODO: detect 404s? and maybe other route errors?
33
- @app.call(env)
33
+ # TODO: detect 404s? and maybe other route errors?
34
+ @app.call(original_env)
34
35
  end
35
36
  end
36
37
  end
@@ -1,19 +1,20 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_support/core_ext/string' # needed for rails5 version of inspector
2
4
 
3
5
  module RoutesCoverage
4
6
  class Result
5
-
6
7
  begin
7
8
  require 'action_dispatch/routing/inspector'
8
9
  class Inspector < ActionDispatch::Routing::RoutesInspector
9
10
  NEW_RAILS = true
10
11
  def collect_all_routes
11
12
  res = collect_routes(@routes)
12
- #TODO: test with engines
13
+ # TODO: test with engines
13
14
  @engines.each do |engine_name, engine_routes|
14
- res += engine_routes.map{|er|
15
+ res += engine_routes.map do |er|
15
16
  er.merge({ engine_name: engine_name })
16
- }
17
+ end
17
18
  end
18
19
  res
19
20
  end
@@ -21,39 +22,35 @@ module RoutesCoverage
21
22
  def collect_routes(routes)
22
23
  routes.collect do |route|
23
24
  ActionDispatch::Routing::RouteWrapper.new(route)
24
- end.reject do |route|
25
- route.internal?
26
- end.collect do |route|
25
+ end.reject(&:internal?).collect do |route| # rubocop:disable Style/MultilineBlockChain
27
26
  collect_engine_routes(route)
28
27
 
29
- { name: route.name,
30
- verb: route.verb,
31
- path: route.path,
32
- reqs: route.reqs,
28
+ { name: route.name,
29
+ verb: route.verb,
30
+ path: route.path,
31
+ reqs: route.reqs,
33
32
  # regexp: route.json_regexp, # removed, this is not present in rails5
34
33
  # added:
35
- original: route,
36
- }
34
+ original: route }
37
35
  end
38
36
  end
39
37
  end
40
-
41
38
  rescue LoadError
42
- #rails 3
39
+ # rails 3
43
40
  require 'rails/application/route_inspector'
44
41
  class Inspector < Rails::Application::RouteInspector
45
42
  NEW_RAILS = false
46
- def collect_all_routes(routes)
43
+ def collect_all_routes(routes) # rubocop:disable Lint/DuplicateMethods
47
44
  res = collect_routes(routes)
48
45
  @engines.each do |engine_name, engine_routes|
49
- res += engine_routes.map{|er|
46
+ res += engine_routes.map do |er|
50
47
  er.merge({ engine_name: engine_name })
51
- }
48
+ end
52
49
  end
53
50
  res
54
51
  end
55
52
 
56
- def collect_routes(routes)
53
+ def collect_routes(routes) # rubocop:disable Lint/DuplicateMethods
57
54
  routes = routes.collect do |route|
58
55
  route_reqs = route.requirements
59
56
 
@@ -70,44 +67,40 @@ module RoutesCoverage
70
67
 
71
68
  collect_engine_routes(reqs, rack_app)
72
69
 
73
- { name: route.name.to_s,
74
- verb: route.verb.source.gsub(/[$^]/, ''),
75
- path: route.path.spec.to_s,
76
- reqs: reqs,
70
+ { name: route.name.to_s,
71
+ verb: route.verb.source.gsub(/[$^]/, ''),
72
+ path: route.path.spec.to_s,
73
+ reqs: reqs,
77
74
  # added:
78
- original: route,
79
- }
75
+ original: route }
80
76
  end
81
77
 
82
78
  # Skip the route if it's internal info route
83
79
  routes.reject { |r| r[:path] =~ %r{/rails/info/properties|^#{Rails.application.config.assets.prefix}} }
84
80
  end
85
81
  end
86
-
87
82
  end
88
83
 
89
- def initialize all_routes, hit_routes, settings
84
+ def initialize(all_routes, hit_routes, settings)
90
85
  @all_routes = all_routes
91
86
  @route_hit_counts = hit_routes
92
87
  @settings = settings
93
88
  end
94
89
 
95
- attr_reader :all_routes
96
-
97
- attr_reader :route_hit_counts
90
+ attr_reader :all_routes, :route_hit_counts
98
91
 
99
92
  def expected_routes
100
93
  return @expected_routes if @expected_routes
101
94
 
102
95
  filter_regex = Regexp.union(@settings.exclude_patterns)
103
- namespaces_regex = Regexp.union(@settings.exclude_namespaces.map{|n| /^\/#{n}/})
96
+ namespaces_regex = Regexp.union(@settings.exclude_namespaces.map { |n| %r{^/#{n}} })
104
97
 
105
- routes_groups = all_routes.group_by{|r|
106
- !!(
98
+ routes_groups = all_routes.group_by do |r|
99
+ (
107
100
  ("#{r.verb.to_s[8..-3]} #{r.path.spec}".strip =~ filter_regex) ||
108
101
  (r.path.spec.to_s =~ namespaces_regex)
109
- )
110
- }
102
+ ).present?
103
+ end
111
104
 
112
105
  @excluded_routes = routes_groups[true] || []
113
106
  @expected_routes = routes_groups[false] || []
@@ -123,11 +116,10 @@ module RoutesCoverage
123
116
  end
124
117
 
125
118
  def hit_routes
126
- #TODO: sort?
119
+ # TODO: sort?
127
120
  @route_hit_counts.keys
128
121
  end
129
122
 
130
-
131
123
  def hit_routes_count
132
124
  @route_hit_counts.size
133
125
  end
@@ -146,6 +138,7 @@ module RoutesCoverage
146
138
 
147
139
  def coverage
148
140
  return 0 unless expected_routes.any?
141
+
149
142
  (hit_routes_count * 100.0 / expected_routes_count).round(@settings.round_precision)
150
143
  end
151
144
 
@@ -158,16 +151,15 @@ module RoutesCoverage
158
151
  end
159
152
 
160
153
  def all_routes_with_hits
161
- if Inspector::NEW_RAILS
162
- res = Inspector.new(all_routes).collect_all_routes
163
- else
164
- res = Inspector.new.collect_all_routes(all_routes)
165
- end
166
- res.each{|val|
154
+ res = if Inspector::NEW_RAILS
155
+ Inspector.new(all_routes).collect_all_routes
156
+ else
157
+ Inspector.new.collect_all_routes(all_routes)
158
+ end
159
+ res.each do |val|
167
160
  val[:hits] = @route_hit_counts[val[:original]] || 0
168
- }
161
+ end
169
162
  res
170
163
  end
171
-
172
164
  end
173
165
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RoutesCoverage
2
- VERSION = "0.4.0"
4
+ VERSION = "0.5.0"
3
5
  end