routes_coverage 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 29bd7e16ae5978198cfd1b76f2806ff6261199e9
4
+ data.tar.gz: 87304e4e85b56a6030420d438b57ed8fb68aa635
5
+ SHA512:
6
+ metadata.gz: dd7deaa57566e7eef61acb913199e39c3c0b14e5ea017054eb60f60b38901aac11ba57e772da71b6a9d64707f03e60179aa7f39e3386178401a6714411e93b0f
7
+ data.tar.gz: 5932d5baf88b7e81267d6b8b8995dc865ff9134cea1bcebe20b201f6a49891bcc2dc6617a34a69366cf9cb82674cd15386f31ff9b21e81b0c198d88e17e7064d
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ /log/
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.0
5
+ before_install: gem install bundler -v 1.14.6
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in routes_coverage.gemspec
4
+ gemspec
5
+
6
+ #TODO: appraisal
7
+ gem 'rails', '4.0.13'
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Vasily Fedoseyev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,40 @@
1
+ # RoutesCoverage
2
+
3
+ Sometimes you need to know which routes are covered by your rails test suite.
4
+
5
+ (more detailed readme coming soon)
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'routes_coverage'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install routes_coverage
22
+
23
+ ## Usage
24
+
25
+ Install the gem and run your tests.
26
+
27
+ ## Development
28
+
29
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
+
31
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/Vasfed/routes_coverage.
36
+
37
+
38
+ ## License
39
+
40
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,17 @@
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
+ task :default => :spec
13
+
14
+ $:.push File.expand_path("../lib", __FILE__)
15
+ require 'routes_coverage/version'
16
+
17
+ Bundler::GemHelper.install_tasks
data/bin/console ADDED
@@ -0,0 +1,15 @@
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 ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,77 @@
1
+ require "routes_coverage/version"
2
+ require "routes_coverage/result"
3
+ require "routes_coverage/middleware"
4
+
5
+ require "routes_coverage/formatters/summary_text"
6
+ require "routes_coverage/formatters/full_text"
7
+
8
+ module RoutesCoverage
9
+ class Railtie < ::Rails::Railtie
10
+ railtie_name :routes_coverage
11
+
12
+ initializer "request_coverage.inject_test_middleware" do
13
+ if RoutesCoverage.enabled?
14
+ ::Rails.application.middleware.use RoutesCoverage::Middleware
15
+ end
16
+ end
17
+ end
18
+
19
+ class Settings
20
+ attr_reader :exclude_patterns
21
+ attr_reader :exclude_namespaces
22
+ attr_accessor :minimum_coverage
23
+ attr_accessor :round_precision
24
+
25
+ attr_accessor :format
26
+
27
+ def initialize
28
+ @exclude_patterns = []
29
+ @exclude_namespaces = []
30
+ @minimum_coverage = 80
31
+ @round_precision = 1
32
+ @format = :summary_text
33
+ end
34
+ end
35
+
36
+ def self.enabled?
37
+ ::Rails.env.test?
38
+ end
39
+
40
+ def self.settings
41
+ @@settings ||= Settings.new
42
+ end
43
+
44
+ mattr_reader :current_result
45
+ mattr_reader :pid
46
+
47
+ def self.reset!
48
+ @@current_result = Result.new
49
+ @@pid = Process.pid
50
+ end
51
+
52
+ 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}"
62
+ end
63
+
64
+ formatter = formatter_class.new(result, settings)
65
+ puts formatter.format
66
+ end
67
+
68
+ end
69
+
70
+ if RoutesCoverage.enabled?
71
+ if defined? RSpec
72
+ require "routes_coverage/adapters/rspec"
73
+ else
74
+ require "routes_coverage/adapters/atexit"
75
+ RoutesCoverage::Adapters::AtExit.use
76
+ end
77
+ end
@@ -0,0 +1,18 @@
1
+ module RoutesCoverage
2
+ module Adapters
3
+ 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
+ # also this may interfere with simplecov, need to use SimpleCov.at_exit do...end if defined
7
+
8
+ # puts "Using at_exit, note this may be interfered by simplecov etc."
9
+ RoutesCoverage.reset!
10
+ at_exit do
11
+ next if RoutesCoverage.pid != Process.pid
12
+ RoutesCoverage.perform_report
13
+ exit
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,73 @@
1
+ RSpec.configure do |config|
2
+ config.add_setting :routes_coverage
3
+ config.routes_coverage = RoutesCoverage.settings
4
+
5
+ config.before(:suite) do
6
+ RoutesCoverage.reset!
7
+ end
8
+
9
+ config.after(:suite) do
10
+ RoutesCoverage.perform_report
11
+ end
12
+ end
13
+
14
+ # RSpec::RoutesCoverage.initialize_routes!
15
+ #
16
+ #
17
+ # if ENV['LIST_ROUTES_COVERAGE']
18
+ # # require 'action_dispatch/routing/inspector'
19
+ #
20
+ # format_routes = ->(routes){
21
+ # formatter = ActionDispatch::Routing::ConsoleFormatter.new
22
+ # ActionDispatch::Routing::RoutesInspector.new(routes).format(formatter)
23
+ # }
24
+ #
25
+ # # inspector = begin
26
+ # # require 'rails/application/route_inspector'
27
+ # # Rails::Application::RouteInspector
28
+ # # rescue LoadError
29
+ # # require 'action_dispatch/routing/inspector'
30
+ # # ActionDispatch::Routing::RoutesInspector
31
+ # # end.new
32
+ #
33
+ # # inspector.instance_eval do
34
+ # # def formatted_routes(routes)
35
+ # # verb_width = routes.map{ |r| r[:verb].length }.max
36
+ # # path_width = routes.map{ |r| r[:path].length }.max
37
+ #
38
+ # # routes.map do |r|
39
+ # # "#{r[:verb].ljust(verb_width)} #{r[:path].ljust(path_width)} #{r[:reqs]}"
40
+ # # end
41
+ # # end
42
+ # # end
43
+ #
44
+ #
45
+ # legend = {
46
+ # magenta: :excluded_routes,
47
+ # green: :manually_tested_routes,
48
+ # blue: :auto_tested_routes,
49
+ # yellow: :pending_routes
50
+ # }
51
+ #
52
+ # legend.each do |color, name|
53
+ # total = name == :excluded_routes ? RSpec::RoutesCoverage.routes_num : RSpec::RoutesCoverage.tested_routes_num
54
+ #
55
+ # puts "\n\n"
56
+ # puts "#{name.to_s.humanize} (#{RSpec::RoutesCoverage.send(name).length}/#{total})".send(color).bold
57
+ # puts "\n"
58
+ # # format_routes.call(RSpec::RoutesCoverage.send(name)).each do |route|
59
+ # # puts ' ' + route.send(color)
60
+ # # end
61
+ # routes = RSpec::RoutesCoverage.send(name)
62
+ # if routes.any?
63
+ # puts format_routes.call(routes).send(color)
64
+ # end
65
+ # end
66
+ # else
67
+ # puts "\n\n"
68
+ # puts 'Routes coverage stats:'
69
+ # puts " Routes to test: #{RSpec::RoutesCoverage.tested_routes_num}/#{RSpec::RoutesCoverage.routes_num}".magenta
70
+ # puts " Manually tested: #{RSpec::RoutesCoverage.manually_tested_routes.length}/#{RSpec::RoutesCoverage.tested_routes_num}".green
71
+ # puts " Auto tested: #{RSpec::RoutesCoverage.auto_tested_routes.length}/#{RSpec::RoutesCoverage.tested_routes_num}".blue
72
+ # print " Pending: #{RSpec::RoutesCoverage.pending_routes.length}/#{RSpec::RoutesCoverage.tested_routes_num}".yellow
73
+ # end
@@ -0,0 +1,62 @@
1
+ require 'action_dispatch/routing/inspector'
2
+
3
+ module RoutesCoverage
4
+ module Formatters
5
+ class FullText < SummaryText
6
+
7
+ class RouteFormatter < ActionDispatch::Routing::ConsoleFormatter
8
+ def no_routes
9
+ @buffer << "\tNone"
10
+ end
11
+
12
+ private
13
+ HEADER = ['Prefix', 'Verb', 'URI Pattern', 'Controller#Action']
14
+ def draw_section(routes)
15
+ header_lengths = HEADER.map(&:length)
16
+ name_width, verb_width, path_width, reqs_width = widths(routes).zip(header_lengths).map(&:max)
17
+
18
+ routes.map do |r|
19
+ "#{r[:name].rjust(name_width)} #{r[:verb].ljust(verb_width)} #{r[:path].ljust(path_width)} #{r[:reqs].ljust(reqs_width)} ?"
20
+ end
21
+ end
22
+
23
+ def draw_header(routes)
24
+ name_width, verb_width, path_width, reqs_width = widths(routes)
25
+
26
+ "#{"Prefix".rjust(name_width)} #{"Verb".ljust(verb_width)} #{"URI Pattern".ljust(path_width)} #{"Controller#Action".ljust(reqs_width)} Hits"
27
+ end
28
+
29
+ def widths(routes)
30
+ [routes.map { |r| r[:name].length }.max || 0,
31
+ routes.map { |r| r[:verb].length }.max || 0,
32
+ routes.map { |r| r[:path].length }.max || 0,
33
+ routes.map { |r| r[:reqs].length }.max || 0,
34
+ ]
35
+ end
36
+ end
37
+
38
+ def hit_routes
39
+ routes = result.hit_routes
40
+
41
+ # verb_width = routes.map{ |r| r[:verb].length }.max
42
+ # path_width = routes.map{ |r| r[:path].length }.max
43
+
44
+ [
45
+ "Covered routes:",
46
+ # "#{"Verb".ljust(verb_width)} #{"Path".ljust(path_width)} Reqs",
47
+ # routes.map do |r|
48
+ # "#{r.verb.ljust(verb_width)} #{r.path.ljust(path_width)} #{r.reqs}"
49
+ # end
50
+ ActionDispatch::Routing::RoutesInspector.new(routes).format(RouteFormatter.new),
51
+ nil,
52
+ "Pending routes:",
53
+ ActionDispatch::Routing::RoutesInspector.new(result.pending_routes).format(RouteFormatter.new),
54
+ ].flatten.join("\n")
55
+ end
56
+
57
+ def format
58
+ "\nRoutes coverage is #{result.coverage}% (#{hits_count})#{status}\n\n#{hit_routes}"
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,29 @@
1
+ module RoutesCoverage
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
13
+ "#{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
+ end
15
+
16
+ def status
17
+ if result.coverage_pass?
18
+ ""
19
+ else
20
+ "\nCoverage failed. Need at least #{(settings.minimum_coverage / 100.0 * result.total_count).ceil}"
21
+ end
22
+ end
23
+
24
+ def format
25
+ "Routes coverage is #{result.coverage}% (#{hits_count})#{status}"
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,20 @@
1
+ module RoutesCoverage
2
+ class Middleware
3
+ def initialize app
4
+ @app = app
5
+ end
6
+
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
+ req = ::Rails.application.routes.request_class.new env
13
+ ::Rails.application.routes.router.recognize(req) do |route|
14
+ RoutesCoverage.current_result.touch_route(route)
15
+ end
16
+ #TODO: detect 404s? and maybe other route errors?
17
+ @app.call(env)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,89 @@
1
+ module RoutesCoverage
2
+ class Result
3
+ def initialize
4
+ @route_hits = Hash.new(0)
5
+ end
6
+
7
+ attr_reader :route_hits
8
+
9
+ def touch_route route
10
+ @route_hits[route] += 1
11
+ end
12
+
13
+ def all_routes
14
+ ::Rails.application.routes.routes.routes
15
+ end
16
+
17
+ def expected_routes
18
+ return @expected_routes if @expected_routes
19
+
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
25
+
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
32
+ }
33
+
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
43
+ end
44
+
45
+ def pending_routes
46
+ expected_routes - hit_routes
47
+ end
48
+
49
+ def excluded_routes
50
+ expected_routes
51
+ @excluded_routes
52
+ end
53
+
54
+ def hit_routes
55
+ #TODO: sort?
56
+ route_hits.keys
57
+ end
58
+
59
+
60
+ def hit_routes_count
61
+ route_hits.size
62
+ end
63
+
64
+ def expected_routes_count
65
+ expected_routes.size
66
+ end
67
+
68
+ def excluded_routes_count
69
+ excluded_routes.size
70
+ end
71
+
72
+ def total_count
73
+ all_routes.size
74
+ end
75
+
76
+ def coverage
77
+ return 'n/a' unless expected_routes.any?
78
+ (hit_routes_count * 100.0 / expected_routes_count).round(RoutesCoverage.settings.round_precision)
79
+ end
80
+
81
+ def avg_hits
82
+ (route_hits.values.sum.to_f / hit_routes_count).round(RoutesCoverage.settings.round_precision)
83
+ end
84
+
85
+ def coverage_pass?
86
+ coverage >= RoutesCoverage.settings.minimum_coverage
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,3 @@
1
+ module RoutesCoverage
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'routes_coverage/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "routes_coverage"
8
+ spec.version = RoutesCoverage::VERSION
9
+ spec.authors = ["Vasily Fedoseyev"]
10
+ spec.email = ["vasilyfedoseyev@gmail.com"]
11
+
12
+ spec.summary = %q{Provides coverage report for your rails routes}
13
+ spec.description = %q{Generates coverage report for routes hit by your request/integration/feature tests including capybara ones}
14
+ spec.homepage = "https://github.com/Vasfed/routes_coverage"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+ # spec.bindir = "exe"
21
+ # spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ["lib"]
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.14"
25
+ spec.add_development_dependency "rake", "~> 10.0"
26
+ # spec.add_development_dependency "minitest", "~> 4.0"
27
+ end
metadata ADDED
@@ -0,0 +1,90 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: routes_coverage
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Vasily Fedoseyev
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-05-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.14'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.14'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ description: Generates coverage report for routes hit by your request/integration/feature
42
+ tests including capybara ones
43
+ email:
44
+ - vasilyfedoseyev@gmail.com
45
+ executables: []
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - ".gitignore"
50
+ - ".travis.yml"
51
+ - Gemfile
52
+ - LICENSE.txt
53
+ - README.md
54
+ - Rakefile
55
+ - bin/console
56
+ - bin/setup
57
+ - lib/routes_coverage.rb
58
+ - lib/routes_coverage/adapters/atexit.rb
59
+ - lib/routes_coverage/adapters/rspec.rb
60
+ - lib/routes_coverage/formatters/full_text.rb
61
+ - lib/routes_coverage/formatters/summary_text.rb
62
+ - lib/routes_coverage/middleware.rb
63
+ - lib/routes_coverage/result.rb
64
+ - lib/routes_coverage/version.rb
65
+ - routes_coverage.gemspec
66
+ homepage: https://github.com/Vasfed/routes_coverage
67
+ licenses:
68
+ - MIT
69
+ metadata: {}
70
+ post_install_message:
71
+ rdoc_options: []
72
+ require_paths:
73
+ - lib
74
+ required_ruby_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ required_rubygems_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ requirements: []
85
+ rubyforge_project:
86
+ rubygems_version: 2.5.1
87
+ signing_key:
88
+ specification_version: 4
89
+ summary: Provides coverage report for your rails routes
90
+ test_files: []