mutant 0.6.7 → 0.7.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Changelog.md +10 -0
- data/README.md +1 -1
- data/config/flay.yml +1 -1
- data/config/reek.yml +11 -40
- data/config/rubocop.yml +1 -1
- data/lib/mutant.rb +10 -2
- data/lib/mutant/actor.rb +64 -0
- data/lib/mutant/actor/actor.rb +50 -0
- data/lib/mutant/actor/env.rb +35 -0
- data/lib/mutant/actor/mailbox.rb +53 -0
- data/lib/mutant/actor/receiver.rb +48 -0
- data/lib/mutant/actor/sender.rb +27 -0
- data/lib/mutant/ast/types.rb +1 -1
- data/lib/mutant/cli.rb +2 -0
- data/lib/mutant/config.rb +2 -1
- data/lib/mutant/env.rb +6 -2
- data/lib/mutant/expression/methods.rb +1 -1
- data/lib/mutant/integration.rb +11 -1
- data/lib/mutant/isolation.rb +1 -2
- data/lib/mutant/meta/example.rb +1 -1
- data/lib/mutant/mutation.rb +47 -21
- data/lib/mutant/mutator/node.rb +1 -1
- data/lib/mutant/mutator/node/literal/symbol.rb +1 -1
- data/lib/mutant/mutator/node/send.rb +4 -3
- data/lib/mutant/reporter/cli.rb +2 -0
- data/lib/mutant/reporter/cli/format.rb +23 -36
- data/lib/mutant/reporter/cli/printer.rb +66 -27
- data/lib/mutant/result.rb +45 -58
- data/lib/mutant/runner.rb +47 -154
- data/lib/mutant/runner/master.rb +174 -0
- data/lib/mutant/runner/scheduler.rb +141 -0
- data/lib/mutant/runner/worker.rb +93 -0
- data/lib/mutant/subject/method/instance.rb +1 -15
- data/lib/mutant/test.rb +2 -15
- data/lib/mutant/version.rb +1 -1
- data/meta/send.rb +16 -0
- data/mutant-rspec.gemspec +1 -1
- data/mutant.gemspec +1 -1
- data/spec/integration/mutant/rspec_spec.rb +0 -6
- data/spec/spec_helper.rb +9 -1
- data/spec/support/fake_actor.rb +93 -0
- data/spec/support/shared_context.rb +135 -0
- data/spec/unit/mutant/actor/actor_spec.rb +35 -0
- data/spec/unit/mutant/actor/binding_spec.rb +32 -0
- data/spec/unit/mutant/actor/env_spec.rb +49 -0
- data/spec/unit/mutant/actor/message_spec.rb +23 -0
- data/spec/unit/mutant/actor/receiver_spec.rb +60 -0
- data/spec/unit/mutant/actor/sender_spec.rb +22 -0
- data/spec/unit/mutant/cli_spec.rb +17 -4
- data/spec/unit/mutant/env_spec.rb +2 -2
- data/spec/unit/mutant/mailbox_spec.rb +33 -0
- data/spec/unit/mutant/mutation_spec.rb +52 -18
- data/spec/unit/mutant/mutator/registry_spec.rb +4 -4
- data/spec/unit/mutant/reporter/cli_spec.rb +131 -249
- data/spec/unit/mutant/result/env_spec.rb +55 -0
- data/spec/unit/mutant/result/subject_spec.rb +43 -0
- data/spec/unit/mutant/runner/master_spec.rb +199 -0
- data/spec/unit/mutant/runner/scheduler_spec.rb +161 -0
- data/spec/unit/mutant/runner/worker_spec.rb +73 -0
- data/spec/unit/mutant/runner_spec.rb +60 -118
- data/spec/unit/mutant/subject/method/instance_spec.rb +18 -31
- data/spec/unit/mutant/warning_filter_spec.rb +1 -1
- metadata +39 -14
- data/lib/mutant/runner/collector.rb +0 -133
- data/lib/mutant/warning_expectation.rb +0 -47
- data/spec/unit/mutant/runner/collector_spec.rb +0 -198
- data/spec/unit/mutant/test_spec.rb +0 -23
- data/spec/unit/mutant/warning_expectation_spec.rb +0 -80
- data/test_app/Gemfile.rspec2 +0 -6
@@ -1,133 +0,0 @@
|
|
1
|
-
module Mutant
|
2
|
-
class Runner
|
3
|
-
# Parallel process collector
|
4
|
-
class Collector
|
5
|
-
include Concord::Public.new(:env)
|
6
|
-
|
7
|
-
# Initialize object
|
8
|
-
#
|
9
|
-
# @return [undefined]
|
10
|
-
#
|
11
|
-
# @api private
|
12
|
-
#
|
13
|
-
def initialize(*)
|
14
|
-
super
|
15
|
-
@start = Time.now
|
16
|
-
@aggregate = Hash.new { |hash, key| hash[key] = [] }
|
17
|
-
@active = Set.new
|
18
|
-
@last_mutation_result = nil
|
19
|
-
end
|
20
|
-
|
21
|
-
# Return last mutation result
|
22
|
-
#
|
23
|
-
# @return [Result::Mutation]
|
24
|
-
# if there is a last mutation result
|
25
|
-
#
|
26
|
-
# @return [nil]
|
27
|
-
# otherwise
|
28
|
-
#
|
29
|
-
# @api private
|
30
|
-
#
|
31
|
-
attr_reader :last_mutation_result
|
32
|
-
|
33
|
-
# Return active subject results
|
34
|
-
#
|
35
|
-
# @return [Array<Result::Subject>]
|
36
|
-
#
|
37
|
-
# @api private
|
38
|
-
#
|
39
|
-
def active_subject_results
|
40
|
-
active_subjects.map(&method(:subject_result))
|
41
|
-
end
|
42
|
-
|
43
|
-
# Return current result
|
44
|
-
#
|
45
|
-
# @return [Result::Env]
|
46
|
-
#
|
47
|
-
# @api private
|
48
|
-
#
|
49
|
-
def result
|
50
|
-
Result::Env.new(
|
51
|
-
env: env,
|
52
|
-
runtime: Time.now - @start,
|
53
|
-
subject_results: subject_results
|
54
|
-
)
|
55
|
-
end
|
56
|
-
|
57
|
-
# Register mutation start
|
58
|
-
#
|
59
|
-
# @param [Mutation] mutation
|
60
|
-
#
|
61
|
-
# @return [self]
|
62
|
-
#
|
63
|
-
# @api private
|
64
|
-
#
|
65
|
-
def start(mutation)
|
66
|
-
@active << mutation
|
67
|
-
self
|
68
|
-
end
|
69
|
-
|
70
|
-
# Handle mutation finish
|
71
|
-
#
|
72
|
-
# @param [Result::Mutation] mutation_result
|
73
|
-
#
|
74
|
-
# @return [self]
|
75
|
-
#
|
76
|
-
# @api private
|
77
|
-
#
|
78
|
-
def finish(mutation_result)
|
79
|
-
@last_mutation_result = mutation_result
|
80
|
-
|
81
|
-
mutation = mutation_result.mutation
|
82
|
-
@active.delete(mutation)
|
83
|
-
|
84
|
-
@aggregate[mutation.subject] << mutation_result
|
85
|
-
|
86
|
-
self
|
87
|
-
end
|
88
|
-
|
89
|
-
private
|
90
|
-
|
91
|
-
# Return current subject results
|
92
|
-
#
|
93
|
-
# @return [Array<Result::Subject>]
|
94
|
-
#
|
95
|
-
# @api private
|
96
|
-
#
|
97
|
-
def subject_results
|
98
|
-
env.subjects.map(&method(:subject_result))
|
99
|
-
end
|
100
|
-
|
101
|
-
# Return active subjects
|
102
|
-
#
|
103
|
-
# @return [Array<Subject>]
|
104
|
-
#
|
105
|
-
# @api private
|
106
|
-
#
|
107
|
-
def active_subjects
|
108
|
-
@active.each_with_object(Set.new) do |mutation, subjects|
|
109
|
-
subjects << mutation.subject
|
110
|
-
end.sort_by(&:identification)
|
111
|
-
end
|
112
|
-
|
113
|
-
# Return current subject result
|
114
|
-
#
|
115
|
-
# @param [Subject] subject
|
116
|
-
#
|
117
|
-
# @return [Array<Subject::Result>]
|
118
|
-
#
|
119
|
-
# @api private
|
120
|
-
#
|
121
|
-
def subject_result(subject)
|
122
|
-
mutation_results = @aggregate[subject].sort_by(&:index)
|
123
|
-
|
124
|
-
Result::Subject.new(
|
125
|
-
subject: subject,
|
126
|
-
runtime: mutation_results.map(&:runtime).inject(0.0, :+),
|
127
|
-
mutation_results: mutation_results
|
128
|
-
)
|
129
|
-
end
|
130
|
-
|
131
|
-
end # Collector
|
132
|
-
end # Runner
|
133
|
-
end # Mutant
|
@@ -1,47 +0,0 @@
|
|
1
|
-
module Mutant
|
2
|
-
# A class to expect some warning message raising on absence of unexpected warnings
|
3
|
-
class WarningExpectation
|
4
|
-
include Adamantium::Flat, Concord.new(:expected)
|
5
|
-
|
6
|
-
# Error raised on expectation miss
|
7
|
-
class ExpectationError < RuntimeError
|
8
|
-
include Concord.new(:unexpected, :expected)
|
9
|
-
|
10
|
-
# Return exception message
|
11
|
-
#
|
12
|
-
# @return [String]
|
13
|
-
#
|
14
|
-
# @api private
|
15
|
-
#
|
16
|
-
def message
|
17
|
-
"Unexpected warnings: #{unexpected.inspect}, expected: #{expected.inspect}"
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
# Execute blocks with warning expectations
|
22
|
-
#
|
23
|
-
# @return [self]
|
24
|
-
#
|
25
|
-
# @api private
|
26
|
-
#
|
27
|
-
def execute(&block)
|
28
|
-
warnings = WarningFilter.use do
|
29
|
-
block.call
|
30
|
-
end
|
31
|
-
|
32
|
-
missing = expected - warnings
|
33
|
-
unexpected = warnings - expected
|
34
|
-
|
35
|
-
if unexpected.any?
|
36
|
-
fail ExpectationError.new(unexpected, expected)
|
37
|
-
end
|
38
|
-
|
39
|
-
if missing.any?
|
40
|
-
$stderr.puts("Expected but missing warnings: #{missing}")
|
41
|
-
end
|
42
|
-
|
43
|
-
self
|
44
|
-
end
|
45
|
-
|
46
|
-
end # WarningExpectation
|
47
|
-
end # Mutant
|
@@ -1,198 +0,0 @@
|
|
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
|
@@ -1,23 +0,0 @@
|
|
1
|
-
RSpec.describe Mutant::Test do
|
2
|
-
let(:object) { described_class.new(integration, expression) }
|
3
|
-
|
4
|
-
let(:integration) { double('Integration', name: 'test-integration') }
|
5
|
-
let(:expression) { double('Expression', syntax: 'test-syntax') }
|
6
|
-
|
7
|
-
describe '#identification' do
|
8
|
-
subject { object.identification }
|
9
|
-
|
10
|
-
it { should eql('test-integration:test-syntax') }
|
11
|
-
end
|
12
|
-
|
13
|
-
describe '#run' do
|
14
|
-
subject { object.run }
|
15
|
-
|
16
|
-
let(:report) { double('Report') }
|
17
|
-
|
18
|
-
it 'runs test via integration' do
|
19
|
-
expect(integration).to receive(:run).with(object).and_return(report)
|
20
|
-
expect(subject).to be(report)
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
@@ -1,80 +0,0 @@
|
|
1
|
-
RSpec.describe Mutant::WarningExpectation do
|
2
|
-
let(:object) { described_class.new(expected_warnings) }
|
3
|
-
|
4
|
-
let(:expected_warnings) { [] }
|
5
|
-
let(:actual_warnings) { [] }
|
6
|
-
|
7
|
-
let(:warning_a) { "foo.rb:10: warning: We have a problem!\n" }
|
8
|
-
let(:warning_b) { "bar.rb:10: warning: We have an other problem!\n" }
|
9
|
-
|
10
|
-
describe '#execute' do
|
11
|
-
subject { object.execute(&block) }
|
12
|
-
|
13
|
-
before do
|
14
|
-
@called = false
|
15
|
-
end
|
16
|
-
|
17
|
-
let(:block) do
|
18
|
-
lambda do
|
19
|
-
@called = true
|
20
|
-
actual_warnings.each(&Kernel.method(:warn))
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
it 'executes block' do
|
25
|
-
expect { subject }.to change { @called }.from(false).to(true)
|
26
|
-
end
|
27
|
-
|
28
|
-
context 'when no warnings occur during block execution' do
|
29
|
-
|
30
|
-
context 'and no warnings are expected' do
|
31
|
-
it_should_behave_like 'a command method'
|
32
|
-
end
|
33
|
-
|
34
|
-
context 'and warnings are expected' do
|
35
|
-
let(:expected_warnings) { [warning_a] }
|
36
|
-
|
37
|
-
before do
|
38
|
-
expect($stderr).to receive(:puts).with("Expected but missing warnings: #{expected_warnings}")
|
39
|
-
end
|
40
|
-
|
41
|
-
it_should_behave_like 'a command method'
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
context 'when warnings occur during block execution' do
|
46
|
-
let(:actual_warnings) { [warning_a, warning_b] }
|
47
|
-
|
48
|
-
context 'and only some no warnings are expected' do
|
49
|
-
let(:expected_warnings) { [warning_a] }
|
50
|
-
|
51
|
-
it 'raises an expectation error' do
|
52
|
-
expect { subject }.to raise_error(Mutant::WarningExpectation::ExpectationError.new([warning_b], expected_warnings))
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
context 'and all warnings are expected' do
|
57
|
-
let(:expected_warnings) { [warning_a, warning_b] }
|
58
|
-
|
59
|
-
it_should_behave_like 'a command method'
|
60
|
-
end
|
61
|
-
|
62
|
-
context 'and there is an expected warning missing' do
|
63
|
-
let(:expected_warnings) { [warning_a] }
|
64
|
-
let(:actual_warnings) { [warning_b] }
|
65
|
-
|
66
|
-
it 'raises an expectation error' do
|
67
|
-
expect { subject }.to raise_error(Mutant::WarningExpectation::ExpectationError.new([warning_b], expected_warnings))
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
RSpec.describe Mutant::WarningExpectation::ExpectationError do
|
75
|
-
describe '#message' do
|
76
|
-
subject { described_class.new(['unexpected-a'], ['expected-b']).message }
|
77
|
-
|
78
|
-
it { should eql('Unexpected warnings: ["unexpected-a"], expected: ["expected-b"]') }
|
79
|
-
end
|
80
|
-
end
|