rubocop-minitest 0.7.0 → 0.10.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 +4 -4
- data/.circleci/config.yml +0 -3
- data/.rubocop.yml +2 -1
- data/.rubocop_todo.yml +0 -7
- data/CHANGELOG.md +58 -0
- data/Gemfile +1 -1
- data/README.md +2 -2
- data/Rakefile +29 -0
- data/config/default.yml +96 -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 +7 -5
- 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_equal.rb +2 -31
- 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 +49 -28
- 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_cop_rule.rb +1 -3
- 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/relnotes/v0.10.1.md +5 -0
- data/relnotes/v0.8.0.md +12 -0
- data/relnotes/v0.8.1.md +5 -0
- data/relnotes/v0.9.0.md +10 -0
- data/rubocop-minitest.gemspec +4 -4
- data/tasks/cops_documentation.rake +83 -54
- data/tasks/cut_release.rake +16 -0
- metadata +40 -12
@@ -3,69 +3,90 @@
|
|
3
3
|
module RuboCop
|
4
4
|
module Cop
|
5
5
|
module Minitest
|
6
|
-
# This
|
6
|
+
# This cop checks for deprecated global expectations
|
7
7
|
# and autocorrects them to use expect format.
|
8
8
|
#
|
9
9
|
# @example
|
10
10
|
# # bad
|
11
|
-
#
|
12
|
-
#
|
11
|
+
# musts.must_equal expected_musts
|
12
|
+
# wonts.wont_match expected_wonts
|
13
|
+
# musts.must_raise TypeError
|
13
14
|
#
|
14
15
|
# # good
|
15
|
-
# _(
|
16
|
-
# _(
|
16
|
+
# _(musts).must_equal expected_musts
|
17
|
+
# _(wonts).wont_match expected_wonts
|
18
|
+
# _ { musts }.must_raise TypeError
|
17
19
|
class GlobalExpectations < Cop
|
18
|
-
MSG = '
|
20
|
+
MSG = 'Use `%<preferred>s` instead.'
|
19
21
|
|
20
22
|
VALUE_MATCHERS = %i[
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
23
|
+
must_be_empty must_equal must_be_close_to must_be_within_delta
|
24
|
+
must_be_within_epsilon must_include must_be_instance_of must_be_kind_of
|
25
|
+
must_match must_be_nil must_be must_respond_to must_be_same_as
|
26
|
+
path_must_exist path_wont_exist wont_be_empty wont_equal wont_be_close_to
|
27
|
+
wont_be_within_delta wont_be_within_epsilon wont_include wont_be_instance_of
|
28
|
+
wont_be_kind_of wont_match wont_be_nil wont_be wont_respond_to wont_be_same_as
|
29
|
+
].freeze
|
27
30
|
|
28
|
-
BLOCK_MATCHERS = %i[must_output must_raise must_throw].freeze
|
31
|
+
BLOCK_MATCHERS = %i[must_output must_raise must_be_silent must_throw].freeze
|
29
32
|
|
30
|
-
|
33
|
+
VALUE_MATCHERS_STR = VALUE_MATCHERS.map do |m|
|
31
34
|
":#{m}"
|
32
35
|
end.join(' ').freeze
|
33
36
|
|
34
|
-
|
35
|
-
|
37
|
+
BLOCK_MATCHERS_STR = BLOCK_MATCHERS.map do |m|
|
38
|
+
":#{m}"
|
39
|
+
end.join(' ').freeze
|
40
|
+
|
41
|
+
# There are aliases for the `_` method - `expect` and `value`
|
42
|
+
DSL_METHODS_LIST = %w[_ value expect].map do |n|
|
43
|
+
":#{n}"
|
44
|
+
end.join(' ').freeze
|
45
|
+
|
46
|
+
def_node_matcher :value_global_expectation?, <<~PATTERN
|
47
|
+
(send !(send nil? {#{DSL_METHODS_LIST}} _) {#{VALUE_MATCHERS_STR}} ...)
|
48
|
+
PATTERN
|
49
|
+
|
50
|
+
def_node_matcher :block_global_expectation?, <<~PATTERN
|
51
|
+
(send
|
52
|
+
[
|
53
|
+
!(send nil? {#{DSL_METHODS_LIST}} _)
|
54
|
+
!(block (send nil? {#{DSL_METHODS_LIST}}) _ _)
|
55
|
+
]
|
56
|
+
{#{BLOCK_MATCHERS_STR}}
|
57
|
+
_
|
58
|
+
)
|
36
59
|
PATTERN
|
37
60
|
|
38
61
|
def on_send(node)
|
39
|
-
return unless
|
62
|
+
return unless value_global_expectation?(node) || block_global_expectation?(node)
|
40
63
|
|
41
|
-
message = format(MSG,
|
42
|
-
add_offense(node, message: message)
|
64
|
+
message = format(MSG, preferred: preferred_receiver(node))
|
65
|
+
add_offense(node, location: node.receiver.source_range, message: message)
|
43
66
|
end
|
44
67
|
|
45
68
|
def autocorrect(node)
|
46
|
-
return unless
|
69
|
+
return unless value_global_expectation?(node) || block_global_expectation?(node)
|
47
70
|
|
48
71
|
lambda do |corrector|
|
49
|
-
receiver = node.receiver.
|
72
|
+
receiver = node.receiver.source_range
|
50
73
|
|
51
74
|
if BLOCK_MATCHERS.include?(node.method_name)
|
52
|
-
corrector.
|
53
|
-
corrector.insert_after(receiver, ' }')
|
75
|
+
corrector.wrap(receiver, '_ { ', ' }')
|
54
76
|
else
|
55
|
-
corrector.
|
56
|
-
corrector.insert_after(receiver, ')')
|
77
|
+
corrector.wrap(receiver, '_(', ')')
|
57
78
|
end
|
58
79
|
end
|
59
80
|
end
|
60
81
|
|
61
82
|
private
|
62
83
|
|
63
|
-
def
|
84
|
+
def preferred_receiver(node)
|
64
85
|
source = node.receiver.source
|
65
86
|
if BLOCK_MATCHERS.include?(node.method_name)
|
66
|
-
|
87
|
+
"_ { #{source} }"
|
67
88
|
else
|
68
|
-
|
89
|
+
"_(#{source})"
|
69
90
|
end
|
70
91
|
end
|
71
92
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Minitest
|
6
|
+
# This cop enforces correct order of expected and
|
7
|
+
# actual arguments for `assert_equal`.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# # bad
|
11
|
+
# assert_equal foo, 2
|
12
|
+
# assert_equal foo, [1, 2]
|
13
|
+
# assert_equal foo, [1, 2], 'message'
|
14
|
+
#
|
15
|
+
# # good
|
16
|
+
# assert_equal 2, foo
|
17
|
+
# assert_equal [1, 2], foo
|
18
|
+
# assert_equal [1, 2], foo, 'message'
|
19
|
+
#
|
20
|
+
class LiteralAsActualArgument < Cop
|
21
|
+
include ArgumentRangeHelper
|
22
|
+
|
23
|
+
MSG = 'Replace the literal with the first argument.'
|
24
|
+
|
25
|
+
def on_send(node)
|
26
|
+
return unless node.method?(:assert_equal)
|
27
|
+
|
28
|
+
actual = node.arguments[1]
|
29
|
+
return unless actual
|
30
|
+
|
31
|
+
add_offense(node, location: all_arguments_range(node)) if actual.recursive_basic_literal?
|
32
|
+
end
|
33
|
+
|
34
|
+
def autocorrect(node)
|
35
|
+
expected, actual, message = *node.arguments
|
36
|
+
|
37
|
+
lambda do |corrector|
|
38
|
+
new_actual_source =
|
39
|
+
if actual.hash_type? && !actual.braces?
|
40
|
+
"{#{actual.source}}"
|
41
|
+
else
|
42
|
+
actual.source
|
43
|
+
end
|
44
|
+
arguments = [new_actual_source, expected.source, message&.source].compact.join(', ')
|
45
|
+
|
46
|
+
corrector.replace(node, "assert_equal(#{arguments})")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -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
|