mutant 0.8.20 → 0.8.21
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.circleci/config.yml +0 -2
- data/Changelog.md +4 -0
- data/Gemfile.lock +17 -18
- data/README.md +21 -4
- data/config/rubocop.yml +3 -4
- data/docs/mutant-minitest.md +4 -2
- data/docs/mutant-rspec.md +104 -14
- data/lib/mutant.rb +2 -1
- data/lib/mutant/ast/meta/send.rb +1 -15
- data/lib/mutant/ast/types.rb +47 -22
- data/lib/mutant/meta.rb +2 -2
- data/lib/mutant/meta/example.rb +2 -1
- data/lib/mutant/meta/example/dsl.rb +12 -9
- data/lib/mutant/meta/example/verification.rb +34 -16
- data/lib/mutant/mutator/node.rb +7 -0
- data/lib/mutant/mutator/node/argument.rb +0 -1
- data/lib/mutant/mutator/node/arguments.rb +13 -1
- data/lib/mutant/mutator/node/block.rb +1 -1
- data/lib/mutant/mutator/node/index.rb +129 -0
- data/lib/mutant/mutator/node/noop.rb +1 -1
- data/lib/mutant/mutator/node/procarg_zero.rb +45 -0
- data/lib/mutant/mutator/node/send.rb +1 -27
- data/lib/mutant/parser.rb +1 -1
- data/lib/mutant/reporter/cli/printer/mutation_result.rb +34 -32
- data/lib/mutant/version.rb +1 -1
- data/lib/mutant/warning_filter.rb +1 -1
- data/lib/mutant/zombifier.rb +1 -1
- data/meta/block.rb +22 -3
- data/meta/index.rb +133 -0
- data/meta/indexasgn.rb +31 -0
- data/meta/lambda.rb +9 -0
- data/meta/regexp.rb +0 -7
- data/meta/regexp/character_types.rb +1 -1
- data/meta/send.rb +0 -146
- data/mutant.gemspec +2 -3
- data/spec/spec_helper.rb +1 -1
- data/spec/support/corpus.rb +28 -12
- data/spec/unit/mutant/ast/meta/send/proc_predicate_spec.rb +1 -1
- data/spec/unit/mutant/ast/meta/send/receiver_possible_top_level_const_predicate_spec.rb +1 -1
- data/spec/unit/mutant/ast/meta/send_spec.rb +6 -8
- data/spec/unit/mutant/meta/example/dsl_spec.rb +9 -9
- data/spec/unit/mutant/meta/example/verification_spec.rb +36 -4
- data/spec/unit/mutant/meta/example_spec.rb +4 -4
- data/spec/unit/mutant/mutator/node_spec.rb +12 -7
- data/spec/unit/mutant/reporter/cli/printer/mutation_result_spec.rb +3 -1
- data/spec/unit/mutant/subject_spec.rb +1 -1
- data/spec/unit/mutant/zombifier_spec.rb +1 -1
- metadata +12 -24
- data/config/flay.yml +0 -3
- data/config/flog.yml +0 -2
- data/lib/mutant/mutator/node/send/index.rb +0 -52
data/lib/mutant/meta.rb
CHANGED
@@ -18,9 +18,9 @@ module Mutant
|
|
18
18
|
# @return [undefined]
|
19
19
|
#
|
20
20
|
# rubocop:disable Performance/Caller
|
21
|
-
def self.add(
|
21
|
+
def self.add(*types, &block)
|
22
22
|
file = caller.first.split(':in', 2).first
|
23
|
-
ALL << DSL.call(file,
|
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'))
|
data/lib/mutant/meta/example.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
module Mutant
|
4
4
|
module Meta
|
5
5
|
class Example
|
6
|
-
include Adamantium, Anima.new(:file, :node, :
|
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,
|
14
|
-
instance = new(file,
|
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,
|
27
|
+
def initialize(file, types)
|
25
28
|
@file = file
|
26
|
-
@
|
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:
|
41
|
-
node:
|
42
|
-
|
43
|
-
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(
|
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
|
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<
|
40
|
+
# @return [Array<Mutation>]
|
38
41
|
def unexpected
|
39
|
-
mutations.
|
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<
|
79
|
+
# @param [Array<Mutation>] mutations
|
54
80
|
#
|
55
81
|
# @return [Array<Hash>]
|
56
|
-
def format_mutations(
|
57
|
-
|
82
|
+
def format_mutations(mutations)
|
83
|
+
mutations.map do |mutation|
|
58
84
|
{
|
59
|
-
'node' => node.inspect,
|
60
|
-
'source' =>
|
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
|
data/lib/mutant/mutator/node.rb
CHANGED
@@ -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]
|
@@ -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
|
-
|
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]
|
@@ -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
|
@@ -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
|
-
|
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
|