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.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog.md +10 -0
  3. data/README.md +1 -1
  4. data/config/flay.yml +1 -1
  5. data/config/reek.yml +11 -40
  6. data/config/rubocop.yml +1 -1
  7. data/lib/mutant.rb +10 -2
  8. data/lib/mutant/actor.rb +64 -0
  9. data/lib/mutant/actor/actor.rb +50 -0
  10. data/lib/mutant/actor/env.rb +35 -0
  11. data/lib/mutant/actor/mailbox.rb +53 -0
  12. data/lib/mutant/actor/receiver.rb +48 -0
  13. data/lib/mutant/actor/sender.rb +27 -0
  14. data/lib/mutant/ast/types.rb +1 -1
  15. data/lib/mutant/cli.rb +2 -0
  16. data/lib/mutant/config.rb +2 -1
  17. data/lib/mutant/env.rb +6 -2
  18. data/lib/mutant/expression/methods.rb +1 -1
  19. data/lib/mutant/integration.rb +11 -1
  20. data/lib/mutant/isolation.rb +1 -2
  21. data/lib/mutant/meta/example.rb +1 -1
  22. data/lib/mutant/mutation.rb +47 -21
  23. data/lib/mutant/mutator/node.rb +1 -1
  24. data/lib/mutant/mutator/node/literal/symbol.rb +1 -1
  25. data/lib/mutant/mutator/node/send.rb +4 -3
  26. data/lib/mutant/reporter/cli.rb +2 -0
  27. data/lib/mutant/reporter/cli/format.rb +23 -36
  28. data/lib/mutant/reporter/cli/printer.rb +66 -27
  29. data/lib/mutant/result.rb +45 -58
  30. data/lib/mutant/runner.rb +47 -154
  31. data/lib/mutant/runner/master.rb +174 -0
  32. data/lib/mutant/runner/scheduler.rb +141 -0
  33. data/lib/mutant/runner/worker.rb +93 -0
  34. data/lib/mutant/subject/method/instance.rb +1 -15
  35. data/lib/mutant/test.rb +2 -15
  36. data/lib/mutant/version.rb +1 -1
  37. data/meta/send.rb +16 -0
  38. data/mutant-rspec.gemspec +1 -1
  39. data/mutant.gemspec +1 -1
  40. data/spec/integration/mutant/rspec_spec.rb +0 -6
  41. data/spec/spec_helper.rb +9 -1
  42. data/spec/support/fake_actor.rb +93 -0
  43. data/spec/support/shared_context.rb +135 -0
  44. data/spec/unit/mutant/actor/actor_spec.rb +35 -0
  45. data/spec/unit/mutant/actor/binding_spec.rb +32 -0
  46. data/spec/unit/mutant/actor/env_spec.rb +49 -0
  47. data/spec/unit/mutant/actor/message_spec.rb +23 -0
  48. data/spec/unit/mutant/actor/receiver_spec.rb +60 -0
  49. data/spec/unit/mutant/actor/sender_spec.rb +22 -0
  50. data/spec/unit/mutant/cli_spec.rb +17 -4
  51. data/spec/unit/mutant/env_spec.rb +2 -2
  52. data/spec/unit/mutant/mailbox_spec.rb +33 -0
  53. data/spec/unit/mutant/mutation_spec.rb +52 -18
  54. data/spec/unit/mutant/mutator/registry_spec.rb +4 -4
  55. data/spec/unit/mutant/reporter/cli_spec.rb +131 -249
  56. data/spec/unit/mutant/result/env_spec.rb +55 -0
  57. data/spec/unit/mutant/result/subject_spec.rb +43 -0
  58. data/spec/unit/mutant/runner/master_spec.rb +199 -0
  59. data/spec/unit/mutant/runner/scheduler_spec.rb +161 -0
  60. data/spec/unit/mutant/runner/worker_spec.rb +73 -0
  61. data/spec/unit/mutant/runner_spec.rb +60 -118
  62. data/spec/unit/mutant/subject/method/instance_spec.rb +18 -31
  63. data/spec/unit/mutant/warning_filter_spec.rb +1 -1
  64. metadata +39 -14
  65. data/lib/mutant/runner/collector.rb +0 -133
  66. data/lib/mutant/warning_expectation.rb +0 -47
  67. data/spec/unit/mutant/runner/collector_spec.rb +0 -198
  68. data/spec/unit/mutant/test_spec.rb +0 -23
  69. data/spec/unit/mutant/warning_expectation_spec.rb +0 -80
  70. 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