mutant 0.8.22 → 0.8.23

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