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.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- data/.github/workflows/main.yml +30 -0
- data/CONTRIBUTING.md +1 -0
- data/Gemfile +4 -5
- data/Gemfile.rails4 +2 -0
- data/Gemfile.rails6 +2 -0
- data/LICENSE.txt +1 -1
- data/README.md +43 -7
- data/changes.md +14 -9
- data/coverband.gemspec +6 -4
- data/lib/coverband/adapters/base.rb +5 -1
- data/lib/coverband/adapters/file_store.rb +1 -1
- data/lib/coverband/adapters/redis_store.rb +1 -1
- data/lib/coverband/at_exit.rb +3 -0
- data/lib/coverband/collectors/view_tracker.rb +16 -7
- data/lib/coverband/reporters/web.rb +2 -1
- data/lib/coverband/utils/dead_methods.rb +63 -0
- data/lib/coverband/utils/method_definition_scanner.rb +96 -0
- data/lib/coverband/utils/tasks.rb +17 -3
- data/lib/coverband/version.rb +1 -1
- data/test/coverband/collectors/view_tracker_test.rb +10 -0
- data/test/coverband/reporters/web_test.rb +34 -0
- data/test/coverband/utils/dead_methods_test.rb +53 -0
- data/test/coverband/utils/method_definition_scanner_test.rb +85 -0
- data/test/fixtures/casting_invitor.rb +60 -0
- data/test/forked/rails_full_stack_test.rb +1 -1
- data/test/forked/rails_full_stack_views_test.rb +51 -0
- data/test/forked/rails_view_tracker_stack_test.rb +44 -0
- data/test/rails4_dummy/app/controllers/dummy_view_controller.rb +16 -0
- data/test/rails4_dummy/app/views/dummy_view/show.html.erb +5 -0
- data/test/rails4_dummy/app/views/dummy_view/show_haml.html.haml +4 -0
- data/test/rails4_dummy/app/views/dummy_view/show_slim.html.slim +4 -0
- data/test/rails4_dummy/config/application.rb +1 -0
- data/test/rails4_dummy/config/routes.rb +3 -0
- data/test/rails5_dummy/app/controllers/dummy_view_controller.rb +16 -0
- data/test/rails5_dummy/app/views/dummy_view/show.html.erb +5 -0
- data/test/rails5_dummy/app/views/dummy_view/show_haml.html.haml +4 -0
- data/test/rails5_dummy/app/views/dummy_view/show_slim.html.slim +4 -0
- data/test/rails5_dummy/config/application.rb +2 -0
- data/test/rails5_dummy/config/coverband.rb +3 -1
- data/test/rails5_dummy/config/routes.rb +3 -0
- data/test/rails6_dummy/app/controllers/dummy_view_controller.rb +16 -0
- data/test/rails6_dummy/app/views/dummy_view/show.html.erb +5 -0
- data/test/rails6_dummy/app/views/dummy_view/show_haml.html.haml +4 -0
- data/test/rails6_dummy/app/views/dummy_view/show_slim.html.slim +4 -0
- data/test/rails6_dummy/config/application.rb +1 -0
- data/test/rails6_dummy/config/routes.rb +3 -0
- data/test/test_helper.rb +1 -0
- data/views/view_tracker.erb +2 -2
- metadata +42 -3
- 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
|
-
|
15
|
-
|
16
|
-
|
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
|
###
|
data/lib/coverband/version.rb
CHANGED
@@ -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 = "./
|
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
|