mutant 0.8.22 → 0.8.23

Sign up to get free protection for your applications and to get access to all the features.
@@ -9,7 +9,7 @@ module Mutant
9
9
  # :reek:TooManyConstants
10
10
  class MutationResult < self
11
11
 
12
- delegate :mutation, :test_result
12
+ delegate :mutation, :isolation_result
13
13
 
14
14
  MAP = {
15
15
  Mutant::Mutation::Evil => :evil_details,
@@ -25,7 +25,6 @@ module Mutant
25
25
  %s
26
26
  Unparsed Source:
27
27
  %s
28
- Test Result:
29
28
  MESSAGE
30
29
 
31
30
  NO_DIFF_MESSAGE = <<~'MESSAGE'
@@ -47,7 +46,6 @@ module Mutant
47
46
  ---- Noop failure -----
48
47
  No code was inserted. And the test did NOT PASS.
49
48
  This is typically a problem of your specs not passing unmutated.
50
- Test Result:
51
49
  MESSAGE
52
50
 
53
51
  FOOTER = '-----------------------'
@@ -68,6 +66,8 @@ module Mutant
68
66
  # @return [undefined]
69
67
  def print_details
70
68
  __send__(MAP.fetch(mutation.class))
69
+
70
+ visit_isolation_result unless isolation_result.success?
71
71
  end
72
72
 
73
73
  # Evil mutation details
@@ -101,7 +101,6 @@ module Mutant
101
101
  # @return [String]
102
102
  def noop_details
103
103
  info(NOOP_MESSAGE)
104
- visit_test_result
105
104
  end
106
105
 
107
106
  # Neutral details
@@ -109,14 +108,13 @@ module Mutant
109
108
  # @return [String]
110
109
  def neutral_details
111
110
  info(NEUTRAL_MESSAGE, original_node.inspect, mutation.source)
112
- visit_test_result
113
111
  end
114
112
 
115
113
  # Visit failed test results
116
114
  #
117
115
  # @return [undefined]
118
- def visit_test_result
119
- visit(TestResult, test_result)
116
+ def visit_isolation_result
117
+ visit(IsolationResult, isolation_result)
120
118
  end
121
119
 
122
120
  # Original node
@@ -108,6 +108,14 @@ module Mutant
108
108
  env.subjects.length
109
109
  end
110
110
 
111
+ # Test if processing needs to stop
112
+ #
113
+ # @return [Boolean]
114
+ #
115
+ def stop?
116
+ env.config.fail_fast && !subject_results.all?(&:success?)
117
+ end
118
+
111
119
  end # Env
112
120
 
113
121
  # Test result
@@ -189,30 +197,30 @@ module Mutant
189
197
  # Mutation result
190
198
  class Mutation
191
199
  include Result, Anima.new(
200
+ :isolation_result,
192
201
  :mutation,
193
- :test_result
202
+ :runtime
194
203
  )
195
204
 
196
- # The runtime
205
+ # Time the tests had been running
197
206
  #
198
207
  # @return [Float]
199
- def runtime
200
- test_result.runtime
208
+ def killtime
209
+ if isolation_result.success?
210
+ isolation_result.value.runtime
211
+ else
212
+ 0.0
213
+ end
201
214
  end
202
215
 
203
- # The time spent on killing
204
- #
205
- # @return [Float]
206
- #
207
- # @api private
208
- alias_method :killtime, :runtime
209
-
210
216
  # Test if mutation was handled successfully
211
217
  #
212
218
  # @return [Boolean]
213
219
  def success?
214
- mutation.class.success?(test_result)
220
+ isolation_result.success? &&
221
+ mutation.class.success?(isolation_result.value)
215
222
  end
223
+ memoize :success?
216
224
 
217
225
  end # Mutation
218
226
  end # Result
@@ -29,7 +29,7 @@ module Mutant
29
29
  #
30
30
  # @return [Boolean]
31
31
  def stop?
32
- env.config.fail_fast && !status.subject_results.all?(&:success?)
32
+ status.stop?
33
33
  end
34
34
 
35
35
  # Handle mutation finish
@@ -43,7 +43,7 @@ module Mutant
43
43
  @subject_results[subject] = Result::Subject.new(
44
44
  subject: subject,
45
45
  mutation_results: previous_mutation_results(subject) + [mutation_result],
46
- tests: mutation_result.test_result.tests
46
+ tests: env.selections.fetch(subject)
47
47
  )
48
48
 
49
49
  self
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Mutant
4
4
  # Current mutant version
5
- VERSION = '0.8.22'.freeze
5
+ VERSION = '0.8.23'.freeze
6
6
  end # Mutant
@@ -36,17 +36,26 @@ module SharedContext
36
36
  # rubocop:disable MethodLength
37
37
  # rubocop:disable AbcSize
38
38
  def setup_shared_context
39
- let(:env) { instance_double(Mutant::Env, config: config, subjects: [subject_a], mutations: mutations) }
40
- let(:job_a) { Mutant::Parallel::Job.new(index: 0, payload: mutation_a) }
41
- let(:job_b) { Mutant::Parallel::Job.new(index: 1, payload: mutation_b) }
42
- let(:test_a) { instance_double(Mutant::Test, identification: 'test-a') }
43
- let(:output) { StringIO.new }
44
- let(:mutations) { [mutation_a, mutation_b] }
45
- let(:mutation_a_node) { s(:false) }
46
- let(:mutation_b_node) { s(:nil) }
47
- let(:mutation_b) { Mutant::Mutation::Evil.new(subject_a, mutation_b_node) }
48
- let(:mutation_a) { Mutant::Mutation::Evil.new(subject_a, mutation_a_node) }
49
- let(:subject_a_node) { s(:true) }
39
+ let(:job_a) { Mutant::Parallel::Job.new(index: 0, payload: mutation_a) }
40
+ let(:job_b) { Mutant::Parallel::Job.new(index: 1, payload: mutation_b) }
41
+ let(:mutation_a) { Mutant::Mutation::Evil.new(subject_a, mutation_a_node) }
42
+ let(:mutation_a_node) { s(:false) }
43
+ let(:mutation_b) { Mutant::Mutation::Evil.new(subject_a, mutation_b_node) }
44
+ let(:mutation_b_node) { s(:nil) }
45
+ let(:mutations) { [mutation_a, mutation_b] }
46
+ let(:output) { StringIO.new }
47
+ let(:subject_a_node) { s(:true) }
48
+ let(:test_a) { instance_double(Mutant::Test, identification: 'test-a') }
49
+
50
+ let(:env) do
51
+ instance_double(
52
+ Mutant::Env,
53
+ config: config,
54
+ mutations: mutations,
55
+ selections: { subject_a => [test_a] },
56
+ subjects: [subject_a]
57
+ )
58
+ end
50
59
 
51
60
  let(:status) do
52
61
  Mutant::Parallel::Status.new(
@@ -86,18 +95,24 @@ module SharedContext
86
95
 
87
96
  let(:mutation_a_result) do
88
97
  Mutant::Result::Mutation.new(
89
- mutation: mutation_a,
90
- test_result: mutation_a_test_result
98
+ mutation: mutation_a,
99
+ isolation_result: mutation_a_isolation_result,
100
+ runtime: 1.0
91
101
  )
92
102
  end
93
103
 
94
104
  let(:mutation_b_result) do
95
105
  Mutant::Result::Mutation.new(
96
- mutation: mutation_a,
97
- test_result: mutation_b_test_result
106
+ isolation_result: mutation_b_isolation_result,
107
+ mutation: mutation_b,
108
+ runtime: 1.0
98
109
  )
99
110
  end
100
111
 
112
+ let(:mutation_a_isolation_result) do
113
+ Mutant::Isolation::Result::Success.new(mutation_a_test_result)
114
+ end
115
+
101
116
  let(:mutation_a_test_result) do
102
117
  Mutant::Result::Test.new(
103
118
  tests: [test_a],
@@ -116,6 +131,10 @@ module SharedContext
116
131
  )
117
132
  end
118
133
 
134
+ let(:mutation_b_isolation_result) do
135
+ Mutant::Isolation::Result::Success.new(mutation_b_test_result)
136
+ end
137
+
119
138
  let(:subject_a_result) do
120
139
  Mutant::Result::Subject.new(
121
140
  subject: subject_a,
@@ -1,73 +1,70 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  RSpec.describe Mutant::Env do
4
- context '#kill' do
5
- let(:object) do
6
- described_class.new(
7
- actor_env: Mutant::Actor::Env.new(Thread),
8
- config: config,
9
- integration: integration,
10
- matchable_scopes: [],
11
- mutations: [],
12
- selector: selector,
13
- subjects: [],
14
- parser: Mutant::Parser.new
15
- )
16
- end
4
+ let(:object) do
5
+ described_class.new(
6
+ actor_env: Mutant::Actor::Env.new(Thread),
7
+ config: config,
8
+ integration: integration,
9
+ matchable_scopes: [],
10
+ mutations: [],
11
+ selector: selector,
12
+ subjects: [mutation_subject],
13
+ parser: Mutant::Parser.new
14
+ )
15
+ end
17
16
 
18
- let(:integration) { instance_double(Mutant::Integration) }
19
- let(:test_a) { instance_double(Mutant::Test) }
20
- let(:test_b) { instance_double(Mutant::Test) }
21
- let(:tests) { [test_a, test_b] }
22
- let(:selector) { instance_double(Mutant::Selector) }
23
- let(:integration_class) { Mutant::Integration::Null }
24
- let(:isolation) { instance_double(Mutant::Isolation::Fork) }
25
- let(:mutation_subject) { instance_double(Mutant::Subject) }
26
-
27
- let(:mutation) do
28
- instance_double(
29
- Mutant::Mutation,
30
- subject: mutation_subject
31
- )
32
- end
17
+ let(:integration) { instance_double(Mutant::Integration) }
18
+ let(:test_a) { instance_double(Mutant::Test) }
19
+ let(:test_b) { instance_double(Mutant::Test) }
20
+ let(:tests) { [test_a, test_b] }
21
+ let(:selector) { instance_double(Mutant::Selector) }
22
+ let(:integration_class) { Mutant::Integration::Null }
23
+ let(:isolation) { Mutant::Isolation::None.new }
24
+ let(:mutation_subject) { instance_double(Mutant::Subject) }
25
+
26
+ let(:mutation) do
27
+ instance_double(
28
+ Mutant::Mutation,
29
+ subject: mutation_subject
30
+ )
31
+ end
33
32
 
34
- let(:config) do
35
- Mutant::Config::DEFAULT.with(
36
- isolation: isolation,
37
- integration: integration_class,
38
- kernel: class_double(Kernel)
39
- )
40
- end
33
+ let(:config) do
34
+ Mutant::Config::DEFAULT.with(
35
+ isolation: isolation,
36
+ integration: integration_class,
37
+ kernel: class_double(Kernel)
38
+ )
39
+ end
41
40
 
41
+ before do
42
+ allow(selector).to receive(:call)
43
+ .with(mutation_subject)
44
+ .and_return(tests)
45
+
46
+ allow(Mutant::Timer).to receive(:now).and_return(2.0, 3.0)
47
+ end
48
+
49
+ describe '#kill' do
42
50
  subject { object.kill(mutation) }
43
51
 
44
52
  shared_examples_for 'mutation kill' do
45
53
  specify do
46
54
  should eql(
47
55
  Mutant::Result::Mutation.new(
48
- mutation: mutation,
49
- test_result: test_result
56
+ isolation_result: isolation_result,
57
+ mutation: mutation,
58
+ runtime: 1.0
50
59
  )
51
60
  )
52
61
  end
53
62
  end
54
63
 
55
- before do
56
- expect(selector).to receive(:call)
57
- .with(mutation_subject)
58
- .and_return(tests)
59
-
60
- allow(Mutant::Timer).to receive(:now).and_return(2.0, 3.0)
61
- end
62
-
63
64
  context 'when isolation does not raise error' do
64
65
  let(:test_result) { instance_double(Mutant::Result::Test) }
65
66
 
66
67
  before do
67
- expect(isolation).to receive(:call)
68
- .ordered
69
- .and_yield
70
-
71
68
  expect(mutation).to receive(:insert)
72
69
  .ordered
73
70
  .with(config.kernel)
@@ -78,25 +75,33 @@ RSpec.describe Mutant::Env do
78
75
  .and_return(test_result)
79
76
  end
80
77
 
78
+ let(:isolation_result) do
79
+ Mutant::Isolation::Result::Success.new(test_result)
80
+ end
81
+
81
82
  include_examples 'mutation kill'
82
83
  end
83
84
 
84
- context 'when isolation does raise error' do
85
+ context 'when code does raise error' do
86
+ let(:exception) { RuntimeError.new('foo') }
87
+
85
88
  before do
86
- expect(isolation).to receive(:call)
87
- .and_raise(Mutant::Isolation::Error, 'test-error')
89
+ expect(mutation).to receive(:insert).and_raise(exception)
88
90
  end
89
91
 
90
- let(:test_result) do
91
- Mutant::Result::Test.new(
92
- output: 'test-error',
93
- passed: false,
94
- runtime: 1.0,
95
- tests: tests
96
- )
92
+ let(:isolation_result) do
93
+ Mutant::Isolation::Result::Exception.new(exception)
97
94
  end
98
95
 
99
96
  include_examples 'mutation kill'
100
97
  end
101
98
  end
99
+
100
+ describe '#selections' do
101
+ subject { object.selections }
102
+
103
+ it 'returns expected selections' do
104
+ expect(subject).to eql(mutation_subject => tests)
105
+ end
106
+ end
102
107
  end
@@ -23,6 +23,95 @@ RSpec.describe Mutant::Isolation::Fork do
23
23
  let(:writer) { instance_double(IO, :writer) }
24
24
  let(:nullio) { instance_double(IO, :nullio) }
25
25
 
26
+ let(:status_success) do
27
+ instance_double(Process::Status, success?: true)
28
+ end
29
+
30
+ let(:fork_success) do
31
+ {
32
+ receiver: process,
33
+ selector: :fork,
34
+ reaction: {
35
+ yields: [],
36
+ return: pid
37
+ }
38
+ }
39
+ end
40
+
41
+ let(:child_wait) do
42
+ {
43
+ receiver: process,
44
+ selector: :wait2,
45
+ arguments: [pid],
46
+ reaction: {
47
+ return: [pid, status_success]
48
+ }
49
+ }
50
+ end
51
+
52
+ let(:writer_close) do
53
+ {
54
+ receiver: writer,
55
+ selector: :close
56
+ }
57
+ end
58
+
59
+ let(:load_success) do
60
+ {
61
+ receiver: marshal,
62
+ selector: :load,
63
+ arguments: [reader],
64
+ reaction: {
65
+ return: block_return
66
+ }
67
+ }
68
+ end
69
+
70
+ let(:killfork) do
71
+ [
72
+ # Inside the killfork
73
+ {
74
+ receiver: reader,
75
+ selector: :close
76
+ },
77
+ {
78
+ receiver: writer,
79
+ selector: :binmode
80
+ },
81
+ {
82
+ receiver: devnull,
83
+ selector: :call,
84
+ reaction: {
85
+ yields: [nullio]
86
+ }
87
+ },
88
+ {
89
+ receiver: stderr,
90
+ selector: :reopen,
91
+ arguments: [nullio]
92
+ },
93
+ {
94
+ receiver: stdout,
95
+ selector: :reopen,
96
+ arguments: [nullio]
97
+ },
98
+ {
99
+ receiver: marshal,
100
+ selector: :dump,
101
+ arguments: [block_return],
102
+ reaction: {
103
+ return: block_return_blob
104
+ }
105
+ },
106
+ {
107
+ receiver: writer,
108
+ selector: :syswrite,
109
+ arguments: [block_return_blob]
110
+ },
111
+ writer_close
112
+ ]
113
+ end
114
+
26
115
  describe '#call' do
27
116
  let(:object) do
28
117
  described_class.new(
@@ -51,6 +140,57 @@ RSpec.describe Mutant::Isolation::Fork do
51
140
  end
52
141
 
53
142
  context 'when no IO operation fails' do
143
+ let(:expectations) do
144
+ [
145
+ *prefork_expectations,
146
+ fork_success,
147
+ *killfork,
148
+ writer_close,
149
+ load_success,
150
+ child_wait
151
+ ].map(&XSpec::MessageExpectation.method(:parse))
152
+ end
153
+
154
+ specify do
155
+ XSpec::ExpectationVerifier.verify(self, expectations) do
156
+ expect(subject).to eql(Mutant::Isolation::Result::Success.new(block_return))
157
+ end
158
+ end
159
+ end
160
+
161
+ context 'when expected exception was raised when reading from child' do
162
+ [ArgumentError, EOFError].each do |exception_class|
163
+ context "on #{exception_class}" do
164
+ let(:exception) { exception_class.new }
165
+
166
+ let(:expectations) do
167
+ [
168
+ *prefork_expectations,
169
+ fork_success,
170
+ *killfork,
171
+ {
172
+ receiver: writer,
173
+ selector: :close,
174
+ reaction: {
175
+ exception: exception
176
+ }
177
+ },
178
+ child_wait
179
+ ].map(&XSpec::MessageExpectation.method(:parse))
180
+ end
181
+
182
+ specify do
183
+ XSpec::ExpectationVerifier.verify(self, expectations) do
184
+ expect(subject).to eql(Mutant::Isolation::Result::Exception.new(exception))
185
+ end
186
+ end
187
+ end
188
+ end
189
+ end
190
+
191
+ context 'when fork fails' do
192
+ let(:result_class) { described_class::ForkError }
193
+
54
194
  let(:expectations) do
55
195
  [
56
196
  *prefork_expectations,
@@ -58,90 +198,44 @@ RSpec.describe Mutant::Isolation::Fork do
58
198
  receiver: process,
59
199
  selector: :fork,
60
200
  reaction: {
61
- yields: [],
62
- return: pid
63
- }
64
- },
65
- # Inside the killfork
66
- {
67
- receiver: reader,
68
- selector: :close
69
- },
70
- {
71
- receiver: writer,
72
- selector: :binmode
73
- },
74
- {
75
- receiver: devnull,
76
- selector: :call,
77
- reaction: {
78
- yields: [nullio]
201
+ return: nil
79
202
  }
80
- },
81
- {
82
- receiver: stderr,
83
- selector: :reopen,
84
- arguments: [nullio]
85
- },
86
- {
87
- receiver: stdout,
88
- selector: :reopen,
89
- arguments: [nullio]
90
- },
91
- {
92
- receiver: marshal,
93
- selector: :dump,
94
- arguments: [block_return],
95
- reaction: {
96
- return: block_return_blob
97
- }
98
- },
99
- {
100
- receiver: writer,
101
- selector: :syswrite,
102
- arguments: [block_return_blob]
103
- },
104
- {
105
- receiver: writer,
106
- selector: :close
107
- },
108
- # Outside the killfork
109
- {
110
- receiver: writer,
111
- selector: :close
112
- },
113
- {
114
- receiver: marshal,
115
- selector: :load,
116
- arguments: [reader],
117
- reaction: {
118
- return: block_return
119
- }
120
- },
121
- {
122
- receiver: process,
123
- selector: :waitpid,
124
- arguments: [pid]
125
203
  }
126
204
  ].map(&XSpec::MessageExpectation.method(:parse))
127
205
  end
128
206
 
129
207
  specify do
130
208
  XSpec::ExpectationVerifier.verify(self, expectations) do
131
- expect(subject).to be(block_return)
209
+ expect(subject).to eql(result_class.new)
132
210
  end
133
211
  end
134
212
  end
135
213
 
136
- context 'when fork fails' do
214
+ context 'when child exits nonzero' do
215
+ let(:status_error) do
216
+ instance_double(Process::Status, success?: false)
217
+ end
218
+
219
+ let(:expected_result) do
220
+ Mutant::Isolation::Result::ErrorChain.new(
221
+ described_class::ChildError.new(status_error),
222
+ Mutant::Isolation::Result::Success.new(block_return)
223
+ )
224
+ end
225
+
137
226
  let(:expectations) do
138
227
  [
139
228
  *prefork_expectations,
229
+ fork_success,
230
+ *killfork,
231
+ writer_close,
232
+ load_success,
140
233
  {
141
- receiver: process,
142
- selector: :fork,
143
- reaction: {
144
- exception: RuntimeError.new('fork(2)')
234
+ receiver: process,
235
+ selector: :wait2,
236
+ arguments: [pid],
237
+ reaction: {
238
+ return: [pid, status_error]
145
239
  }
146
240
  }
147
241
  ].map(&XSpec::MessageExpectation.method(:parse))
@@ -149,7 +243,7 @@ RSpec.describe Mutant::Isolation::Fork do
149
243
 
150
244
  specify do
151
245
  XSpec::ExpectationVerifier.verify(self, expectations) do
152
- expect { expect(subject) }.to raise_error(described_class::Error, 'fork(2)')
246
+ expect(subject).to eql(expected_result)
153
247
  end
154
248
  end
155
249
  end