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.
- 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
|