mutant 0.9.8 → 0.9.9

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 (161) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +2 -2
  3. data/Changelog.md +6 -0
  4. data/README.md +63 -23
  5. data/config/reek.yml +1 -0
  6. data/lib/mutant.rb +4 -0
  7. data/lib/mutant/ast.rb +0 -9
  8. data/lib/mutant/ast/find_metaclass_containing.rb +48 -0
  9. data/lib/mutant/ast/meta/send.rb +0 -6
  10. data/lib/mutant/bootstrap.rb +0 -36
  11. data/lib/mutant/cli.rb +5 -49
  12. data/lib/mutant/color.rb +0 -3
  13. data/lib/mutant/config.rb +0 -8
  14. data/lib/mutant/context.rb +0 -3
  15. data/lib/mutant/diff.rb +0 -17
  16. data/lib/mutant/env.rb +0 -6
  17. data/lib/mutant/expression/method.rb +6 -6
  18. data/lib/mutant/expression/methods.rb +6 -6
  19. data/lib/mutant/expression/parser.rb +0 -6
  20. data/lib/mutant/integration.rb +0 -18
  21. data/lib/mutant/isolation/fork.rb +0 -22
  22. data/lib/mutant/license.rb +11 -0
  23. data/lib/mutant/matcher.rb +0 -14
  24. data/lib/mutant/matcher/config.rb +0 -11
  25. data/lib/mutant/matcher/method.rb +0 -31
  26. data/lib/mutant/matcher/method/instance.rb +0 -8
  27. data/lib/mutant/matcher/method/metaclass.rb +86 -0
  28. data/lib/mutant/matcher/method/singleton.rb +0 -25
  29. data/lib/mutant/matcher/methods.rb +17 -28
  30. data/lib/mutant/matcher/namespace.rb +0 -10
  31. data/lib/mutant/matcher/scope.rb +2 -4
  32. data/lib/mutant/meta/example/dsl.rb +0 -21
  33. data/lib/mutant/meta/example/verification.rb +0 -20
  34. data/lib/mutant/mutation.rb +0 -3
  35. data/lib/mutant/mutator.rb +1 -29
  36. data/lib/mutant/mutator/node.rb +1 -66
  37. data/lib/mutant/mutator/node/and_asgn.rb +0 -3
  38. data/lib/mutant/mutator/node/argument.rb +0 -15
  39. data/lib/mutant/mutator/node/arguments.rb +0 -20
  40. data/lib/mutant/mutator/node/begin.rb +0 -3
  41. data/lib/mutant/mutator/node/binary.rb +0 -23
  42. data/lib/mutant/mutator/node/block.rb +0 -15
  43. data/lib/mutant/mutator/node/break.rb +0 -3
  44. data/lib/mutant/mutator/node/case.rb +0 -9
  45. data/lib/mutant/mutator/node/class.rb +0 -3
  46. data/lib/mutant/mutator/node/conditional_loop.rb +0 -3
  47. data/lib/mutant/mutator/node/const.rb +0 -3
  48. data/lib/mutant/mutator/node/define.rb +0 -11
  49. data/lib/mutant/mutator/node/defined.rb +0 -3
  50. data/lib/mutant/mutator/node/dstr.rb +0 -3
  51. data/lib/mutant/mutator/node/dsym.rb +0 -3
  52. data/lib/mutant/mutator/node/generic.rb +0 -3
  53. data/lib/mutant/mutator/node/if.rb +0 -12
  54. data/lib/mutant/mutator/node/index.rb +0 -27
  55. data/lib/mutant/mutator/node/kwbegin.rb +0 -3
  56. data/lib/mutant/mutator/node/literal.rb +0 -3
  57. data/lib/mutant/mutator/node/literal/array.rb +0 -6
  58. data/lib/mutant/mutator/node/literal/boolean.rb +0 -4
  59. data/lib/mutant/mutator/node/literal/float.rb +0 -9
  60. data/lib/mutant/mutator/node/literal/hash.rb +0 -9
  61. data/lib/mutant/mutator/node/literal/integer.rb +0 -9
  62. data/lib/mutant/mutator/node/literal/nil.rb +0 -3
  63. data/lib/mutant/mutator/node/literal/range.rb +0 -6
  64. data/lib/mutant/mutator/node/literal/regex.rb +0 -6
  65. data/lib/mutant/mutator/node/literal/string.rb +0 -3
  66. data/lib/mutant/mutator/node/literal/symbol.rb +0 -3
  67. data/lib/mutant/mutator/node/masgn.rb +0 -3
  68. data/lib/mutant/mutator/node/match_current_line.rb +0 -3
  69. data/lib/mutant/mutator/node/mlhs.rb +0 -3
  70. data/lib/mutant/mutator/node/named_value/access.rb +2 -14
  71. data/lib/mutant/mutator/node/named_value/constant_assignment.rb +0 -9
  72. data/lib/mutant/mutator/node/named_value/variable_assignment.rb +0 -6
  73. data/lib/mutant/mutator/node/next.rb +0 -3
  74. data/lib/mutant/mutator/node/noop.rb +0 -3
  75. data/lib/mutant/mutator/node/nthref.rb +0 -3
  76. data/lib/mutant/mutator/node/op_asgn.rb +0 -3
  77. data/lib/mutant/mutator/node/or_asgn.rb +0 -3
  78. data/lib/mutant/mutator/node/procarg_zero.rb +0 -3
  79. data/lib/mutant/mutator/node/regopt.rb +0 -6
  80. data/lib/mutant/mutator/node/resbody.rb +0 -6
  81. data/lib/mutant/mutator/node/rescue.rb +2 -19
  82. data/lib/mutant/mutator/node/return.rb +0 -3
  83. data/lib/mutant/mutator/node/sclass.rb +20 -0
  84. data/lib/mutant/mutator/node/send.rb +2 -61
  85. data/lib/mutant/mutator/node/send/attribute_assignment.rb +0 -9
  86. data/lib/mutant/mutator/node/send/binary.rb +0 -11
  87. data/lib/mutant/mutator/node/send/conditional.rb +0 -3
  88. data/lib/mutant/mutator/node/splat.rb +0 -3
  89. data/lib/mutant/mutator/node/super.rb +0 -3
  90. data/lib/mutant/mutator/node/when.rb +0 -19
  91. data/lib/mutant/mutator/node/yield.rb +0 -3
  92. data/lib/mutant/mutator/node/zsuper.rb +0 -3
  93. data/lib/mutant/mutator/util/array.rb +0 -6
  94. data/lib/mutant/mutator/util/symbol.rb +0 -3
  95. data/lib/mutant/parallel.rb +0 -13
  96. data/lib/mutant/parallel/driver.rb +0 -10
  97. data/lib/mutant/parallel/worker.rb +0 -22
  98. data/lib/mutant/reporter/cli.rb +0 -5
  99. data/lib/mutant/reporter/cli/format.rb +0 -9
  100. data/lib/mutant/reporter/cli/printer.rb +0 -40
  101. data/lib/mutant/reporter/cli/printer/env_progress.rb +0 -15
  102. data/lib/mutant/reporter/cli/printer/isolation_result.rb +0 -18
  103. data/lib/mutant/reporter/cli/printer/mutation_progress_result.rb +0 -5
  104. data/lib/mutant/reporter/cli/printer/mutation_result.rb +0 -21
  105. data/lib/mutant/reporter/cli/printer/status_progressive.rb +0 -8
  106. data/lib/mutant/reporter/cli/printer/subject_progress.rb +0 -9
  107. data/lib/mutant/repository/diff.rb +1 -13
  108. data/lib/mutant/repository/diff/ranges.rb +0 -11
  109. data/lib/mutant/result.rb +0 -3
  110. data/lib/mutant/runner.rb +0 -18
  111. data/lib/mutant/runner/sink.rb +0 -5
  112. data/lib/mutant/subject.rb +0 -8
  113. data/lib/mutant/subject/method.rb +0 -3
  114. data/lib/mutant/subject/method/instance.rb +0 -5
  115. data/lib/mutant/subject/method/metaclass.rb +30 -0
  116. data/lib/mutant/transform.rb +0 -92
  117. data/lib/mutant/version.rb +1 -1
  118. data/lib/mutant/warnings.rb +0 -6
  119. data/lib/mutant/zombifier.rb +2 -34
  120. data/meta/and.rb +0 -2
  121. data/meta/array.rb +0 -3
  122. data/meta/begin.rb +0 -3
  123. data/meta/block.rb +0 -3
  124. data/meta/break.rb +0 -1
  125. data/meta/case.rb +0 -6
  126. data/meta/casgn.rb +0 -3
  127. data/meta/cvasgn.rb +0 -1
  128. data/meta/def.rb +0 -7
  129. data/meta/ensure.rb +0 -1
  130. data/meta/false.rb +0 -1
  131. data/meta/gvasgn.rb +0 -1
  132. data/meta/hash.rb +0 -4
  133. data/meta/if.rb +0 -5
  134. data/meta/ivasgn.rb +0 -1
  135. data/meta/kwbegin.rb +0 -1
  136. data/meta/lvasgn.rb +0 -1
  137. data/meta/match_current_line.rb +0 -1
  138. data/meta/next.rb +0 -1
  139. data/meta/or.rb +0 -2
  140. data/meta/regexp.rb +0 -1
  141. data/meta/rescue.rb +0 -6
  142. data/meta/sclass.rb +12 -0
  143. data/meta/send.rb +0 -4
  144. data/meta/true.rb +0 -1
  145. data/meta/until.rb +0 -1
  146. data/meta/while.rb +0 -2
  147. data/meta/yield.rb +0 -1
  148. data/mutant.sh +12 -0
  149. data/spec/unit/mutant/ast/find_metaclass_containing_spec.rb +64 -0
  150. data/spec/unit/mutant/expression/methods_spec.rb +7 -2
  151. data/spec/unit/mutant/license_spec.rb +15 -3
  152. data/spec/unit/mutant/matcher/method/metaclass_spec.rb +108 -0
  153. data/spec/unit/mutant/matcher/methods/metaclass_spec.rb +62 -0
  154. data/spec/unit/mutant/matcher/namespace_spec.rb +3 -1
  155. data/spec/unit/mutant/matcher/scope_spec.rb +11 -1
  156. data/spec/unit/mutant/meta/example_spec.rb +3 -3
  157. data/spec/unit/mutant/mutator/node_spec.rb +1 -6
  158. data/spec/unit/mutant/subject/method/metaclass_spec.rb +63 -0
  159. data/test_app/lib/test_app.rb +1 -0
  160. data/test_app/lib/test_app/metaclasses.rb +108 -0
  161. metadata +17 -2
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ class Matcher
5
+ class Method
6
+ # Matcher for metaclass methods
7
+ # i.e. ones defined using class << self or class << CONSTANT. It might??
8
+ # work for methods defined like class << obj, but I don't think the
9
+ # plumbing will be in place in the subject for that to work
10
+ class Metaclass < self
11
+
12
+ # New singleton method matcher
13
+ #
14
+ # @param [Class, Module] scope
15
+ # @param [Symbol] method_name
16
+ #
17
+ # @return [Matcher::Method::Singleton]
18
+ def self.new(scope, method_name)
19
+ super(scope, method_name, Evaluator)
20
+ end
21
+
22
+ # Metaclass method evaluator
23
+ class Evaluator < Evaluator
24
+ # Terminology note: the "receiver" is the `self` in `class << self`
25
+ SUBJECT_CLASS = Subject::Method::Metaclass
26
+ NAME_INDEX = 0
27
+ CONST_NAME_INDEX = 1
28
+ SCLASS_RECEIVER_INDEX = 0
29
+ RECEIVER_WARNING = 'Can only match :def inside :sclass on ' \
30
+ ':self or :const, got :sclass on %p ' \
31
+ 'unable to match'
32
+
33
+ private
34
+
35
+ def match?(node)
36
+ n_def?(node) &&
37
+ name?(node) &&
38
+ line?(node) &&
39
+ metaclass_receiver?(node)
40
+ end
41
+
42
+ def metaclass_receiver?(node)
43
+ candidate = metaclass_containing(node)
44
+ candidate && metaclass_target?(candidate)
45
+ end
46
+
47
+ def metaclass_containing(node)
48
+ AST::FindMetaclassContaining.call(ast, node)
49
+ end
50
+
51
+ def line?(node)
52
+ node
53
+ .location
54
+ .line
55
+ .equal?(source_line)
56
+ end
57
+
58
+ def name?(node)
59
+ node.children.fetch(NAME_INDEX).equal?(method_name)
60
+ end
61
+
62
+ def metaclass_target?(node)
63
+ receiver = node.children.fetch(SCLASS_RECEIVER_INDEX)
64
+ case receiver.type
65
+ when :self
66
+ true
67
+ when :const
68
+ sclass_const_name?(receiver)
69
+ else
70
+ env.warn(RECEIVER_WARNING % receiver.type)
71
+ nil
72
+ end
73
+ end
74
+
75
+ def sclass_const_name?(node)
76
+ name = node.children.fetch(CONST_NAME_INDEX)
77
+ name.to_s.eql?(context.unqualified_name)
78
+ end
79
+
80
+ end # Evaluator
81
+
82
+ private_constant(*constants(false))
83
+ end # Singleton
84
+ end # Method
85
+ end # Matcher
86
+ end # Mutant
@@ -25,20 +25,10 @@ module Mutant
25
25
 
26
26
  private
27
27
 
28
- # Test for node match
29
- #
30
- # @param [Parser::AST::Node] node
31
- #
32
- # @return [Boolean]
33
28
  def match?(node)
34
29
  n_defs?(node) && line?(node) && name?(node) && receiver?(node)
35
30
  end
36
31
 
37
- # Test for line match
38
- #
39
- # @param [Parser::AST::Node] node
40
- #
41
- # @return [Boolean]
42
32
  def line?(node)
43
33
  node
44
34
  .location
@@ -46,20 +36,10 @@ module Mutant
46
36
  .equal?(source_line)
47
37
  end
48
38
 
49
- # Test for name match
50
- #
51
- # @param [Parser::AST::Node] node
52
- #
53
- # @return [Boolean]
54
39
  def name?(node)
55
40
  node.children.fetch(NAME_INDEX).equal?(method_name)
56
41
  end
57
42
 
58
- # Test for receiver match
59
- #
60
- # @param [Parser::AST::Node] node
61
- #
62
- # @return [Boolean]
63
43
  def receiver?(node)
64
44
  receiver = node.children.fetch(RECEIVER_INDEX)
65
45
  case receiver.type
@@ -73,11 +53,6 @@ module Mutant
73
53
  end
74
54
  end
75
55
 
76
- # Test if receiver name matches context
77
- #
78
- # @param [Parser::AST::Node] node
79
- #
80
- # @return [Boolean]
81
56
  def receiver_name?(node)
82
57
  name = node.children.fetch(NAME_INDEX)
83
58
  name.to_s.eql?(context.unqualified_name)
@@ -27,16 +27,10 @@ module Mutant
27
27
 
28
28
  private
29
29
 
30
- # method matcher class
31
- #
32
- # @return [Class:Matcher::Method]
33
30
  def matcher
34
31
  self.class::MATCHER
35
32
  end
36
33
 
37
- # Available methods scope
38
- #
39
- # @return [Enumerable<Method, UnboundMethod>]
40
34
  def methods
41
35
  candidate_names.each_with_object([]) do |name, methods|
42
36
  method = access(name)
@@ -45,9 +39,6 @@ module Mutant
45
39
  end
46
40
  memoize :methods
47
41
 
48
- # Candidate method names on target scope
49
- #
50
- # @return [Enumerable<Symbol>]
51
42
  def candidate_names
52
43
  CANDIDATE_NAMES
53
44
  .map(&candidate_scope.method(:public_send))
@@ -55,10 +46,8 @@ module Mutant
55
46
  .sort
56
47
  end
57
48
 
58
- # Candidate scope
59
- #
60
- # @return [Class, Module]
61
49
  abstract_method :candidate_scope
50
+ private :candidate_scope
62
51
 
63
52
  # Matcher for singleton methods
64
53
  class Singleton < self
@@ -66,18 +55,10 @@ module Mutant
66
55
 
67
56
  private
68
57
 
69
- # Method object on scope
70
- #
71
- # @param [Symbol] method_name
72
- #
73
- # @return [Method]
74
58
  def access(method_name)
75
59
  scope.method(method_name)
76
60
  end
77
61
 
78
- # Candidate scope
79
- #
80
- # @return [Class]
81
62
  def candidate_scope
82
63
  scope.singleton_class
83
64
  end
@@ -85,24 +66,32 @@ module Mutant
85
66
 
86
67
  end # Singleton
87
68
 
69
+ # Matcher for metaclass methods
70
+ class Metaclass < self
71
+ MATCHER = Matcher::Method::Metaclass
72
+
73
+ private
74
+
75
+ def access(method_name)
76
+ scope.method(method_name)
77
+ end
78
+
79
+ def candidate_scope
80
+ scope.singleton_class
81
+ end
82
+ memoize :candidate_scope, freezer: :noop
83
+ end # Metaclass
84
+
88
85
  # Matcher for instance methods
89
86
  class Instance < self
90
87
  MATCHER = Matcher::Method::Instance
91
88
 
92
89
  private
93
90
 
94
- # Method object on scope
95
- #
96
- # @param [Symbol] method_name
97
- #
98
- # @return [UnboundMethod]
99
91
  def access(method_name)
100
92
  scope.instance_method(method_name)
101
93
  end
102
94
 
103
- # Candidate scope
104
- #
105
- # @return [Class, Module]
106
95
  def candidate_scope
107
96
  scope
108
97
  end
@@ -19,22 +19,12 @@ module Mutant
19
19
 
20
20
  private
21
21
 
22
- # The matched scopes
23
- #
24
- # @param [Env] env
25
- #
26
- # @return [Enumerable<Scope>]
27
22
  def matched_scopes(env)
28
23
  env
29
24
  .matchable_scopes
30
25
  .select(&method(:match?))
31
26
  end
32
27
 
33
- # Test scope if matches expression
34
- #
35
- # @param [Scope] scope
36
- #
37
- # @return [Boolean]
38
28
  def match?(scope)
39
29
  expression.prefix?(scope.expression)
40
30
  end
@@ -13,7 +13,8 @@ module Mutant
13
13
 
14
14
  MATCHERS = [
15
15
  Matcher::Methods::Singleton,
16
- Matcher::Methods::Instance
16
+ Matcher::Methods::Instance,
17
+ Matcher::Methods::Metaclass
17
18
  ].freeze
18
19
 
19
20
  private_constant(*constants(false))
@@ -29,9 +30,6 @@ module Mutant
29
30
 
30
31
  private
31
32
 
32
- # Effective matchers
33
- #
34
- # @return [Enumerable<Matcher>]
35
33
  def effective_matchers
36
34
  MATCHERS.map { |matcher| matcher.new(scope) }
37
35
  end
@@ -49,21 +49,11 @@ module Mutant
49
49
 
50
50
  private
51
51
 
52
- # Set original source
53
- #
54
- # @param [String,Parser::AST::Node] input
55
- #
56
- # @return [undefined]
57
52
  def source(input)
58
53
  fail 'source already defined' if @node
59
54
  @node = node(input)
60
55
  end
61
56
 
62
- # Add expected mutation
63
- #
64
- # @param [String,Parser::AST::Node] input
65
- #
66
- # @return [undefined]
67
57
  def mutation(input)
68
58
  node = node(input)
69
59
  if @expected.include?(node)
@@ -72,22 +62,11 @@ module Mutant
72
62
  @expected << node
73
63
  end
74
64
 
75
- # Add singleton mutations
76
- #
77
- # @return [undefined]
78
65
  def singleton_mutations
79
66
  mutation('nil')
80
67
  mutation('self')
81
68
  end
82
69
 
83
- # Helper method to coerce input to node
84
- #
85
- # @param [String,Parser::AST::Node] input
86
- #
87
- # @return [Parser::AST::Node]
88
- #
89
- # @raise [RuntimeError]
90
- # in case input cannot be coerced
91
70
  def node(input)
92
71
  case input
93
72
  when String
@@ -35,9 +35,6 @@ module Mutant
35
35
 
36
36
  private
37
37
 
38
- # Unexpected mutations
39
- #
40
- # @return [Array<Mutation>]
41
38
  def unexpected
42
39
  mutations.reject do |mutation|
43
40
  example.expected.include?(mutation.node)
@@ -45,9 +42,6 @@ module Mutant
45
42
  end
46
43
  memoize :unexpected
47
44
 
48
- # Missing mutations
49
- #
50
- # @return [Array<Mutation>]
51
45
  def missing
52
46
  (example.expected - mutations.map(&:node)).map do |node|
53
47
  Mutation::Evil.new(self, node)
@@ -55,9 +49,6 @@ module Mutant
55
49
  end
56
50
  memoize :missing
57
51
 
58
- # Mutations that generated invalid syntax
59
- #
60
- # @return [Enumerable<Mutation>]
61
52
  def invalid_syntax
62
53
  mutations.reject do |mutation|
63
54
  ::Parser::CurrentRuby.parse(mutation.source)
@@ -66,19 +57,11 @@ module Mutant
66
57
  end
67
58
  memoize :invalid_syntax
68
59
 
69
- # Mutations with no diff to original
70
- #
71
- # @return [Enumerable<Mutation>]
72
60
  def no_diffs
73
61
  mutations.select { |mutation| mutation.source.eql?(example.source) }
74
62
  end
75
63
  memoize :no_diffs
76
64
 
77
- # Mutation report
78
- #
79
- # @param [Array<Mutation>] mutations
80
- #
81
- # @return [Array<Hash>]
82
65
  def format_mutations(mutations)
83
66
  mutations.map do |mutation|
84
67
  {
@@ -88,9 +71,6 @@ module Mutant
88
71
  end
89
72
  end
90
73
 
91
- # No diff mutation report
92
- #
93
- # @return [Array, nil]
94
74
  def no_diff_report
95
75
  no_diffs.map do |mutation|
96
76
  {
@@ -74,9 +74,6 @@ module Mutant
74
74
 
75
75
  private
76
76
 
77
- # SHA1 sum of source and subject identification
78
- #
79
- # @return [String]
80
77
  def sha1
81
78
  Digest::SHA1.hexdigest(subject.identification + CODE_DELIMITER + source)
82
79
  end
@@ -18,9 +18,6 @@ module Mutant
18
18
  self::REGISTRY.lookup(node.type).call(node, parent)
19
19
  end
20
20
 
21
- # Register node class handler
22
- #
23
- # @return [undefined]
24
21
  def self.handle(*types)
25
22
  types.each do |type|
26
23
  self::REGISTRY.register(type, self)
@@ -35,13 +32,6 @@ module Mutant
35
32
 
36
33
  private
37
34
 
38
- # Initialize object
39
- #
40
- # @param [Object] input
41
- # @param [Object] parent
42
- # @param [#call(node)] block
43
- #
44
- # @return [undefined]
45
35
  def initialize(_input, _parent = nil)
46
36
  super
47
37
 
@@ -50,41 +40,23 @@ module Mutant
50
40
  dispatch
51
41
  end
52
42
 
53
- # Test if generated object is not guarded from emitting
54
- #
55
- # @param [Object] object
56
- #
57
- # @return [Boolean]
58
43
  def new?(object)
59
44
  !object.eql?(input)
60
45
  end
61
46
 
62
- # Dispatch node generations
63
- #
64
- # @return [undefined]
65
47
  abstract_method :dispatch
48
+ private :dispatch
66
49
 
67
- # Emit generated mutation if object is not equivalent to input
68
- #
69
- # @param [Object] object
70
- #
71
- # @return [undefined]
72
50
  def emit(object)
73
51
  return unless new?(object)
74
52
 
75
53
  output << object
76
54
  end
77
55
 
78
- # Run input with mutator
79
- #
80
- # @return [undefined]
81
56
  def run(mutator)
82
57
  mutator.call(input).each(&method(:emit))
83
58
  end
84
59
 
85
- # Shortcut to create a new unfrozen duplicate of input
86
- #
87
- # @return [Object]
88
60
  def dup_input
89
61
  input.dup
90
62
  end