mutant 0.6.3 → 0.6.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -2
  3. data/Changelog.md +6 -0
  4. data/Gemfile.devtools +1 -1
  5. data/bin/mutant +2 -1
  6. data/config/flay.yml +1 -1
  7. data/config/reek.yml +3 -0
  8. data/lib/mutant.rb +5 -3
  9. data/lib/mutant/cli.rb +2 -2
  10. data/lib/mutant/config.rb +1 -1
  11. data/lib/mutant/diff.rb +1 -1
  12. data/lib/mutant/env.rb +1 -1
  13. data/lib/mutant/expression/methods.rb +16 -0
  14. data/lib/mutant/isolation.rb +16 -2
  15. data/lib/mutant/mutator/node/const.rb +1 -1
  16. data/lib/mutant/mutator/node/generic.rb +1 -1
  17. data/lib/mutant/mutator/registry.rb +1 -1
  18. data/lib/mutant/reporter/cli.rb +1 -1
  19. data/lib/mutant/reporter/cli/format.rb +0 -12
  20. data/lib/mutant/reporter/cli/printer.rb +1 -1
  21. data/lib/mutant/result.rb +1 -1
  22. data/lib/mutant/runner.rb +50 -29
  23. data/lib/mutant/runner/collector.rb +10 -11
  24. data/lib/mutant/subject.rb +1 -4
  25. data/lib/mutant/subject/method.rb +11 -0
  26. data/lib/mutant/version.rb +1 -1
  27. data/meta/send.rb +13 -0
  28. data/mutant.gemspec +2 -2
  29. data/spec/spec_helper.rb +2 -9
  30. data/spec/support/corpus.rb +5 -5
  31. data/spec/support/rb_bug.rb +18 -0
  32. data/spec/unit/mutant/cli_spec.rb +2 -2
  33. data/spec/unit/mutant/expression/methods_spec.rb +7 -1
  34. data/spec/unit/mutant/isolation_spec.rb +22 -2
  35. data/spec/unit/mutant/matcher/method/instance_spec.rb +5 -5
  36. data/spec/unit/mutant/matcher/method/singleton_spec.rb +5 -5
  37. data/spec/unit/mutant/reporter/cli_spec.rb +13 -14
  38. data/spec/unit/mutant/runner/collector_spec.rb +198 -0
  39. data/spec/unit/mutant/runner_spec.rb +2 -3
  40. data/spec/unit/mutant/subject/method/instance_spec.rb +8 -0
  41. data/spec/unit/mutant/subject/method/singleton_spec.rb +8 -0
  42. data/spec/unit/mutant/warning_filter_spec.rb +1 -1
  43. data/test_app/Gemfile.devtools +1 -1
  44. data/test_app/lib/test_app.rb +4 -0
  45. metadata +22 -19
  46. data/lib/parser_extensions.rb +0 -25
@@ -114,10 +114,7 @@ module Mutant
114
114
  #
115
115
  # @api private
116
116
  #
117
- def match_expressions
118
- [expression].concat(context.match_expressions)
119
- end
120
- memoize :match_expressions
117
+ abstract_method :match_expressions
121
118
 
122
119
  private
123
120
 
@@ -32,6 +32,17 @@ module Mutant
32
32
  end
33
33
  memoize :expression
34
34
 
35
+ # Return match expressions
36
+ #
37
+ # @return [Array<Expression>]
38
+ #
39
+ # @api private
40
+ #
41
+ def match_expressions
42
+ [expression].concat(context.match_expressions)
43
+ end
44
+ memoize :match_expressions
45
+
35
46
  private
36
47
 
37
48
  # Return scope
@@ -1,4 +1,4 @@
1
1
  module Mutant
2
2
  # The current mutant version
3
- VERSION = '0.6.3'.freeze
3
+ VERSION = '0.6.4'.freeze
4
4
  end # Mutant
@@ -145,6 +145,19 @@ Mutant::Meta::Example.add do
145
145
  mutation 'foo.instance_of?(bar)'
146
146
  end
147
147
 
148
+ Mutant::Meta::Example.add do
149
+ source 'foo.is_a?(bar)'
150
+
151
+ singleton_mutations
152
+ mutation 'foo'
153
+ mutation 'bar'
154
+ mutation 'foo.is_a?'
155
+ mutation 'foo.is_a?(nil)'
156
+ mutation 'foo.is_a?(self)'
157
+ mutation 'self.is_a?(bar)'
158
+ mutation 'foo.instance_of?(bar)'
159
+ end
160
+
148
161
  Mutant::Meta::Example.add do
149
162
  source 'foo.kind_of?(bar)'
150
163
 
@@ -30,14 +30,14 @@ Gem::Specification.new do |gem|
30
30
  gem.add_runtime_dependency('morpher', '~> 0.2.3')
31
31
  gem.add_runtime_dependency('procto', '~> 0.0.2')
32
32
  gem.add_runtime_dependency('abstract_type', '~> 0.0.7')
33
- gem.add_runtime_dependency('unparser', '~> 0.1.14')
33
+ gem.add_runtime_dependency('unparser', '~> 0.1.15')
34
34
  gem.add_runtime_dependency('ice_nine', '~> 0.11.0')
35
35
  gem.add_runtime_dependency('adamantium', '~> 0.2.0')
36
36
  gem.add_runtime_dependency('memoizable', '~> 0.4.2')
37
37
  gem.add_runtime_dependency('equalizer', '~> 0.0.9')
38
- gem.add_runtime_dependency('inflecto', '~> 0.0.2')
39
38
  gem.add_runtime_dependency('anima', '~> 0.2.0')
40
39
  gem.add_runtime_dependency('concord', '~> 0.1.5')
41
40
 
42
41
  gem.add_development_dependency('bundler', '~> 1.3', '>= 1.3.5')
42
+ gem.add_development_dependency('ffi', '~> 1.9.6')
43
43
  end
@@ -1,10 +1,6 @@
1
1
  if ENV['COVERAGE'] == 'true'
2
2
  require 'simplecov'
3
3
 
4
- SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
5
- SimpleCov::Formatter::HTMLFormatter,
6
- ]
7
-
8
4
  SimpleCov.start do
9
5
  command_name 'spec:unit'
10
6
 
@@ -16,10 +12,11 @@ if ENV['COVERAGE'] == 'true'
16
12
  add_filter 'lib/mutant/zombifier'
17
13
  add_filter 'lib/mutant/zombifier/*'
18
14
 
19
- minimum_coverage 89.77 # TODO: raise this to 100, then mutation test
15
+ minimum_coverage 97.64 # TODO: raise this to 100, then mutation test
20
16
  end
21
17
  end
22
18
 
19
+ require 'tempfile'
23
20
  require 'concord'
24
21
  require 'adamantium'
25
22
  require 'devtools/spec_helper'
@@ -52,8 +49,4 @@ RSpec.configure do |config|
52
49
  config.include(CompressHelper)
53
50
  config.include(ParserHelper)
54
51
  config.include(Mutant::AST::Sexp)
55
-
56
- config.expect_with :rspec do |rspec|
57
- rspec.syntax = :expect
58
- end
59
52
  end
@@ -1,5 +1,6 @@
1
1
  require 'morpher'
2
2
  require 'anima'
3
+ require 'mutant'
3
4
 
4
5
  # Namespace module for corpus testing
5
6
  module Corpus
@@ -51,8 +52,8 @@ module Corpus
51
52
  start = Time.now
52
53
  paths = Pathname.glob(repo_path.join('**/*.rb')).sort_by(&:size).reverse
53
54
  options = {
54
- finish: method(:finish),
55
- start: method(:start),
55
+ finish: method(:finish),
56
+ start: method(:start),
56
57
  in_processes: parallel_processes
57
58
  }
58
59
  total = Parallel.map(paths, options) do |path|
@@ -135,9 +136,8 @@ module Corpus
135
136
  # @api private
136
137
  #
137
138
  def parallel_processes
138
- case
139
- when Devtools.circle_ci?
140
- CIRCLE_CI_CONTAINER_PROCESSES
139
+ if ENV['CI']
140
+ Mutant::Config::DEFAULT.jobs
141
141
  else
142
142
  Parallel.processor_count
143
143
  end
@@ -0,0 +1,18 @@
1
+ require 'ffi'
2
+
3
+ module RbBug
4
+ extend FFI::Library
5
+ ffi_lib 'ruby'
6
+ attach_function :rb_bug, [:string, :varargs], :void
7
+
8
+ # Call the test bug
9
+ #
10
+ # @return [undefined]
11
+ #
12
+ # @api private
13
+ #
14
+ def self.call
15
+ rb_bug('%s', :string, 'test bug')
16
+ end
17
+
18
+ end # RbBug
@@ -128,7 +128,7 @@ Environment:
128
128
  --zombie Run mutant zombified
129
129
  -I, --include DIRECTORY Add DIRECTORY to $LOAD_PATH
130
130
  -r, --require NAME Require file with NAME
131
- -j, --jobs NUMBER Number of kill processes. Defaults to number of processors.
131
+ -j, --jobs NUMBER Number of kill jobs. Defaults to number of processors.
132
132
 
133
133
  Options:
134
134
  --score COVERAGE Fail unless COVERAGE is not reached exactly
@@ -178,7 +178,7 @@ Options:
178
178
  it_should_behave_like 'a cli parser'
179
179
 
180
180
  it 'configures expected coverage' do
181
- expect(subject.config.processes).to eql(0)
181
+ expect(subject.config.jobs).to eql(0)
182
182
  end
183
183
  end
184
184
 
@@ -16,7 +16,13 @@ RSpec.describe Mutant::Expression::Methods do
16
16
  it { should be(object.syntax.length) }
17
17
  end
18
18
 
19
- context 'when other is an unequivalent expression' do
19
+ context 'when other is matched' do
20
+ let(:other) { described_class.parse('TestApp::Literal#foo') }
21
+
22
+ it { should be(object.syntax.length) }
23
+ end
24
+
25
+ context 'when other is an not matched expression' do
20
26
  let(:other) { described_class.parse('Foo*') }
21
27
 
22
28
  it { should be(0) }
@@ -39,9 +39,29 @@ RSpec.describe Mutant::Isolation::Fork do
39
39
  expect(object.call { :foo }).to be(:foo)
40
40
  end
41
41
 
42
- it 'wraps Parallel::DeadWorker exceptions' do
43
- expect { object.call { fail Parallel::DeadWorker } }.to raise_error(Mutant::Isolation::Error)
42
+ it 'wraps exceptions' do
43
+ expect { object.call { fail } }.to raise_error(Mutant::Isolation::Error)
44
44
  end
45
45
 
46
+ it 'wraps exceptions caused by crashing ruby' do
47
+ expect do
48
+ object.call do
49
+ fail RbBug.call
50
+ end
51
+ end.to raise_error(Mutant::Isolation::Error)
52
+ end
53
+
54
+ it 'redirects $stderr of children to /dev/null' do
55
+ begin
56
+ Tempfile.open('mutant-test') do |file|
57
+ $stderr = file
58
+ object.call { $stderr.puts('test') }
59
+ file.rewind
60
+ expect(file.read).to eql('')
61
+ end
62
+ ensure
63
+ $stderr = STDERR
64
+ end
65
+ end
46
66
  end
47
67
  end
@@ -43,14 +43,14 @@ RSpec.describe Mutant::Matcher::Method::Instance do
43
43
 
44
44
  context 'when method is defined once' do
45
45
  let(:scope) { base::DefinedOnce }
46
- let(:method_line) { 7 }
46
+ let(:method_line) { 10 }
47
47
 
48
48
  it_should_behave_like 'a method matcher'
49
49
  end
50
50
 
51
51
  context 'when method is defined once with a memoizer' do
52
52
  let(:scope) { base::WithMemoizer }
53
- let(:method_line) { 12 }
53
+ let(:method_line) { 15 }
54
54
 
55
55
  it_should_behave_like 'a method matcher'
56
56
  end
@@ -58,7 +58,7 @@ RSpec.describe Mutant::Matcher::Method::Instance do
58
58
  context 'when method is defined multiple times' do
59
59
  context 'on different lines' do
60
60
  let(:scope) { base::DefinedMultipleTimes::DifferentLines }
61
- let(:method_line) { 21 }
61
+ let(:method_line) { 24 }
62
62
  let(:method_arity) { 1 }
63
63
 
64
64
  it_should_behave_like 'a method matcher'
@@ -66,7 +66,7 @@ RSpec.describe Mutant::Matcher::Method::Instance do
66
66
 
67
67
  context 'on the same line' do
68
68
  let(:scope) { base::DefinedMultipleTimes::SameLineSameScope }
69
- let(:method_line) { 26 }
69
+ let(:method_line) { 29 }
70
70
  let(:method_arity) { 1 }
71
71
 
72
72
  it_should_behave_like 'a method matcher'
@@ -74,7 +74,7 @@ RSpec.describe Mutant::Matcher::Method::Instance do
74
74
 
75
75
  context 'on the same line with different scope' do
76
76
  let(:scope) { base::DefinedMultipleTimes::SameLineDifferentScope }
77
- let(:method_line) { 30 }
77
+ let(:method_line) { 33 }
78
78
  let(:method_arity) { 1 }
79
79
 
80
80
  it_should_behave_like 'a method matcher'
@@ -23,7 +23,7 @@ RSpec.describe Mutant::Matcher::Method::Singleton, '#each' do
23
23
 
24
24
  context 'when also defined on lvar' do
25
25
  let(:scope) { base::AlsoDefinedOnLvar }
26
- let(:method_line) { 63 }
26
+ let(:method_line) { 66 }
27
27
 
28
28
  it_should_behave_like 'a method matcher'
29
29
 
@@ -37,7 +37,7 @@ RSpec.describe Mutant::Matcher::Method::Singleton, '#each' do
37
37
 
38
38
  context 'when defined on self' do
39
39
  let(:scope) { base::DefinedOnSelf }
40
- let(:method_line) { 58 }
40
+ let(:method_line) { 61 }
41
41
 
42
42
  it_should_behave_like 'a method matcher'
43
43
  end
@@ -46,13 +46,13 @@ RSpec.describe Mutant::Matcher::Method::Singleton, '#each' do
46
46
 
47
47
  context 'inside namespace' do
48
48
  let(:scope) { base::DefinedOnConstant::InsideNamespace }
49
- let(:method_line) { 68 }
49
+ let(:method_line) { 71 }
50
50
 
51
51
  it_should_behave_like 'a method matcher'
52
52
  end
53
53
 
54
54
  context 'outside namespace' do
55
- let(:method_line) { 75 }
55
+ let(:method_line) { 78 }
56
56
  let(:scope) { base::DefinedOnConstant::OutsideNamespace }
57
57
 
58
58
  it_should_behave_like 'a method matcher'
@@ -62,7 +62,7 @@ RSpec.describe Mutant::Matcher::Method::Singleton, '#each' do
62
62
  context 'when defined multiple times in the same line' do
63
63
  context 'with method on different scope' do
64
64
  let(:scope) { base::DefinedMultipleTimes::SameLine::DifferentScope }
65
- let(:method_line) { 94 }
65
+ let(:method_line) { 97 }
66
66
  let(:method_arity) { 1 }
67
67
 
68
68
  it_should_behave_like 'a method matcher'
@@ -33,7 +33,6 @@ RSpec.describe Mutant::Reporter::CLI do
33
33
 
34
34
  let(:result) do
35
35
  Mutant::Result::Env.new(
36
- done: true,
37
36
  env: env,
38
37
  runtime: 1.1,
39
38
  subject_results: subject_results
@@ -51,7 +50,7 @@ RSpec.describe Mutant::Reporter::CLI do
51
50
  )
52
51
  end
53
52
 
54
- let(:config) { Mutant::Config::DEFAULT.update(processes: 1) }
53
+ let(:config) { Mutant::Config::DEFAULT.update(jobs: 1) }
55
54
  let(:mutation_class) { Mutant::Mutation::Evil }
56
55
  let(:matchable_scopes) { double('Matchable Scopes', length: 10) }
57
56
 
@@ -199,7 +198,7 @@ RSpec.describe Mutant::Reporter::CLI do
199
198
  Matcher: #<Mutant::Matcher::Config match_expressions=[] subject_ignores=[] subject_selects=[]>
200
199
  Integration: null
201
200
  Expect Coverage: 100.00%
202
- Processes: 1
201
+ Jobs: 1
203
202
  Includes: []
204
203
  Requires: []
205
204
  REPORT
@@ -257,7 +256,7 @@ RSpec.describe Mutant::Reporter::CLI do
257
256
  Matcher: #<Mutant::Matcher::Config match_expressions=[] subject_ignores=[] subject_selects=[]>
258
257
  Integration: null
259
258
  Expect Coverage: 100.00%
260
- Processes: 1
259
+ Jobs: 1
261
260
  Includes: []
262
261
  Requires: []
263
262
  Available Subjects: 1
@@ -286,7 +285,7 @@ RSpec.describe Mutant::Reporter::CLI do
286
285
  Matcher: #<Mutant::Matcher::Config match_expressions=[] subject_ignores=[] subject_selects=[]>
287
286
  Integration: null
288
287
  Expect Coverage: 100.00%
289
- Processes: 1
288
+ Jobs: 1
290
289
  Includes: []
291
290
  Requires: []
292
291
  Available Subjects: 1
@@ -323,7 +322,7 @@ RSpec.describe Mutant::Reporter::CLI do
323
322
  Matcher: #<Mutant::Matcher::Config match_expressions=[] subject_ignores=[] subject_selects=[]>
324
323
  Integration: null
325
324
  Expect Coverage: 100.00%
326
- Processes: 1
325
+ Jobs: 1
327
326
  Includes: []
328
327
  Requires: []
329
328
  Available Subjects: 1
@@ -350,7 +349,7 @@ RSpec.describe Mutant::Reporter::CLI do
350
349
  Matcher: #<Mutant::Matcher::Config match_expressions=[] subject_ignores=[] subject_selects=[]>
351
350
  Integration: null
352
351
  Expect Coverage: 100.00%
353
- Processes: 1
352
+ Jobs: 1
354
353
  Includes: []
355
354
  Requires: []
356
355
  Available Subjects: 1
@@ -385,7 +384,7 @@ RSpec.describe Mutant::Reporter::CLI do
385
384
  Matcher: #<Mutant::Matcher::Config match_expressions=[] subject_ignores=[] subject_selects=[]>
386
385
  Integration: null
387
386
  Expect Coverage: 100.00%
388
- Processes: 1
387
+ Jobs: 1
389
388
  Includes: []
390
389
  Requires: []
391
390
  Available Subjects: 1
@@ -418,7 +417,7 @@ RSpec.describe Mutant::Reporter::CLI do
418
417
  Matcher: #<Mutant::Matcher::Config match_expressions=[] subject_ignores=[] subject_selects=[]>
419
418
  Integration: null
420
419
  Expect Coverage: 100.00%
421
- Processes: 1
420
+ Jobs: 1
422
421
  Includes: []
423
422
  Requires: []
424
423
  Available Subjects: 1
@@ -451,7 +450,7 @@ RSpec.describe Mutant::Reporter::CLI do
451
450
  Matcher: #<Mutant::Matcher::Config match_expressions=[] subject_ignores=[] subject_selects=[]>
452
451
  Integration: null
453
452
  Expect Coverage: 100.00%
454
- Processes: 1
453
+ Jobs: 1
455
454
  Includes: []
456
455
  Requires: []
457
456
  Available Subjects: 1
@@ -492,7 +491,7 @@ RSpec.describe Mutant::Reporter::CLI do
492
491
  Matcher: #<Mutant::Matcher::Config match_expressions=[] subject_ignores=[] subject_selects=[]>
493
492
  Integration: null
494
493
  Expect Coverage: 100.00%
495
- Processes: 1
494
+ Jobs: 1
496
495
  Includes: []
497
496
  Requires: []
498
497
  Available Subjects: 1
@@ -527,7 +526,7 @@ RSpec.describe Mutant::Reporter::CLI do
527
526
  Matcher: #<Mutant::Matcher::Config match_expressions=[] subject_ignores=[] subject_selects=[]>
528
527
  Integration: null
529
528
  Expect Coverage: 100.00%
530
- Processes: 1
529
+ Jobs: 1
531
530
  Includes: []
532
531
  Requires: []
533
532
  Available Subjects: 1
@@ -548,14 +547,14 @@ RSpec.describe Mutant::Reporter::CLI do
548
547
  let(:subjects) { [] }
549
548
  let(:subject_results) { [] }
550
549
 
551
- let(:config) { Mutant::Config::DEFAULT.update(processes: 1, includes: %w[include-dir], requires: %w[require-name]) }
550
+ let(:config) { Mutant::Config::DEFAULT.update(jobs: 1, includes: %w[include-dir], requires: %w[require-name]) }
552
551
 
553
552
  it_reports(<<-REPORT)
554
553
  Mutant configuration:
555
554
  Matcher: #<Mutant::Matcher::Config match_expressions=[] subject_ignores=[] subject_selects=[]>
556
555
  Integration: null
557
556
  Expect Coverage: 100.00%
558
- Processes: 1
557
+ Jobs: 1
559
558
  Includes: ["include-dir"]
560
559
  Requires: ["require-name"]
561
560
  Available Subjects: 0
@@ -0,0 +1,198 @@
1
+ require 'spec_helper'
2
+
3
+ describe Mutant::Runner::Collector 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
+ let(:env) do
11
+ double(
12
+ 'env',
13
+ subjects: [mutation_a.subject]
14
+ )
15
+ end
16
+
17
+ let(:mutation_a) do
18
+ double(
19
+ 'mutation a',
20
+ subject: double('subject', identification: 'A')
21
+ )
22
+ end
23
+
24
+ let(:mutation_a_result) do
25
+ double(
26
+ 'mutation a result',
27
+ index: 0,
28
+ runtime: 0.0,
29
+ mutation: mutation_a
30
+ )
31
+ end
32
+
33
+ let(:subject_a_result) do
34
+ Mutant::Result::Subject.new(
35
+ subject: mutation_a.subject,
36
+ runtime: 0.0,
37
+ mutation_results: [mutation_a_result]
38
+ )
39
+ end
40
+
41
+ let(:active_subject_result) do
42
+ subject_a_result.update(mutation_results: [])
43
+ end
44
+
45
+ let(:active_subject_results) do
46
+ [active_subject_result]
47
+ end
48
+
49
+ describe '.new' do
50
+ it 'initializes instance variables' do
51
+ expect(object.instance_variables).to include(:@last_mutation_result)
52
+ end
53
+ end
54
+
55
+ describe '#start' do
56
+ subject { object.start(mutation_a) }
57
+
58
+ it 'tracs the mutation as active' do
59
+ expect { subject }.to change { object.active_subject_results }.from([]).to(active_subject_results)
60
+ end
61
+
62
+ it_should_behave_like 'a command method'
63
+ end
64
+
65
+ describe '#finish' do
66
+ subject { object.finish(mutation_a_result) }
67
+
68
+ before do
69
+ object.start(mutation_a)
70
+ end
71
+
72
+ it 'removes the tracking of mutation as active' do
73
+ expect { subject }.to change { object.active_subject_results }.from(active_subject_results).to([])
74
+ end
75
+
76
+ it 'sets last mutation result' do
77
+ expect { subject }.to change { object.last_mutation_result }.from(nil).to(mutation_a_result)
78
+ end
79
+
80
+ it 'aggregates results in #result' do
81
+ subject
82
+ expect(object.result).to eql(
83
+ Mutant::Result::Env.new(
84
+ env: object.env,
85
+ runtime: 0.0,
86
+ subject_results: [subject_a_result]
87
+ )
88
+ )
89
+ end
90
+
91
+ it_should_behave_like 'a command method'
92
+ end
93
+
94
+ describe '#last_mutation_result' do
95
+ subject { object.last_mutation_result }
96
+
97
+ context 'when empty' do
98
+ it { should be(nil) }
99
+ end
100
+
101
+ context 'with partial state' do
102
+ before do
103
+ object.start(mutation_a)
104
+ end
105
+
106
+ it { should be(nil) }
107
+ end
108
+
109
+ context 'with full state' do
110
+ before do
111
+ object.start(mutation_a)
112
+ object.finish(mutation_a_result)
113
+ end
114
+
115
+ it { should be(mutation_a_result) }
116
+ end
117
+ end
118
+
119
+ describe '#active_subject_results' do
120
+ subject { object.active_subject_results }
121
+
122
+ context 'when empty' do
123
+ it { should eql([]) }
124
+ end
125
+
126
+ context 'on partial state' do
127
+ let(:mutation_b) do
128
+ double(
129
+ 'mutation b',
130
+ subject: double(
131
+ 'subject',
132
+ identification: 'B'
133
+ )
134
+ )
135
+ end
136
+
137
+ let(:mutation_b_result) do
138
+ double(
139
+ 'mutation b result',
140
+ index: 0,
141
+ runtime: 0.0,
142
+ mutation: mutation_b
143
+ )
144
+ end
145
+
146
+ let(:subject_b_result) do
147
+ Mutant::Result::Subject.new(
148
+ subject: mutation_b.subject,
149
+ runtime: 0.0,
150
+ mutation_results: [mutation_b_result]
151
+ )
152
+ end
153
+
154
+ let(:active_subject_results) { [subject_a_result, subject_b_result] }
155
+
156
+ before do
157
+ object.start(mutation_b)
158
+ object.start(mutation_a)
159
+ end
160
+
161
+ it { should eql(active_subject_results.map { |result| result.update(mutation_results: []) }) }
162
+ end
163
+
164
+ context 'on full state' do
165
+ before do
166
+ object.start(mutation_a)
167
+ object.finish(mutation_a_result)
168
+ end
169
+
170
+ it { should eql([]) }
171
+ end
172
+ end
173
+
174
+ describe '#result' do
175
+ subject { object.result }
176
+
177
+ context 'when empty' do
178
+ it { should eql(Mutant::Result::Env.new(env: object.env, runtime: 0.0, subject_results: [active_subject_result])) }
179
+ end
180
+
181
+ context 'on partial state' do
182
+ before do
183
+ object.start(mutation_a)
184
+ end
185
+
186
+ it { should eql(Mutant::Result::Env.new(env: object.env, runtime: 0.0, subject_results: [active_subject_result])) }
187
+ end
188
+
189
+ context 'on full state' do
190
+ before do
191
+ object.start(mutation_a)
192
+ object.finish(mutation_a_result)
193
+ end
194
+
195
+ it { should eql(Mutant::Result::Env.new(env: object.env, runtime: 0.0, subject_results: [subject_a_result])) }
196
+ end
197
+ end
198
+ end