mutant 0.6.7 → 0.7.1
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/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
|