routes_coverage 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d0ca681a352b4b2c633a83b99cc7c5b4334e2e40
4
- data.tar.gz: d3d3126f530afe3fff1978c801a5194247b24580
3
+ metadata.gz: dcc558504676f10b8f11d5c803288084c92b7c84
4
+ data.tar.gz: 063d639635c7c5b3fbd17396f129882559541cfb
5
5
  SHA512:
6
- metadata.gz: 9d1a5f0dff0432078c4f359f5feb98e0687a9bb778c9ec6b4fa8de40421eefd92c10b5b61f259556a2ee7be6b9aeefb0fd7286fee5f59a0e1ef56b20e1be61ac
7
- data.tar.gz: 5fc59b5bfa01ec2a87c99fb861e67795cd33ebdc167bc03b3923ab50e2216bb6ca3f1b1733a18da5b8f56d3b9c36a5ebabd2469c3f96f56678f0b16d5278bd76
6
+ metadata.gz: d6dfb367c8a7f0487959ad3ef831cf057ef3a1ea3849583a89b0c52df259ffe58b27ad59a6ec03420d17bbd192f71fed14af2fdfe385de4298960f07db347060
7
+ data.tar.gz: a5308eb14d95cb0afb8a95221fad4c5ac7f6ef3897d159680ede52d1b6db6e51b77268e52992fa3c0e27986746c1adb823d0bfe687b20fc4bd85e9a6ca769047
data/.gitignore CHANGED
@@ -8,3 +8,4 @@
8
8
  /spec/reports/
9
9
  /tmp/
10
10
  /log/
11
+ /coverage/
data/Appraisals CHANGED
@@ -18,6 +18,11 @@ appraise "rails-40+rspec" do
18
18
  gem "rspec-rails"
19
19
  end
20
20
 
21
+ appraise "rails-40+simplecov" do
22
+ gem "rails", "~>4.0.0"
23
+ gem "simplecov"
24
+ end
25
+
21
26
  appraise "rails-51" do
22
27
  gem "rails", "~>5.1.0"
23
28
  end
data/Gemfile CHANGED
@@ -1,7 +1,10 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- # Specify your gem's dependencies in routes_coverage.gemspec
4
- gemspec
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
5
6
 
6
- #TODO: appraisal
7
+ # rails should be included before us
7
8
  gem 'rails', '4.0.13'
9
+
10
+ gemspec
data/README.md CHANGED
@@ -39,6 +39,14 @@ To get more detailed information somewhere in your test helper add
39
39
 
40
40
  ```ruby
41
41
  RoutesCoverage.settings.format = :full_text
42
+ ```
43
+
44
+ or into RSpec config:
45
+
46
+ ```ruby
47
+ RSpec.configure do |config|
48
+ config.routes_coverage.format = :full_text
49
+ end
42
50
  ```
43
51
 
44
52
  Routes coverage is 11.1% (1 of 9 routes hit at 1.0 hits average)
@@ -59,6 +67,41 @@ RoutesCoverage.settings.format = :full_text
59
67
  PUT /reqs/:id(.:format) dummy#update
60
68
  DELETE /reqs/:id(.:format) dummy#destroy
61
69
 
70
+ ### Usage with SimpleCov
71
+
72
+ Use `RoutesCoverage.settings.format = :simplecov_html` along with simplecov to generate a html report like this:
73
+
74
+ ![Html output example](/assets/html_output_screenshot.png?raw=true "Html Output example")
75
+
76
+ at the moment it shares styles with simplecov's one,
77
+ code coverage report does not need to be generated each time as long as you have `/coverage` directory with all the resources.
78
+
79
+ ### Configuration
80
+
81
+ Set options in `RoutesCoverage.settings` or rspec's `config.routes_coverage`:
82
+
83
+ ```ruby
84
+ RSpec.configure do |config|
85
+ config.routes_coverage.format = :full_text
86
+ config.routes_coverage.exclude_patterns << %r{PATCH /reqs} # excludes all requests matching regex
87
+ config.routes_coverage.exclude_namespaces << 'somenamespace' # excludes /somenamespace/*
88
+ config.routes_coverage.minimum_coverage = 80 # %, your coverage goal
89
+ config.routes_coverage.round_precision = 0 # just round to whole percents
90
+ end
91
+ ```
92
+
93
+ or
94
+
95
+ ```ruby
96
+ RoutesCoverage.configure do |config|
97
+ config.format = :full_text
98
+ # ...
99
+ end
100
+ ```
101
+
102
+
103
+ Excluded routes do not show in pending, but are shown if they're hit.
104
+
62
105
 
63
106
  ## Development
64
107
 
data/Rakefile CHANGED
@@ -9,6 +9,12 @@ Rake::TestTask.new(:spec) do |t|
9
9
  t.libs.push 'spec'
10
10
  end
11
11
 
12
+ Rake::TestTask.new(:dummytest) do |t| t.pattern = 'spec/fixtures/dummy_test.rb' end
13
+ Rake::TestTask.new(:dummytest_full) do |t| t.pattern = 'spec/fixtures/dummy_test_full.rb' end
14
+ Rake::TestTask.new(:dummytest_filter) do |t| t.pattern = 'spec/fixtures/dummy_test_nsfilters.rb' end
15
+ Rake::TestTask.new(:dummytest_groups) do |t| t.pattern = 'spec/fixtures/dummy_test_groups.rb' end
16
+
17
+
12
18
  task :default => :spec
13
19
 
14
20
  $:.push File.expand_path("../lib", __FILE__)
@@ -0,0 +1,2 @@
1
+ ---
2
+ BUNDLE_RETRY: "1"
@@ -4,4 +4,4 @@ source "https://rubygems.org"
4
4
 
5
5
  gem "rails", "~>4.0.0"
6
6
 
7
- gemspec :path => "../"
7
+ gemspec path: "../"
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- routes_coverage (0.0.2)
4
+ routes_coverage (0.0.3)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -5,4 +5,4 @@ source "https://rubygems.org"
5
5
  gem "rails", "~>4.0.0"
6
6
  gem "rspec-rails"
7
7
 
8
- gemspec :path => "../"
8
+ gemspec path: "../"
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- routes_coverage (0.0.2)
4
+ routes_coverage (0.0.3)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "~>4.0.0"
6
+ gem "simplecov"
7
+
8
+ gemspec path: "../"
@@ -0,0 +1,97 @@
1
+ PATH
2
+ remote: ..
3
+ specs:
4
+ routes_coverage (0.0.3)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ actionmailer (4.0.13)
10
+ actionpack (= 4.0.13)
11
+ mail (~> 2.5, >= 2.5.4)
12
+ actionpack (4.0.13)
13
+ activesupport (= 4.0.13)
14
+ builder (~> 3.1.0)
15
+ erubis (~> 2.7.0)
16
+ rack (~> 1.5.2)
17
+ rack-test (~> 0.6.2)
18
+ activemodel (4.0.13)
19
+ activesupport (= 4.0.13)
20
+ builder (~> 3.1.0)
21
+ activerecord (4.0.13)
22
+ activemodel (= 4.0.13)
23
+ activerecord-deprecated_finders (~> 1.0.2)
24
+ activesupport (= 4.0.13)
25
+ arel (~> 4.0.0)
26
+ activerecord-deprecated_finders (1.0.4)
27
+ activesupport (4.0.13)
28
+ i18n (~> 0.6, >= 0.6.9)
29
+ minitest (~> 4.2)
30
+ multi_json (~> 1.3)
31
+ thread_safe (~> 0.1)
32
+ tzinfo (~> 0.3.37)
33
+ appraisal (2.2.0)
34
+ bundler
35
+ rake
36
+ thor (>= 0.14.0)
37
+ arel (4.0.2)
38
+ builder (3.1.4)
39
+ concurrent-ruby (1.0.5)
40
+ docile (1.1.5)
41
+ erubis (2.7.0)
42
+ i18n (0.8.1)
43
+ json (1.8.6)
44
+ mail (2.6.5)
45
+ mime-types (>= 1.16, < 4)
46
+ mime-types (3.1)
47
+ mime-types-data (~> 3.2015)
48
+ mime-types-data (3.2016.0521)
49
+ minitest (4.7.5)
50
+ multi_json (1.12.1)
51
+ rack (1.5.5)
52
+ rack-test (0.6.3)
53
+ rack (>= 1.0)
54
+ rails (4.0.13)
55
+ actionmailer (= 4.0.13)
56
+ actionpack (= 4.0.13)
57
+ activerecord (= 4.0.13)
58
+ activesupport (= 4.0.13)
59
+ bundler (>= 1.3.0, < 2.0)
60
+ railties (= 4.0.13)
61
+ sprockets-rails (~> 2.0)
62
+ railties (4.0.13)
63
+ actionpack (= 4.0.13)
64
+ activesupport (= 4.0.13)
65
+ rake (>= 0.8.7)
66
+ thor (>= 0.18.1, < 2.0)
67
+ rake (10.5.0)
68
+ simplecov (0.14.1)
69
+ docile (~> 1.1.0)
70
+ json (>= 1.8, < 3)
71
+ simplecov-html (~> 0.10.0)
72
+ simplecov-html (0.10.0)
73
+ sprockets (3.7.1)
74
+ concurrent-ruby (~> 1.0)
75
+ rack (> 1, < 3)
76
+ sprockets-rails (2.3.3)
77
+ actionpack (>= 3.0)
78
+ activesupport (>= 3.0)
79
+ sprockets (>= 2.8, < 4.0)
80
+ thor (0.19.4)
81
+ thread_safe (0.3.6)
82
+ tzinfo (0.3.53)
83
+
84
+ PLATFORMS
85
+ ruby
86
+
87
+ DEPENDENCIES
88
+ appraisal
89
+ bundler (~> 1.14)
90
+ minitest
91
+ rails (~> 4.0.0)
92
+ rake (~> 10.0)
93
+ routes_coverage!
94
+ simplecov
95
+
96
+ BUNDLED WITH
97
+ 1.14.6
@@ -4,4 +4,4 @@ source "https://rubygems.org"
4
4
 
5
5
  gem "rails", "~>4.2.0"
6
6
 
7
- gemspec :path => "../"
7
+ gemspec path: "../"
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- routes_coverage (0.0.2)
4
+ routes_coverage (0.0.3)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -4,4 +4,4 @@ source "https://rubygems.org"
4
4
 
5
5
  gem "rails", "~>5.0.0"
6
6
 
7
- gemspec :path => "../"
7
+ gemspec path: "../"
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- routes_coverage (0.0.2)
4
+ routes_coverage (0.0.3)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -4,4 +4,4 @@ source "https://rubygems.org"
4
4
 
5
5
  gem "rails", "~>5.1.0"
6
6
 
7
- gemspec :path => "../"
7
+ gemspec path: "../"
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- routes_coverage (0.0.2)
4
+ routes_coverage (0.0.3)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -0,0 +1,12 @@
1
+ module RoutesCoverage
2
+ module Adapters
3
+ class SimpleCov
4
+ def self.use
5
+ RoutesCoverage.reset!
6
+ ::SimpleCov.at_exit{
7
+ RoutesCoverage.perform_report
8
+ }
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,15 @@
1
+ module RoutesCoverage
2
+ module Formatters
3
+ class Base
4
+ def initialize result, groups, settings
5
+ @result = result
6
+ @groups = groups
7
+ @settings = settings
8
+ end
9
+
10
+ attr_reader :result
11
+ attr_reader :groups
12
+ attr_reader :settings
13
+ end
14
+ end
15
+ end
@@ -1,29 +1,7 @@
1
- require 'active_support/core_ext/string' # needed for rails5 version of inspector
2
- require 'action_dispatch/routing/inspector'
3
-
4
1
  module RoutesCoverage
5
2
  module Formatters
6
3
  class FullText < SummaryText
7
4
 
8
- class Inspector < ActionDispatch::Routing::RoutesInspector
9
- def collect_routes(routes)
10
- routes.collect do |route|
11
- ActionDispatch::Routing::RouteWrapper.new(route)
12
- end.reject do |route|
13
- route.internal?
14
- end.collect do |route|
15
- collect_engine_routes(route)
16
-
17
- { name: route.name,
18
- verb: route.verb,
19
- path: route.path,
20
- reqs: route.reqs,
21
- # regexp: route.json_regexp, # removed, this is not present in rails5
22
- original: route, # added
23
- }
24
- end
25
- end
26
- end
27
5
 
28
6
  class RouteFormatter < ActionDispatch::Routing::ConsoleFormatter
29
7
 
@@ -50,7 +28,7 @@ module RoutesCoverage
50
28
  if @output_hits
51
29
  # hits = " ?"
52
30
  original_route = r[:original].__getobj__ # SimpleDelegator
53
- hits = " #{@result.route_hits[original_route]}"
31
+ hits = " #{@result.route_hit_counts[original_route]}"
54
32
  end
55
33
  "#{r[:name].rjust(name_width) if @output_prefix} #{r[:verb].ljust(verb_width)} #{r[:path].ljust(path_width)} #{r[:reqs].ljust(reqs_width)}#{hits}"
56
34
  end
@@ -83,15 +61,16 @@ module RoutesCoverage
83
61
  # routes.map do |r|
84
62
  # "#{r.verb.ljust(verb_width)} #{r.path.ljust(path_width)} #{r.reqs}"
85
63
  # end
86
- Inspector.new(routes).format(RouteFormatter.new(result, settings, true)),
64
+ #TODO: replace formatter
65
+ Result::Inspector.new(routes).format(RouteFormatter.new(result, settings, true)),
87
66
  nil,
88
67
  "Pending routes:",
89
- Inspector.new(result.pending_routes).format(RouteFormatter.new),
68
+ Result::Inspector.new(result.pending_routes).format(RouteFormatter.new),
90
69
  ].flatten.join("\n")
91
70
  end
92
71
 
93
72
  def format
94
- "\nRoutes coverage is #{result.coverage}% (#{hits_count})#{status}\n\n#{hit_routes}"
73
+ "#{super}\n\n#{hit_routes}"
95
74
  end
96
75
  end
97
76
  end
@@ -0,0 +1,53 @@
1
+ <!DOCTYPE html>
2
+ <html xmlns='http://www.w3.org/1999/xhtml'>
3
+ <head>
4
+ <title>Code coverage for <%= SimpleCov.project_name %></title>
5
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
6
+ <script src='<%= assets_path('application.js') %>' type='text/javascript'></script>
7
+ <link href='<%= assets_path('application.css') %>' media='screen, projection, print' rel='stylesheet' type='text/css'>
8
+ <link rel="shortcut icon" type="image/png" href="<%= assets_path("favicon_#{coverage_css_class(result.coverage)}.png") %>" />
9
+ <link rel="icon" type="image/png" href="<%= assets_path('favicon.png') %>" />
10
+
11
+ <script>
12
+ $(document).ready(function() {
13
+ $('.route_list').dataTable({
14
+ "aaSorting": [], //[[ 1, "asc" ]],
15
+ "bPaginate": false,
16
+ "bJQueryUI": true,
17
+ "aoColumns": [
18
+ null, //name
19
+ null, //verb
20
+ null, //path
21
+ null, //action
22
+ null, //covered yes/no
23
+ null, //hits count
24
+ ]
25
+ });
26
+ });
27
+ </script>
28
+ </head>
29
+
30
+ <body>
31
+ <div id="loading">
32
+ Loading...
33
+ </div>
34
+ <div id="wrapper" style="display:none;">
35
+ <div class="timestamp">Generated <%= timeago(Time.now) %></div>
36
+ <ul class="group_tabs"></ul>
37
+
38
+ <div id="content">
39
+ <%= route_group_result("All Routes", result) %>
40
+
41
+ <% groups.each do |name, group_result| %>
42
+ <%= route_group_result(name, group_result) %>
43
+ <% end %>
44
+ </div>
45
+
46
+ <div id="footer">
47
+ Generated by <a href="http://github.com/Vasfed/routes_coverage">routes_coverage</a> v<%= RoutesCoverage::VERSION %>
48
+ and simplecov-html v<%= SimpleCov::Formatter::HTMLFormatter::VERSION %><br/>
49
+ </div>
50
+
51
+ </div>
52
+ </body>
53
+ </html>
@@ -0,0 +1,48 @@
1
+ <div class="file_list_container" id="<%= title_id %>">
2
+ <h2>
3
+ <span class="group_name"><%= title %></span>
4
+ (<span class="covered_percent"><span class="<%= coverage_css_class(result.coverage.to_f) %>"><%= result.coverage %>%</span></span>
5
+ covered at
6
+ <span class="covered_strength">
7
+ <span class="<%#= strength_css_class(result.avg_hits) %>">
8
+ <%= result.avg_hits.round(2) %>
9
+ </span>
10
+ </span> hits/route)
11
+ </h2>
12
+ <a name="<%= title_id %>"></a>
13
+ <div>
14
+ <b><%= result.total_count %></b> routes in total.
15
+ <b><%= result.expected_routes_count %></b> relevant routes.
16
+ <span class="green"><b><%= result.hit_routes_count %></b> covered</span> and
17
+ <span class="red"><b><%= result.pending_routes.size %></b> missed </span>
18
+ </div>
19
+ <table class="route_list">
20
+ <thead>
21
+ <tr>
22
+ <th>Name</th>
23
+ <th>Verb</th>
24
+ <th>Path</th>
25
+ <th>Controller</th>
26
+ <th>Covered</th>
27
+ <th>Hits</th>
28
+ </tr>
29
+ </thead>
30
+ <tbody>
31
+ <% result.all_routes_with_hits.each do |route| %>
32
+ <tr>
33
+ <td class="strong"><%= route[:name] %></td>
34
+ <td class="strong"><%= route[:verb] %></td>
35
+ <td class="strong"><%= route[:path] %></td>
36
+ <td class="strong">
37
+ <% if route[:engine_name] %>
38
+ engine <%= route[:engine_name] %>
39
+ <% end %>
40
+ <%= route[:reqs] %>
41
+ </td>
42
+ <td class="strong <%= hits_css_class route[:hits] %>"><%= route[:hits] > 0 ? 'Yes' : 'No' %></td>
43
+ <td class="<%= hits_css_class route[:hits] %> strong"><%= route[:hits] %></td>
44
+ </tr>
45
+ <% end %>
46
+ </tbody>
47
+ </table>
48
+ </div>
@@ -0,0 +1,97 @@
1
+ require "erb"
2
+ require "cgi"
3
+ require "fileutils"
4
+ require "digest/sha1"
5
+ require "time"
6
+
7
+ module RoutesCoverage
8
+ module Formatters
9
+ class SimpleCovHtml < Base
10
+ def format
11
+ #TODO: copy assets, if simplecov does not generate a report along us
12
+
13
+ # Dir[File.join(File.dirname(__FILE__), "../public/*")].each do |path|
14
+ # FileUtils.cp_r(path, asset_output_path)
15
+ # end
16
+
17
+ routes_filename = "routes.html"
18
+
19
+ File.open(File.join(output_path, routes_filename), "wb") do |file|
20
+ file.puts template("layout").result(binding)
21
+ end
22
+
23
+ "Routes coverage is #{result.coverage}% Report generated to #{output_path}/#{routes_filename}"
24
+ end
25
+
26
+ private
27
+
28
+ def template(name)
29
+ ERB.new(File.read(File.join(File.dirname(__FILE__), "html_views", "#{name}.erb")))
30
+ end
31
+
32
+ def output_path
33
+ SimpleCov.coverage_path
34
+ end
35
+
36
+ def asset_output_path
37
+ return @asset_output_path if defined?(@asset_output_path) && @asset_output_path
38
+ @asset_output_path = File.join(output_path, "assets", SimpleCov::Formatter::HTMLFormatter::VERSION)
39
+ FileUtils.mkdir_p(@asset_output_path)
40
+ @asset_output_path
41
+ end
42
+
43
+ def assets_path(name)
44
+ File.join("./assets", SimpleCov::Formatter::HTMLFormatter::VERSION, name)
45
+ end
46
+
47
+ # Returns the html for the given source_file
48
+ def formatted_source_file(source_file)
49
+ template("source_file").result(binding)
50
+ end
51
+
52
+ # Returns a table containing the given source files
53
+ def route_group_result(title, result)
54
+ title_id = title.gsub(/^[^a-zA-Z]+/, "").gsub(/[^a-zA-Z0-9\-\_]/, "")
55
+ # Silence a warning by using the following variable to assign to itself:
56
+ # "warning: possibly useless use of a variable in void context"
57
+ # The variable is used by ERB via binding.
58
+ title_id = title_id
59
+ template("route_group").result(binding)
60
+ end
61
+
62
+ def coverage_css_class(covered_percent)
63
+ if covered_percent > 90
64
+ "green"
65
+ elsif covered_percent > 80
66
+ "yellow"
67
+ else
68
+ "red"
69
+ end
70
+ end
71
+
72
+ def strength_css_class(covered_strength)
73
+ if covered_strength > 1
74
+ "green"
75
+ elsif covered_strength == 1
76
+ "yellow"
77
+ else
78
+ "red"
79
+ end
80
+ end
81
+
82
+ def hits_css_class hits
83
+ hits > 0 ? 'green' : 'red'
84
+ end
85
+
86
+ # Return a (kind of) unique id for the source file given. Uses SHA1 on path for the id
87
+ def id(source_file)
88
+ Digest::SHA1.hexdigest(source_file.filename)
89
+ end
90
+
91
+ def timeago(time)
92
+ "<abbr class=\"timeago\" title=\"#{time.iso8601}\">#{time.iso8601}</abbr>"
93
+ end
94
+
95
+ end
96
+ end
97
+ end
@@ -1,29 +1,31 @@
1
1
  module RoutesCoverage
2
2
  module Formatters
3
- class SummaryText
4
- def initialize result, settings
5
- @result = result
6
- @settings = settings
7
- end
8
-
9
- attr_reader :result
10
- attr_reader :settings
11
-
12
- def hits_count
3
+ class SummaryText < Base
4
+ def hits_count result
13
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}"
14
6
  end
15
7
 
16
8
  def status
17
9
  return unless settings.minimum_coverage
18
- if result.coverage_pass?
19
- ""
20
- else
21
- "\nCoverage failed. Need at least #{(settings.minimum_coverage / 100.0 * result.total_count).ceil}"
10
+ unless result.coverage_pass?
11
+ "Coverage is too low"
22
12
  end
23
13
  end
24
14
 
25
15
  def format
26
- "\nRoutes coverage is #{result.coverage}% (#{hits_count})#{status}"
16
+ buffer = [
17
+ "Routes coverage is #{result.coverage}% (#{hits_count result})"
18
+ ]
19
+
20
+ if groups.any?
21
+ buffer += groups.map{|group_name, group_result|
22
+ " #{group_name}: #{group_result.coverage}% (#{hits_count group_result})"
23
+ }
24
+ end
25
+
26
+ buffer << status
27
+
28
+ buffer.compact.join("\n")
27
29
  end
28
30
  end
29
31
  end
@@ -5,13 +5,9 @@ module RoutesCoverage
5
5
  end
6
6
 
7
7
  def call env
8
- # method = env["REQUEST_METHOD"]
9
- # path = env["REQUEST_PATH"] || env["PATH_INFO"] || env["ORIGINAL_FULLPATH"]
10
- # puts "req #{method} #{env["REQUEST_PATH"]} || #{env["ORIGINAL_FULLPATH"]} || #{env["PATH_INFO"]}"
11
- # puts "env is #{env.inspect}"
12
8
  req = ::Rails.application.routes.request_class.new env
13
9
  ::Rails.application.routes.router.recognize(req) do |route|
14
- RoutesCoverage.current_result.touch_route(route)
10
+ RoutesCoverage._touch_route(route)
15
11
  end
16
12
  #TODO: detect 404s? and maybe other route errors?
17
13
  @app.call(env)
@@ -1,45 +1,33 @@
1
+ require 'active_support/core_ext/string' # needed for rails5 version of inspector
2
+ require 'action_dispatch/routing/inspector'
3
+
1
4
  module RoutesCoverage
2
5
  class Result
3
- def initialize
4
- @route_hits = Hash.new(0)
6
+ def initialize all_routes, hit_routes, settings
7
+ @all_routes = all_routes
8
+ @route_hit_counts = hit_routes
9
+ @settings = settings
5
10
  end
6
11
 
7
- attr_reader :route_hits
8
-
9
- def touch_route route
10
- @route_hits[route] += 1
11
- end
12
+ attr_reader :all_routes
12
13
 
13
- def all_routes
14
- ::Rails.application.routes.routes.routes
15
- end
14
+ attr_reader :route_hit_counts
16
15
 
17
16
  def expected_routes
18
17
  return @expected_routes if @expected_routes
19
18
 
20
- routes = all_routes.dup
21
-
22
- if defined?(::Sprockets) && defined?(::Sprockets::Environment)
23
- routes.reject!{|r| r.app.is_a?(::Sprockets::Environment) }
24
- end
19
+ filter_regex = Regexp.union(@settings.exclude_patterns)
20
+ namespaces_regex = Regexp.union(@settings.exclude_namespaces.map{|n| /^\/#{n}/})
25
21
 
26
- excluded_routes = []
27
- regex = Regexp.union(RoutesCoverage.settings.exclude_patterns)
28
- routes.reject!{|r|
29
- if "#{r.verb.to_s[8..-3]} #{r.path.spec}".strip =~ regex
30
- excluded_routes << r
31
- end
22
+ routes_groups = all_routes.group_by{|r|
23
+ !!(
24
+ ("#{r.verb.to_s[8..-3]} #{r.path.spec}".strip =~ filter_regex) ||
25
+ (r.path.spec.to_s =~ namespaces_regex)
26
+ )
32
27
  }
33
28
 
34
- namespaces_regex = Regexp.union(RoutesCoverage.settings.exclude_namespaces.map{|n| /^\/#{n}/})
35
- routes.reject!{|r|
36
- if r.path.spec.to_s =~ namespaces_regex
37
- excluded_routes << r
38
- end
39
- }
40
-
41
- @excluded_routes = excluded_routes
42
- @expected_routes = routes
29
+ @excluded_routes = routes_groups[true] || []
30
+ @expected_routes = routes_groups[false] || []
43
31
  end
44
32
 
45
33
  def pending_routes
@@ -53,12 +41,12 @@ module RoutesCoverage
53
41
 
54
42
  def hit_routes
55
43
  #TODO: sort?
56
- route_hits.keys
44
+ @route_hit_counts.keys
57
45
  end
58
46
 
59
47
 
60
48
  def hit_routes_count
61
- route_hits.size
49
+ @route_hit_counts.size
62
50
  end
63
51
 
64
52
  def expected_routes_count
@@ -75,15 +63,56 @@ module RoutesCoverage
75
63
 
76
64
  def coverage
77
65
  return 'n/a' unless expected_routes.any?
78
- (hit_routes_count * 100.0 / expected_routes_count).round(RoutesCoverage.settings.round_precision)
66
+ (hit_routes_count * 100.0 / expected_routes_count).round(@settings.round_precision)
79
67
  end
80
68
 
81
69
  def avg_hits
82
- (route_hits.values.sum.to_f / hit_routes_count).round(RoutesCoverage.settings.round_precision)
70
+ (@route_hit_counts.values.sum.to_f / hit_routes_count).round(@settings.round_precision)
83
71
  end
84
72
 
85
73
  def coverage_pass?
86
- !RoutesCoverage.settings.minimum_coverage || (coverage >= RoutesCoverage.settings.minimum_coverage)
74
+ !@settings.minimum_coverage || (coverage.to_f >= @settings.minimum_coverage)
87
75
  end
76
+
77
+ def all_routes_with_hits
78
+ res = Inspector.new(all_routes).collect_all_routes
79
+ res.each{|val|
80
+ val[:hits] = @route_hit_counts[val[:original]] || 0
81
+ }
82
+ res
83
+ end
84
+
85
+ class Inspector < ActionDispatch::Routing::RoutesInspector
86
+ def collect_all_routes
87
+ res = collect_routes(@routes)
88
+ #TODO: test with engines
89
+ @engines.each do |engine_name, engine_routes|
90
+ res += engine_routes.map{|er|
91
+ er.merge({ engine_name: engine_name })
92
+ }
93
+ end
94
+ res
95
+ end
96
+
97
+ def collect_routes(routes)
98
+ routes.collect do |route|
99
+ ActionDispatch::Routing::RouteWrapper.new(route)
100
+ end.reject do |route|
101
+ route.internal?
102
+ end.collect do |route|
103
+ collect_engine_routes(route)
104
+
105
+ { name: route.name,
106
+ verb: route.verb,
107
+ path: route.path,
108
+ reqs: route.reqs,
109
+ # regexp: route.json_regexp, # removed, this is not present in rails5
110
+ # added:
111
+ original: route,
112
+ }
113
+ end
114
+ end
115
+ end
116
+
88
117
  end
89
118
  end
@@ -1,3 +1,3 @@
1
1
  module RoutesCoverage
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
@@ -2,8 +2,10 @@ require "routes_coverage/version"
2
2
  require "routes_coverage/result"
3
3
  require "routes_coverage/middleware"
4
4
 
5
+ require "routes_coverage/formatters/base"
5
6
  require "routes_coverage/formatters/summary_text"
6
7
  require "routes_coverage/formatters/full_text"
8
+ require "routes_coverage/formatters/simplecov_html"
7
9
 
8
10
  module RoutesCoverage
9
11
  class Railtie < ::Rails::Railtie
@@ -24,12 +26,30 @@ module RoutesCoverage
24
26
 
25
27
  attr_accessor :format
26
28
 
29
+ attr_reader :groups
30
+
27
31
  def initialize
28
32
  @exclude_patterns = []
29
33
  @exclude_namespaces = []
30
- @minimum_coverage = 80
34
+ @minimum_coverage = 1
31
35
  @round_precision = 1
32
36
  @format = :summary_text
37
+ @groups = {}
38
+ end
39
+
40
+ def formatter_class
41
+ case format
42
+ when :full_text
43
+ Formatters::FullText
44
+ when :summary_text
45
+ Formatters::SummaryText
46
+ when :simplecov_html
47
+ Formatters::SimpleCovHtml
48
+ when Formatters::Base
49
+ format
50
+ else
51
+ raise "Unknown formatter #{settings.format.inspect}"
52
+ end
33
53
  end
34
54
  end
35
55
 
@@ -41,33 +61,60 @@ module RoutesCoverage
41
61
  @@settings ||= Settings.new
42
62
  end
43
63
 
44
- mattr_reader :current_result
64
+ def self.configure
65
+ yield self.settings
66
+ end
67
+
45
68
  mattr_reader :pid
46
69
 
47
70
  def self.reset!
48
- @@current_result = Result.new
71
+ @@route_hit_count = Hash.new(0)
49
72
  @@pid = Process.pid
50
73
  end
51
74
 
52
75
  def self.perform_report
53
- result = current_result
54
-
55
- formatter_class = case settings.format
56
- when :full_text
57
- Formatters::FullText
58
- when :summary_text
59
- Formatters::SummaryText
60
- else
61
- raise "Unknown formatter #{settings.format.inspect}"
76
+
77
+ all_routes = ::Rails.application.routes.routes.routes.dup
78
+
79
+ if defined?(::Sprockets) && defined?(::Sprockets::Environment)
80
+ all_routes.reject!{|r| r.app.is_a?(::Sprockets::Environment) }
62
81
  end
63
82
 
64
- formatter = formatter_class.new(result, settings)
65
- puts formatter.format
83
+ all_result = Result.new(
84
+ all_routes,
85
+ @@route_hit_count,
86
+ settings
87
+ )
88
+
89
+
90
+ groups = Hash[settings.groups.map{|group_name, regex|
91
+ [group_name,
92
+ Result.new(
93
+ all_routes.select{|r| r.path.spec.to_s =~ regex},
94
+ Hash[@@route_hit_count.select{|r,_hits| r.path.spec.to_s =~ regex}],
95
+ settings
96
+ )
97
+ ]
98
+ }]
99
+
100
+ #TODO: group 'ungroupped'
101
+
102
+ puts
103
+ puts settings.formatter_class.new(all_result, groups, settings).format
66
104
  end
67
105
 
106
+
107
+ def self._touch_route route
108
+ reset! unless @@route_hit_count
109
+ @@route_hit_count[route] += 1
110
+ end
68
111
  end
69
112
 
70
113
  if RoutesCoverage.enabled?
114
+ if defined? SimpleCov
115
+ #TODO: use SimpleCov.at_exit
116
+ end
117
+
71
118
  if defined? RSpec
72
119
  require "routes_coverage/adapters/rspec"
73
120
  else
@@ -15,7 +15,7 @@ Gem::Specification.new do |spec|
15
15
  spec.license = "MIT"
16
16
 
17
17
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
- f.match(%r{^(test|spec|features)/})
18
+ f.match(%r{^(test|spec|features|assets)/})
19
19
  end
20
20
  spec.require_paths = ["lib"]
21
21
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: routes_coverage
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vasily Fedoseyev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-05-12 00:00:00.000000000 Z
11
+ date: 2017-05-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -83,10 +83,13 @@ files:
83
83
  - Rakefile
84
84
  - bin/console
85
85
  - bin/setup
86
+ - gemfiles/.bundle/config
86
87
  - gemfiles/rails_40.gemfile
87
88
  - gemfiles/rails_40.gemfile.lock
88
89
  - gemfiles/rails_40_rspec.gemfile
89
90
  - gemfiles/rails_40_rspec.gemfile.lock
91
+ - gemfiles/rails_40_simplecov.gemfile
92
+ - gemfiles/rails_40_simplecov.gemfile.lock
90
93
  - gemfiles/rails_42.gemfile
91
94
  - gemfiles/rails_42.gemfile.lock
92
95
  - gemfiles/rails_5.gemfile
@@ -96,7 +99,12 @@ files:
96
99
  - lib/routes_coverage.rb
97
100
  - lib/routes_coverage/adapters/atexit.rb
98
101
  - lib/routes_coverage/adapters/rspec.rb
102
+ - lib/routes_coverage/adapters/simplecov.rb
103
+ - lib/routes_coverage/formatters/base.rb
99
104
  - lib/routes_coverage/formatters/full_text.rb
105
+ - lib/routes_coverage/formatters/html_views/layout.erb
106
+ - lib/routes_coverage/formatters/html_views/route_group.erb
107
+ - lib/routes_coverage/formatters/simplecov_html.rb
100
108
  - lib/routes_coverage/formatters/summary_text.rb
101
109
  - lib/routes_coverage/middleware.rb
102
110
  - lib/routes_coverage/result.rb