mutant 0.7.3 → 0.7.4
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/.rubocop.yml +2 -0
- data/.travis.yml +3 -2
- data/Changelog.md +7 -2
- data/Gemfile +0 -1
- data/Gemfile.devtools +9 -37
- data/README.md +1 -1
- data/circle.yml +1 -1
- data/config/flay.yml +1 -1
- data/config/reek.yml +6 -19
- data/config/rubocop.yml +58 -63
- data/lib/mutant.rb +8 -4
- data/lib/mutant/ast.rb +1 -1
- data/lib/mutant/cli.rb +12 -6
- data/lib/mutant/env.rb +17 -1
- data/lib/mutant/isolation.rb +4 -2
- data/lib/mutant/loader.rb +4 -0
- data/lib/mutant/matcher/compiler.rb +2 -0
- data/lib/mutant/mutation.rb +2 -0
- data/lib/mutant/mutator/node/const.rb +1 -1
- data/lib/mutant/mutator/node/generic.rb +1 -1
- data/lib/mutant/mutator/node/if.rb +2 -0
- data/lib/mutant/parallel.rb +93 -0
- data/lib/mutant/{runner → parallel}/master.rb +90 -45
- data/lib/mutant/parallel/source.rb +73 -0
- data/lib/mutant/{runner → parallel}/worker.rb +13 -30
- data/lib/mutant/reporter/cli.rb +8 -11
- data/lib/mutant/reporter/cli/printer.rb +14 -8
- data/lib/mutant/result.rb +0 -10
- data/lib/mutant/runner.rb +49 -43
- data/lib/mutant/runner/{scheduler.rb → sink.rb} +9 -68
- data/lib/mutant/version.rb +1 -1
- data/lib/mutant/zombifier/file.rb +28 -9
- data/meta/if.rb +8 -0
- data/meta/match_current_line.rb +1 -0
- data/spec/integration/mutant/corpus_spec.rb +1 -1
- data/spec/integration/mutant/null_spec.rb +1 -1
- data/spec/integration/mutant/rspec_spec.rb +1 -1
- data/spec/integration/mutant/test_mutator_handles_types_spec.rb +1 -1
- data/spec/integration/mutant/zombie_spec.rb +1 -1
- data/spec/support/corpus.rb +2 -0
- data/spec/support/fake_actor.rb +20 -10
- data/spec/support/mutation_verifier.rb +2 -0
- data/spec/support/shared_context.rb +12 -19
- data/spec/unit/mutant/env_spec.rb +20 -2
- data/spec/unit/mutant/expression_spec.rb +4 -1
- data/spec/unit/mutant/parallel/master_spec.rb +339 -0
- data/spec/unit/mutant/parallel/source/array_spec.rb +47 -0
- data/spec/unit/mutant/{runner → parallel}/worker_spec.rb +23 -26
- data/spec/unit/mutant/parallel_spec.rb +16 -0
- data/spec/unit/mutant/reporter/cli_spec.rb +1 -1
- data/spec/unit/mutant/reporter/trace_spec.rb +9 -0
- data/spec/unit/mutant/result/env_spec.rb +0 -55
- data/spec/unit/mutant/runner/driver_spec.rb +26 -0
- data/spec/unit/mutant/runner/sink_spec.rb +162 -0
- data/spec/unit/mutant/runner_spec.rb +60 -63
- data/spec/unit/mutant/warning_filter_spec.rb +2 -1
- data/test_app/Gemfile.devtools +9 -37
- metadata +21 -11
- data/spec/unit/mutant/runner/master_spec.rb +0 -199
- data/spec/unit/mutant/runner/scheduler_spec.rb +0 -161
data/lib/mutant/env.rb
CHANGED
@@ -71,6 +71,22 @@ module Mutant
|
|
71
71
|
#
|
72
72
|
attr_reader :matchable_scopes
|
73
73
|
|
74
|
+
# Kill mutation
|
75
|
+
#
|
76
|
+
# @param [Mutation] mutation
|
77
|
+
#
|
78
|
+
# @return [Result::Mutation]
|
79
|
+
#
|
80
|
+
# @api private
|
81
|
+
#
|
82
|
+
def kill_mutation(mutation)
|
83
|
+
test_result = mutation.kill(config.isolation, config.integration)
|
84
|
+
Result::Mutation.new(
|
85
|
+
mutation: mutation,
|
86
|
+
test_result: test_result
|
87
|
+
)
|
88
|
+
end
|
89
|
+
|
74
90
|
private
|
75
91
|
|
76
92
|
# Return scope name
|
@@ -109,7 +125,7 @@ module Mutant
|
|
109
125
|
def expression(scope)
|
110
126
|
name = scope_name(scope) or return
|
111
127
|
|
112
|
-
unless name.
|
128
|
+
unless name.instance_of?(String)
|
113
129
|
warn("#{scope.class}#name from: #{scope.inspect} returned #{name.inspect}. #{SEMANTICS_MESSAGE}")
|
114
130
|
return
|
115
131
|
end
|
data/lib/mutant/isolation.rb
CHANGED
@@ -17,7 +17,7 @@ module Mutant
|
|
17
17
|
def self.call(&block)
|
18
18
|
block.call
|
19
19
|
rescue => exception
|
20
|
-
|
20
|
+
raise Error, exception
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
@@ -37,6 +37,8 @@ module Mutant
|
|
37
37
|
#
|
38
38
|
# @api private
|
39
39
|
#
|
40
|
+
# rubocop:disable MethodLength
|
41
|
+
#
|
40
42
|
def self.call(&block)
|
41
43
|
reader, writer = IO.pipe.map(&:binmode)
|
42
44
|
|
@@ -52,7 +54,7 @@ module Mutant
|
|
52
54
|
writer.close
|
53
55
|
Marshal.load(reader.read)
|
54
56
|
rescue => exception
|
55
|
-
|
57
|
+
raise Error, exception
|
56
58
|
ensure
|
57
59
|
Process.waitpid(pid) if pid
|
58
60
|
end
|
data/lib/mutant/loader.rb
CHANGED
data/lib/mutant/mutation.rb
CHANGED
@@ -19,7 +19,7 @@ module Mutant
|
|
19
19
|
emit_singletons unless parent_node && n_const?(parent_node)
|
20
20
|
emit_type(nil, *children.drop(1))
|
21
21
|
children.each_with_index do |child, index|
|
22
|
-
mutate_child(index) if child.
|
22
|
+
mutate_child(index) if child.instance_of?(Parser::AST::Node)
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
@@ -47,6 +47,7 @@ module Mutant
|
|
47
47
|
def mutate_if_branch
|
48
48
|
emit_type(condition, else_branch, nil) if else_branch
|
49
49
|
return unless if_branch
|
50
|
+
emit(if_branch)
|
50
51
|
emit_if_branch_mutations
|
51
52
|
emit_type(condition, if_branch, nil)
|
52
53
|
end
|
@@ -59,6 +60,7 @@ module Mutant
|
|
59
60
|
#
|
60
61
|
def mutate_else_branch
|
61
62
|
return unless else_branch
|
63
|
+
emit(else_branch)
|
62
64
|
emit_else_branch_mutations
|
63
65
|
emit_type(condition, nil, else_branch)
|
64
66
|
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module Mutant
|
2
|
+
# Parallel excecution engine of arbitrary payloads
|
3
|
+
module Parallel
|
4
|
+
|
5
|
+
# Driver for parallelized execution
|
6
|
+
class Driver
|
7
|
+
include Concord.new(:binding)
|
8
|
+
|
9
|
+
# Return scheduler status
|
10
|
+
#
|
11
|
+
# @return [Object]
|
12
|
+
#
|
13
|
+
# @api private
|
14
|
+
#
|
15
|
+
def status
|
16
|
+
binding.call(__method__)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Stop master gracefully
|
20
|
+
#
|
21
|
+
# @return [self]
|
22
|
+
#
|
23
|
+
# @api private
|
24
|
+
#
|
25
|
+
def stop
|
26
|
+
binding.call(__method__)
|
27
|
+
self
|
28
|
+
end
|
29
|
+
end # Driver
|
30
|
+
|
31
|
+
# Run async computation returing driver
|
32
|
+
#
|
33
|
+
# @return [Driver]
|
34
|
+
#
|
35
|
+
# @api private
|
36
|
+
#
|
37
|
+
def self.async(config)
|
38
|
+
Driver.new(config.env.new_mailbox.bind(Master.call(config)))
|
39
|
+
end
|
40
|
+
|
41
|
+
# Job result sink
|
42
|
+
class Sink
|
43
|
+
include AbstractType
|
44
|
+
|
45
|
+
# Process job result
|
46
|
+
#
|
47
|
+
# @param [Object]
|
48
|
+
#
|
49
|
+
# @return [self]
|
50
|
+
#
|
51
|
+
# @api private
|
52
|
+
#
|
53
|
+
abstract_method :result
|
54
|
+
|
55
|
+
# Return status
|
56
|
+
#
|
57
|
+
# @return [Object]
|
58
|
+
#
|
59
|
+
# @api private
|
60
|
+
#
|
61
|
+
abstract_method :status
|
62
|
+
|
63
|
+
# Test if processing should stop
|
64
|
+
#
|
65
|
+
# @return [Boolean]
|
66
|
+
#
|
67
|
+
# @api private
|
68
|
+
#
|
69
|
+
abstract_method :stop?
|
70
|
+
end # Sink
|
71
|
+
|
72
|
+
# Job to push to workers
|
73
|
+
class Job
|
74
|
+
include Adamantium::Flat, Anima.new(:index, :payload)
|
75
|
+
end # Job
|
76
|
+
|
77
|
+
# Job result object received from workers
|
78
|
+
class JobResult
|
79
|
+
include Adamantium::Flat, Anima.new(:job, :payload)
|
80
|
+
end # JobResult
|
81
|
+
|
82
|
+
# Parallel run configuration
|
83
|
+
class Config
|
84
|
+
include Anima::Update, Adamantium::Flat, Anima.new(:env, :processor, :source, :sink, :jobs)
|
85
|
+
end # Config
|
86
|
+
|
87
|
+
# Parallel execution status
|
88
|
+
class Status
|
89
|
+
include Adamantium::Flat, Anima::Update, Anima.new(:payload, :done, :active_jobs)
|
90
|
+
end
|
91
|
+
|
92
|
+
end # Parallel
|
93
|
+
end # Mutant
|
@@ -1,8 +1,8 @@
|
|
1
1
|
module Mutant
|
2
|
-
|
3
|
-
# Master
|
2
|
+
module Parallel
|
3
|
+
# Master parallel worker
|
4
4
|
class Master
|
5
|
-
include Concord.new(:
|
5
|
+
include Concord.new(:config, :actor)
|
6
6
|
|
7
7
|
private_class_method :new
|
8
8
|
|
@@ -14,14 +14,12 @@ module Mutant
|
|
14
14
|
#
|
15
15
|
# @api private
|
16
16
|
#
|
17
|
-
def self.call(
|
18
|
-
|
19
|
-
new(
|
17
|
+
def self.call(config)
|
18
|
+
config.env.spawn do |actor|
|
19
|
+
new(config, actor).__send__(:run)
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
-
private
|
24
|
-
|
25
23
|
# Initialize object
|
26
24
|
#
|
27
25
|
# @return [undefined]
|
@@ -31,30 +29,44 @@ module Mutant
|
|
31
29
|
def initialize(*)
|
32
30
|
super
|
33
31
|
|
34
|
-
@
|
35
|
-
@workers
|
36
|
-
@
|
37
|
-
@
|
32
|
+
@stop = false
|
33
|
+
@workers = 0
|
34
|
+
@active_jobs = Set.new
|
35
|
+
@index = 0
|
38
36
|
end
|
39
37
|
|
38
|
+
private
|
39
|
+
|
40
40
|
# Run work loop
|
41
41
|
#
|
42
42
|
# @return [self]
|
43
43
|
#
|
44
44
|
# @api private
|
45
45
|
#
|
46
|
+
# rubocop:disable MethodLength
|
47
|
+
#
|
46
48
|
def run
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
49
|
+
config.jobs.times do
|
50
|
+
@workers += 1
|
51
|
+
config.env.spawn do |worker_actor|
|
52
|
+
Worker.run(
|
53
|
+
actor: worker_actor,
|
54
|
+
processor: config.processor,
|
55
|
+
parent: actor.sender
|
56
|
+
)
|
57
|
+
end
|
53
58
|
end
|
54
59
|
|
55
60
|
receive_loop
|
56
61
|
end
|
57
62
|
|
63
|
+
MAP = IceNine.deep_freeze(
|
64
|
+
ready: :handle_ready,
|
65
|
+
status: :handle_status,
|
66
|
+
result: :handle_result,
|
67
|
+
stop: :handle_stop
|
68
|
+
)
|
69
|
+
|
58
70
|
# Handle messages
|
59
71
|
#
|
60
72
|
# @param [Actor::Message] message
|
@@ -65,18 +77,10 @@ module Mutant
|
|
65
77
|
#
|
66
78
|
def handle(message)
|
67
79
|
type, payload = message.type, message.payload
|
68
|
-
|
69
|
-
when :ready
|
70
|
-
ready_worker(payload)
|
71
|
-
when :status
|
72
|
-
handle_status(payload)
|
73
|
-
when :result
|
74
|
-
handle_result(payload)
|
75
|
-
when :stop
|
76
|
-
handle_stop(payload)
|
77
|
-
else
|
80
|
+
method = MAP.fetch(type) do
|
78
81
|
fail Actor::ProtocolError, "Unexpected message: #{type.inspect}"
|
79
82
|
end
|
83
|
+
__send__(method, payload)
|
80
84
|
end
|
81
85
|
|
82
86
|
# Run receive loop
|
@@ -86,10 +90,7 @@ module Mutant
|
|
86
90
|
# @api private
|
87
91
|
#
|
88
92
|
def receive_loop
|
89
|
-
|
90
|
-
break if @workers.zero? && @stop
|
91
|
-
handle(actor.receiver.call)
|
92
|
-
end
|
93
|
+
handle(actor.receiver.call) until @workers.zero? && @stop
|
93
94
|
end
|
94
95
|
|
95
96
|
# Handle status
|
@@ -101,7 +102,12 @@ module Mutant
|
|
101
102
|
# @api private
|
102
103
|
#
|
103
104
|
def handle_status(sender)
|
104
|
-
|
105
|
+
status = Status.new(
|
106
|
+
payload: sink.status,
|
107
|
+
done: sink.stop? || @workers.zero?,
|
108
|
+
active_jobs: @active_jobs.dup.freeze
|
109
|
+
)
|
110
|
+
sender.call(Actor::Message.new(:status, status))
|
105
111
|
end
|
106
112
|
|
107
113
|
# Handle result
|
@@ -113,9 +119,8 @@ module Mutant
|
|
113
119
|
# @api private
|
114
120
|
#
|
115
121
|
def handle_result(job_result)
|
116
|
-
|
117
|
-
|
118
|
-
@stopping = env.config.fail_fast && @scheduler.status.done
|
122
|
+
@active_jobs.delete(job_result.job)
|
123
|
+
sink.result(job_result.payload)
|
119
124
|
end
|
120
125
|
|
121
126
|
# Handle stop
|
@@ -127,7 +132,6 @@ module Mutant
|
|
127
132
|
# @api private
|
128
133
|
#
|
129
134
|
def handle_stop(sender)
|
130
|
-
@stopping = true
|
131
135
|
@stop = true
|
132
136
|
receive_loop
|
133
137
|
sender.call(Actor::Message.new(:stop))
|
@@ -141,18 +145,29 @@ module Mutant
|
|
141
145
|
#
|
142
146
|
# @api private
|
143
147
|
#
|
144
|
-
def
|
145
|
-
if
|
148
|
+
def handle_ready(sender)
|
149
|
+
if stop_work?
|
146
150
|
stop_worker(sender)
|
147
151
|
return
|
148
152
|
end
|
149
153
|
|
150
|
-
job
|
154
|
+
sender.call(Actor::Message.new(:job, next_job))
|
155
|
+
end
|
151
156
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
157
|
+
# Return next job if any
|
158
|
+
#
|
159
|
+
# @return [Job]
|
160
|
+
# if next job is available
|
161
|
+
#
|
162
|
+
# @return [nil]
|
163
|
+
#
|
164
|
+
def next_job
|
165
|
+
Job.new(
|
166
|
+
index: @index,
|
167
|
+
payload: source.next
|
168
|
+
).tap do |job|
|
169
|
+
@index += 1
|
170
|
+
@active_jobs << job
|
156
171
|
end
|
157
172
|
end
|
158
173
|
|
@@ -169,6 +184,36 @@ module Mutant
|
|
169
184
|
sender.call(Actor::Message.new(:stop))
|
170
185
|
end
|
171
186
|
|
187
|
+
# Test if scheduling stopped
|
188
|
+
#
|
189
|
+
# @return [Boolean]
|
190
|
+
#
|
191
|
+
# @api private
|
192
|
+
#
|
193
|
+
def stop_work?
|
194
|
+
@stop || !source.next? || sink.stop?
|
195
|
+
end
|
196
|
+
|
197
|
+
# Return source
|
198
|
+
#
|
199
|
+
# @return [Source]
|
200
|
+
#
|
201
|
+
# @api private
|
202
|
+
#
|
203
|
+
def source
|
204
|
+
config.source
|
205
|
+
end
|
206
|
+
|
207
|
+
# Return source
|
208
|
+
#
|
209
|
+
# @return [Sink]
|
210
|
+
#
|
211
|
+
# @api private
|
212
|
+
#
|
213
|
+
def sink
|
214
|
+
config.sink
|
215
|
+
end
|
216
|
+
|
172
217
|
end # Master
|
173
|
-
end #
|
218
|
+
end # Parallel
|
174
219
|
end # Mutant
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Mutant
|
2
|
+
module Parallel
|
3
|
+
# Job source for parallel execution
|
4
|
+
class Source
|
5
|
+
include AbstractType
|
6
|
+
|
7
|
+
NoJobError = Class.new(RuntimeError)
|
8
|
+
|
9
|
+
# Return next job
|
10
|
+
#
|
11
|
+
# @return [Object]
|
12
|
+
#
|
13
|
+
# @raise [NoJobError]
|
14
|
+
# when no next job is available
|
15
|
+
#
|
16
|
+
# @api private
|
17
|
+
#
|
18
|
+
abstract_method :next
|
19
|
+
|
20
|
+
# Test if next job is available
|
21
|
+
#
|
22
|
+
# @return [Boolean]
|
23
|
+
#
|
24
|
+
# @api private
|
25
|
+
#
|
26
|
+
abstract_method :next?
|
27
|
+
|
28
|
+
# Job source backed by a finite array
|
29
|
+
class Array
|
30
|
+
include Concord.new(:jobs)
|
31
|
+
|
32
|
+
# Initialize objecto
|
33
|
+
#
|
34
|
+
# @return [undefined]
|
35
|
+
#
|
36
|
+
# @api private
|
37
|
+
#
|
38
|
+
def initialize(*)
|
39
|
+
super
|
40
|
+
|
41
|
+
@next_index = 0
|
42
|
+
end
|
43
|
+
|
44
|
+
# Test if next job is available
|
45
|
+
#
|
46
|
+
# @return [Boolean]
|
47
|
+
#
|
48
|
+
# @api private
|
49
|
+
#
|
50
|
+
def next?
|
51
|
+
@next_index < jobs.length
|
52
|
+
end
|
53
|
+
|
54
|
+
# Return next job
|
55
|
+
#
|
56
|
+
# @return [Object]
|
57
|
+
#
|
58
|
+
# @raise [NoJobError]
|
59
|
+
# when no next job is available
|
60
|
+
#
|
61
|
+
# @api private
|
62
|
+
#
|
63
|
+
def next
|
64
|
+
fail NoJobError unless next?
|
65
|
+
jobs.fetch(@next_index).tap do
|
66
|
+
@next_index += 1
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
end # Array
|
71
|
+
end # Source
|
72
|
+
end # Parallel
|
73
|
+
end # Mutant
|