mutant 0.8.20 → 0.8.21

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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +0 -2
  3. data/Changelog.md +4 -0
  4. data/Gemfile.lock +17 -18
  5. data/README.md +21 -4
  6. data/config/rubocop.yml +3 -4
  7. data/docs/mutant-minitest.md +4 -2
  8. data/docs/mutant-rspec.md +104 -14
  9. data/lib/mutant.rb +2 -1
  10. data/lib/mutant/ast/meta/send.rb +1 -15
  11. data/lib/mutant/ast/types.rb +47 -22
  12. data/lib/mutant/meta.rb +2 -2
  13. data/lib/mutant/meta/example.rb +2 -1
  14. data/lib/mutant/meta/example/dsl.rb +12 -9
  15. data/lib/mutant/meta/example/verification.rb +34 -16
  16. data/lib/mutant/mutator/node.rb +7 -0
  17. data/lib/mutant/mutator/node/argument.rb +0 -1
  18. data/lib/mutant/mutator/node/arguments.rb +13 -1
  19. data/lib/mutant/mutator/node/block.rb +1 -1
  20. data/lib/mutant/mutator/node/index.rb +129 -0
  21. data/lib/mutant/mutator/node/noop.rb +1 -1
  22. data/lib/mutant/mutator/node/procarg_zero.rb +45 -0
  23. data/lib/mutant/mutator/node/send.rb +1 -27
  24. data/lib/mutant/parser.rb +1 -1
  25. data/lib/mutant/reporter/cli/printer/mutation_result.rb +34 -32
  26. data/lib/mutant/version.rb +1 -1
  27. data/lib/mutant/warning_filter.rb +1 -1
  28. data/lib/mutant/zombifier.rb +1 -1
  29. data/meta/block.rb +22 -3
  30. data/meta/index.rb +133 -0
  31. data/meta/indexasgn.rb +31 -0
  32. data/meta/lambda.rb +9 -0
  33. data/meta/regexp.rb +0 -7
  34. data/meta/regexp/character_types.rb +1 -1
  35. data/meta/send.rb +0 -146
  36. data/mutant.gemspec +2 -3
  37. data/spec/spec_helper.rb +1 -1
  38. data/spec/support/corpus.rb +28 -12
  39. data/spec/unit/mutant/ast/meta/send/proc_predicate_spec.rb +1 -1
  40. data/spec/unit/mutant/ast/meta/send/receiver_possible_top_level_const_predicate_spec.rb +1 -1
  41. data/spec/unit/mutant/ast/meta/send_spec.rb +6 -8
  42. data/spec/unit/mutant/meta/example/dsl_spec.rb +9 -9
  43. data/spec/unit/mutant/meta/example/verification_spec.rb +36 -4
  44. data/spec/unit/mutant/meta/example_spec.rb +4 -4
  45. data/spec/unit/mutant/mutator/node_spec.rb +12 -7
  46. data/spec/unit/mutant/reporter/cli/printer/mutation_result_spec.rb +3 -1
  47. data/spec/unit/mutant/subject_spec.rb +1 -1
  48. data/spec/unit/mutant/zombifier_spec.rb +1 -1
  49. metadata +12 -24
  50. data/config/flay.yml +0 -3
  51. data/config/flog.yml +0 -2
  52. data/lib/mutant/mutator/node/send/index.rb +0 -52
@@ -18,9 +18,9 @@ module Mutant
18
18
  # @return [undefined]
19
19
  #
20
20
  # rubocop:disable Performance/Caller
21
- def self.add(type, &block)
21
+ def self.add(*types, &block)
22
22
  file = caller.first.split(':in', 2).first
23
- ALL << DSL.call(file, type, block)
23
+ ALL << DSL.call(file, Set.new(types), block)
24
24
  end
25
25
 
26
26
  Pathname.glob(Pathname.new(__dir__).parent.parent.join('meta', '*.rb'))
@@ -3,7 +3,7 @@
3
3
  module Mutant
4
4
  module Meta
5
5
  class Example
6
- include Adamantium, Anima.new(:file, :node, :node_type, :expected)
6
+ include Adamantium, Anima.new(:file, :node, :types, :expected)
7
7
 
8
8
  # Verification instance for example
9
9
  #
@@ -11,6 +11,7 @@ module Mutant
11
11
  def verification
12
12
  Verification.new(self, generated)
13
13
  end
14
+ memoize :verification
14
15
 
15
16
  # Normalized source
16
17
  #
@@ -9,9 +9,12 @@ module Mutant
9
9
 
10
10
  # Run DSL on block
11
11
  #
12
+ # @param [Pathname] file
13
+ # @param [Set<Symbol>] types
14
+ #
12
15
  # @return [Example]
13
- def self.call(file, type, block)
14
- instance = new(file, type)
16
+ def self.call(file, types, block)
17
+ instance = new(file, types)
15
18
  instance.instance_eval(&block)
16
19
  instance.example
17
20
  end
@@ -21,9 +24,9 @@ module Mutant
21
24
  # Initialize object
22
25
  #
23
26
  # @return [undefined]
24
- def initialize(file, type)
27
+ def initialize(file, types)
25
28
  @file = file
26
- @type = type
29
+ @types = types
27
30
  @node = nil
28
31
  @expected = []
29
32
  end
@@ -37,10 +40,10 @@ module Mutant
37
40
  def example
38
41
  fail 'source not defined' unless @node
39
42
  Example.new(
40
- file: @file,
41
- node: @node,
42
- node_type: @type,
43
- expected: @expected
43
+ file: @file,
44
+ node: @node,
45
+ types: @types,
46
+ expected: @expected
44
47
  )
45
48
  end
46
49
 
@@ -96,7 +99,7 @@ module Mutant
96
99
  def node(input)
97
100
  case input
98
101
  when String
99
- Unparser::Preprocessor.run(::Parser::CurrentRuby.parse(input))
102
+ Unparser::Preprocessor.run(Unparser.parse(input))
100
103
  when ::Parser::AST::Node
101
104
  input
102
105
  else
@@ -11,8 +11,9 @@ module Mutant
11
11
  #
12
12
  # @return [Boolean]
13
13
  def success?
14
- missing.empty? && unexpected.empty? && no_diffs.empty?
14
+ [missing, unexpected, no_diffs, invalid_syntax].all?(&:empty?)
15
15
  end
16
+ memoize :success?
16
17
 
17
18
  # Error report
18
19
  #
@@ -26,20 +27,45 @@ module Mutant
26
27
  'original_source' => example.source,
27
28
  'missing' => format_mutations(missing),
28
29
  'unexpected' => format_mutations(unexpected),
30
+ 'invalid_syntax' => format_mutations(invalid_syntax),
29
31
  'no_diff' => no_diff_report
30
32
  )
31
33
  end
34
+ memoize :error_report
32
35
 
33
36
  private
34
37
 
35
38
  # Unexpected mutations
36
39
  #
37
- # @return [Array<Parser::AST::Node>]
40
+ # @return [Array<Mutation>]
38
41
  def unexpected
39
- mutations.map(&:node) - example.expected
42
+ mutations.reject do |mutation|
43
+ example.expected.include?(mutation.node)
44
+ end
40
45
  end
41
46
  memoize :unexpected
42
47
 
48
+ # Missing mutations
49
+ #
50
+ # @return [Array<Mutation>]
51
+ def missing
52
+ (example.expected - mutations.map(&:node)).map do |node|
53
+ Mutation::Evil.new(self, node)
54
+ end
55
+ end
56
+ memoize :missing
57
+
58
+ # Mutations that generated invalid syntax
59
+ #
60
+ # @return [Enumerable<Mutation>]
61
+ def invalid_syntax
62
+ mutations.reject do |mutation|
63
+ ::Parser::CurrentRuby.parse(mutation.source)
64
+ rescue ::Parser::SyntaxError # rubocop:disable Lint/HandleExceptions
65
+ end
66
+ end
67
+ memoize :invalid_syntax
68
+
43
69
  # Mutations with no diff to original
44
70
  #
45
71
  # @return [Enumerable<Mutation>]
@@ -50,14 +76,14 @@ module Mutant
50
76
 
51
77
  # Mutation report
52
78
  #
53
- # @param [Array<Parser::AST::Node>] nodes
79
+ # @param [Array<Mutation>] mutations
54
80
  #
55
81
  # @return [Array<Hash>]
56
- def format_mutations(nodes)
57
- nodes.map do |node|
82
+ def format_mutations(mutations)
83
+ mutations.map do |mutation|
58
84
  {
59
- 'node' => node.inspect,
60
- 'source' => Unparser.unparse(node)
85
+ 'node' => mutation.node.inspect,
86
+ 'source' => mutation.source
61
87
  }
62
88
  end
63
89
  end
@@ -74,14 +100,6 @@ module Mutant
74
100
  end
75
101
  end
76
102
 
77
- # Missing mutations
78
- #
79
- # @return [Array<Parser::AST::Node>]
80
- def missing
81
- example.expected - mutations.map(&:node)
82
- end
83
- memoize :missing
84
-
85
103
  end # Verification
86
104
  end # Example
87
105
  end # Meta
@@ -96,6 +96,13 @@ module Mutant
96
96
  emit(::Parser::AST::Node.new(node.type, children))
97
97
  end
98
98
 
99
+ # Emit propagation if node can stand alone
100
+ #
101
+ # @return [undefined]
102
+ def emit_propagation(node)
103
+ emit(node) unless AST::Types::NOT_STANDALONE.include?(node.type)
104
+ end
105
+
99
106
  # Emit singleton literals
100
107
  #
101
108
  # @return [undefined]
@@ -67,7 +67,6 @@ module Mutant
67
67
  end
68
68
 
69
69
  end # Optional
70
-
71
70
  end # Argument
72
71
  end # Node
73
72
  end # Mutator
@@ -24,11 +24,23 @@ module Mutant
24
24
  # @return [undefined]
25
25
  def emit_argument_presence
26
26
  emit_type
27
+
27
28
  Util::Array::Presence.call(children).each do |children|
28
- emit_type(*children)
29
+ if children.one? && n_mlhs?(Mutant::Util.one(children))
30
+ emit_procarg(Mutant::Util.one(children))
31
+ else
32
+ emit_type(*children)
33
+ end
29
34
  end
30
35
  end
31
36
 
37
+ # Emit procarg form
38
+ #
39
+ # @return [undefined]
40
+ def emit_procarg(arg)
41
+ emit_type(s(:procarg0, *arg))
42
+ end
43
+
32
44
  # Emit argument mutations
33
45
  #
34
46
  # @return [undefined]
@@ -17,7 +17,7 @@ module Mutant
17
17
  # @return [undefined]
18
18
  def dispatch
19
19
  emit_singletons
20
- emit(send)
20
+ emit(send) unless n_lambda?(send)
21
21
  emit_send_mutations(&method(:n_send?))
22
22
  emit_arguments_mutations
23
23
 
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ class Mutator
5
+ class Node
6
+ # Base mutator for index operations
7
+ class Index < self
8
+ NO_VALUE_RANGE = (1..-1).freeze
9
+ SEND_REPLACEMENTS = %i[at fetch key?].freeze
10
+
11
+ private_constant(*constants(false))
12
+
13
+ children :receiver
14
+
15
+ private
16
+
17
+ # Emit mutations
18
+ #
19
+ # @return [undefined]
20
+ def dispatch
21
+ emit_singletons
22
+ emit_receiver_mutations { |node| !n_nil?(node) }
23
+ emit(receiver)
24
+ emit_send_forms
25
+ emit_drop_mutation
26
+ mutate_indices
27
+ end
28
+
29
+ # Emit send forms
30
+ #
31
+ # @return [undefined]
32
+ def emit_send_forms
33
+ return if asgn_left?
34
+
35
+ SEND_REPLACEMENTS.each do |selector|
36
+ emit(s(:send, receiver, selector, *indices))
37
+ end
38
+ end
39
+
40
+ # Emit mutation `foo[n..-1]` -> `foo.drop(n)`
41
+ #
42
+ # @return [undefined]
43
+ def emit_drop_mutation
44
+ return unless indices.one? && n_irange?(Mutant::Util.one(indices))
45
+
46
+ start, ending = *indices.first
47
+
48
+ return unless ending.eql?(s(:int, -1))
49
+
50
+ emit(s(:send, receiver, :drop, start))
51
+ end
52
+
53
+ # Mutate indices
54
+ #
55
+ # @return [undefined]
56
+ def mutate_indices
57
+ children_indices(index_range).each do |index|
58
+ emit_propagation(children.fetch(index)) unless asgn_left?
59
+ delete_child(index)
60
+ mutate_child(index)
61
+ end
62
+ end
63
+
64
+ # The index nodes
65
+ #
66
+ # @return [Enumerable<Parser::AST::Node>]
67
+ def indices
68
+ children[index_range]
69
+ end
70
+
71
+ class Read < self
72
+
73
+ handle :index
74
+
75
+ private
76
+
77
+ # The range index children can be found
78
+ #
79
+ # @return [Range]
80
+ def index_range
81
+ NO_VALUE_RANGE
82
+ end
83
+ end
84
+
85
+ # Mutator for index assignments
86
+ class Assign < self
87
+ REGULAR_RANGE = (1..-2).freeze
88
+
89
+ private_constant(*constants(false))
90
+
91
+ handle :indexasgn
92
+
93
+ private
94
+
95
+ # Emit mutations
96
+ #
97
+ # @return [undefined]
98
+ def dispatch
99
+ super()
100
+
101
+ return if asgn_left?
102
+
103
+ emit_index_read
104
+ emit(children.last)
105
+ mutate_child(children.length.pred)
106
+ end
107
+
108
+ # Emit index read
109
+ #
110
+ # @return [undefined]
111
+ def emit_index_read
112
+ emit(s(:index, receiver, *children[index_range]))
113
+ end
114
+
115
+ # Index indices
116
+ #
117
+ # @return [Range<Integer>]
118
+ def index_range
119
+ if asgn_left?
120
+ NO_VALUE_RANGE
121
+ else
122
+ REGULAR_RANGE
123
+ end
124
+ end
125
+ end # Assign
126
+ end # Index
127
+ end # Node
128
+ end # Mutator
129
+ end # Mutant
@@ -7,7 +7,7 @@ module Mutant
7
7
  # Mutation emitter to handle noop nodes
8
8
  class Noop < self
9
9
 
10
- handle(:__ENCODING__, :block_pass, :cbase)
10
+ handle(:__ENCODING__, :block_pass, :cbase, :lambda)
11
11
 
12
12
  private
13
13
 
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ class Mutator
5
+ class Node
6
+ class ProcargZero < self
7
+ MAP = {
8
+ ::Parser::AST::Node => :emit_argument_node_mutations,
9
+ Symbol => :emit_argument_symbol_mutations
10
+ }.freeze
11
+
12
+ private_constant(*constants(false))
13
+
14
+ handle :procarg0
15
+
16
+ children :argument
17
+
18
+ private
19
+
20
+ # Emit mutations
21
+ #
22
+ # @return [undefined]
23
+ def dispatch
24
+ __send__(MAP.fetch(argument.class))
25
+ end
26
+
27
+ # Emit argument symbol mutations
28
+ #
29
+ # @return [undefined]
30
+ def emit_argument_symbol_mutations
31
+ emit_type(:"_#{argument}") unless argument.to_s.start_with?('_')
32
+ end
33
+
34
+ # Emit argument node mutations
35
+ #
36
+ # @return [undefined]
37
+ def emit_argument_node_mutations
38
+ emit_argument_mutations
39
+ first = Mutant::Util.one(argument.children)
40
+ emit_type(first)
41
+ end
42
+ end # ProcargZero
43
+ end # Node
44
+ end # Mutator
45
+ end # Mutant
@@ -40,7 +40,6 @@ module Mutant
40
40
  values_at: %i[fetch_values],
41
41
  match: %i[match?],
42
42
  '=~': %i[match?],
43
- :[] => %i[at fetch key?],
44
43
  :== => %i[eql? equal?],
45
44
  :>= => %i[> == eql? equal?],
46
45
  :<= => %i[< == eql? equal?],
@@ -61,17 +60,7 @@ module Mutant
61
60
  # @return [undefined]
62
61
  def dispatch
63
62
  emit_singletons
64
- if meta.index_assignment?
65
- run(Index::Assign)
66
- else
67
- non_index_dispatch
68
- end
69
- end
70
63
 
71
- # Perform non index dispatch
72
- #
73
- # @return [undefined]
74
- def non_index_dispatch
75
64
  if meta.binary_method_operator?
76
65
  run(Binary)
77
66
  elsif meta.attribute_assignment?
@@ -117,7 +106,6 @@ module Mutant
117
106
  emit_dig_mutation
118
107
  emit_double_negation_mutation
119
108
  emit_lambda_mutation
120
- emit_drop_mutation
121
109
  end
122
110
 
123
111
  # Emit selector mutations specific to top level constants
@@ -167,19 +155,6 @@ module Mutant
167
155
  emit(s(:send, fetch_mutation, :dig, *tail))
168
156
  end
169
157
 
170
- # Emit mutation `foo[n..-1]` -> `foo.drop(n)`
171
- #
172
- # @return [undefined]
173
- def emit_drop_mutation
174
- return if !selector.equal?(:[]) || !arguments.one? || !n_irange?(arguments.first)
175
-
176
- start, ending = *arguments.first
177
-
178
- return unless ending.eql?(s(:int, -1))
179
-
180
- emit(s(:send, receiver, :drop, start))
181
- end
182
-
183
158
  # Emit mutation from `to_i` to `Integer(...)`
184
159
  #
185
160
  # @return [undefined]
@@ -227,8 +202,7 @@ module Mutant
227
202
  #
228
203
  # @return [undefined]
229
204
  def emit_argument_propagation
230
- node = arguments.first
231
- emit(node) if arguments.one? && !NOT_STANDALONE.include?(node.type)
205
+ emit_propagation(Mutant::Util.one(arguments)) if arguments.one?
232
206
  end
233
207
 
234
208
  # Emit receiver mutations