rubocop-minitest 0.8.1 → 0.10.3

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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +18 -3
  3. data/.rubocop.yml +3 -4
  4. data/.rubocop_todo.yml +8 -7
  5. data/CHANGELOG.md +51 -0
  6. data/Gemfile +1 -1
  7. data/LICENSE.txt +1 -1
  8. data/README.md +18 -2
  9. data/Rakefile +29 -0
  10. data/config/default.yml +96 -16
  11. data/docs/antora.yml +7 -0
  12. data/docs/modules/ROOT/nav.adoc +6 -0
  13. data/docs/modules/ROOT/pages/cops.adoc +48 -0
  14. data/docs/modules/ROOT/pages/cops_minitest.adoc +1021 -0
  15. data/docs/modules/ROOT/pages/index.adoc +5 -0
  16. data/docs/modules/ROOT/pages/installation.adoc +15 -0
  17. data/docs/modules/ROOT/pages/usage.adoc +32 -0
  18. data/{manual → legacy-docs}/cops.md +0 -0
  19. data/{manual → legacy-docs}/cops_minitest.md +0 -0
  20. data/{manual → legacy-docs}/index.md +0 -0
  21. data/{manual → legacy-docs}/installation.md +0 -0
  22. data/{manual → legacy-docs}/usage.md +0 -0
  23. data/lib/rubocop/cop/generator.rb +56 -0
  24. data/lib/rubocop/cop/minitest/assert_empty_literal.rb +15 -0
  25. data/lib/rubocop/cop/minitest/assert_in_delta.rb +27 -0
  26. data/lib/rubocop/cop/minitest/assert_kind_of.rb +25 -0
  27. data/lib/rubocop/cop/minitest/assert_output.rb +49 -0
  28. data/lib/rubocop/cop/minitest/assert_path_exists.rb +58 -0
  29. data/lib/rubocop/cop/minitest/assert_silent.rb +45 -0
  30. data/lib/rubocop/cop/minitest/assertion_in_lifecycle_hook.rb +43 -0
  31. data/lib/rubocop/cop/minitest/global_expectations.rb +3 -5
  32. data/lib/rubocop/cop/minitest/literal_as_actual_argument.rb +52 -0
  33. data/lib/rubocop/cop/minitest/multiple_assertions.rb +63 -0
  34. data/lib/rubocop/cop/minitest/refute_equal.rb +1 -1
  35. data/lib/rubocop/cop/minitest/refute_in_delta.rb +27 -0
  36. data/lib/rubocop/cop/minitest/refute_kind_of.rb +25 -0
  37. data/lib/rubocop/cop/minitest/refute_path_exists.rb +58 -0
  38. data/lib/rubocop/cop/minitest/test_method_name.rb +79 -0
  39. data/lib/rubocop/cop/minitest/unspecified_exception.rb +36 -0
  40. data/lib/rubocop/cop/minitest_cops.rb +15 -0
  41. data/lib/rubocop/cop/mixin/argument_range_helper.rb +10 -0
  42. data/lib/rubocop/cop/mixin/in_delta_mixin.rb +50 -0
  43. data/lib/rubocop/cop/mixin/minitest_cop_rule.rb +1 -1
  44. data/lib/rubocop/cop/mixin/minitest_exploration_helpers.rb +97 -0
  45. data/lib/rubocop/minitest/version.rb +8 -1
  46. data/mkdocs.yml +3 -3
  47. data/relnotes/v0.10.0.md +21 -0
  48. data/relnotes/v0.10.1.md +5 -0
  49. data/relnotes/v0.10.2.md +5 -0
  50. data/relnotes/v0.10.3.md +5 -0
  51. data/relnotes/v0.9.0.md +10 -0
  52. data/rubocop-minitest.gemspec +5 -5
  53. data/tasks/cops_documentation.rake +15 -264
  54. data/tasks/cut_release.rake +16 -0
  55. metadata +52 -18
@@ -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
@@ -4,7 +4,7 @@ module RuboCop
4
4
  module Cop
5
5
  module Minitest
6
6
  # This cop enforces the use of `refute_equal(expected, object)`
7
- # over `assert_equal(expected != actual)` or `assert(! expected == actual)`.
7
+ # over `assert(expected != actual)` or `assert(! expected == actual)`.
8
8
  #
9
9
  # @example
10
10
  # # bad
@@ -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,79 @@
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
+ # It aims to prevent tests that aren't executed by forgetting to start test method name with `test_`.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # class FooTest < Minitest::Test
12
+ # def does_something
13
+ # assert_equal 42, do_something
14
+ # end
15
+ # end
16
+ #
17
+ # # good
18
+ # class FooTest < Minitest::Test
19
+ # def test_does_something
20
+ # assert_equal 42, do_something
21
+ # end
22
+ # end
23
+ #
24
+ # # good
25
+ # class FooTest < Minitest::Test
26
+ # def helper_method(argument)
27
+ # end
28
+ # end
29
+ #
30
+ class TestMethodName < Cop
31
+ include MinitestExplorationHelpers
32
+ include DefNode
33
+
34
+ MSG = 'Test method name should start with `test_` prefix.'
35
+
36
+ def on_class(class_node)
37
+ return unless test_class?(class_node)
38
+
39
+ class_elements(class_node).each do |node|
40
+ add_offense(node, location: :name) if offense?(node)
41
+ end
42
+ end
43
+
44
+ def autocorrect(node)
45
+ lambda do |corrector|
46
+ corrector.replace(node.loc.name, "test_#{node.method_name}")
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def class_elements(class_node)
53
+ class_def = class_node.body
54
+ return [] unless class_def
55
+
56
+ if class_def.def_type?
57
+ [class_def]
58
+ else
59
+ class_def.each_child_node(:def).to_a
60
+ end
61
+ end
62
+
63
+ def offense?(node)
64
+ return false if node.each_child_node(:send).none? { |send_node| assertion_method?(send_node.method_name) }
65
+
66
+ public?(node) && node.arguments.empty? && !test_method_name?(node) && !lifecycle_hook_method?(node)
67
+ end
68
+
69
+ def public?(node)
70
+ !non_public?(node)
71
+ end
72
+
73
+ def test_method_name?(node)
74
+ node.method_name.to_s.start_with?('test_')
75
+ end
76
+ end
77
+ end
78
+ end
79
+ 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
@@ -57,7 +57,7 @@ module RuboCop
57
57
  private
58
58
 
59
59
  def peel_redundant_parentheses_from(arguments)
60
- return arguments unless arguments.first.begin_type?
60
+ return arguments unless arguments.first&.begin_type?
61
61
 
62
62
  peel_redundant_parentheses_from(arguments.first.children)
63
63
  end
@@ -0,0 +1,97 @@
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
+ ASSERTION_METHODS = %i[
14
+ assert assert_empty assert_equal assert_in_delta assert_in_epsilon assert_includes assert_instance_of
15
+ assert_kind_of assert_match assert_nil assert_operator assert_output assert_path_exists assert_predicate
16
+ assert_raises assert_respond_to assert_same assert_send assert_silent assert_throws
17
+ refute refute_empty refute_equal refute_in_delta refute_in_epsilon refute_includes refute_instance_of
18
+ refute_kind_of refute_match refute_nil refute_operator refute_path_exists refute_predicate
19
+ refute_respond_to refute_same
20
+ ].freeze
21
+
22
+ LIFECYCLE_HOOK_METHODS = %i[
23
+ before_setup
24
+ setup
25
+ after_setup
26
+ before_teardown
27
+ teardown
28
+ after_teardown
29
+ ].to_set.freeze
30
+
31
+ private
32
+
33
+ def test_class?(class_node)
34
+ class_node.parent_class && class_node.identifier.source.end_with?('Test')
35
+ end
36
+
37
+ def test_case?(node)
38
+ return false unless node&.def_type? && test_case_name?(node.method_name)
39
+
40
+ class_ancestor = node.each_ancestor(:class).first
41
+ test_class?(class_ancestor)
42
+ end
43
+
44
+ def test_cases(class_node)
45
+ class_def_nodes(class_node)
46
+ .select { |def_node| test_case_name?(def_node.method_name) }
47
+ end
48
+
49
+ def lifecycle_hooks(class_node)
50
+ class_def_nodes(class_node)
51
+ .select { |def_node| lifecycle_hook_method?(def_node) }
52
+ end
53
+
54
+ def class_def_nodes(class_node)
55
+ class_def = class_node.body
56
+ return [] unless class_def
57
+
58
+ if class_def.def_type?
59
+ [class_def]
60
+ else
61
+ class_def.each_child_node(:def).to_a
62
+ end
63
+ end
64
+
65
+ def test_case_name?(name)
66
+ name.to_s.start_with?('test_')
67
+ end
68
+
69
+ def assertions(def_node)
70
+ method_def = def_node.body
71
+ return [] unless method_def
72
+
73
+ send_nodes =
74
+ if method_def.send_type?
75
+ [method_def]
76
+ else
77
+ method_def.each_child_node(:send)
78
+ end
79
+
80
+ send_nodes.select { |send_node| assertion?(send_node) }
81
+ end
82
+
83
+ def assertion?(node)
84
+ node.send_type? &&
85
+ ASSERTION_PREFIXES.any? { |prefix| node.method_name.to_s.start_with?(prefix) }
86
+ end
87
+
88
+ def assertion_method?(method_name)
89
+ ASSERTION_METHODS.include?(method_name)
90
+ end
91
+
92
+ def lifecycle_hook_method?(node)
93
+ node.def_type? && LIFECYCLE_HOOK_METHODS.include?(node.method_name)
94
+ end
95
+ end
96
+ end
97
+ end