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