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