mutant 0.11.11 → 0.11.12
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/lib/mutant/bootstrap.rb +8 -7
- data/lib/mutant/cli/command/environment.rb +5 -3
- data/lib/mutant/cli/command/util.rb +17 -2
- data/lib/mutant/config.rb +76 -40
- data/lib/mutant/env.rb +1 -1
- data/lib/mutant/license/subscription/opensource.rb +1 -1
- data/lib/mutant/matcher/method.rb +8 -1
- data/lib/mutant/meta/example.rb +4 -1
- data/lib/mutant/mutation/config.rb +36 -0
- data/lib/mutant/mutator/node/arguments.rb +1 -1
- data/lib/mutant/mutator/node/begin.rb +2 -1
- data/lib/mutant/mutator/node/define.rb +5 -2
- data/lib/mutant/mutator/node/kwargs.rb +2 -2
- data/lib/mutant/mutator/node/literal/regex.rb +1 -1
- data/lib/mutant/mutator/node/literal/symbol.rb +2 -2
- data/lib/mutant/mutator/node/named_value/constant_assignment.rb +1 -1
- data/lib/mutant/mutator/node/named_value/variable_assignment.rb +2 -2
- data/lib/mutant/mutator/node/resbody.rb +0 -10
- data/lib/mutant/mutator/node.rb +47 -1
- data/lib/mutant/mutator/util/array.rb +0 -17
- data/lib/mutant/mutator.rb +2 -26
- data/lib/mutant/reporter/cli/printer/config.rb +1 -1
- data/lib/mutant/subject/config.rb +5 -4
- data/lib/mutant/subject.rb +4 -1
- data/lib/mutant/transform.rb +14 -0
- data/lib/mutant/version.rb +1 -1
- data/lib/mutant.rb +2 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3c75928a9c55d93ebfad196c028271b8c82d7d9eb94e48873ac75c9ee4bb212f
|
4
|
+
data.tar.gz: 27149ec498db15629bb18c55a5840e3ea8c369542a85bf9d3658cadc78fc8f4a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 44867690e5c1c367df8b22ec3fa5b35941328e12fd10a26a252baa3815c955fbd37f1adaaaadfafbcdc1f72280ce013d37996d001c163930c084b95b07905362
|
7
|
+
data.tar.gz: b22e71a0ae7f1ecb96345e7996e61c4ee843c7b82442f21dea3516be132503fa9c0193443f1d7da9196fbb27f23f3ef858ee58f81c03b7cda8d4f4dcced1a9a7
|
data/lib/mutant/bootstrap.rb
CHANGED
@@ -24,16 +24,15 @@ module Mutant
|
|
24
24
|
|
25
25
|
# Run Bootstrap
|
26
26
|
#
|
27
|
-
# @param [
|
28
|
-
# @param [Config] config
|
27
|
+
# @param [Env] env
|
29
28
|
#
|
30
29
|
# @return [Either<String, Env>]
|
31
30
|
#
|
32
31
|
# rubocop:disable Metrics/MethodLength
|
33
|
-
def self.call(
|
34
|
-
env = load_hooks(
|
32
|
+
def self.call(env)
|
33
|
+
env = load_hooks(env)
|
35
34
|
.tap(&method(:infect))
|
36
|
-
.with(matchable_scopes: matchable_scopes(
|
35
|
+
.with(matchable_scopes: matchable_scopes(env))
|
37
36
|
|
38
37
|
subjects = start_subject(env, Matcher.from_config(env.config.matcher).call(env))
|
39
38
|
|
@@ -82,8 +81,10 @@ module Mutant
|
|
82
81
|
end
|
83
82
|
private_class_method :infect
|
84
83
|
|
85
|
-
def self.matchable_scopes(
|
86
|
-
|
84
|
+
def self.matchable_scopes(env)
|
85
|
+
config = env.config
|
86
|
+
|
87
|
+
scopes = env.world.object_space.each_object(Module).each_with_object([]) do |scope, aggregate|
|
87
88
|
expression = expression(config.reporter, config.expression_parser, scope) || next
|
88
89
|
aggregate << Scope.new(scope, expression)
|
89
90
|
end
|
@@ -23,9 +23,11 @@ module Mutant
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def bootstrap
|
26
|
-
|
26
|
+
env = Env.empty(world, @config)
|
27
|
+
|
28
|
+
Config.load_config_file(env)
|
27
29
|
.fmap(&method(:expand))
|
28
|
-
.bind { Bootstrap.call(
|
30
|
+
.bind { Bootstrap.call(env.with(config: @config)) }
|
29
31
|
end
|
30
32
|
|
31
33
|
def expand(file_config)
|
@@ -121,7 +123,7 @@ module Mutant
|
|
121
123
|
set(jobs: Integer(number))
|
122
124
|
end
|
123
125
|
parser.on('-t', '--mutation-timeout NUMBER', 'Per mutation analysis timeout') do |number|
|
124
|
-
set(
|
126
|
+
set(mutation: @config.mutation.with(timeout: Float(number)))
|
125
127
|
end
|
126
128
|
end
|
127
129
|
end # Run
|
@@ -14,6 +14,12 @@ module Mutant
|
|
14
14
|
OPTIONS = %i[add_target_options].freeze
|
15
15
|
|
16
16
|
def action
|
17
|
+
@ignore_patterns.map! do |syntax|
|
18
|
+
AST::Pattern.parse(syntax).from_right do |message|
|
19
|
+
return Either::Left.new(message)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
17
23
|
@targets.each(&method(:print_mutations))
|
18
24
|
Either::Right.new(nil)
|
19
25
|
end
|
@@ -50,18 +56,27 @@ module Mutant
|
|
50
56
|
def initialize(_arguments)
|
51
57
|
super
|
52
58
|
|
53
|
-
@targets
|
59
|
+
@targets = []
|
60
|
+
@ignore_patterns = []
|
54
61
|
end
|
55
62
|
|
56
63
|
def add_target_options(parser)
|
57
64
|
parser.on('-e', '--evaluate SOURCE') do |source|
|
58
65
|
@targets << Target::Source.new(source)
|
59
66
|
end
|
67
|
+
|
68
|
+
parser.on('-i', '--ignore-pattern AST_PATTERN') do |pattern|
|
69
|
+
@ignore_patterns << pattern
|
70
|
+
end
|
60
71
|
end
|
61
72
|
|
62
73
|
def print_mutations(target)
|
63
74
|
world.stdout.puts(target.identification)
|
64
|
-
|
75
|
+
|
76
|
+
Mutator::Node.mutate(
|
77
|
+
config: Mutant::Mutation::Config::DEFAULT.with(ignore_patterns: @ignore_patterns),
|
78
|
+
node: target.node
|
79
|
+
).each do |mutation|
|
65
80
|
Reporter::CLI::Printer::Mutation.call(
|
66
81
|
world.stdout,
|
67
82
|
Mutant::Mutation::Evil.new(target, mutation)
|
data/lib/mutant/config.rb
CHANGED
@@ -19,7 +19,7 @@ module Mutant
|
|
19
19
|
:isolation,
|
20
20
|
:jobs,
|
21
21
|
:matcher,
|
22
|
-
:
|
22
|
+
:mutation,
|
23
23
|
:reporter,
|
24
24
|
:requires,
|
25
25
|
:zombie
|
@@ -39,6 +39,20 @@ module Mutant
|
|
39
39
|
mutant.yml
|
40
40
|
].freeze
|
41
41
|
|
42
|
+
MUTATION_TIMEOUT_DEPRECATION = <<~'MESSAGE'
|
43
|
+
Deprecated configuration toplevel key `mutation_timeout` found.
|
44
|
+
|
45
|
+
This key will be removed in the next mayor version.
|
46
|
+
Instead place your mutation timeout configuration under the `mutation` key
|
47
|
+
like this:
|
48
|
+
|
49
|
+
```
|
50
|
+
# mutant.yml
|
51
|
+
mutation:
|
52
|
+
timeout: 10.0 # float here.
|
53
|
+
```
|
54
|
+
MESSAGE
|
55
|
+
|
42
56
|
private_constant(*constants(false))
|
43
57
|
|
44
58
|
# Merge with other config
|
@@ -59,7 +73,7 @@ module Mutant
|
|
59
73
|
integration: other.integration || integration,
|
60
74
|
jobs: other.jobs || jobs,
|
61
75
|
matcher: matcher.merge(other.matcher),
|
62
|
-
|
76
|
+
mutation: mutation.merge(other.mutation),
|
63
77
|
requires: requires + other.requires,
|
64
78
|
zombie: zombie || other.zombie
|
65
79
|
)
|
@@ -69,17 +83,16 @@ module Mutant
|
|
69
83
|
|
70
84
|
# Load config file
|
71
85
|
#
|
72
|
-
# @param [
|
73
|
-
# @param [Config] config
|
86
|
+
# @param [Env] env
|
74
87
|
#
|
75
88
|
# @return [Either<String,Config>]
|
76
|
-
def self.load_config_file(
|
89
|
+
def self.load_config_file(env)
|
77
90
|
files = CANDIDATES
|
78
|
-
.map(&world.pathname.public_method(:new))
|
91
|
+
.map(&env.world.pathname.public_method(:new))
|
79
92
|
.select(&:readable?)
|
80
93
|
|
81
94
|
if files.one?
|
82
|
-
load_contents(files.first).fmap(&DEFAULT.public_method(:with))
|
95
|
+
load_contents(env, files.first).fmap(&DEFAULT.public_method(:with))
|
83
96
|
elsif files.empty?
|
84
97
|
Either::Right.new(DEFAULT)
|
85
98
|
else
|
@@ -97,14 +110,30 @@ module Mutant
|
|
97
110
|
)
|
98
111
|
end
|
99
112
|
|
100
|
-
def self.load_contents(path)
|
113
|
+
def self.load_contents(env, path)
|
101
114
|
Transform::Named
|
102
|
-
.new(
|
115
|
+
.new(
|
116
|
+
path.to_s,
|
117
|
+
sequence(env.config.reporter)
|
118
|
+
)
|
103
119
|
.call(path)
|
104
120
|
.lmap(&:compact_message)
|
105
121
|
end
|
106
122
|
private_class_method :load_contents
|
107
123
|
|
124
|
+
def self.sequence(reporter)
|
125
|
+
Transform::Sequence.new(
|
126
|
+
[
|
127
|
+
Transform::Exception.new(SystemCallError, :read.to_proc),
|
128
|
+
Transform::Exception.new(YAML::SyntaxError, YAML.public_method(:safe_load)),
|
129
|
+
Transform::Primitive.new(Hash),
|
130
|
+
Transform::Success.new(->(hash) { deprecations(reporter, hash) }),
|
131
|
+
*TRANSFORMS
|
132
|
+
]
|
133
|
+
)
|
134
|
+
end
|
135
|
+
private_class_method :sequence
|
136
|
+
|
108
137
|
# The configuration from the environment
|
109
138
|
#
|
110
139
|
# @return [Config]
|
@@ -139,38 +168,45 @@ module Mutant
|
|
139
168
|
|
140
169
|
Either::Right.new(hash)
|
141
170
|
end
|
142
|
-
TRANSFORM = Transform::Sequence.new(
|
143
|
-
[
|
144
|
-
Transform::Exception.new(SystemCallError, :read.to_proc),
|
145
|
-
Transform::Exception.new(YAML::SyntaxError, YAML.public_method(:safe_load)),
|
146
|
-
Transform::Hash.new(
|
147
|
-
optional: [
|
148
|
-
Transform::Hash::Key.new('coverage_criteria', ->(value) { CoverageCriteria::TRANSFORM.call(value) }),
|
149
|
-
Transform::Hash::Key.new(
|
150
|
-
'environment_variables',
|
151
|
-
Transform::Sequence.new(
|
152
|
-
[
|
153
|
-
Transform::Primitive.new(Hash),
|
154
|
-
Transform::Block.capture(:environment_variables, &method(:parse_environment_variables))
|
155
|
-
]
|
156
|
-
)
|
157
|
-
),
|
158
|
-
Transform::Hash::Key.new('fail_fast', Transform::BOOLEAN),
|
159
|
-
Transform::Hash::Key.new('hooks', PATHNAME_ARRAY),
|
160
|
-
Transform::Hash::Key.new('includes', Transform::STRING_ARRAY),
|
161
|
-
Transform::Hash::Key.new('integration', Transform::STRING),
|
162
|
-
Transform::Hash::Key.new('jobs', Transform::INTEGER),
|
163
|
-
Transform::Hash::Key.new('matcher', Matcher::Config::LOADER),
|
164
|
-
Transform::Hash::Key.new('mutation_timeout', Transform::FLOAT),
|
165
|
-
Transform::Hash::Key.new('requires', Transform::STRING_ARRAY)
|
166
|
-
],
|
167
|
-
required: []
|
168
|
-
),
|
169
|
-
Transform::Hash::Symbolize.new
|
170
|
-
]
|
171
|
-
)
|
172
171
|
|
173
|
-
|
172
|
+
def self.deprecations(reporter, hash)
|
173
|
+
if hash.key?('mutation_timeout')
|
174
|
+
reporter.warn(MUTATION_TIMEOUT_DEPRECATION)
|
175
|
+
|
176
|
+
(hash['mutation'] ||= {})['timeout'] ||= hash.delete('mutation_timeout')
|
177
|
+
end
|
178
|
+
|
179
|
+
hash
|
180
|
+
end
|
181
|
+
|
182
|
+
TRANSFORMS = [
|
183
|
+
Transform::Hash.new(
|
184
|
+
optional: [
|
185
|
+
Transform::Hash::Key.new('coverage_criteria', ->(value) { CoverageCriteria::TRANSFORM.call(value) }),
|
186
|
+
Transform::Hash::Key.new(
|
187
|
+
'environment_variables',
|
188
|
+
Transform::Sequence.new(
|
189
|
+
[
|
190
|
+
Transform::Primitive.new(Hash),
|
191
|
+
Transform::Block.capture(:environment_variables, &method(:parse_environment_variables))
|
192
|
+
]
|
193
|
+
)
|
194
|
+
),
|
195
|
+
Transform::Hash::Key.new('fail_fast', Transform::BOOLEAN),
|
196
|
+
Transform::Hash::Key.new('hooks', PATHNAME_ARRAY),
|
197
|
+
Transform::Hash::Key.new('includes', Transform::STRING_ARRAY),
|
198
|
+
Transform::Hash::Key.new('integration', Transform::STRING),
|
199
|
+
Transform::Hash::Key.new('jobs', Transform::INTEGER),
|
200
|
+
Transform::Hash::Key.new('matcher', Matcher::Config::LOADER),
|
201
|
+
Transform::Hash::Key.new('mutation', Mutation::Config::TRANSFORM),
|
202
|
+
Transform::Hash::Key.new('requires', Transform::STRING_ARRAY)
|
203
|
+
],
|
204
|
+
required: []
|
205
|
+
),
|
206
|
+
Transform::Hash::Symbolize.new
|
207
|
+
].freeze
|
208
|
+
|
209
|
+
private_constant(:TRANSFORMS)
|
174
210
|
end # Config
|
175
211
|
# rubocop:enable Metrics/ClassLength
|
176
212
|
end # Mutant
|
data/lib/mutant/env.rb
CHANGED
@@ -137,7 +137,7 @@ module Mutant
|
|
137
137
|
private
|
138
138
|
|
139
139
|
def run_mutation_tests(mutation, tests)
|
140
|
-
config.isolation.call(config.
|
140
|
+
config.isolation.call(config.mutation.timeout) do
|
141
141
|
hooks.run(:mutation_insert_pre, mutation)
|
142
142
|
result = mutation.insert(world.kernel)
|
143
143
|
hooks.run(:mutation_insert_post, mutation)
|
@@ -99,13 +99,20 @@ module Mutant
|
|
99
99
|
node = matched_node_path.last || return
|
100
100
|
|
101
101
|
self.class::SUBJECT_CLASS.new(
|
102
|
-
config:
|
102
|
+
config: subject_config(node),
|
103
103
|
context: context,
|
104
104
|
node: node,
|
105
105
|
visibility: visibility
|
106
106
|
)
|
107
107
|
end
|
108
108
|
|
109
|
+
def subject_config(node)
|
110
|
+
Subject::Config.parse(
|
111
|
+
comments: ast.comment_associations.fetch(node, []),
|
112
|
+
mutation: env.config.mutation
|
113
|
+
)
|
114
|
+
end
|
115
|
+
|
109
116
|
def matched_node_path
|
110
117
|
AST.find_last_path(ast.node, &method(:match?))
|
111
118
|
end
|
data/lib/mutant/meta/example.rb
CHANGED
@@ -55,7 +55,10 @@ module Mutant
|
|
55
55
|
#
|
56
56
|
# @return [Enumerable<Mutant::Mutation>]
|
57
57
|
def generated
|
58
|
-
Mutator.mutate(
|
58
|
+
Mutator::Node.mutate(
|
59
|
+
config: Mutation::Config::DEFAULT,
|
60
|
+
node: node
|
61
|
+
).map do |node|
|
59
62
|
Mutation::Evil.new(self, node)
|
60
63
|
end
|
61
64
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mutant
|
4
|
+
class Mutation
|
5
|
+
class Config
|
6
|
+
include Anima.new(:ignore_patterns, :timeout)
|
7
|
+
|
8
|
+
DEFAULT = new(
|
9
|
+
timeout: nil,
|
10
|
+
ignore_patterns: []
|
11
|
+
)
|
12
|
+
|
13
|
+
ignore_pattern = Transform::Block.capture('ignore pattern', &AST::Pattern.method(:parse))
|
14
|
+
|
15
|
+
TRANSFORM = Transform::Sequence.new(
|
16
|
+
[
|
17
|
+
Transform::Hash.new(
|
18
|
+
optional: [
|
19
|
+
Transform::Hash::Key.new('ignore_patterns', Transform::Array.new(ignore_pattern)),
|
20
|
+
Transform::Hash::Key.new('timeout', Transform::FLOAT)
|
21
|
+
],
|
22
|
+
required: []
|
23
|
+
),
|
24
|
+
Transform::Hash::Symbolize.new,
|
25
|
+
Transform::Success.new(DEFAULT.method(:with))
|
26
|
+
]
|
27
|
+
)
|
28
|
+
|
29
|
+
def merge(other)
|
30
|
+
with(
|
31
|
+
timeout: other.timeout || timeout
|
32
|
+
)
|
33
|
+
end
|
34
|
+
end # Config
|
35
|
+
end # Mutation
|
36
|
+
end # Mutant
|
@@ -50,7 +50,7 @@ module Mutant
|
|
50
50
|
|
51
51
|
def emit_argument_mutations
|
52
52
|
children.each_with_index do |child, index|
|
53
|
-
|
53
|
+
mutate(node: child).each do |mutant|
|
54
54
|
next if invalid_argument_replacement?(mutant, index)
|
55
55
|
emit_child_update(index, mutant)
|
56
56
|
end
|
@@ -13,8 +13,11 @@ module Mutant
|
|
13
13
|
emit_optarg_body_assignments
|
14
14
|
emit_body(N_RAISE)
|
15
15
|
emit_body(N_ZSUPER)
|
16
|
-
|
17
|
-
|
16
|
+
|
17
|
+
return unless body && !ignore?(body)
|
18
|
+
|
19
|
+
emit_body(nil) unless body.children.any?(&method(:ignore?))
|
20
|
+
emit_body_mutations
|
18
21
|
end
|
19
22
|
|
20
23
|
def emit_optarg_body_assignments
|
@@ -20,14 +20,14 @@ module Mutant
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def emit_argument_presence
|
23
|
-
Util::Array::Presence.call(children).each do |children|
|
23
|
+
Util::Array::Presence.call(input: children, parent: nil).each do |children|
|
24
24
|
emit_type(*children) unless children.empty?
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
28
|
def emit_argument_mutations
|
29
29
|
children.each_with_index do |child, index|
|
30
|
-
|
30
|
+
mutate(node: child).each do |mutant|
|
31
31
|
unless forbid_argument?(mutant)
|
32
32
|
emit_child_update(index, mutant)
|
33
33
|
end
|
@@ -33,7 +33,7 @@ module Mutant
|
|
33
33
|
# Regular expressions with interpolation are skipped.
|
34
34
|
return unless (body_ast = AST::Regexp.expand_regexp_ast(input))
|
35
35
|
|
36
|
-
|
36
|
+
mutate(node: body_ast).each do |mutation|
|
37
37
|
source = AST::Regexp.to_expression(mutation).to_s
|
38
38
|
emit_type(s(:str, source), options)
|
39
39
|
end
|
@@ -34,11 +34,11 @@ module Mutant
|
|
34
34
|
def mutate_name
|
35
35
|
prefix, regexp = MAP.fetch(node.type)
|
36
36
|
stripped = name.to_s.sub(regexp, EMPTY_STRING)
|
37
|
-
|
37
|
+
|
38
|
+
Util::Symbol.call(input: stripped, parent: nil).each do |name|
|
38
39
|
emit_name(:"#{prefix}#{name}")
|
39
40
|
end
|
40
41
|
end
|
41
|
-
|
42
42
|
end # VariableAssignment
|
43
43
|
end # NamedValue
|
44
44
|
end # Node
|
@@ -15,17 +15,7 @@ module Mutant
|
|
15
15
|
def dispatch
|
16
16
|
emit_assignment(nil)
|
17
17
|
emit_body_mutations if body
|
18
|
-
mutate_captures
|
19
18
|
end
|
20
|
-
|
21
|
-
def mutate_captures
|
22
|
-
return unless captures
|
23
|
-
Util::Array::Element.call(captures.children).each do |matchers|
|
24
|
-
next if matchers.any?(&method(:n_nil?))
|
25
|
-
emit_captures(s(:array, *matchers))
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
19
|
end # Resbody
|
30
20
|
end # Node
|
31
21
|
end # Mutator
|
data/lib/mutant/mutator/node.rb
CHANGED
@@ -10,8 +10,37 @@ module Mutant
|
|
10
10
|
include AbstractType, Unparser::Constants
|
11
11
|
include AST::NamedChildren, AST::NodePredicates, AST::Sexp, AST::Nodes
|
12
12
|
|
13
|
+
include anima.add(:config)
|
14
|
+
|
13
15
|
TAUTOLOGY = ->(_input) { true }
|
14
16
|
|
17
|
+
REGISTRY = Registry.new(->(_) { Node::Generic })
|
18
|
+
|
19
|
+
# Lookup and invoke dedicated AST mutator
|
20
|
+
#
|
21
|
+
# @param input [Parser::AST::Node]
|
22
|
+
# @param parent [nil,Mutant::Mutator::Node]
|
23
|
+
#
|
24
|
+
# @return [Set<Parser::AST::Node>]
|
25
|
+
def self.mutate(config:, node:, parent: nil)
|
26
|
+
config.ignore_patterns.each do |pattern|
|
27
|
+
return Set.new if pattern.match?(node)
|
28
|
+
end
|
29
|
+
|
30
|
+
self::REGISTRY.lookup(node.type).call(
|
31
|
+
config: config,
|
32
|
+
input: node,
|
33
|
+
parent: parent
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.handle(*types)
|
38
|
+
types.each do |type|
|
39
|
+
self::REGISTRY.register(type, self)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
private_class_method :handle
|
43
|
+
|
15
44
|
# Helper to define a named child
|
16
45
|
#
|
17
46
|
# @param [Parser::AST::Node] node
|
@@ -37,9 +66,13 @@ module Mutant
|
|
37
66
|
alias_method :node, :input
|
38
67
|
alias_method :dup_node, :dup_input
|
39
68
|
|
69
|
+
def mutate(node:, parent: nil)
|
70
|
+
self.class.mutate(config: config, node: node, parent: parent)
|
71
|
+
end
|
72
|
+
|
40
73
|
def mutate_child(index, &block)
|
41
74
|
block ||= TAUTOLOGY
|
42
|
-
|
75
|
+
mutate(node: children.fetch(index), parent: self).each do |mutation|
|
43
76
|
next unless block.call(mutation)
|
44
77
|
emit_child_update(index, mutation)
|
45
78
|
end
|
@@ -96,6 +129,19 @@ module Mutant
|
|
96
129
|
end
|
97
130
|
end
|
98
131
|
|
132
|
+
def run(mutator)
|
133
|
+
mutator.call(
|
134
|
+
config: config,
|
135
|
+
input: input,
|
136
|
+
parent: nil
|
137
|
+
).each(&method(:emit))
|
138
|
+
end
|
139
|
+
|
140
|
+
def ignore?(node)
|
141
|
+
config.ignore_patterns.any? do |pattern|
|
142
|
+
pattern.match?(node)
|
143
|
+
end
|
144
|
+
end
|
99
145
|
end # Node
|
100
146
|
end # Mutator
|
101
147
|
end # Mutant
|
@@ -21,23 +21,6 @@ module Mutant
|
|
21
21
|
end
|
22
22
|
|
23
23
|
end # Presence
|
24
|
-
|
25
|
-
# Array element mutator
|
26
|
-
class Element < Util
|
27
|
-
|
28
|
-
private
|
29
|
-
|
30
|
-
def dispatch
|
31
|
-
input.each_with_index do |element, index|
|
32
|
-
Mutator.mutate(element).each do |mutation|
|
33
|
-
dup = dup_input
|
34
|
-
dup[index] = mutation
|
35
|
-
emit(dup)
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
end # Element
|
41
24
|
end # Array
|
42
25
|
end # Util
|
43
26
|
end # Mutator
|
data/lib/mutant/mutator.rb
CHANGED
@@ -3,33 +3,13 @@
|
|
3
3
|
module Mutant
|
4
4
|
# Generator for mutations
|
5
5
|
class Mutator
|
6
|
-
|
7
|
-
REGISTRY = Registry.new(->(_) { Node::Generic })
|
8
|
-
|
9
6
|
include(
|
10
7
|
Adamantium,
|
11
|
-
Concord.new(:input, :parent),
|
12
8
|
AbstractType,
|
9
|
+
Anima.new(:input, :parent),
|
13
10
|
Procto
|
14
11
|
)
|
15
12
|
|
16
|
-
# Lookup and invoke dedicated AST mutator
|
17
|
-
#
|
18
|
-
# @param node [Parser::AST::Node]
|
19
|
-
# @param parent [nil,Mutant::Mutator::Node]
|
20
|
-
#
|
21
|
-
# @return [Set<Parser::AST::Node>]
|
22
|
-
def self.mutate(node, parent = nil)
|
23
|
-
self::REGISTRY.lookup(node.type).call(node, parent)
|
24
|
-
end
|
25
|
-
|
26
|
-
def self.handle(*types)
|
27
|
-
types.each do |type|
|
28
|
-
self::REGISTRY.register(type, self)
|
29
|
-
end
|
30
|
-
end
|
31
|
-
private_class_method :handle
|
32
|
-
|
33
13
|
# Return output
|
34
14
|
#
|
35
15
|
# @return [Set<Parser::AST::Node>]
|
@@ -39,7 +19,7 @@ module Mutant
|
|
39
19
|
|
40
20
|
private
|
41
21
|
|
42
|
-
def initialize(
|
22
|
+
def initialize(_attributes)
|
43
23
|
super
|
44
24
|
|
45
25
|
@output = Set.new
|
@@ -60,10 +40,6 @@ module Mutant
|
|
60
40
|
output << object
|
61
41
|
end
|
62
42
|
|
63
|
-
def run(mutator)
|
64
|
-
mutator.call(input).each(&method(:emit))
|
65
|
-
end
|
66
|
-
|
67
43
|
def dup_input
|
68
44
|
input.dup
|
69
45
|
end
|
@@ -20,7 +20,7 @@ module Mutant
|
|
20
20
|
info 'Jobs: %s', object.jobs || 'auto'
|
21
21
|
info 'Includes: %s', object.includes
|
22
22
|
info 'Requires: %s', object.requires
|
23
|
-
info 'MutationTimeout: %0.9g', object.
|
23
|
+
info 'MutationTimeout: %0.9g', object.mutation.timeout if object.mutation.timeout
|
24
24
|
end
|
25
25
|
# rubocop:enable Metrics/AbcSize
|
26
26
|
|
@@ -3,16 +3,17 @@
|
|
3
3
|
module Mutant
|
4
4
|
class Subject
|
5
5
|
class Config
|
6
|
-
include Adamantium, Anima.new(:inline_disable)
|
6
|
+
include Adamantium, Anima.new(:inline_disable, :mutation)
|
7
7
|
|
8
|
-
DEFAULT = new(inline_disable: false)
|
8
|
+
DEFAULT = new(inline_disable: false, mutation: Mutation::Config::DEFAULT)
|
9
9
|
|
10
10
|
DISABLE_REGEXP = /(\s|^)mutant:disable(?:\s|$)/.freeze
|
11
11
|
SYNTAX_REGEXP = /\A(?:#|=begin\n)/.freeze
|
12
12
|
|
13
|
-
def self.parse(comments)
|
13
|
+
def self.parse(comments:, mutation:)
|
14
14
|
new(
|
15
|
-
inline_disable: comments.any? { |comment| DISABLE_REGEXP.match?(comment_body(comment)) }
|
15
|
+
inline_disable: comments.any? { |comment| DISABLE_REGEXP.match?(comment_body(comment)) },
|
16
|
+
mutation: mutation
|
16
17
|
)
|
17
18
|
end
|
18
19
|
|
data/lib/mutant/subject.rb
CHANGED
@@ -12,7 +12,10 @@ module Mutant
|
|
12
12
|
# @return [undefined]
|
13
13
|
def mutations
|
14
14
|
[neutral_mutation].concat(
|
15
|
-
Mutator.mutate(
|
15
|
+
Mutator::Node.mutate(
|
16
|
+
config: config.mutation,
|
17
|
+
node: node
|
18
|
+
).map do |mutant|
|
16
19
|
Mutation::Evil.new(self, wrap_node(mutant))
|
17
20
|
end
|
18
21
|
)
|
data/lib/mutant/transform.rb
CHANGED
@@ -421,6 +421,20 @@ module Mutant
|
|
421
421
|
end
|
422
422
|
end # Sequence
|
423
423
|
|
424
|
+
# Always successful transformation
|
425
|
+
class Success < self
|
426
|
+
include Concord.new(:block)
|
427
|
+
|
428
|
+
# Apply transformation to input
|
429
|
+
#
|
430
|
+
# @param [Object]
|
431
|
+
#
|
432
|
+
# @return [Either<Error, Object>]
|
433
|
+
def call(input)
|
434
|
+
success(block.call(input))
|
435
|
+
end
|
436
|
+
end # Sequence
|
437
|
+
|
424
438
|
# Generic exception transformer
|
425
439
|
class Exception < self
|
426
440
|
include Concord.new(:error_class, :block)
|
data/lib/mutant/version.rb
CHANGED
data/lib/mutant.rb
CHANGED
@@ -93,6 +93,7 @@ require 'mutant/parallel/source'
|
|
93
93
|
require 'mutant/parallel/worker'
|
94
94
|
require 'mutant/require_highjack'
|
95
95
|
require 'mutant/mutation'
|
96
|
+
require 'mutant/mutation/config'
|
96
97
|
require 'mutant/mutator'
|
97
98
|
require 'mutant/mutator/util'
|
98
99
|
require 'mutant/mutator/util/array'
|
@@ -288,7 +289,7 @@ module Mutant
|
|
288
289
|
isolation: Mutant::Isolation::Fork.new(WORLD),
|
289
290
|
jobs: nil,
|
290
291
|
matcher: Matcher::Config::DEFAULT,
|
291
|
-
|
292
|
+
mutation: Mutation::Config::DEFAULT,
|
292
293
|
reporter: Reporter::CLI.build(WORLD.stdout),
|
293
294
|
requires: EMPTY_ARRAY,
|
294
295
|
zombie: false
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mutant
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.11.
|
4
|
+
version: 0.11.12
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Markus Schirp
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-06-
|
11
|
+
date: 2022-06-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: diff-lcs
|
@@ -249,6 +249,7 @@ files:
|
|
249
249
|
- lib/mutant/meta/example/dsl.rb
|
250
250
|
- lib/mutant/meta/example/verification.rb
|
251
251
|
- lib/mutant/mutation.rb
|
252
|
+
- lib/mutant/mutation/config.rb
|
252
253
|
- lib/mutant/mutator.rb
|
253
254
|
- lib/mutant/mutator/node.rb
|
254
255
|
- lib/mutant/mutator/node/and_asgn.rb
|