rubocop-minitest 0.9.0 → 0.10.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/.rubocop.yml +1 -3
- data/CHANGELOG.md +22 -0
- data/Gemfile +1 -1
- data/README.md +2 -2
- data/Rakefile +29 -0
- data/config/default.yml +95 -16
- data/docs/antora.yml +7 -0
- data/docs/modules/ROOT/nav.adoc +6 -0
- data/docs/modules/ROOT/pages/cops.adoc +37 -0
- data/docs/modules/ROOT/pages/cops_minitest.adoc +1014 -0
- data/docs/modules/ROOT/pages/index.adoc +5 -0
- data/docs/modules/ROOT/pages/installation.adoc +15 -0
- data/docs/modules/ROOT/pages/usage.adoc +32 -0
- data/{manual → legacy-docs}/cops.md +0 -0
- data/{manual → legacy-docs}/cops_minitest.md +0 -0
- data/{manual → legacy-docs}/index.md +0 -0
- data/{manual → legacy-docs}/installation.md +0 -0
- data/{manual → legacy-docs}/usage.md +0 -0
- data/lib/rubocop/cop/generator.rb +56 -0
- data/lib/rubocop/cop/minitest/assert_empty_literal.rb +15 -0
- data/lib/rubocop/cop/minitest/assert_in_delta.rb +27 -0
- data/lib/rubocop/cop/minitest/assert_kind_of.rb +25 -0
- data/lib/rubocop/cop/minitest/assert_output.rb +49 -0
- data/lib/rubocop/cop/minitest/assert_path_exists.rb +58 -0
- data/lib/rubocop/cop/minitest/assert_silent.rb +45 -0
- data/lib/rubocop/cop/minitest/assertion_in_lifecycle_hook.rb +43 -0
- data/lib/rubocop/cop/minitest/global_expectations.rb +2 -4
- data/lib/rubocop/cop/minitest/literal_as_actual_argument.rb +52 -0
- data/lib/rubocop/cop/minitest/multiple_assertions.rb +63 -0
- data/lib/rubocop/cop/minitest/refute_in_delta.rb +27 -0
- data/lib/rubocop/cop/minitest/refute_kind_of.rb +25 -0
- data/lib/rubocop/cop/minitest/refute_path_exists.rb +58 -0
- data/lib/rubocop/cop/minitest/test_method_name.rb +70 -0
- data/lib/rubocop/cop/minitest/unspecified_exception.rb +36 -0
- data/lib/rubocop/cop/minitest_cops.rb +15 -0
- data/lib/rubocop/cop/mixin/argument_range_helper.rb +10 -0
- data/lib/rubocop/cop/mixin/in_delta_mixin.rb +50 -0
- data/lib/rubocop/cop/mixin/minitest_exploration_helpers.rb +84 -0
- data/lib/rubocop/minitest/version.rb +1 -1
- data/mkdocs.yml +2 -2
- data/relnotes/v0.10.0.md +21 -0
- data/rubocop-minitest.gemspec +3 -3
- data/tasks/cops_documentation.rake +83 -52
- data/tasks/cut_release.rake +16 -0
- metadata +35 -11
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Minitest
|
6
|
+
# This cop checks if test cases contain too many assertion calls.
|
7
|
+
# The maximum allowed assertion calls is configurable.
|
8
|
+
#
|
9
|
+
# @example Max: 1
|
10
|
+
# # bad
|
11
|
+
# class FooTest < Minitest::Test
|
12
|
+
# def test_asserts_twice
|
13
|
+
# assert_equal(42, do_something)
|
14
|
+
# assert_empty(array)
|
15
|
+
# end
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# # good
|
19
|
+
# class FooTest < Minitest::Test
|
20
|
+
# def test_asserts_once
|
21
|
+
# assert_equal(42, do_something)
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# def test_another_asserts_once
|
25
|
+
# assert_empty(array)
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
class MultipleAssertions < Cop
|
30
|
+
include ConfigurableMax
|
31
|
+
include MinitestExplorationHelpers
|
32
|
+
|
33
|
+
MSG = 'Test case has too many assertions [%<total>d/%<max>d].'
|
34
|
+
|
35
|
+
def on_class(class_node)
|
36
|
+
return unless test_class?(class_node)
|
37
|
+
|
38
|
+
test_cases(class_node).each do |node|
|
39
|
+
assertions_count = assertions_count(node)
|
40
|
+
|
41
|
+
next unless assertions_count > max_assertions
|
42
|
+
|
43
|
+
self.max = assertions_count
|
44
|
+
|
45
|
+
message = format(MSG, total: assertions_count, max: max_assertions)
|
46
|
+
add_offense(node, location: :name, message: message)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def assertions_count(node)
|
53
|
+
base = assertion?(node) ? 1 : 0
|
54
|
+
base + node.each_child_node.sum { |c| assertions_count(c) }
|
55
|
+
end
|
56
|
+
|
57
|
+
def max_assertions
|
58
|
+
Integer(cop_config.fetch('Max', 3))
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Minitest
|
6
|
+
# This cop enforces the test to use `refute_in_delta`
|
7
|
+
# instead of using `refute_equal` to compare floats.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# # bad
|
11
|
+
# refute_equal(0.2, actual)
|
12
|
+
# refute_equal(0.2, actual, 'message')
|
13
|
+
#
|
14
|
+
# # good
|
15
|
+
# refute_in_delta(0.2, actual)
|
16
|
+
# refute_in_delta(0.2, actual, 0.001, 'message')
|
17
|
+
#
|
18
|
+
class RefuteInDelta < Cop
|
19
|
+
include InDeltaMixin
|
20
|
+
|
21
|
+
def_node_matcher :equal_floats_call, <<~PATTERN
|
22
|
+
(send nil? :refute_equal $_ $_ $...)
|
23
|
+
PATTERN
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Minitest
|
6
|
+
# This cop enforces the use of `refute_kind_of(Class, object)`
|
7
|
+
# over `refute(object.kind_of?(Class))`.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# # bad
|
11
|
+
# refute(object.kind_of?(Class))
|
12
|
+
# refute(object.kind_of?(Class), 'message')
|
13
|
+
#
|
14
|
+
# # good
|
15
|
+
# refute_kind_of(Class, object)
|
16
|
+
# refute_kind_of(Class, object, 'message')
|
17
|
+
#
|
18
|
+
class RefuteKindOf < Cop
|
19
|
+
extend MinitestCopRule
|
20
|
+
|
21
|
+
define_rule :refute, target_method: :kind_of?, inverse: true
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Minitest
|
6
|
+
# This cop enforces the test to use `refute_path_exists`
|
7
|
+
# instead of using `refute(File.exist?(path))`.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# # bad
|
11
|
+
# refute(File.exist?(path))
|
12
|
+
# refute(File.exist?(path), 'message')
|
13
|
+
#
|
14
|
+
# # good
|
15
|
+
# refute_path_exists(path)
|
16
|
+
# refute_path_exists(path, 'message')
|
17
|
+
#
|
18
|
+
class RefutePathExists < Cop
|
19
|
+
MSG = 'Prefer using `%<good_method>s` over `%<bad_method>s`.'
|
20
|
+
|
21
|
+
def_node_matcher :refute_file_exists, <<~PATTERN
|
22
|
+
(send nil? :refute
|
23
|
+
(send
|
24
|
+
(const _ :File) {:exist? :exists?} $_)
|
25
|
+
$...)
|
26
|
+
PATTERN
|
27
|
+
|
28
|
+
def on_send(node)
|
29
|
+
refute_file_exists(node) do |path, failure_message|
|
30
|
+
failure_message = failure_message.first
|
31
|
+
good_method = build_good_method(path, failure_message)
|
32
|
+
message = format(MSG, good_method: good_method, bad_method: node.source)
|
33
|
+
|
34
|
+
add_offense(node, message: message)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def autocorrect(node)
|
39
|
+
refute_file_exists(node) do |path, failure_message|
|
40
|
+
failure_message = failure_message.first
|
41
|
+
|
42
|
+
lambda do |corrector|
|
43
|
+
replacement = build_good_method(path, failure_message)
|
44
|
+
corrector.replace(node, replacement)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def build_good_method(path, message)
|
52
|
+
args = [path.source, message&.source].compact.join(', ')
|
53
|
+
"refute_path_exists(#{args})"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Minitest
|
6
|
+
# This cop enforces that test method names start with `test_` prefix.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# class FooTest < Minitest::Test
|
11
|
+
# def does_something
|
12
|
+
# assert_equal 42, do_something
|
13
|
+
# end
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# # good
|
17
|
+
# class FooTest < Minitest::Test
|
18
|
+
# def test_does_something
|
19
|
+
# assert_equal 42, do_something
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
class TestMethodName < Cop
|
24
|
+
include MinitestExplorationHelpers
|
25
|
+
include DefNode
|
26
|
+
|
27
|
+
MSG = 'Test method name should start with `test_` prefix.'
|
28
|
+
|
29
|
+
def on_class(class_node)
|
30
|
+
return unless test_class?(class_node)
|
31
|
+
|
32
|
+
class_elements(class_node).each do |node|
|
33
|
+
add_offense(node, location: :name) if offense?(node)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def autocorrect(node)
|
38
|
+
lambda do |corrector|
|
39
|
+
corrector.replace(node.loc.name, "test_#{node.method_name}")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def class_elements(class_node)
|
46
|
+
class_def = class_node.body
|
47
|
+
return [] unless class_def
|
48
|
+
|
49
|
+
if class_def.def_type?
|
50
|
+
[class_def]
|
51
|
+
else
|
52
|
+
class_def.each_child_node(:def).to_a
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def offense?(node)
|
57
|
+
public?(node) && !test_method_name?(node) && !lifecycle_hook_method?(node)
|
58
|
+
end
|
59
|
+
|
60
|
+
def public?(node)
|
61
|
+
!non_public?(node)
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_method_name?(node)
|
65
|
+
node.method_name.to_s.start_with?('test_')
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Minitest
|
6
|
+
# This cop checks for a specified error in `assert_raises`.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# assert_raises { raise FooException }
|
11
|
+
# assert_raises('This should have raised') { raise FooException }
|
12
|
+
#
|
13
|
+
# # good
|
14
|
+
# assert_raises(FooException) { raise FooException }
|
15
|
+
# assert_raises(FooException, 'This should have raised') { raise FooException }
|
16
|
+
#
|
17
|
+
class UnspecifiedException < Cop
|
18
|
+
MSG = 'Specify the exception being captured.'
|
19
|
+
|
20
|
+
def on_block(block_node)
|
21
|
+
node = block_node.send_node
|
22
|
+
return unless node.method?(:assert_raises)
|
23
|
+
|
24
|
+
add_offense(node) if unspecified_exception?(node)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def unspecified_exception?(node)
|
30
|
+
args = node.arguments
|
31
|
+
args.empty? || (args.size == 1 && args[0].str_type?)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -1,22 +1,37 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative 'mixin/argument_range_helper'
|
4
|
+
require_relative 'mixin/in_delta_mixin'
|
4
5
|
require_relative 'mixin/minitest_cop_rule'
|
6
|
+
require_relative 'mixin/minitest_exploration_helpers'
|
5
7
|
require_relative 'minitest/assert_empty'
|
6
8
|
require_relative 'minitest/assert_empty_literal'
|
7
9
|
require_relative 'minitest/assert_equal'
|
10
|
+
require_relative 'minitest/assert_in_delta'
|
11
|
+
require_relative 'minitest/assertion_in_lifecycle_hook'
|
12
|
+
require_relative 'minitest/assert_kind_of'
|
8
13
|
require_relative 'minitest/assert_nil'
|
9
14
|
require_relative 'minitest/assert_includes'
|
10
15
|
require_relative 'minitest/assert_instance_of'
|
11
16
|
require_relative 'minitest/assert_match'
|
17
|
+
require_relative 'minitest/assert_output'
|
18
|
+
require_relative 'minitest/assert_path_exists'
|
12
19
|
require_relative 'minitest/assert_respond_to'
|
20
|
+
require_relative 'minitest/assert_silent'
|
13
21
|
require_relative 'minitest/assert_truthy'
|
14
22
|
require_relative 'minitest/global_expectations'
|
23
|
+
require_relative 'minitest/literal_as_actual_argument'
|
24
|
+
require_relative 'minitest/multiple_assertions'
|
15
25
|
require_relative 'minitest/refute_empty'
|
16
26
|
require_relative 'minitest/refute_false'
|
17
27
|
require_relative 'minitest/refute_equal'
|
28
|
+
require_relative 'minitest/refute_in_delta'
|
29
|
+
require_relative 'minitest/refute_kind_of'
|
18
30
|
require_relative 'minitest/refute_nil'
|
19
31
|
require_relative 'minitest/refute_includes'
|
20
32
|
require_relative 'minitest/refute_match'
|
21
33
|
require_relative 'minitest/refute_instance_of'
|
34
|
+
require_relative 'minitest/refute_path_exists'
|
22
35
|
require_relative 'minitest/refute_respond_to'
|
36
|
+
require_relative 'minitest/test_method_name'
|
37
|
+
require_relative 'minitest/unspecified_exception'
|
@@ -26,6 +26,16 @@ module RuboCop
|
|
26
26
|
second_argument.source_range.end_pos
|
27
27
|
)
|
28
28
|
end
|
29
|
+
|
30
|
+
def all_arguments_range(node)
|
31
|
+
first_argument = node.first_argument
|
32
|
+
last_argument = node.arguments.last
|
33
|
+
|
34
|
+
range_between(
|
35
|
+
first_argument.source_range.begin_pos,
|
36
|
+
last_argument.source_range.end_pos
|
37
|
+
)
|
38
|
+
end
|
29
39
|
end
|
30
40
|
end
|
31
41
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
# Common functionality for `AssertInDelta` and `RefuteInDelta` cops.
|
6
|
+
module InDeltaMixin
|
7
|
+
MSG = 'Prefer using `%<good_method>s` over `%<bad_method>s`.'
|
8
|
+
|
9
|
+
def on_send(node)
|
10
|
+
equal_floats_call(node) do |expected, actual, message|
|
11
|
+
message = message.first
|
12
|
+
|
13
|
+
if expected.float_type? || actual.float_type?
|
14
|
+
message = format(MSG,
|
15
|
+
good_method: build_good_method(expected, actual, message),
|
16
|
+
bad_method: node.source)
|
17
|
+
|
18
|
+
add_offense(node, message: message)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def autocorrect(node)
|
24
|
+
equal_floats_call(node) do |expected, actual, message|
|
25
|
+
message = message.first
|
26
|
+
replacement = build_good_method(expected, actual, message)
|
27
|
+
|
28
|
+
lambda do |corrector|
|
29
|
+
corrector.replace(node, replacement)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def build_good_method(expected, actual, message)
|
37
|
+
if message
|
38
|
+
"#{assertion_method}_in_delta(#{expected.source}, #{actual.source}, 0.001, #{message.source})"
|
39
|
+
else
|
40
|
+
"#{assertion_method}_in_delta(#{expected.source}, #{actual.source})"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def assertion_method
|
45
|
+
class_name = self.class.name.split('::').last
|
46
|
+
class_name[/\A[[:upper:]][[:lower:]]+/].downcase
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
module RuboCop
|
6
|
+
module Cop
|
7
|
+
# Helper methods for different explorations against test files and test cases.
|
8
|
+
module MinitestExplorationHelpers
|
9
|
+
extend NodePattern::Macros
|
10
|
+
|
11
|
+
ASSERTION_PREFIXES = %w[assert refute].freeze
|
12
|
+
|
13
|
+
LIFECYCLE_HOOK_METHODS = %i[
|
14
|
+
before_setup
|
15
|
+
setup
|
16
|
+
after_setup
|
17
|
+
before_teardown
|
18
|
+
teardown
|
19
|
+
after_teardown
|
20
|
+
].to_set.freeze
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def test_class?(class_node)
|
25
|
+
class_node.parent_class && class_node.identifier.source.end_with?('Test')
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_case?(node)
|
29
|
+
return false unless node.def_type? && test_case_name?(node.method_name)
|
30
|
+
|
31
|
+
class_ancestor = node.each_ancestor(:class).first
|
32
|
+
test_class?(class_ancestor)
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_cases(class_node)
|
36
|
+
class_def_nodes(class_node)
|
37
|
+
.select { |def_node| test_case_name?(def_node.method_name) }
|
38
|
+
end
|
39
|
+
|
40
|
+
def lifecycle_hooks(class_node)
|
41
|
+
class_def_nodes(class_node)
|
42
|
+
.select { |def_node| lifecycle_hook_method?(def_node) }
|
43
|
+
end
|
44
|
+
|
45
|
+
def class_def_nodes(class_node)
|
46
|
+
class_def = class_node.body
|
47
|
+
return [] unless class_def
|
48
|
+
|
49
|
+
if class_def.def_type?
|
50
|
+
[class_def]
|
51
|
+
else
|
52
|
+
class_def.each_child_node(:def).to_a
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_case_name?(name)
|
57
|
+
name.to_s.start_with?('test_')
|
58
|
+
end
|
59
|
+
|
60
|
+
def assertions(def_node)
|
61
|
+
method_def = def_node.body
|
62
|
+
return [] unless method_def
|
63
|
+
|
64
|
+
send_nodes =
|
65
|
+
if method_def.send_type?
|
66
|
+
[method_def]
|
67
|
+
else
|
68
|
+
method_def.each_child_node(:send)
|
69
|
+
end
|
70
|
+
|
71
|
+
send_nodes.select { |send_node| assertion?(send_node) }
|
72
|
+
end
|
73
|
+
|
74
|
+
def assertion?(node)
|
75
|
+
node.send_type? &&
|
76
|
+
ASSERTION_PREFIXES.any? { |prefix| node.method_name.to_s.start_with?(prefix) }
|
77
|
+
end
|
78
|
+
|
79
|
+
def lifecycle_hook_method?(node)
|
80
|
+
node.def_type? && LIFECYCLE_HOOK_METHODS.include?(node.method_name)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|