mutant 0.7.3 → 0.7.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +2 -0
  3. data/.travis.yml +3 -2
  4. data/Changelog.md +7 -2
  5. data/Gemfile +0 -1
  6. data/Gemfile.devtools +9 -37
  7. data/README.md +1 -1
  8. data/circle.yml +1 -1
  9. data/config/flay.yml +1 -1
  10. data/config/reek.yml +6 -19
  11. data/config/rubocop.yml +58 -63
  12. data/lib/mutant.rb +8 -4
  13. data/lib/mutant/ast.rb +1 -1
  14. data/lib/mutant/cli.rb +12 -6
  15. data/lib/mutant/env.rb +17 -1
  16. data/lib/mutant/isolation.rb +4 -2
  17. data/lib/mutant/loader.rb +4 -0
  18. data/lib/mutant/matcher/compiler.rb +2 -0
  19. data/lib/mutant/mutation.rb +2 -0
  20. data/lib/mutant/mutator/node/const.rb +1 -1
  21. data/lib/mutant/mutator/node/generic.rb +1 -1
  22. data/lib/mutant/mutator/node/if.rb +2 -0
  23. data/lib/mutant/parallel.rb +93 -0
  24. data/lib/mutant/{runner → parallel}/master.rb +90 -45
  25. data/lib/mutant/parallel/source.rb +73 -0
  26. data/lib/mutant/{runner → parallel}/worker.rb +13 -30
  27. data/lib/mutant/reporter/cli.rb +8 -11
  28. data/lib/mutant/reporter/cli/printer.rb +14 -8
  29. data/lib/mutant/result.rb +0 -10
  30. data/lib/mutant/runner.rb +49 -43
  31. data/lib/mutant/runner/{scheduler.rb → sink.rb} +9 -68
  32. data/lib/mutant/version.rb +1 -1
  33. data/lib/mutant/zombifier/file.rb +28 -9
  34. data/meta/if.rb +8 -0
  35. data/meta/match_current_line.rb +1 -0
  36. data/spec/integration/mutant/corpus_spec.rb +1 -1
  37. data/spec/integration/mutant/null_spec.rb +1 -1
  38. data/spec/integration/mutant/rspec_spec.rb +1 -1
  39. data/spec/integration/mutant/test_mutator_handles_types_spec.rb +1 -1
  40. data/spec/integration/mutant/zombie_spec.rb +1 -1
  41. data/spec/support/corpus.rb +2 -0
  42. data/spec/support/fake_actor.rb +20 -10
  43. data/spec/support/mutation_verifier.rb +2 -0
  44. data/spec/support/shared_context.rb +12 -19
  45. data/spec/unit/mutant/env_spec.rb +20 -2
  46. data/spec/unit/mutant/expression_spec.rb +4 -1
  47. data/spec/unit/mutant/parallel/master_spec.rb +339 -0
  48. data/spec/unit/mutant/parallel/source/array_spec.rb +47 -0
  49. data/spec/unit/mutant/{runner → parallel}/worker_spec.rb +23 -26
  50. data/spec/unit/mutant/parallel_spec.rb +16 -0
  51. data/spec/unit/mutant/reporter/cli_spec.rb +1 -1
  52. data/spec/unit/mutant/reporter/trace_spec.rb +9 -0
  53. data/spec/unit/mutant/result/env_spec.rb +0 -55
  54. data/spec/unit/mutant/runner/driver_spec.rb +26 -0
  55. data/spec/unit/mutant/runner/sink_spec.rb +162 -0
  56. data/spec/unit/mutant/runner_spec.rb +60 -63
  57. data/spec/unit/mutant/warning_filter_spec.rb +2 -1
  58. data/test_app/Gemfile.devtools +9 -37
  59. metadata +21 -11
  60. data/spec/unit/mutant/runner/master_spec.rb +0 -199
  61. data/spec/unit/mutant/runner/scheduler_spec.rb +0 -161
@@ -0,0 +1,47 @@
1
+ RSpec.describe Mutant::Parallel::Source::Array do
2
+ let(:object) { described_class.new(jobs) }
3
+
4
+ let(:job_a) { double('Job A') }
5
+ let(:job_b) { double('Job B') }
6
+ let(:job_c) { double('Job B') }
7
+
8
+ let(:jobs) { [job_a, job_b, job_c] }
9
+
10
+ describe '#next' do
11
+ subject { object.next }
12
+
13
+ context 'when there is a next job' do
14
+ it 'returns that job' do
15
+ should be(job_a)
16
+ end
17
+
18
+ it 'does not return the same job twice' do
19
+ expect(object.next).to be(job_a)
20
+ expect(object.next).to be(job_b)
21
+ expect(object.next).to be(job_c)
22
+ end
23
+ end
24
+
25
+ context 'when there is no next job' do
26
+ let(:jobs) { [] }
27
+
28
+ it 'raises error' do
29
+ expect { subject }.to raise_error(Mutant::Parallel::Source::NoJobError)
30
+ end
31
+ end
32
+ end
33
+
34
+ describe '#next?' do
35
+ subject { object.next? }
36
+
37
+ context 'when there is a next job' do
38
+ it { should be(true) }
39
+ end
40
+
41
+ context 'when there is no next job' do
42
+ let(:jobs) { [] }
43
+
44
+ it { should be(false) }
45
+ end
46
+ end
47
+ end
@@ -1,21 +1,27 @@
1
- RSpec.describe Mutant::Runner::Worker do
2
- setup_shared_context
3
-
4
- let(:actor) { actor_env.mailbox(:worker) }
5
- let(:parent) { actor_env.mailbox(:parent).sender }
6
-
7
- before do
8
- message_sequence.add(:parent, :ready, actor.sender)
1
+ RSpec.describe Mutant::Parallel::Worker do
2
+ let(:actor_env) do
3
+ FakeActor::Env.new(message_sequence, actor_names)
9
4
  end
10
5
 
6
+ let(:message_sequence) { FakeActor::MessageSequence.new }
7
+ let(:processor) { double('Processor') }
8
+ let(:actor) { actor_env.mailbox(:worker) }
9
+ let(:parent) { actor_env.mailbox(:parent).sender }
10
+ let(:payload) { double('Payload') }
11
+ let(:result_payload) { double('Result Payload') }
12
+
11
13
  let(:attributes) do
12
14
  {
13
- config: config,
14
- parent: parent,
15
- id: 1
15
+ processor: processor,
16
+ parent: parent,
17
+ actor: actor
16
18
  }
17
19
  end
18
20
 
21
+ before do
22
+ message_sequence.add(:parent, :ready, actor.sender)
23
+ end
24
+
19
25
  describe '.run' do
20
26
  subject { described_class.run(attributes) }
21
27
 
@@ -26,7 +32,7 @@ RSpec.describe Mutant::Runner::Worker do
26
32
  let(:test_result) { double('Test Result') }
27
33
 
28
34
  before do
29
- expect(mutation).to receive(:kill).with(config.isolation, config.integration).and_return(test_result).ordered
35
+ expect(processor).to receive(:call).with(payload).and_return(result_payload)
30
36
 
31
37
  message_sequence.add(:worker, :job, job)
32
38
  message_sequence.add(:parent, :result, job_result)
@@ -34,25 +40,16 @@ RSpec.describe Mutant::Runner::Worker do
34
40
  message_sequence.add(:worker, :stop)
35
41
  end
36
42
 
37
- let(:test) { double('Test') }
38
- let(:index) { double('Index') }
39
- let(:test_result) { double('Test Result') }
40
- let(:mutation) { double('Mutation') }
41
- let(:job_result) { Mutant::Runner::JobResult.new(job: job, result: mutation_result) }
42
- let(:job) { Mutant::Runner::Job.new(index: index, mutation: mutation) }
43
-
44
- let(:mutation_result) do
45
- Mutant::Result::Mutation.new(
46
- mutation: mutation,
47
- test_result: test_result
48
- )
49
- end
43
+ let(:index) { double('Index') }
44
+ let(:test_result) { double('Test Result') }
45
+ let(:job_result) { Mutant::Parallel::JobResult.new(job: job, payload: result_payload) }
46
+ let(:job) { Mutant::Parallel::Job.new(index: index, payload: payload) }
50
47
 
51
48
  it 'signals ready and status to parent' do
52
49
  subject
53
50
  end
54
51
 
55
- it { should eql(actor.sender) }
52
+ it { should be(described_class) }
56
53
 
57
54
  it 'consumes all messages' do
58
55
  expect { subject }.to change(&message_sequence.method(:consumed?)).from(false).to(true)
@@ -0,0 +1,16 @@
1
+ RSpec.describe Mutant::Parallel do
2
+ describe '.async' do
3
+ subject { described_class.async(config) }
4
+
5
+ let(:config) { double('Config', env: env) }
6
+ let(:env) { double('ENV', new_mailbox: mailbox) }
7
+ let(:mailbox) { Mutant::Actor::Mailbox.new }
8
+ let(:master) { double('Master') }
9
+
10
+ before do
11
+ expect(described_class::Master).to receive(:call).with(config).and_return(master)
12
+ end
13
+
14
+ it { should eql(described_class::Driver.new(mailbox.bind(master))) }
15
+ end
16
+ end
@@ -254,7 +254,7 @@ RSpec.describe Mutant::Reporter::CLI do
254
254
  end
255
255
 
256
256
  describe '#report' do
257
- subject { object.report(status.env_result) }
257
+ subject { object.report(status.payload) }
258
258
 
259
259
  context 'with full coverage' do
260
260
  it_reports(<<-REPORT)
@@ -0,0 +1,9 @@
1
+ RSpec.describe Mutant::Reporter::Trace do
2
+ let(:object) { described_class.new }
3
+
4
+ describe '#delay' do
5
+ subject { object.delay }
6
+
7
+ it { should equal(0.0) }
8
+ end
9
+ end
@@ -1,55 +0,0 @@
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,26 @@
1
+ RSpec.describe Mutant::Parallel::Driver do
2
+ let(:object) { described_class.new(binding) }
3
+
4
+ let(:binding) { double('Binding') }
5
+ let(:result) { double('Result') }
6
+
7
+ describe '#stop' do
8
+ subject { object.stop }
9
+
10
+ before do
11
+ expect(binding).to receive(:call).with(:stop)
12
+ end
13
+
14
+ it_should_behave_like 'a command method'
15
+ end
16
+
17
+ describe '#status' do
18
+ subject { object.status }
19
+
20
+ before do
21
+ expect(binding).to receive(:call).with(:status).and_return(result)
22
+ end
23
+
24
+ it { should be(result) }
25
+ end
26
+ end
@@ -0,0 +1,162 @@
1
+ require 'spec_helper'
2
+
3
+ describe Mutant::Runner::Sink do
4
+ setup_shared_context
5
+
6
+ shared_context 'one result' do
7
+ before do
8
+ object.result(mutation_a_result)
9
+ end
10
+ end
11
+
12
+ shared_context 'two results' do
13
+ before do
14
+ object.result(mutation_a_result)
15
+ object.result(mutation_b_result)
16
+ end
17
+ end
18
+
19
+ let(:object) { described_class.new(env) }
20
+
21
+ before do
22
+ allow(Time).to receive(:now).and_return(Time.now)
23
+ end
24
+
25
+ describe '#result' do
26
+ subject { object.result(mutation_a_result) }
27
+
28
+ it 'aggregates results in #status' do
29
+ subject
30
+ object.result(mutation_b_result)
31
+ expect(object.status).to eql(
32
+ Mutant::Result::Env.new(
33
+ env: env,
34
+ runtime: 0.0,
35
+ subject_results: [subject_a_result]
36
+ )
37
+ )
38
+ end
39
+
40
+ it_should_behave_like 'a command method'
41
+ end
42
+
43
+ describe '#status' do
44
+ subject { object.status }
45
+
46
+ context 'no results' do
47
+ let(:expected_status) do
48
+ Mutant::Result::Env.new(env: env, runtime: 0.0, subject_results: [])
49
+ end
50
+
51
+ it { should eql(expected_status) }
52
+ end
53
+
54
+ context 'one result' do
55
+ include_context 'one result'
56
+
57
+ update(:subject_a_result) { { mutation_results: [mutation_a_result] } }
58
+
59
+ let(:expected_status) do
60
+ Mutant::Result::Env.new(env: env, runtime: 0.0, subject_results: [subject_a_result])
61
+ end
62
+
63
+ it { should eql(expected_status) }
64
+ end
65
+
66
+ context 'two results' do
67
+ include_context 'two results'
68
+
69
+ let(:expected_status) do
70
+ Mutant::Result::Env.new(env: env, runtime: 0.0, subject_results: [subject_a_result])
71
+ end
72
+
73
+ it { should eql(expected_status) }
74
+ end
75
+ end
76
+
77
+ describe '#stop?' do
78
+ subject { object.stop? }
79
+
80
+ context 'without fail fast' do
81
+ context 'no results' do
82
+ it { should be(false) }
83
+ end
84
+
85
+ context 'one result' do
86
+ include_context 'one result'
87
+
88
+ context 'when result is successful' do
89
+ it { should be(false) }
90
+ end
91
+
92
+ context 'when result failed' do
93
+ update(:mutation_a_test_result) { { passed: true } }
94
+
95
+ it { should be(false) }
96
+ end
97
+ end
98
+
99
+ context 'two results' do
100
+ include_context 'two results'
101
+
102
+ context 'when results are successful' do
103
+ it { should be(false) }
104
+ end
105
+
106
+ context 'when first result is unsuccessful' do
107
+ update(:mutation_a_test_result) { { passed: true } }
108
+
109
+ it { should be(false) }
110
+ end
111
+
112
+ context 'when second result is unsuccessful' do
113
+ update(:mutation_b_test_result) { { passed: true } }
114
+
115
+ it { should be(false) }
116
+ end
117
+ end
118
+ end
119
+
120
+ context 'with fail fast' do
121
+ update(:config) { { fail_fast: true } }
122
+
123
+ context 'no results' do
124
+ it { should be(false) }
125
+ end
126
+
127
+ context 'one result' do
128
+ include_context 'one result'
129
+
130
+ context 'when result is successful' do
131
+ it { should be(false) }
132
+ end
133
+
134
+ context 'when result failed' do
135
+ update(:mutation_a_test_result) { { passed: true } }
136
+
137
+ it { should be(true) }
138
+ end
139
+ end
140
+
141
+ context 'two results' do
142
+ include_context 'two results'
143
+
144
+ context 'when results are successful' do
145
+ it { should be(false) }
146
+ end
147
+
148
+ context 'when first result is unsuccessful' do
149
+ update(:mutation_a_test_result) { { passed: true } }
150
+
151
+ it { should be(true) }
152
+ end
153
+
154
+ context 'when second result is unsuccessful' do
155
+ update(:mutation_b_test_result) { { passed: true } }
156
+
157
+ it { should be(true) }
158
+ end
159
+ end
160
+ end
161
+ end
162
+ end
@@ -1,87 +1,84 @@
1
1
  RSpec.describe Mutant::Runner do
2
- setup_shared_context
3
-
4
- let(:integration) { double('Integration') }
5
- let(:master_sender) { actor_env.spawn }
6
- let(:runner_actor) { actor_env.mailbox(:runner) }
2
+ # setup_shared_context
3
+ class FakeEnv
4
+ def self.kill_mutation
5
+ end
7
6
 
8
- before do
9
- expect(integration).to receive(:setup).ordered
10
- expect(Mutant::Runner::Master).to receive(:call).with(env).and_return(master_sender).ordered
7
+ def self.mutations
8
+ []
9
+ end
11
10
  end
12
11
 
13
12
  describe '.call' do
14
- update(:config) { { integration: integration } }
15
- let(:actor_names) { [:runner, :master] }
16
-
17
- subject { described_class.call(env) }
13
+ let(:integration) { double('Integration') }
14
+ let(:reporter) { double('Reporter', delay: delay) }
15
+ let(:driver) { double('Driver') }
16
+ let(:delay) { double('Delay') }
17
+ let(:env) { FakeEnv }
18
+ let(:env_result) { double('Env Result') }
19
+ let(:actor_env) { double('Actor ENV') }
20
+
21
+ let(:config) do
22
+ double(
23
+ 'Config',
24
+ integration: integration,
25
+ reporter: reporter,
26
+ actor_env: actor_env,
27
+ jobs: 1
28
+ )
29
+ end
18
30
 
19
- context 'when status done gets returned immediately' do
20
- before do
21
- message_sequence.add(:runner, :status, actor_env.mailbox(:current).sender)
22
- message_sequence.add(:current, :status, status)
23
- message_sequence.add(:runner, :stop, actor_env.mailbox(:current).sender)
24
- message_sequence.add(:current, :stop)
25
- end
31
+ before do
32
+ allow(FakeEnv).to receive(:config).and_return(config)
33
+ end
26
34
 
27
- it 'returns env result' do
28
- should be(status.env_result)
29
- end
35
+ let(:parallel_config) do
36
+ Mutant::Parallel::Config.new(
37
+ jobs: 1,
38
+ env: actor_env,
39
+ source: Mutant::Parallel::Source::Array.new(env.mutations),
40
+ sink: Mutant::Runner::Sink.new(env),
41
+ processor: env.method(:kill_mutation)
42
+ )
43
+ end
30
44
 
31
- it 'logs start' do
32
- expect { subject }.to change { config.reporter.start_calls }.from([]).to([env])
33
- end
45
+ before do
46
+ expect(reporter).to receive(:start).with(env).ordered
47
+ expect(integration).to receive(:setup).ordered
48
+ expect(Mutant::Parallel).to receive(:async).with(parallel_config).and_return(driver).ordered
49
+ end
34
50
 
35
- it 'logs process' do
36
- expect { subject }.to change { config.reporter.progress_calls }.from([]).to([status])
37
- end
51
+ subject { described_class.call(env) }
38
52
 
39
- it 'logs result' do
40
- expect { subject }.to change { config.reporter.report_calls }.from([]).to([status.env_result])
41
- end
53
+ context 'when runner finishes immediately' do
54
+ let(:status) { double('Status', done: true, payload: env_result) }
42
55
 
43
- it 'consumes all messages' do
44
- expect { subject }.to change(&message_sequence.method(:consumed?)).from(false).to(true)
56
+ before do
57
+ expect(driver).to receive(:status).and_return(status)
58
+ expect(reporter).to receive(:progress).with(status).ordered
59
+ expect(driver).to receive(:stop).ordered
60
+ expect(reporter).to receive(:report).with(env_result).ordered
45
61
  end
46
62
  end
47
63
 
48
- context 'when status done gets returned immediately' do
49
- let(:incomplete_status) { status.update(done: false) }
64
+ context 'when report iterations are done' do
65
+ let(:status_a) { double('Status A', done: false) }
66
+ let(:status_b) { double('Status B', done: true, payload: env_result) }
50
67
 
51
68
  before do
52
- expect(Kernel).to receive(:sleep).with(0.0).exactly(2).times.ordered
69
+ expect(driver).to receive(:status).and_return(status_a).ordered
70
+ expect(reporter).to receive(:progress).with(status_a).ordered
71
+ expect(Kernel).to receive(:sleep).with(reporter.delay).ordered
53
72
 
54
- current_sender = actor_env.mailbox(:current).sender
73
+ expect(driver).to receive(:status).and_return(status_b).ordered
74
+ expect(reporter).to receive(:progress).with(status_b).ordered
75
+ expect(driver).to receive(:stop).ordered
55
76
 
56
- message_sequence.add(:runner, :status, current_sender)
57
- message_sequence.add(:current, :status, incomplete_status)
58
- message_sequence.add(:runner, :status, current_sender)
59
- message_sequence.add(:current, :status, incomplete_status)
60
- message_sequence.add(:runner, :status, current_sender)
61
- message_sequence.add(:current, :status, status)
62
- message_sequence.add(:runner, :stop, current_sender)
63
- message_sequence.add(:current, :stop)
77
+ expect(reporter).to receive(:report).with(env_result).ordered
64
78
  end
65
79
 
66
80
  it 'returns env result' do
67
- should be(status.env_result)
68
- end
69
-
70
- it 'logs start' do
71
- expect { subject }.to change { config.reporter.start_calls }.from([]).to([env])
72
- end
73
-
74
- it 'logs result' do
75
- expect { subject }.to change { config.reporter.report_calls }.from([]).to([status.env_result])
76
- end
77
-
78
- it 'logs process' do
79
- expected = [incomplete_status, incomplete_status, status]
80
- expect { subject }.to change { config.reporter.progress_calls }.from([]).to(expected)
81
- end
82
-
83
- it 'consumes all messages' do
84
- expect { subject }.to change(&message_sequence.method(:consumed?)).from(false).to(true)
81
+ should be(env_result)
85
82
  end
86
83
  end
87
84
  end