routes_coverage 0.4.3 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/README.md +1 -0
- data/lib/routes_coverage/adapters/atexit.rb +6 -3
- data/lib/routes_coverage/adapters/rspec.rb +2 -0
- data/lib/routes_coverage/adapters/simplecov.rb +4 -2
- data/lib/routes_coverage/formatters/base.rb +4 -4
- data/lib/routes_coverage/formatters/full_text.rb +39 -37
- data/lib/routes_coverage/formatters/html.rb +20 -27
- data/lib/routes_coverage/formatters/summary_text.rb +10 -7
- data/lib/routes_coverage/middleware.rb +10 -13
- data/lib/routes_coverage/result.rb +37 -45
- data/lib/routes_coverage/version.rb +3 -1
- data/lib/routes_coverage.rb +83 -73
- data/routes_coverage.gemspec +14 -8
- metadata +20 -37
- data/.gitattributes +0 -1
- data/.gitignore +0 -12
- data/.travis.yml +0 -5
- data/Appraisals +0 -33
- data/Gemfile +0 -15
- data/Rakefile +0 -36
- data/bin/console +0 -15
- data/bin/setup +0 -7
- data/gemfiles/.bundle/config +0 -2
- data/gemfiles/rails_3.gemfile +0 -11
- data/gemfiles/rails_40.gemfile +0 -10
- data/gemfiles/rails_40_rspec.gemfile +0 -11
- data/gemfiles/rails_40_simplecov.gemfile +0 -10
- data/gemfiles/rails_42.gemfile +0 -10
- data/gemfiles/rails_5.gemfile +0 -10
- data/gemfiles/rails_51.gemfile +0 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 576b62caebf21a9e1448f549a213eaf5770ca8486c1ad94183c1bd8617bb9916
|
4
|
+
data.tar.gz: 9a150fe8e215e92ba00dee273700ebe69f08baca5606cc7980b09fcc854c0969
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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,13 +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
8
|
prev_block = ::SimpleCov.at_exit
|
7
|
-
::SimpleCov.at_exit
|
9
|
+
::SimpleCov.at_exit do
|
8
10
|
RoutesCoverage.perform_report
|
9
11
|
prev_block.call
|
10
|
-
|
12
|
+
end
|
11
13
|
end
|
12
14
|
end
|
13
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
|
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
|
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
|
32
|
+
def no_routes(_routes_from_rails5 = nil)
|
32
33
|
@buffer << "\tNone"
|
33
34
|
end
|
34
35
|
|
35
36
|
private
|
36
|
-
|
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
|
-
#
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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
|
-
|
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
|
-
[
|
65
|
-
|
66
|
-
|
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
|
87
|
-
|
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
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
#
|
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
|
-
|
99
|
-
|
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#{
|
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
|
-
|
40
|
+
|
41
|
+
@output_path = nil # invalidate cache
|
39
42
|
@coverage_dir = (dir || "coverage")
|
40
43
|
end
|
41
44
|
|
42
45
|
def output_path
|
43
|
-
@
|
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
|
55
|
-
File.read(File.expand_path("../../../compiled_assets/#{name}",
|
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
|
87
|
-
hits
|
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
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
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
|
5
|
-
"#{result.hit_routes_count} of #{result.expected_routes_count}
|
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
|
-
|
11
|
-
|
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
|
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,10 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module RoutesCoverage
|
2
4
|
class Middleware
|
3
|
-
def initialize
|
5
|
+
def initialize(app)
|
4
6
|
@app = app
|
5
7
|
end
|
6
8
|
|
7
|
-
def call
|
9
|
+
def call(original_env)
|
8
10
|
# router changes env/request during recognition so need a copy:
|
9
11
|
env = original_env.dup
|
10
12
|
req = ::Rails.application.routes.request_class.new env
|
@@ -13,18 +15,13 @@ module RoutesCoverage
|
|
13
15
|
dispatcher = route.app
|
14
16
|
if dispatcher.respond_to?(:dispatcher?)
|
15
17
|
req.path_parameters = parameters
|
16
|
-
unless dispatcher.matches?(req) # && dispatcher.dispatcher?
|
17
|
-
dispatcher = nil
|
18
|
-
end
|
18
|
+
dispatcher = nil unless dispatcher.matches?(req) # && dispatcher.dispatcher?
|
19
19
|
else # rails < 4.2
|
20
20
|
dispatcher = route.app
|
21
|
-
req.env['action_dispatch.request.path_parameters'] =
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
else
|
26
|
-
dispatcher = nil
|
27
|
-
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))
|
28
25
|
end
|
29
26
|
end
|
30
27
|
next unless dispatcher
|
@@ -33,7 +30,7 @@ module RoutesCoverage
|
|
33
30
|
# there may be multiple matching routes - we should match only first
|
34
31
|
break
|
35
32
|
end
|
36
|
-
#TODO: detect 404s? and maybe other route errors?
|
33
|
+
# TODO: detect 404s? and maybe other route errors?
|
37
34
|
@app.call(original_env)
|
38
35
|
end
|
39
36
|
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
|
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:
|
30
|
-
verb:
|
31
|
-
path:
|
32
|
-
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
|
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:
|
74
|
-
verb:
|
75
|
-
path:
|
76
|
-
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
|
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|
|
96
|
+
namespaces_regex = Regexp.union(@settings.exclude_namespaces.map { |n| %r{^/#{n}} })
|
104
97
|
|
105
|
-
routes_groups = all_routes.group_by
|
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
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
res.each
|
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
|
data/lib/routes_coverage.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "routes_coverage/version"
|
2
4
|
require "routes_coverage/result"
|
3
5
|
require "routes_coverage/middleware"
|
@@ -12,24 +14,13 @@ module RoutesCoverage
|
|
12
14
|
railtie_name :routes_coverage
|
13
15
|
|
14
16
|
initializer "request_coverage.inject_test_middleware" do
|
15
|
-
if RoutesCoverage.enabled?
|
16
|
-
::Rails.application.middleware.use RoutesCoverage::Middleware
|
17
|
-
end
|
17
|
+
::Rails.application.middleware.use RoutesCoverage::Middleware if RoutesCoverage.enabled?
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
21
|
class Settings
|
22
|
-
attr_reader :exclude_patterns
|
23
|
-
|
24
|
-
attr_accessor :exclude_put_fallbacks
|
25
|
-
|
26
|
-
attr_accessor :perform_report
|
27
|
-
attr_accessor :minimum_coverage
|
28
|
-
attr_accessor :round_precision
|
29
|
-
|
30
|
-
attr_accessor :format
|
31
|
-
|
32
|
-
attr_reader :groups
|
22
|
+
attr_reader :exclude_patterns, :exclude_namespaces, :groups
|
23
|
+
attr_accessor :exclude_put_fallbacks, :perform_report, :minimum_coverage, :round_precision, :format
|
33
24
|
|
34
25
|
def initialize
|
35
26
|
@exclude_patterns = []
|
@@ -63,96 +54,115 @@ module RoutesCoverage
|
|
63
54
|
end
|
64
55
|
|
65
56
|
def self.settings
|
66
|
-
|
57
|
+
@settings ||= Settings.new
|
67
58
|
end
|
68
59
|
|
69
60
|
def self.configure
|
70
|
-
yield
|
61
|
+
yield settings
|
71
62
|
end
|
72
63
|
|
73
|
-
|
64
|
+
# used in at_exit adapter to skip subprocesses
|
65
|
+
def self.pid
|
66
|
+
@pid
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.route_hit_count
|
70
|
+
@route_hit_count
|
71
|
+
end
|
74
72
|
|
75
73
|
def self.reset!
|
76
|
-
|
77
|
-
|
74
|
+
@route_hit_count = Hash.new(0)
|
75
|
+
@pid = Process.pid
|
78
76
|
end
|
79
77
|
|
80
78
|
def self.perform_report
|
81
79
|
return unless settings.perform_report
|
82
80
|
|
81
|
+
all_routes = _collect_all_routes
|
82
|
+
all_result = Result.new(all_routes, route_hit_count, settings)
|
83
|
+
groups = _collect_route_groups(all_routes)
|
84
|
+
|
85
|
+
if groups.size > 1
|
86
|
+
ungroupped_routes = all_routes.reject do |r|
|
87
|
+
groups.values.any? do |group_routes|
|
88
|
+
group_routes.all_routes.include? r
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
if ungroupped_routes.any?
|
93
|
+
groups["Ungroupped"] = Result.new(ungroupped_routes, route_hit_count.slice(ungroupped_routes), settings)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
puts
|
98
|
+
puts settings.formatter_class.new(all_result, groups, settings).format # rubocop:disable Rails/Output
|
99
|
+
end
|
100
|
+
|
101
|
+
def self._collect_all_routes
|
83
102
|
all_routes = ::Rails.application.routes.routes.routes.dup
|
84
103
|
|
85
104
|
if defined?(::Sprockets) && defined?(::Sprockets::Environment)
|
86
|
-
all_routes.reject!{|r| r.app.is_a?(::Sprockets::Environment) }
|
105
|
+
all_routes.reject! { |r| r.app.is_a?(::Sprockets::Environment) }
|
87
106
|
end
|
88
107
|
|
89
108
|
if settings.exclude_put_fallbacks
|
90
|
-
all_routes.reject!
|
109
|
+
all_routes.reject! do |put_route|
|
91
110
|
(
|
92
111
|
put_route.verb == /^PUT$/ ||
|
93
112
|
put_route.verb == "PUT" # rails 5
|
94
113
|
) &&
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
114
|
+
put_route.name.nil? &&
|
115
|
+
route_hit_count[put_route].zero? &&
|
116
|
+
all_routes.any? do |patch_route|
|
117
|
+
(
|
118
|
+
patch_route.verb == /^PATCH$/ ||
|
119
|
+
patch_route.verb == "PATCH" # rails5
|
120
|
+
) &&
|
121
|
+
patch_route.defaults == put_route.defaults &&
|
122
|
+
patch_route.ip == put_route.ip &&
|
123
|
+
patch_route.path.spec.to_s == put_route.path.spec.to_s
|
124
|
+
end
|
125
|
+
end
|
107
126
|
end
|
127
|
+
all_routes
|
128
|
+
end
|
108
129
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
if ungroupped_routes.any?
|
134
|
-
groups["Ungroupped"] = Result.new(
|
135
|
-
ungroupped_routes,
|
136
|
-
Hash[@@route_hit_count.select{|r,_hits| ungroupped_routes.include? r}],
|
137
|
-
settings
|
138
|
-
)
|
130
|
+
def self._collect_route_groups(all_routes)
|
131
|
+
settings.groups.map do |group_name, matcher|
|
132
|
+
group_routes = all_routes.select do |route|
|
133
|
+
if matcher.respond_to?(:call)
|
134
|
+
matcher.call(route)
|
135
|
+
elsif matcher.is_a?(Hash)
|
136
|
+
matcher.all? do |key, value|
|
137
|
+
case key
|
138
|
+
when :path
|
139
|
+
route.path.spec.to_s =~ value
|
140
|
+
when :constraints
|
141
|
+
value.all? do |constraint_name, constraint_value|
|
142
|
+
if constraint_value.present?
|
143
|
+
route.constraints[constraint_name] && route.constraints[constraint_name].match?(constraint_value)
|
144
|
+
else
|
145
|
+
route.constraints[constraint_name].blank?
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
else
|
151
|
+
route.path.spec.to_s.match?(matcher)
|
152
|
+
end
|
139
153
|
end
|
140
|
-
end
|
141
154
|
|
142
|
-
|
143
|
-
|
155
|
+
[group_name, Result.new(group_routes, route_hit_count.slice(group_routes), settings)]
|
156
|
+
end.to_h
|
144
157
|
end
|
145
158
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
@@route_hit_count[route] += 1
|
159
|
+
def self._touch_route(route)
|
160
|
+
reset! unless route_hit_count
|
161
|
+
route_hit_count[route] += 1
|
150
162
|
end
|
151
163
|
end
|
152
164
|
|
153
|
-
if defined? RSpec
|
154
|
-
require "routes_coverage/adapters/rspec"
|
155
|
-
end
|
165
|
+
require "routes_coverage/adapters/rspec" if defined? RSpec
|
156
166
|
|
157
167
|
if RoutesCoverage.enabled?
|
158
168
|
if defined?(SimpleCov) && SimpleCov.running
|
data/routes_coverage.gemspec
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
-
#
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
5
|
require 'routes_coverage/version'
|
5
6
|
|
@@ -9,18 +10,23 @@ Gem::Specification.new do |spec|
|
|
9
10
|
spec.authors = ["Vasily Fedoseyev"]
|
10
11
|
spec.email = ["vasilyfedoseyev@gmail.com"]
|
11
12
|
|
12
|
-
spec.summary =
|
13
|
-
spec.description =
|
13
|
+
spec.summary = "Provides coverage report for your rails routes"
|
14
|
+
spec.description = "Generates coverage report for routes hit by your request/integration/feature tests "\
|
15
|
+
"including capybara ones"
|
14
16
|
spec.homepage = "https://github.com/Vasfed/routes_coverage"
|
15
17
|
spec.license = "MIT"
|
16
18
|
|
17
|
-
spec.
|
18
|
-
|
19
|
+
spec.required_ruby_version = ">= 2.0"
|
20
|
+
|
21
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
22
|
+
f.match(%r{^(test|spec|features|assets|bin|gemfiles)/}) ||
|
23
|
+
f.start_with?('.') ||
|
24
|
+
%w[Appraisals Gemfile Rakefile].include?(f)
|
19
25
|
end
|
20
26
|
spec.require_paths = ["lib"]
|
21
27
|
|
28
|
+
spec.add_development_dependency 'appraisal'
|
22
29
|
spec.add_development_dependency "bundler", "~> 1.14"
|
23
|
-
spec.add_development_dependency "rake", "~> 10.0"
|
24
30
|
spec.add_development_dependency "minitest"
|
25
|
-
spec.add_development_dependency
|
31
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
26
32
|
end
|
metadata
CHANGED
@@ -1,43 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: routes_coverage
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Vasily Fedoseyev
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-11-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: appraisal
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '0'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: bundler
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '1.14'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '1.14'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: minitest
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -53,19 +53,19 @@ dependencies:
|
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: rake
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- - "
|
59
|
+
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '0'
|
61
|
+
version: '10.0'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- - "
|
66
|
+
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '0'
|
68
|
+
version: '10.0'
|
69
69
|
description: Generates coverage report for routes hit by your request/integration/feature
|
70
70
|
tests including capybara ones
|
71
71
|
email:
|
@@ -74,26 +74,10 @@ executables: []
|
|
74
74
|
extensions: []
|
75
75
|
extra_rdoc_files: []
|
76
76
|
files:
|
77
|
-
- ".gitattributes"
|
78
|
-
- ".gitignore"
|
79
|
-
- ".travis.yml"
|
80
|
-
- Appraisals
|
81
|
-
- Gemfile
|
82
77
|
- LICENSE.txt
|
83
78
|
- README.md
|
84
|
-
- Rakefile
|
85
|
-
- bin/console
|
86
|
-
- bin/setup
|
87
79
|
- compiled_assets/routes.css
|
88
80
|
- compiled_assets/routes.js
|
89
|
-
- gemfiles/.bundle/config
|
90
|
-
- gemfiles/rails_3.gemfile
|
91
|
-
- gemfiles/rails_40.gemfile
|
92
|
-
- gemfiles/rails_40_rspec.gemfile
|
93
|
-
- gemfiles/rails_40_simplecov.gemfile
|
94
|
-
- gemfiles/rails_42.gemfile
|
95
|
-
- gemfiles/rails_5.gemfile
|
96
|
-
- gemfiles/rails_51.gemfile
|
97
81
|
- lib/routes_coverage.rb
|
98
82
|
- lib/routes_coverage/adapters/atexit.rb
|
99
83
|
- lib/routes_coverage/adapters/rspec.rb
|
@@ -112,7 +96,7 @@ homepage: https://github.com/Vasfed/routes_coverage
|
|
112
96
|
licenses:
|
113
97
|
- MIT
|
114
98
|
metadata: {}
|
115
|
-
post_install_message:
|
99
|
+
post_install_message:
|
116
100
|
rdoc_options: []
|
117
101
|
require_paths:
|
118
102
|
- lib
|
@@ -120,16 +104,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
120
104
|
requirements:
|
121
105
|
- - ">="
|
122
106
|
- !ruby/object:Gem::Version
|
123
|
-
version: '0'
|
107
|
+
version: '2.0'
|
124
108
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
125
109
|
requirements:
|
126
110
|
- - ">="
|
127
111
|
- !ruby/object:Gem::Version
|
128
112
|
version: '0'
|
129
113
|
requirements: []
|
130
|
-
|
131
|
-
|
132
|
-
signing_key:
|
114
|
+
rubygems_version: 3.0.9
|
115
|
+
signing_key:
|
133
116
|
specification_version: 4
|
134
117
|
summary: Provides coverage report for your rails routes
|
135
118
|
test_files: []
|
data/.gitattributes
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
compiled_assets/* linguist-generated
|
data/.gitignore
DELETED
data/.travis.yml
DELETED
data/Appraisals
DELETED
@@ -1,33 +0,0 @@
|
|
1
|
-
# to test against all, run: appraisal rake spec
|
2
|
-
|
3
|
-
|
4
|
-
appraise "rails-40" do
|
5
|
-
gem "rails", "~>4.0.0"
|
6
|
-
end
|
7
|
-
|
8
|
-
appraise "rails-42" do
|
9
|
-
gem "rails", "~>4.2.0"
|
10
|
-
end
|
11
|
-
|
12
|
-
appraise "rails-5" do
|
13
|
-
gem "rails", "~>5.0.0"
|
14
|
-
end
|
15
|
-
|
16
|
-
appraise "rails-40+rspec" do
|
17
|
-
gem "rails", "~>4.0.0"
|
18
|
-
gem "rspec-rails"
|
19
|
-
end
|
20
|
-
|
21
|
-
appraise "rails-40+simplecov" do
|
22
|
-
gem "rails", "~>4.0.0"
|
23
|
-
gem "simplecov"
|
24
|
-
end
|
25
|
-
|
26
|
-
appraise "rails-51" do
|
27
|
-
gem "rails", "~>5.1.0"
|
28
|
-
end
|
29
|
-
|
30
|
-
appraise 'rails-3' do
|
31
|
-
gem 'rails', '~>3.2.22'
|
32
|
-
gem 'test-unit'
|
33
|
-
end
|
data/Gemfile
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
source 'https://rubygems.org'
|
2
|
-
|
3
|
-
#NB: gem's dependencies are in routes_coverage.gemspec
|
4
|
-
#NB: all other non-listed gems should go into Appraisals,
|
5
|
-
# this file is only for quick tests
|
6
|
-
|
7
|
-
# rails should be included before us
|
8
|
-
gem 'rails', '5.2.0.rc1'
|
9
|
-
gem 'simplecov', require: false
|
10
|
-
|
11
|
-
# for assets:
|
12
|
-
gem 'sprockets'
|
13
|
-
gem 'sass'
|
14
|
-
|
15
|
-
gemspec
|
data/Rakefile
DELETED
@@ -1,36 +0,0 @@
|
|
1
|
-
require 'bundler'
|
2
|
-
require 'bundler/setup'
|
3
|
-
require "bundler/gem_tasks"
|
4
|
-
|
5
|
-
require 'rake/testtask'
|
6
|
-
|
7
|
-
Rake::TestTask.new(:spec) do |t|
|
8
|
-
t.pattern = 'spec/**/*_spec.rb'
|
9
|
-
t.libs.push 'spec'
|
10
|
-
end
|
11
|
-
|
12
|
-
Rake::TestTask.new(:dummytest) do |t| t.pattern = 'spec/fixtures/dummy_test.rb' end
|
13
|
-
Rake::TestTask.new(:dummytest_html) do |t| t.pattern = 'spec/fixtures/dummy_html.rb' end
|
14
|
-
Rake::TestTask.new(:dummytest_full) do |t| t.pattern = 'spec/fixtures/dummy_test_full.rb' end
|
15
|
-
Rake::TestTask.new(:dummytest_filter) do |t| t.pattern = 'spec/fixtures/dummy_test_nsfilters.rb' end
|
16
|
-
Rake::TestTask.new(:dummytest_groups) do |t| t.pattern = 'spec/fixtures/dummy_test_groups.rb' end
|
17
|
-
|
18
|
-
|
19
|
-
task :default => :spec
|
20
|
-
|
21
|
-
$:.push File.expand_path("../lib", __FILE__)
|
22
|
-
require 'routes_coverage/version'
|
23
|
-
|
24
|
-
namespace :assets do
|
25
|
-
desc "Compiles all assets"
|
26
|
-
task :compile do
|
27
|
-
puts "Compiling assets"
|
28
|
-
require "sprockets"
|
29
|
-
assets = Sprockets::Environment.new
|
30
|
-
assets.append_path "assets/javascripts"
|
31
|
-
assets.append_path "assets/stylesheets"
|
32
|
-
compiled_path = "compiled_assets"
|
33
|
-
assets["application.js"].write_to("#{compiled_path}/routes.js")
|
34
|
-
assets["application.css"].write_to("#{compiled_path}/routes.css")
|
35
|
-
end
|
36
|
-
end
|
data/bin/console
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require "bundler/setup"
|
4
|
-
require "rails"
|
5
|
-
require "routes_coverage"
|
6
|
-
|
7
|
-
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
-
# with your gem easier. You can also use a different console, if you like.
|
9
|
-
|
10
|
-
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
-
# require "pry"
|
12
|
-
# Pry.start
|
13
|
-
|
14
|
-
require "irb"
|
15
|
-
IRB.start(__FILE__)
|
data/bin/setup
DELETED
data/gemfiles/.bundle/config
DELETED
data/gemfiles/rails_3.gemfile
DELETED
data/gemfiles/rails_40.gemfile
DELETED
data/gemfiles/rails_42.gemfile
DELETED
data/gemfiles/rails_5.gemfile
DELETED