mutant 0.6.7 → 0.7.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Changelog.md +10 -0
- data/README.md +1 -1
- data/config/flay.yml +1 -1
- data/config/reek.yml +11 -40
- data/config/rubocop.yml +1 -1
- data/lib/mutant.rb +10 -2
- data/lib/mutant/actor.rb +64 -0
- data/lib/mutant/actor/actor.rb +50 -0
- data/lib/mutant/actor/env.rb +35 -0
- data/lib/mutant/actor/mailbox.rb +53 -0
- data/lib/mutant/actor/receiver.rb +48 -0
- data/lib/mutant/actor/sender.rb +27 -0
- data/lib/mutant/ast/types.rb +1 -1
- data/lib/mutant/cli.rb +2 -0
- data/lib/mutant/config.rb +2 -1
- data/lib/mutant/env.rb +6 -2
- data/lib/mutant/expression/methods.rb +1 -1
- data/lib/mutant/integration.rb +11 -1
- data/lib/mutant/isolation.rb +1 -2
- data/lib/mutant/meta/example.rb +1 -1
- data/lib/mutant/mutation.rb +47 -21
- data/lib/mutant/mutator/node.rb +1 -1
- data/lib/mutant/mutator/node/literal/symbol.rb +1 -1
- data/lib/mutant/mutator/node/send.rb +4 -3
- data/lib/mutant/reporter/cli.rb +2 -0
- data/lib/mutant/reporter/cli/format.rb +23 -36
- data/lib/mutant/reporter/cli/printer.rb +66 -27
- data/lib/mutant/result.rb +45 -58
- data/lib/mutant/runner.rb +47 -154
- data/lib/mutant/runner/master.rb +174 -0
- data/lib/mutant/runner/scheduler.rb +141 -0
- data/lib/mutant/runner/worker.rb +93 -0
- data/lib/mutant/subject/method/instance.rb +1 -15
- data/lib/mutant/test.rb +2 -15
- data/lib/mutant/version.rb +1 -1
- data/meta/send.rb +16 -0
- data/mutant-rspec.gemspec +1 -1
- data/mutant.gemspec +1 -1
- data/spec/integration/mutant/rspec_spec.rb +0 -6
- data/spec/spec_helper.rb +9 -1
- data/spec/support/fake_actor.rb +93 -0
- data/spec/support/shared_context.rb +135 -0
- data/spec/unit/mutant/actor/actor_spec.rb +35 -0
- data/spec/unit/mutant/actor/binding_spec.rb +32 -0
- data/spec/unit/mutant/actor/env_spec.rb +49 -0
- data/spec/unit/mutant/actor/message_spec.rb +23 -0
- data/spec/unit/mutant/actor/receiver_spec.rb +60 -0
- data/spec/unit/mutant/actor/sender_spec.rb +22 -0
- data/spec/unit/mutant/cli_spec.rb +17 -4
- data/spec/unit/mutant/env_spec.rb +2 -2
- data/spec/unit/mutant/mailbox_spec.rb +33 -0
- data/spec/unit/mutant/mutation_spec.rb +52 -18
- data/spec/unit/mutant/mutator/registry_spec.rb +4 -4
- data/spec/unit/mutant/reporter/cli_spec.rb +131 -249
- data/spec/unit/mutant/result/env_spec.rb +55 -0
- data/spec/unit/mutant/result/subject_spec.rb +43 -0
- data/spec/unit/mutant/runner/master_spec.rb +199 -0
- data/spec/unit/mutant/runner/scheduler_spec.rb +161 -0
- data/spec/unit/mutant/runner/worker_spec.rb +73 -0
- data/spec/unit/mutant/runner_spec.rb +60 -118
- data/spec/unit/mutant/subject/method/instance_spec.rb +18 -31
- data/spec/unit/mutant/warning_filter_spec.rb +1 -1
- metadata +39 -14
- data/lib/mutant/runner/collector.rb +0 -133
- data/lib/mutant/warning_expectation.rb +0 -47
- data/spec/unit/mutant/runner/collector_spec.rb +0 -198
- data/spec/unit/mutant/test_spec.rb +0 -23
- data/spec/unit/mutant/warning_expectation_spec.rb +0 -80
- data/test_app/Gemfile.rspec2 +0 -6
@@ -0,0 +1,55 @@
|
|
1
|
+
RSpec.describe Mutant::Result::Env do
|
2
|
+
let(:object) do
|
3
|
+
described_class.new(
|
4
|
+
env: double('Env', config: config),
|
5
|
+
runtime: double('Runtime'),
|
6
|
+
subject_results: subject_results
|
7
|
+
)
|
8
|
+
end
|
9
|
+
|
10
|
+
let(:config) { double('Config', fail_fast: fail_fast) }
|
11
|
+
|
12
|
+
describe '#continue?' do
|
13
|
+
subject { object.continue? }
|
14
|
+
|
15
|
+
context 'config sets fail_fast flag' do
|
16
|
+
let(:fail_fast) { true }
|
17
|
+
|
18
|
+
context 'when mutation results are empty' do
|
19
|
+
let(:subject_results) { [] }
|
20
|
+
|
21
|
+
it { should be(true) }
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'with failing mutation result' do
|
25
|
+
let(:subject_results) { [double('Subject Result', success?: false)] }
|
26
|
+
|
27
|
+
it { should be(false) }
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'with successful mutation result' do
|
31
|
+
let(:subject_results) { [double('Subject Result', success?: true)] }
|
32
|
+
|
33
|
+
it { should be(true) }
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'with failed and successful mutation result' do
|
37
|
+
let(:subject_results) do
|
38
|
+
[
|
39
|
+
double('Subject Result', success?: true),
|
40
|
+
double('Subject Result', success?: false)
|
41
|
+
]
|
42
|
+
end
|
43
|
+
|
44
|
+
it { should be(false) }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'config does not set fail fast flag' do
|
49
|
+
let(:fail_fast) { false }
|
50
|
+
let(:subject_results) { double('subject results') }
|
51
|
+
|
52
|
+
it { should be(true) }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
RSpec.describe Mutant::Result::Subject do
|
2
|
+
let(:object) do
|
3
|
+
described_class.new(
|
4
|
+
subject: mutation_subject,
|
5
|
+
mutation_results: mutation_results
|
6
|
+
)
|
7
|
+
end
|
8
|
+
|
9
|
+
let(:mutation_subject) { double('Subject') }
|
10
|
+
|
11
|
+
describe '#continue?' do
|
12
|
+
subject { object.continue? }
|
13
|
+
|
14
|
+
context 'when mutation results are empty' do
|
15
|
+
let(:mutation_results) { [] }
|
16
|
+
|
17
|
+
it { should be(true) }
|
18
|
+
end
|
19
|
+
|
20
|
+
context 'with failing mutation result' do
|
21
|
+
let(:mutation_results) { [double('Mutation Result', success?: false)] }
|
22
|
+
|
23
|
+
it { should be(false) }
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'with successful mutation result' do
|
27
|
+
let(:mutation_results) { [double('Mutation Result', success?: true)] }
|
28
|
+
|
29
|
+
it { should be(true) }
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'with failed and successful mutation result' do
|
33
|
+
let(:mutation_results) do
|
34
|
+
[
|
35
|
+
double('Mutation Result', success?: true),
|
36
|
+
double('Mutation Result', success?: false)
|
37
|
+
]
|
38
|
+
end
|
39
|
+
|
40
|
+
it { should be(false) }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,199 @@
|
|
1
|
+
RSpec.describe Mutant::Runner::Master do
|
2
|
+
setup_shared_context
|
3
|
+
|
4
|
+
describe 'object initialization' do
|
5
|
+
subject { described_class.__send__(:new, env, double('actor')) }
|
6
|
+
|
7
|
+
it 'initialized instance variables' do
|
8
|
+
expect(subject.instance_variable_get(:@stop)).to be(false)
|
9
|
+
expect(subject.instance_variable_get(:@stopping)).to be(false)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe '.call' do
|
14
|
+
let(:actor_names) { [:master, :worker_a] }
|
15
|
+
let(:worker_a) { actor_env.actor(:worker_a).sender }
|
16
|
+
let(:worker_b) { actor_env.actor(:worker_b).sender }
|
17
|
+
let(:parent) { actor_env.actor(:parent).sender }
|
18
|
+
|
19
|
+
let(:job) { double('Job') }
|
20
|
+
|
21
|
+
before do
|
22
|
+
expect(Time).to receive(:now).and_return(Time.at(0)).at_most(5).times
|
23
|
+
expect(Mutant::Runner::Worker).to receive(:run).with(
|
24
|
+
id: 0,
|
25
|
+
config: env.config,
|
26
|
+
parent: actor_env.actor(:master).sender
|
27
|
+
).and_return(worker_a)
|
28
|
+
end
|
29
|
+
|
30
|
+
subject { described_class.call(env) }
|
31
|
+
|
32
|
+
context 'jobs done before external stop' do
|
33
|
+
before do
|
34
|
+
message_sequence.add(:master, :ready, worker_a)
|
35
|
+
message_sequence.add(:worker_a, :job, job_a)
|
36
|
+
message_sequence.add(:master, :result, job_a_result)
|
37
|
+
|
38
|
+
message_sequence.add(:master, :ready, worker_a)
|
39
|
+
message_sequence.add(:worker_a, :job, job_b)
|
40
|
+
message_sequence.add(:master, :result, job_b_result)
|
41
|
+
|
42
|
+
message_sequence.add(:master, :ready, worker_a)
|
43
|
+
message_sequence.add(:worker_a, :stop)
|
44
|
+
|
45
|
+
message_sequence.add(:master, :stop, parent)
|
46
|
+
message_sequence.add(:parent, :stop)
|
47
|
+
end
|
48
|
+
|
49
|
+
it { should eql(actor_env.actor(:master).sender) }
|
50
|
+
|
51
|
+
it 'consumes all messages' do
|
52
|
+
expect { subject }.to change(&message_sequence.method(:consumed?)).from(false).to(true)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context 'stop by fail fast trigger first' do
|
57
|
+
update(:config) { { fail_fast: true } }
|
58
|
+
update(:mutation_b_test_result) { { passed: true } }
|
59
|
+
|
60
|
+
before do
|
61
|
+
message_sequence.add(:master, :ready, worker_a)
|
62
|
+
message_sequence.add(:worker_a, :job, job_a)
|
63
|
+
message_sequence.add(:master, :result, job_a_result)
|
64
|
+
|
65
|
+
message_sequence.add(:master, :ready, worker_a)
|
66
|
+
message_sequence.add(:worker_a, :job, job_b)
|
67
|
+
message_sequence.add(:master, :result, job_b_result)
|
68
|
+
|
69
|
+
message_sequence.add(:master, :ready, worker_a)
|
70
|
+
message_sequence.add(:worker_a, :stop)
|
71
|
+
|
72
|
+
message_sequence.add(:master, :stop, parent)
|
73
|
+
message_sequence.add(:parent, :stop)
|
74
|
+
end
|
75
|
+
|
76
|
+
it { should eql(actor_env.actor(:master).sender) }
|
77
|
+
|
78
|
+
it 'consumes all messages' do
|
79
|
+
expect { subject }.to change(&message_sequence.method(:consumed?)).from(false).to(true)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context 'stop by fail fast trigger last' do
|
84
|
+
update(:config) { { fail_fast: true } }
|
85
|
+
update(:mutation_a_test_result) { { passed: true } }
|
86
|
+
|
87
|
+
before do
|
88
|
+
message_sequence.add(:master, :ready, worker_a)
|
89
|
+
message_sequence.add(:worker_a, :job, job_a)
|
90
|
+
message_sequence.add(:master, :result, job_a_result)
|
91
|
+
|
92
|
+
message_sequence.add(:master, :ready, worker_a)
|
93
|
+
message_sequence.add(:worker_a, :stop)
|
94
|
+
|
95
|
+
message_sequence.add(:master, :stop, parent)
|
96
|
+
message_sequence.add(:parent, :stop)
|
97
|
+
end
|
98
|
+
|
99
|
+
it { should eql(actor_env.actor(:master).sender) }
|
100
|
+
|
101
|
+
it 'consumes all messages' do
|
102
|
+
expect { subject }.to change(&message_sequence.method(:consumed?)).from(false).to(true)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
context 'jobs active while external stop' do
|
107
|
+
before do
|
108
|
+
message_sequence.add(:master, :ready, worker_a)
|
109
|
+
message_sequence.add(:worker_a, :job, job_a)
|
110
|
+
message_sequence.add(:master, :stop, parent)
|
111
|
+
message_sequence.add(:master, :result, job_a_result)
|
112
|
+
|
113
|
+
message_sequence.add(:master, :status, parent)
|
114
|
+
message_sequence.add(:parent, :status, empty_status.update(active_jobs: [job_a].to_set))
|
115
|
+
|
116
|
+
message_sequence.add(:master, :ready, worker_a)
|
117
|
+
message_sequence.add(:worker_a, :stop)
|
118
|
+
|
119
|
+
message_sequence.add(:parent, :stop)
|
120
|
+
end
|
121
|
+
|
122
|
+
it { should eql(actor_env.actor(:master).sender) }
|
123
|
+
|
124
|
+
it 'consumes all messages' do
|
125
|
+
expect { subject }.to change(&message_sequence.method(:consumed?)).from(false).to(true)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
context 'stop with pending jobs' do
|
130
|
+
before do
|
131
|
+
message_sequence.add(:master, :stop, parent)
|
132
|
+
message_sequence.add(:master, :ready, worker_a)
|
133
|
+
message_sequence.add(:worker_a, :stop)
|
134
|
+
message_sequence.add(:parent, :stop)
|
135
|
+
end
|
136
|
+
|
137
|
+
it { should eql(actor_env.actor(:master).sender) }
|
138
|
+
|
139
|
+
it 'consumes all messages' do
|
140
|
+
expect { subject }.to change(&message_sequence.method(:consumed?)).from(false).to(true)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
context 'unhandled message received' do
|
145
|
+
before do
|
146
|
+
message_sequence.add(:master, :foo, parent)
|
147
|
+
end
|
148
|
+
|
149
|
+
it 'raises message' do
|
150
|
+
expect { subject }.to raise_error(Mutant::Actor::ProtocolError, 'Unexpected message: :foo')
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
context 'request status late' do
|
155
|
+
let(:expected_status) { status.update(env_result: env_result.update(runtime: 0.0)) }
|
156
|
+
|
157
|
+
before do
|
158
|
+
message_sequence.add(:master, :ready, worker_a)
|
159
|
+
message_sequence.add(:worker_a, :job, job_a)
|
160
|
+
message_sequence.add(:master, :result, job_a_result)
|
161
|
+
|
162
|
+
message_sequence.add(:master, :ready, worker_a)
|
163
|
+
message_sequence.add(:worker_a, :job, job_b)
|
164
|
+
message_sequence.add(:master, :result, job_b_result)
|
165
|
+
|
166
|
+
message_sequence.add(:master, :ready, worker_a)
|
167
|
+
message_sequence.add(:worker_a, :stop)
|
168
|
+
|
169
|
+
message_sequence.add(:master, :status, parent)
|
170
|
+
message_sequence.add(:parent, :status, expected_status)
|
171
|
+
message_sequence.add(:master, :stop, parent)
|
172
|
+
message_sequence.add(:parent, :stop)
|
173
|
+
end
|
174
|
+
|
175
|
+
it { should eql(actor_env.actor(:master).sender) }
|
176
|
+
|
177
|
+
it 'consumes all messages' do
|
178
|
+
expect { subject }.to change(&message_sequence.method(:consumed?)).from(false).to(true)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
context 'request status early' do
|
183
|
+
before do
|
184
|
+
message_sequence.add(:master, :status, parent)
|
185
|
+
message_sequence.add(:parent, :status, empty_status)
|
186
|
+
message_sequence.add(:master, :stop, parent)
|
187
|
+
message_sequence.add(:master, :ready, worker_a)
|
188
|
+
message_sequence.add(:worker_a, :stop)
|
189
|
+
message_sequence.add(:parent, :stop)
|
190
|
+
end
|
191
|
+
|
192
|
+
it { should eql(actor_env.actor(:master).sender) }
|
193
|
+
|
194
|
+
it 'consumes all messages' do
|
195
|
+
expect { subject }.to change(&message_sequence.method(:consumed?)).from(false).to(true)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Mutant::Runner::Scheduler do
|
4
|
+
let(:object) { described_class.new(env) }
|
5
|
+
|
6
|
+
before do
|
7
|
+
allow(Time).to receive(:now).and_return(Time.now)
|
8
|
+
end
|
9
|
+
|
10
|
+
setup_shared_context
|
11
|
+
|
12
|
+
let(:active_subject_a_result) do
|
13
|
+
subject_a_result.update(mutation_results: [])
|
14
|
+
end
|
15
|
+
|
16
|
+
describe '#job_result' do
|
17
|
+
subject { object.job_result(job_a_result) }
|
18
|
+
|
19
|
+
before do
|
20
|
+
expect(object.next_job).to eql(job_a)
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'removes the tracking of job as active' do
|
24
|
+
expect { subject }.to change { object.status.active_jobs }.from([job_a].to_set).to(Set.new)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'aggregates results in #status' do
|
28
|
+
subject
|
29
|
+
object.job_result(job_b_result)
|
30
|
+
expect(object.status.env_result).to eql(
|
31
|
+
Mutant::Result::Env.new(
|
32
|
+
env: env,
|
33
|
+
runtime: 0.0,
|
34
|
+
subject_results: [subject_a_result]
|
35
|
+
)
|
36
|
+
)
|
37
|
+
end
|
38
|
+
|
39
|
+
it_should_behave_like 'a command method'
|
40
|
+
end
|
41
|
+
|
42
|
+
describe '#next_job' do
|
43
|
+
subject { object.next_job }
|
44
|
+
|
45
|
+
context 'when there is a next job' do
|
46
|
+
let(:mutations) { [mutation_a, mutation_b] }
|
47
|
+
|
48
|
+
it { should eql(job_a) }
|
49
|
+
|
50
|
+
it 'does not return the same job again' do
|
51
|
+
subject
|
52
|
+
expect(object.next_job).to eql(job_b)
|
53
|
+
expect(object.next_job).to be(nil)
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'does record job as active' do
|
57
|
+
expect { subject }.to change { object.status.active_jobs }.from(Set.new).to([job_a].to_set)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context 'when there is no next job' do
|
62
|
+
let(:mutations) { [] }
|
63
|
+
it { should be(nil) }
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe '#status' do
|
68
|
+
subject { object.status }
|
69
|
+
|
70
|
+
context 'when empty' do
|
71
|
+
let(:expected_status) do
|
72
|
+
Mutant::Runner::Status.new(
|
73
|
+
env_result: Mutant::Result::Env.new(env: env, runtime: 0.0, subject_results: []),
|
74
|
+
active_jobs: Set.new,
|
75
|
+
done: false
|
76
|
+
)
|
77
|
+
end
|
78
|
+
|
79
|
+
it { should eql(expected_status) }
|
80
|
+
end
|
81
|
+
|
82
|
+
context 'when jobs are active' do
|
83
|
+
before do
|
84
|
+
object.next_job
|
85
|
+
object.next_job
|
86
|
+
end
|
87
|
+
|
88
|
+
let(:expected_status) do
|
89
|
+
Mutant::Runner::Status.new(
|
90
|
+
env_result: Mutant::Result::Env.new(env: env, runtime: 0.0, subject_results: []),
|
91
|
+
active_jobs: [job_a, job_b].to_set,
|
92
|
+
done: false
|
93
|
+
)
|
94
|
+
end
|
95
|
+
|
96
|
+
it { should eql(expected_status) }
|
97
|
+
end
|
98
|
+
|
99
|
+
context 'remaining jobs are active' do
|
100
|
+
before do
|
101
|
+
object.next_job
|
102
|
+
object.next_job
|
103
|
+
object.job_result(job_a_result)
|
104
|
+
end
|
105
|
+
|
106
|
+
update(:subject_a_result) { { mutation_results: [mutation_a_result] } }
|
107
|
+
|
108
|
+
let(:expected_status) do
|
109
|
+
Mutant::Runner::Status.new(
|
110
|
+
env_result: Mutant::Result::Env.new(env: env, runtime: 0.0, subject_results: [subject_a_result]),
|
111
|
+
active_jobs: [job_b].to_set,
|
112
|
+
done: false
|
113
|
+
)
|
114
|
+
end
|
115
|
+
|
116
|
+
it { should eql(expected_status) }
|
117
|
+
end
|
118
|
+
|
119
|
+
context 'under fail fast config with failed result' do
|
120
|
+
before do
|
121
|
+
object.next_job
|
122
|
+
object.next_job
|
123
|
+
object.job_result(job_a_result)
|
124
|
+
end
|
125
|
+
|
126
|
+
update(:subject_a_result) { { mutation_results: [mutation_a_result] } }
|
127
|
+
update(:mutation_a_test_result) { { passed: true } }
|
128
|
+
update(:config) { { fail_fast: true } }
|
129
|
+
|
130
|
+
let(:expected_status) do
|
131
|
+
Mutant::Runner::Status.new(
|
132
|
+
env_result: Mutant::Result::Env.new(env: env, runtime: 0.0, subject_results: [subject_a_result]),
|
133
|
+
active_jobs: [job_b].to_set,
|
134
|
+
done: true
|
135
|
+
)
|
136
|
+
end
|
137
|
+
|
138
|
+
it { should eql(expected_status) }
|
139
|
+
end
|
140
|
+
|
141
|
+
context 'when done' do
|
142
|
+
before do
|
143
|
+
object.next_job
|
144
|
+
object.next_job
|
145
|
+
object.status
|
146
|
+
object.job_result(job_a_result)
|
147
|
+
object.job_result(job_b_result)
|
148
|
+
end
|
149
|
+
|
150
|
+
let(:expected_status) do
|
151
|
+
Mutant::Runner::Status.new(
|
152
|
+
env_result: Mutant::Result::Env.new(env: env, runtime: 0.0, subject_results: [subject_a_result]),
|
153
|
+
active_jobs: Set.new,
|
154
|
+
done: true
|
155
|
+
)
|
156
|
+
end
|
157
|
+
|
158
|
+
it { should eql(expected_status) }
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|