rubocop-minitest 0.8.1 → 0.10.3

Sign up to get free protection for your applications and to get access to all the features.
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