mutant 0.8.20 → 0.8.21

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