coverband 5.0.1 → 5.1.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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
  3. data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  4. data/.github/workflows/main.yml +30 -0
  5. data/CONTRIBUTING.md +1 -0
  6. data/Gemfile +4 -5
  7. data/Gemfile.rails4 +2 -0
  8. data/Gemfile.rails6 +2 -0
  9. data/LICENSE.txt +1 -1
  10. data/README.md +43 -7
  11. data/changes.md +14 -9
  12. data/coverband.gemspec +6 -4
  13. data/lib/coverband/adapters/base.rb +5 -1
  14. data/lib/coverband/adapters/file_store.rb +1 -1
  15. data/lib/coverband/adapters/redis_store.rb +1 -1
  16. data/lib/coverband/at_exit.rb +3 -0
  17. data/lib/coverband/collectors/view_tracker.rb +16 -7
  18. data/lib/coverband/reporters/web.rb +2 -1
  19. data/lib/coverband/utils/dead_methods.rb +63 -0
  20. data/lib/coverband/utils/method_definition_scanner.rb +96 -0
  21. data/lib/coverband/utils/tasks.rb +17 -3
  22. data/lib/coverband/version.rb +1 -1
  23. data/test/coverband/collectors/view_tracker_test.rb +10 -0
  24. data/test/coverband/reporters/web_test.rb +34 -0
  25. data/test/coverband/utils/dead_methods_test.rb +53 -0
  26. data/test/coverband/utils/method_definition_scanner_test.rb +85 -0
  27. data/test/fixtures/casting_invitor.rb +60 -0
  28. data/test/forked/rails_full_stack_test.rb +1 -1
  29. data/test/forked/rails_full_stack_views_test.rb +51 -0
  30. data/test/forked/rails_view_tracker_stack_test.rb +44 -0
  31. data/test/rails4_dummy/app/controllers/dummy_view_controller.rb +16 -0
  32. data/test/rails4_dummy/app/views/dummy_view/show.html.erb +5 -0
  33. data/test/rails4_dummy/app/views/dummy_view/show_haml.html.haml +4 -0
  34. data/test/rails4_dummy/app/views/dummy_view/show_slim.html.slim +4 -0
  35. data/test/rails4_dummy/config/application.rb +1 -0
  36. data/test/rails4_dummy/config/routes.rb +3 -0
  37. data/test/rails5_dummy/app/controllers/dummy_view_controller.rb +16 -0
  38. data/test/rails5_dummy/app/views/dummy_view/show.html.erb +5 -0
  39. data/test/rails5_dummy/app/views/dummy_view/show_haml.html.haml +4 -0
  40. data/test/rails5_dummy/app/views/dummy_view/show_slim.html.slim +4 -0
  41. data/test/rails5_dummy/config/application.rb +2 -0
  42. data/test/rails5_dummy/config/coverband.rb +3 -1
  43. data/test/rails5_dummy/config/routes.rb +3 -0
  44. data/test/rails6_dummy/app/controllers/dummy_view_controller.rb +16 -0
  45. data/test/rails6_dummy/app/views/dummy_view/show.html.erb +5 -0
  46. data/test/rails6_dummy/app/views/dummy_view/show_haml.html.haml +4 -0
  47. data/test/rails6_dummy/app/views/dummy_view/show_slim.html.slim +4 -0
  48. data/test/rails6_dummy/config/application.rb +1 -0
  49. data/test/rails6_dummy/config/routes.rb +3 -0
  50. data/test/test_helper.rb +1 -0
  51. data/views/view_tracker.erb +2 -2
  52. metadata +42 -3
  53. data/.travis.yml +0 -48
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ if defined?(RubyVM::AbstractSyntaxTree)
4
+ module Coverband
5
+ module Utils
6
+ class MethodDefinitionScanner
7
+ attr_reader :path
8
+
9
+ def initialize(path)
10
+ @path = path
11
+ end
12
+
13
+ def scan
14
+ scan_node(RubyVM::AbstractSyntaxTree.parse_file(path), nil)
15
+ end
16
+
17
+ def self.scan(path)
18
+ new(path).scan
19
+ end
20
+
21
+ class MethodBody
22
+ def initialize(method_definition)
23
+ @method_definition = method_definition
24
+ end
25
+
26
+ def coverage?(file_coverage)
27
+ body_coverage =
28
+ file_coverage[(first_line_number - 1)..(last_line_number - 1)]
29
+ body_coverage.map(&:to_i).any?(&:positive?)
30
+ end
31
+
32
+ private
33
+
34
+ def first_line_number
35
+ @method_definition.first_line_number + 1
36
+ end
37
+
38
+ def last_line_number
39
+ @method_definition.last_line_number - 1
40
+ end
41
+ end
42
+
43
+ class MethodDefinition
44
+ attr_reader :last_line_number,
45
+ :first_line_number,
46
+ :name,
47
+ :class_name,
48
+ :file_path
49
+
50
+ def initialize(
51
+ first_line_number:,
52
+ last_line_number:,
53
+ name:,
54
+ class_name:,
55
+ file_path:
56
+ )
57
+ @first_line_number = first_line_number
58
+ @last_line_number = last_line_number
59
+ @name = name
60
+ @class_name = class_name
61
+ @file_path = file_path
62
+ end
63
+
64
+ def body
65
+ MethodBody.new(self)
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ def scan_node(node, class_name)
72
+ definitions = []
73
+ return definitions unless node.is_a?(RubyVM::AbstractSyntaxTree::Node)
74
+ current_class = node.type == :CLASS ? node.children.first.children.last : class_name
75
+ if node.type == :DEFN
76
+ definitions <<
77
+ MethodDefinition.new(
78
+ first_line_number: node.first_lineno,
79
+ last_line_number: node.last_lineno,
80
+ name: node.children.first,
81
+ class_name: current_class,
82
+ file_path: path
83
+ )
84
+ end
85
+ definitions + scan_children(node, current_class)
86
+ end
87
+
88
+ def scan_children(node, current_class)
89
+ node.children.flatten.compact.map { |child|
90
+ scan_node(child, current_class)
91
+ }.flatten
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -9,11 +9,25 @@ namespace :coverband do
9
9
  Coverband::Reporters::ConsoleReport.report(Coverband.configuration.store)
10
10
  end
11
11
 
12
+ if defined?(RubyVM::AbstractSyntaxTree)
13
+ require "coverband/utils/dead_methods"
14
+
15
+ desc "Output all dead methods"
16
+ task :dead_methods do
17
+ Coverband::Utils::DeadMethods.output_all
18
+ end
19
+ end
20
+
12
21
  desc "report runtime Coverband code coverage"
13
22
  task :coverage_server do
14
- Rake.application["environment"].invoke if Rake::Task.task_defined?("environment")
15
- Coverband.configuration.store.merge_mode = true if Coverband.configuration.store.is_a?(Coverband::Adapters::FileStore)
16
- Rack::Server.start app: Coverband::Reporters::Web.new, Port: ENV.fetch("COVERBAND_COVERAGE_PORT", 1022).to_i
23
+ if Rake::Task.task_defined?("environment")
24
+ Rake.application["environment"].invoke
25
+ end
26
+ if Coverband.configuration.store.is_a?(Coverband::Adapters::FileStore)
27
+ Coverband.configuration.store.merge_mode = true
28
+ end
29
+ Rack::Server.start app: Coverband::Reporters::Web.new,
30
+ Port: ENV.fetch("COVERBAND_COVERAGE_PORT", 9022).to_i
17
31
  end
18
32
 
19
33
  ###
@@ -5,5 +5,5 @@
5
5
  # use format '4.2.1.rc.1' ~> 4.2.1.rc to prerelease versions like v4.2.1.rc.2 and v4.2.1.rc.3
6
6
  ###
7
7
  module Coverband
8
- VERSION = "5.0.1"
8
+ VERSION = '5.1.0'
9
9
  end
@@ -42,6 +42,16 @@ class ReporterTest < Minitest::Test
42
42
  assert_equal [file_path], tracker.used_views.keys
43
43
  end
44
44
 
45
+ test "track partials that include the word _mailer in the path" do
46
+ Coverband::Collectors::ViewTracker.expects(:supported_version?).returns(true)
47
+ store = fake_store
48
+ file_path = "#{File.expand_path(Coverband.configuration.root)}/_mailer/file"
49
+ tracker = Coverband::Collectors::ViewTracker.new(store: store, roots: "dir")
50
+ tracker.track_views("name", "start", "finish", "id", identifier: file_path)
51
+ tracker.report_views_tracked
52
+ assert_equal [file_path], tracker.used_views.keys
53
+ end
54
+
45
55
  test "ignore partials that include the folder vendor in the path" do
46
56
  Coverband::Collectors::ViewTracker.expects(:supported_version?).returns(true)
47
57
  store = fake_store
@@ -43,4 +43,38 @@ if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.2.0")
43
43
  end
44
44
  end
45
45
  end
46
+
47
+ module Coverband
48
+ class AuthWebTest < Minitest::Test
49
+ include Rack::Test::Methods
50
+
51
+ def setup
52
+ super
53
+ @store = Coverband.configuration.store
54
+ Coverband.configure do |config|
55
+ config.password = "test_pass"
56
+ end
57
+ end
58
+
59
+ def app
60
+ Coverband::Reporters::Web.new
61
+ end
62
+
63
+ def teardown
64
+ super
65
+ end
66
+
67
+ test "renders index with basic auth" do
68
+ basic_authorize "anything", "test_pass"
69
+ get "/"
70
+ assert last_response.ok?
71
+ assert_match "Coverband Home", last_response.body
72
+ end
73
+
74
+ test "renders 401 auth error when not provided" do
75
+ get "/"
76
+ assert_equal 401, last_response.status
77
+ end
78
+ end
79
+ end
46
80
  end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require File.expand_path("../../test_helper", File.dirname(__FILE__))
4
+
5
+ if defined?(RubyVM::AbstractSyntaxTree)
6
+ require "coverband/utils/dead_methods"
7
+ module Coverband
8
+ module Utils
9
+ class DeadMethodsTest < Minitest::Test
10
+ attr_accessor :coverband
11
+
12
+ def setup
13
+ super
14
+ @coverband = Coverband::Collectors::Coverage.instance
15
+ end
16
+
17
+ def test_dog_dead_methods
18
+ file_path = require_unique_file
19
+ coverage = [nil, nil, 1, 1, 0, nil, nil]
20
+ dead_methods =
21
+ DeadMethods.scan(file_path: file_path, coverage: coverage)
22
+ assert_equal(1, dead_methods.length)
23
+ dead_method = dead_methods.first
24
+ assert_equal(4, dead_method.first_line_number)
25
+ assert_equal(6, dead_method.last_line_number)
26
+ assert_equal(file_path, dead_method.file_path)
27
+ end
28
+
29
+ def test_all_dead_methods
30
+ require_unique_file
31
+ @coverband.report_coverage
32
+ dead_methods = DeadMethods.scan_all
33
+ dead_method = dead_methods.find { |method| method.class_name == :Dog }
34
+ assert(dead_method)
35
+ assert_equal(4, dead_method.first_line_number)
36
+ assert_equal(6, dead_method.last_line_number)
37
+ end
38
+
39
+ def test_output_all
40
+ require_unique_file
41
+ @coverband.report_coverage
42
+ DeadMethods.output_all
43
+ end
44
+
45
+ def test_dog_methods_not_dead
46
+ file = require_unique_file
47
+ coverage = [nil, nil, 1, 1, 1, nil, nil]
48
+ assert_empty(DeadMethods.scan(file_path: file, coverage: coverage))
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require File.expand_path("../../test_helper", File.dirname(__FILE__))
4
+
5
+ if defined?(RubyVM::AbstractSyntaxTree)
6
+ require "coverband/utils/method_definition_scanner"
7
+ module Coverband
8
+ module Utils
9
+ class MethodBodyTest < Minitest::Test
10
+ def test_no_method_body_coverage
11
+ method_body =
12
+ MethodDefinitionScanner::MethodBody.new(
13
+ MethodDefinitionScanner::MethodDefinition.new(
14
+ first_line_number: 4,
15
+ last_line_number: 6,
16
+ name: :bark,
17
+ class_name: :Dog,
18
+ file_path: "./test/dog.rb"
19
+ )
20
+ )
21
+ refute(method_body.coverage?([nil, nil, 1, 1, 0, nil, 1]))
22
+ end
23
+
24
+ def test_method_body_coverage
25
+ method_body =
26
+ MethodDefinitionScanner::MethodBody.new(
27
+ MethodDefinitionScanner::MethodDefinition.new(
28
+ first_line_number: 4,
29
+ last_line_number: 6,
30
+ name: :bark,
31
+ class_name: :Dog,
32
+ file_path: "./test/dog.rb"
33
+ )
34
+ )
35
+ assert(method_body.coverage?([nil, nil, 1, 1, 1, nil, 1]))
36
+ end
37
+ end
38
+
39
+ class MethodDefinitionScannerTest < Minitest::Test
40
+ def test_scan
41
+ method_definitions = MethodDefinitionScanner.scan("./test/dog.rb")
42
+ assert(method_definitions)
43
+ assert_equal(1, method_definitions.length)
44
+ method_definition = method_definitions.first # assert_equal(4, method.first_line)
45
+ assert_equal(4, method_definition.first_line_number)
46
+ assert_equal(6, method_definition.last_line_number)
47
+ assert_equal(:bark, method_definition.name)
48
+ assert_equal(:Dog, method_definition.class_name)
49
+ end
50
+
51
+ def test_scan_large_class
52
+ method_definitions =
53
+ MethodDefinitionScanner.scan("./test/fixtures/casting_invitor.rb")
54
+ method_first_line_numbers =
55
+ method_definitions.map(&:first_line_number)
56
+ assert_equal(
57
+ [6, 13, 17, 35, 40, 44, 48, 52],
58
+ method_first_line_numbers
59
+ )
60
+ method_last_line_numbers = method_definitions.map(&:last_line_number)
61
+ assert_equal(
62
+ [11, 15, 31, 38, 42, 46, 50, 59],
63
+ method_last_line_numbers
64
+ )
65
+ method_names = method_definitions.map(&:name)
66
+ assert_equal(
67
+ %i[
68
+ initialize
69
+ valid?
70
+ deliver
71
+ invalid_invitees
72
+ invitee_list
73
+ valid_message?
74
+ valid_invitees?
75
+ create_invitation
76
+ ],
77
+ method_names
78
+ )
79
+ class_names = method_definitions.map(&:class_name)
80
+ assert_equal(8.times.map { :CastingInviter }, class_names)
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,60 @@
1
+ class CastingInviter
2
+ EMAIL_REGEX = /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/
3
+
4
+ attr_reader :message, :invitees, :casting
5
+
6
+ def initialize(attributes = {})
7
+ @message = attributes[:message] || ""
8
+ @invitees = attributes[:invitees] || ""
9
+ @sender = attributes[:sender]
10
+ @casting = attributes[:casting]
11
+ end
12
+
13
+ def valid?
14
+ valid_message? && valid_invitees?
15
+ end
16
+
17
+ def deliver
18
+ if valid?
19
+ invitee_list.each do |email|
20
+ invitation = create_invitation(email)
21
+ Mailer.invitation_notification(invitation, @message)
22
+ end
23
+ else
24
+ failure_message =
25
+ "Your #{
26
+ @casting
27
+ } message couldn’t be sent. Invitees emails or message are invalid"
28
+ invitation = create_invitation(@sender)
29
+ Mailer.invitation_notification(invitation, failure_message)
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def invalid_invitees
36
+ @invalid_invitees ||=
37
+ invitee_list.map { |item| item unless item.match(EMAIL_REGEX) }.compact
38
+ end
39
+
40
+ def invitee_list
41
+ @invitee_list ||= @invitees.gsub(/\s+/, "").split(/[\n,;]+/)
42
+ end
43
+
44
+ def valid_message?
45
+ @message.present?
46
+ end
47
+
48
+ def valid_invitees?
49
+ invalid_invitees.empty?
50
+ end
51
+
52
+ def create_invitation(email)
53
+ Invitation.create(
54
+ casting: @casting,
55
+ sender: @sender,
56
+ invitee_email: email,
57
+ status: "pending"
58
+ )
59
+ end
60
+ end
@@ -35,7 +35,7 @@ class RailsFullStackTest < Minitest::Test
35
35
  end
36
36
 
37
37
  # Test eager load data stored separately
38
- dummy_controller = "./test/rails#{Rails::VERSION::MAJOR}_dummy/app/controllers/dummy_controller.rb"
38
+ dummy_controller = "./app/controllers/dummy_controller.rb"
39
39
  store.type = :eager_loading
40
40
  eager_expected = [1, 1, 0, nil, nil]
41
41
  results = store.coverage[dummy_controller]["data"]
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require File.expand_path("../rails_test_helper", File.dirname(__FILE__))
4
+
5
+ class RailsFullStackTest < Minitest::Test
6
+ include Capybara::DSL
7
+ include Capybara::Minitest::Assertions
8
+
9
+ def setup
10
+ super
11
+ rails_setup
12
+ Coverband.report_coverage
13
+ end
14
+
15
+ def teardown
16
+ super
17
+ Capybara.reset_sessions!
18
+ Capybara.use_default_driver
19
+ end
20
+
21
+ test "verify erb haml slim support" do
22
+ visit "/dummy_view/show"
23
+ assert_content("I am no dummy view tracker text")
24
+ Coverband.report_coverage
25
+ Coverband.configuration.view_tracker&.report_views_tracked
26
+ visit "/coverage/view_tracker"
27
+ assert_content("Used Views: (1)")
28
+ assert_content("Unused Views: (2)")
29
+ assert_selector("li.used-views", text: "dummy_view/show.html.erb")
30
+ assert_selector("li.unused-views", text: "dummy_view/show_haml.html.haml")
31
+ assert_selector("li.unused-views", text: "dummy_view/show_slim.html.slim")
32
+
33
+ visit "/dummy_view/show_haml"
34
+ assert_content("I am haml text")
35
+ Coverband.report_coverage
36
+ Coverband.configuration.view_tracker&.report_views_tracked
37
+ visit "/coverage/view_tracker"
38
+ assert_content("Used Views: (2)")
39
+ assert_content("Unused Views: (1)")
40
+ assert_selector("li.used-views", text: "dummy_view/show_haml.html.haml")
41
+
42
+ visit "/dummy_view/show_slim"
43
+ assert_content("I am slim text")
44
+ Coverband.report_coverage
45
+ Coverband.configuration.view_tracker&.report_views_tracked
46
+ visit "/coverage/view_tracker"
47
+ assert_content("Used Views: (3)")
48
+ assert_content("Unused Views: (0)")
49
+ assert_selector("li.used-views", text: "dummy_view/show_slim.html.slim")
50
+ end
51
+ end