mutant 0.7.3 → 0.7.4
Sign up to get free protection for your applications and to get access to all the features.
- 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/version.rb
CHANGED
@@ -10,6 +10,10 @@ module Mutant
|
|
10
10
|
#
|
11
11
|
# @api private
|
12
12
|
#
|
13
|
+
# Probably one of the only valid uses of eval.
|
14
|
+
#
|
15
|
+
# rubocop:disable Lint/Eval
|
16
|
+
#
|
13
17
|
def zombify(namespace)
|
14
18
|
$stderr.puts("Zombifying #{path}")
|
15
19
|
eval(
|
@@ -33,15 +37,7 @@ module Mutant
|
|
33
37
|
# @api private
|
34
38
|
#
|
35
39
|
def self.find(logical_name)
|
36
|
-
file_name =
|
37
|
-
case ::File.extname(logical_name)
|
38
|
-
when '.so'
|
39
|
-
return
|
40
|
-
when '.rb'
|
41
|
-
logical_name
|
42
|
-
else
|
43
|
-
"#{logical_name}.rb"
|
44
|
-
end
|
40
|
+
file_name = expand_file_name(logical_name)
|
45
41
|
|
46
42
|
$LOAD_PATH.each do |path|
|
47
43
|
path = Pathname.new(path).join(file_name)
|
@@ -52,6 +48,29 @@ module Mutant
|
|
52
48
|
nil
|
53
49
|
end
|
54
50
|
|
51
|
+
# Return expanded file name
|
52
|
+
#
|
53
|
+
# @param [String] logical_name
|
54
|
+
#
|
55
|
+
# @return [nil]
|
56
|
+
# if no expansion is possible
|
57
|
+
#
|
58
|
+
# @return [String]
|
59
|
+
#
|
60
|
+
# @api private
|
61
|
+
#
|
62
|
+
def self.expand_file_name(logical_name)
|
63
|
+
case ::File.extname(logical_name)
|
64
|
+
when '.so'
|
65
|
+
return
|
66
|
+
when '.rb'
|
67
|
+
logical_name
|
68
|
+
else
|
69
|
+
"#{logical_name}.rb"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
private_class_method :expand_file_name
|
73
|
+
|
55
74
|
private
|
56
75
|
|
57
76
|
# Return node
|
data/meta/if.rb
CHANGED
@@ -14,6 +14,12 @@ Mutant::Meta::Example.add do
|
|
14
14
|
# Deleted else branch
|
15
15
|
mutation 'if condition; true end'
|
16
16
|
|
17
|
+
# Promote if branch
|
18
|
+
mutation 'true'
|
19
|
+
|
20
|
+
# Promote else branch
|
21
|
+
mutation 'false'
|
22
|
+
|
17
23
|
# Deleted if branch resulting in unless rendering
|
18
24
|
mutation 'unless condition; false; end'
|
19
25
|
|
@@ -39,6 +45,7 @@ Mutant::Meta::Example.add do
|
|
39
45
|
mutation 'if true; true; end'
|
40
46
|
mutation 'if false; true; end'
|
41
47
|
mutation 'if nil; true; end'
|
48
|
+
mutation 'true'
|
42
49
|
end
|
43
50
|
|
44
51
|
Mutant::Meta::Example.add do
|
@@ -52,4 +59,5 @@ Mutant::Meta::Example.add do
|
|
52
59
|
mutation 'unless condition; false; end'
|
53
60
|
mutation 'unless condition; nil; end'
|
54
61
|
mutation 'if condition; true; end'
|
62
|
+
mutation 'true'
|
55
63
|
end
|
data/meta/match_current_line.rb
CHANGED
data/spec/support/corpus.rb
CHANGED
data/spec/support/fake_actor.rb
CHANGED
@@ -3,34 +3,44 @@ require 'mutant/actor'
|
|
3
3
|
# A fake actor used from specs
|
4
4
|
module FakeActor
|
5
5
|
class Expectation
|
6
|
-
include Concord::Public.new(:name, :message)
|
6
|
+
include Concord::Public.new(:name, :message, :block)
|
7
|
+
include Equalizer.new(:name, :message)
|
8
|
+
|
9
|
+
def self.new(_name, _message, _block = nil)
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
13
|
+
def verify(other)
|
14
|
+
unless eql?(other)
|
15
|
+
fail "Got:\n#{other.inspect}\nExpected:\n#{inspect}"
|
16
|
+
end
|
17
|
+
block.call(other.message) if block
|
18
|
+
end
|
7
19
|
end
|
8
20
|
|
9
21
|
class MessageSequence
|
10
|
-
include Adamantium::Flat, Concord.new(:messages)
|
22
|
+
include Adamantium::Flat, Concord::Public.new(:messages)
|
11
23
|
|
12
24
|
def self.new
|
13
25
|
super([])
|
14
26
|
end
|
15
27
|
|
16
|
-
def add(name, *message_arguments)
|
17
|
-
messages << Expectation.new(name, Mutant::Actor::Message.new(*message_arguments))
|
28
|
+
def add(name, *message_arguments, &block)
|
29
|
+
messages << Expectation.new(name, Mutant::Actor::Message.new(*message_arguments), block)
|
18
30
|
self
|
19
31
|
end
|
20
32
|
|
21
33
|
def sending(expectation)
|
22
|
-
|
34
|
+
fail "Unexpected send: #{expectation.inspect}" if messages.empty?
|
23
35
|
expected = messages.shift
|
24
|
-
|
25
|
-
raise "Got:\n#{expectation.inspect}\nExpected:\n#{expected.inspect}"
|
26
|
-
end
|
36
|
+
expected.verify(expectation)
|
27
37
|
self
|
28
38
|
end
|
29
39
|
|
30
40
|
def receiving(name)
|
31
|
-
|
41
|
+
fail "No message to read for #{name.inspect}" if messages.empty?
|
32
42
|
expected = messages.shift
|
33
|
-
|
43
|
+
fail "Unexpected message #{expected.inspect} for #{name.inspect}" unless expected.name.eql?(name)
|
34
44
|
expected.message
|
35
45
|
end
|
36
46
|
|
@@ -8,16 +8,17 @@ module SharedContext
|
|
8
8
|
def messages(&block)
|
9
9
|
let(:message_sequence) do
|
10
10
|
FakeActor::MessageSequence.new.tap do |sequence|
|
11
|
-
|
11
|
+
sequence.instance_eval(&block)
|
12
12
|
end
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
16
|
# rubocop:disable MethodLength
|
17
|
+
# rubocop:disable AbcSize
|
17
18
|
def setup_shared_context
|
18
19
|
let(:env) { double('env', config: config, subjects: [subject_a], mutations: mutations) }
|
19
|
-
let(:job_a) { Mutant::
|
20
|
-
let(:job_b) { Mutant::
|
20
|
+
let(:job_a) { Mutant::Parallel::Job.new(index: 0, payload: mutation_a) }
|
21
|
+
let(:job_b) { Mutant::Parallel::Job.new(index: 1, payload: mutation_b) }
|
21
22
|
let(:job_a_result) { Mutant::Runner::JobResult.new(job: job_a, result: mutation_a_result) }
|
22
23
|
let(:job_b_result) { Mutant::Runner::JobResult.new(job: job_b, result: mutation_b_result) }
|
23
24
|
let(:mutations) { [mutation_a, mutation_b] }
|
@@ -27,6 +28,14 @@ module SharedContext
|
|
27
28
|
let(:actor_names) { [] }
|
28
29
|
let(:message_sequence) { FakeActor::MessageSequence.new }
|
29
30
|
|
31
|
+
let(:status) do
|
32
|
+
Mutant::Parallel::Status.new(
|
33
|
+
active_jobs: [].to_set,
|
34
|
+
payload: env_result,
|
35
|
+
done: true
|
36
|
+
)
|
37
|
+
end
|
38
|
+
|
30
39
|
let(:config) do
|
31
40
|
Mutant::Config::DEFAULT.update(
|
32
41
|
actor_env: actor_env,
|
@@ -53,22 +62,6 @@ module SharedContext
|
|
53
62
|
allow(subject_a).to receive(:mutations).and_return([mutation_a, mutation_b])
|
54
63
|
end
|
55
64
|
|
56
|
-
let(:empty_status) do
|
57
|
-
Mutant::Runner::Status.new(
|
58
|
-
active_jobs: Set.new,
|
59
|
-
env_result: env_result.update(subject_results: [], runtime: 0.0),
|
60
|
-
done: false
|
61
|
-
)
|
62
|
-
end
|
63
|
-
|
64
|
-
let(:status) do
|
65
|
-
Mutant::Runner::Status.new(
|
66
|
-
active_jobs: Set.new,
|
67
|
-
env_result: env_result,
|
68
|
-
done: true
|
69
|
-
)
|
70
|
-
end
|
71
|
-
|
72
65
|
let(:env_result) do
|
73
66
|
Mutant::Result::Env.new(
|
74
67
|
env: env,
|
@@ -8,11 +8,13 @@ RSpec.describe Mutant::Env do
|
|
8
8
|
it 'warns via reporter' do
|
9
9
|
klass = Class.new do
|
10
10
|
def self.name
|
11
|
-
|
11
|
+
fail
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
-
expected_warnings = [
|
15
|
+
expected_warnings = [
|
16
|
+
"Class#name from: #{klass} raised an error: RuntimeError. #{Mutant::Env::SEMANTICS_MESSAGE}"
|
17
|
+
]
|
16
18
|
|
17
19
|
expect { subject }.to change { config.reporter.warn_calls }.from([]).to(expected_warnings)
|
18
20
|
|
@@ -46,4 +48,20 @@ RSpec.describe Mutant::Env do
|
|
46
48
|
end
|
47
49
|
end
|
48
50
|
end
|
51
|
+
|
52
|
+
context '#kill_mutation' do
|
53
|
+
let(:object) { described_class.new(config) }
|
54
|
+
let(:result) { double('Result') }
|
55
|
+
let(:mutation) { double('Mutation') }
|
56
|
+
|
57
|
+
subject { object.kill_mutation(mutation) }
|
58
|
+
|
59
|
+
before do
|
60
|
+
expect(mutation).to receive(:kill).with(config.isolation, config.integration).and_return(result)
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'uses the configured integration and isolation to kill mutation' do
|
64
|
+
should eql(Mutant::Result::Mutation.new(mutation: mutation, test_result: result))
|
65
|
+
end
|
66
|
+
end
|
49
67
|
end
|
@@ -51,7 +51,10 @@ RSpec.describe Mutant::Expression do
|
|
51
51
|
let(:input) { 'foo bar' }
|
52
52
|
|
53
53
|
it 'raises an exception' do
|
54
|
-
expect { subject }.to raise_error(
|
54
|
+
expect { subject }.to raise_error(
|
55
|
+
Mutant::Expression::InvalidExpressionError,
|
56
|
+
'Expression: "foo bar" is not valid'
|
57
|
+
)
|
55
58
|
end
|
56
59
|
end
|
57
60
|
|
@@ -0,0 +1,339 @@
|
|
1
|
+
RSpec.describe Mutant::Parallel::Master do
|
2
|
+
let(:message_sequence) { FakeActor::MessageSequence.new }
|
3
|
+
let(:actor_names) { [:master, :worker_a, :worker_b] }
|
4
|
+
let(:status) { double('Status') }
|
5
|
+
let(:sink) { FakeSink.new }
|
6
|
+
let(:processor) { double('Processor') }
|
7
|
+
let(:worker_a) { actor_env.mailbox(:worker_a).sender }
|
8
|
+
let(:worker_b) { actor_env.mailbox(:worker_b).sender }
|
9
|
+
let(:parent) { actor_env.mailbox(:parent).sender }
|
10
|
+
let(:job_payload_a) { double('Job Payload A') }
|
11
|
+
let(:job_payload_b) { double('Job Payload B') }
|
12
|
+
let(:job_result_payload_a) { double('Job Result Payload A') }
|
13
|
+
let(:job_result_payload_b) { double('Job Result Payload B') }
|
14
|
+
let(:job_a) { Mutant::Parallel::Job.new(index: 0, payload: job_payload_a) }
|
15
|
+
let(:job_b) { Mutant::Parallel::Job.new(index: 1, payload: job_payload_b) }
|
16
|
+
let(:job_result_a) { Mutant::Parallel::JobResult.new(job: job_a, payload: job_result_payload_a) }
|
17
|
+
let(:job_result_b) { Mutant::Parallel::JobResult.new(job: job_b, payload: job_result_payload_b) }
|
18
|
+
|
19
|
+
let(:actor_env) do
|
20
|
+
FakeActor::Env.new(message_sequence, actor_names)
|
21
|
+
end
|
22
|
+
|
23
|
+
shared_examples_for 'master behavior' do
|
24
|
+
it { should eql(actor_env.mailbox(:master).sender) }
|
25
|
+
|
26
|
+
it 'has expected results in sink' do
|
27
|
+
subject
|
28
|
+
expect(sink.results).to eql(expected_results)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'consumes all messages' do
|
32
|
+
subject
|
33
|
+
expect(message_sequence.messages).to eql([])
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
let(:config) do
|
38
|
+
Mutant::Parallel::Config.new(
|
39
|
+
jobs: 1,
|
40
|
+
env: actor_env,
|
41
|
+
source: Mutant::Parallel::Source::Array.new([job_payload_a, job_payload_b]),
|
42
|
+
sink: sink,
|
43
|
+
processor: processor
|
44
|
+
)
|
45
|
+
end
|
46
|
+
|
47
|
+
class FakeSink
|
48
|
+
def initialize
|
49
|
+
@results = []
|
50
|
+
@stop = false
|
51
|
+
end
|
52
|
+
|
53
|
+
attr_reader :results
|
54
|
+
|
55
|
+
def status
|
56
|
+
@results.length
|
57
|
+
end
|
58
|
+
|
59
|
+
def result(result)
|
60
|
+
@results << result
|
61
|
+
end
|
62
|
+
|
63
|
+
def stop
|
64
|
+
@stop = true
|
65
|
+
self
|
66
|
+
end
|
67
|
+
|
68
|
+
def stop?
|
69
|
+
@stop
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Needed because of rubies undefined-ivar-read-is-nil stuff
|
74
|
+
describe 'object initialization' do
|
75
|
+
let(:object) { described_class.send(:new, config, actor_env.mailbox(:master)) }
|
76
|
+
|
77
|
+
it 'initializes falsy ivars'do
|
78
|
+
expect(object.instance_variable_get(:@stop)).to be(false)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe '.call' do
|
83
|
+
|
84
|
+
before do
|
85
|
+
expect(Mutant::Parallel::Worker).to receive(:run).with(
|
86
|
+
actor: actor_env.mailbox(:worker_a),
|
87
|
+
processor: processor,
|
88
|
+
parent: actor_env.mailbox(:master).sender
|
89
|
+
).and_return(worker_a)
|
90
|
+
end
|
91
|
+
|
92
|
+
subject { described_class.call(config) }
|
93
|
+
|
94
|
+
context 'with multiple workers configured' do
|
95
|
+
let(:config) { super().update(jobs: 2) }
|
96
|
+
let(:expected_results) { [] }
|
97
|
+
|
98
|
+
before do
|
99
|
+
expect(Mutant::Parallel::Worker).to receive(:run).with(
|
100
|
+
actor: actor_env.mailbox(:worker_b),
|
101
|
+
processor: processor,
|
102
|
+
parent: actor_env.mailbox(:master).sender
|
103
|
+
).and_return(worker_b)
|
104
|
+
|
105
|
+
sink.stop
|
106
|
+
|
107
|
+
message_sequence.add(:master, :ready, worker_a)
|
108
|
+
message_sequence.add(:worker_a, :stop)
|
109
|
+
|
110
|
+
message_sequence.add(:master, :ready, worker_b)
|
111
|
+
message_sequence.add(:worker_b, :stop)
|
112
|
+
|
113
|
+
message_sequence.add(:master, :stop, parent)
|
114
|
+
message_sequence.add(:parent, :stop)
|
115
|
+
end
|
116
|
+
|
117
|
+
include_examples 'master behavior'
|
118
|
+
end
|
119
|
+
|
120
|
+
context 'explicit stop by scheduler state' do
|
121
|
+
context 'before jobs are processed' do
|
122
|
+
let(:expected_results) { [] }
|
123
|
+
|
124
|
+
before do
|
125
|
+
sink.stop
|
126
|
+
|
127
|
+
message_sequence.add(:master, :ready, worker_a)
|
128
|
+
message_sequence.add(:worker_a, :stop)
|
129
|
+
|
130
|
+
message_sequence.add(:master, :stop, parent)
|
131
|
+
message_sequence.add(:parent, :stop)
|
132
|
+
end
|
133
|
+
|
134
|
+
include_examples 'master behavior'
|
135
|
+
end
|
136
|
+
|
137
|
+
context 'while jobs are processed' do
|
138
|
+
let(:expected_results) { [job_result_payload_a] }
|
139
|
+
|
140
|
+
let(:sink) do
|
141
|
+
super().instance_eval do
|
142
|
+
def stop?
|
143
|
+
@results.length.equal?(1)
|
144
|
+
end
|
145
|
+
self
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
before do
|
150
|
+
message_sequence.add(:master, :ready, worker_a)
|
151
|
+
message_sequence.add(:worker_a, :job, job_a)
|
152
|
+
message_sequence.add(:master, :result, job_result_a)
|
153
|
+
|
154
|
+
message_sequence.add(:master, :ready, worker_a)
|
155
|
+
message_sequence.add(:worker_a, :stop)
|
156
|
+
|
157
|
+
message_sequence.add(:master, :stop, parent)
|
158
|
+
message_sequence.add(:parent, :stop)
|
159
|
+
end
|
160
|
+
|
161
|
+
it { should eql(actor_env.mailbox(:master).sender) }
|
162
|
+
|
163
|
+
it 'consumes all messages' do
|
164
|
+
expect { subject }.to change(&message_sequence.method(:consumed?)).from(false).to(true)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
context 'external stop' do
|
170
|
+
context 'after jobs are done' do
|
171
|
+
let(:expected_results) { [job_result_payload_a, job_result_payload_b] }
|
172
|
+
|
173
|
+
before do
|
174
|
+
message_sequence.add(:master, :ready, worker_a)
|
175
|
+
message_sequence.add(:worker_a, :job, job_a)
|
176
|
+
message_sequence.add(:master, :result, job_result_a)
|
177
|
+
|
178
|
+
message_sequence.add(:master, :ready, worker_a)
|
179
|
+
message_sequence.add(:worker_a, :job, job_b)
|
180
|
+
message_sequence.add(:master, :result, job_result_b)
|
181
|
+
|
182
|
+
message_sequence.add(:master, :ready, worker_a)
|
183
|
+
message_sequence.add(:worker_a, :stop)
|
184
|
+
|
185
|
+
message_sequence.add(:master, :stop, parent)
|
186
|
+
message_sequence.add(:parent, :stop)
|
187
|
+
end
|
188
|
+
|
189
|
+
include_examples 'master behavior'
|
190
|
+
end
|
191
|
+
|
192
|
+
context 'when no jobs are active' do
|
193
|
+
let(:expected_results) { [job_result_payload_a] }
|
194
|
+
|
195
|
+
before do
|
196
|
+
message_sequence.add(:master, :ready, worker_a)
|
197
|
+
message_sequence.add(:worker_a, :job, job_a)
|
198
|
+
message_sequence.add(:master, :stop, parent)
|
199
|
+
message_sequence.add(:master, :result, job_result_a)
|
200
|
+
|
201
|
+
message_sequence.add(:master, :ready, worker_a)
|
202
|
+
message_sequence.add(:worker_a, :stop)
|
203
|
+
|
204
|
+
message_sequence.add(:parent, :stop)
|
205
|
+
end
|
206
|
+
|
207
|
+
include_examples 'master behavior'
|
208
|
+
end
|
209
|
+
|
210
|
+
context 'before any job got processed' do
|
211
|
+
let(:expected_results) { [] }
|
212
|
+
|
213
|
+
before do
|
214
|
+
message_sequence.add(:master, :stop, parent)
|
215
|
+
message_sequence.add(:master, :ready, worker_a)
|
216
|
+
message_sequence.add(:worker_a, :stop)
|
217
|
+
message_sequence.add(:parent, :stop)
|
218
|
+
end
|
219
|
+
|
220
|
+
include_examples 'master behavior'
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
context 'requesting status' do
|
225
|
+
context 'when jobs are done' do
|
226
|
+
let(:expected_status) { Mutant::Parallel::Status.new(payload: 2, active_jobs: Set.new, done: true) }
|
227
|
+
let(:expected_results) { [job_result_payload_a, job_result_payload_b] }
|
228
|
+
|
229
|
+
before do
|
230
|
+
message_sequence.add(:master, :ready, worker_a)
|
231
|
+
message_sequence.add(:worker_a, :job, job_a)
|
232
|
+
message_sequence.add(:master, :result, job_result_a)
|
233
|
+
|
234
|
+
message_sequence.add(:master, :ready, worker_a)
|
235
|
+
message_sequence.add(:worker_a, :job, job_b)
|
236
|
+
message_sequence.add(:master, :result, job_result_b)
|
237
|
+
|
238
|
+
message_sequence.add(:master, :ready, worker_a)
|
239
|
+
message_sequence.add(:worker_a, :stop)
|
240
|
+
|
241
|
+
message_sequence.add(:master, :status, parent)
|
242
|
+
|
243
|
+
# Special bit to kill a mutation that results in mutable active_jobs being passed.
|
244
|
+
message_sequence.add(:parent, :status, expected_status) do |message|
|
245
|
+
expect(message.payload.active_jobs.frozen?).to be(true)
|
246
|
+
end
|
247
|
+
message_sequence.add(:master, :stop, parent)
|
248
|
+
message_sequence.add(:parent, :stop)
|
249
|
+
end
|
250
|
+
|
251
|
+
include_examples 'master behavior'
|
252
|
+
end
|
253
|
+
|
254
|
+
context 'just after scheduler stops' do
|
255
|
+
let(:expected_status) { Mutant::Parallel::Status.new(payload: 1, active_jobs: [].to_set, done: true) }
|
256
|
+
let(:expected_results) { [job_result_payload_a] }
|
257
|
+
|
258
|
+
let(:sink) do
|
259
|
+
super().instance_eval do
|
260
|
+
def stop?
|
261
|
+
@results.length.equal?(1)
|
262
|
+
end
|
263
|
+
self
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
before do
|
268
|
+
message_sequence.add(:master, :ready, worker_a)
|
269
|
+
message_sequence.add(:worker_a, :job, job_a)
|
270
|
+
message_sequence.add(:master, :result, job_result_a)
|
271
|
+
|
272
|
+
message_sequence.add(:master, :status, parent)
|
273
|
+
message_sequence.add(:parent, :status, expected_status)
|
274
|
+
|
275
|
+
message_sequence.add(:master, :ready, worker_a)
|
276
|
+
message_sequence.add(:worker_a, :stop)
|
277
|
+
|
278
|
+
message_sequence.add(:master, :stop, parent)
|
279
|
+
message_sequence.add(:parent, :stop)
|
280
|
+
end
|
281
|
+
|
282
|
+
include_examples 'master behavior'
|
283
|
+
end
|
284
|
+
|
285
|
+
context 'when jobs are active' do
|
286
|
+
let(:expected_status) { Mutant::Parallel::Status.new(payload: 1, active_jobs: [job_b].to_set, done: false) }
|
287
|
+
let(:expected_results) { [job_result_payload_a, job_result_payload_b] }
|
288
|
+
|
289
|
+
before do
|
290
|
+
message_sequence.add(:master, :ready, worker_a)
|
291
|
+
message_sequence.add(:worker_a, :job, job_a)
|
292
|
+
message_sequence.add(:master, :result, job_result_a)
|
293
|
+
|
294
|
+
message_sequence.add(:master, :ready, worker_a)
|
295
|
+
message_sequence.add(:worker_a, :job, job_b)
|
296
|
+
|
297
|
+
message_sequence.add(:master, :status, parent)
|
298
|
+
message_sequence.add(:parent, :status, expected_status)
|
299
|
+
|
300
|
+
message_sequence.add(:master, :result, job_result_b)
|
301
|
+
|
302
|
+
message_sequence.add(:master, :ready, worker_a)
|
303
|
+
message_sequence.add(:worker_a, :stop)
|
304
|
+
|
305
|
+
message_sequence.add(:master, :stop, parent)
|
306
|
+
message_sequence.add(:parent, :stop)
|
307
|
+
end
|
308
|
+
|
309
|
+
include_examples 'master behavior'
|
310
|
+
end
|
311
|
+
|
312
|
+
context 'before jobs are done' do
|
313
|
+
let(:expected_status) { Mutant::Parallel::Status.new(payload: 0, active_jobs: Set.new, done: false) }
|
314
|
+
let(:expected_results) { [] }
|
315
|
+
|
316
|
+
before do
|
317
|
+
message_sequence.add(:master, :status, parent)
|
318
|
+
message_sequence.add(:parent, :status, expected_status)
|
319
|
+
message_sequence.add(:master, :stop, parent)
|
320
|
+
message_sequence.add(:master, :ready, worker_a)
|
321
|
+
message_sequence.add(:worker_a, :stop)
|
322
|
+
message_sequence.add(:parent, :stop)
|
323
|
+
end
|
324
|
+
|
325
|
+
include_examples 'master behavior'
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
context 'unhandled message received' do
|
330
|
+
before do
|
331
|
+
message_sequence.add(:master, :foo, parent)
|
332
|
+
end
|
333
|
+
|
334
|
+
it 'raises message' do
|
335
|
+
expect { subject }.to raise_error(Mutant::Actor::ProtocolError, 'Unexpected message: :foo')
|
336
|
+
end
|
337
|
+
end
|
338
|
+
end
|
339
|
+
end
|