mutant 0.7.9 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Changelog.md +11 -1
- data/README.md +5 -12
- data/bin/mutant +17 -1
- data/config/flay.yml +1 -1
- data/config/flog.yml +1 -1
- data/config/reek.yml +4 -4
- data/config/rubocop.yml +21 -3
- data/lib/mutant.rb +15 -61
- data/lib/mutant/cli.rb +1 -7
- data/lib/mutant/color.rb +2 -2
- data/lib/mutant/config.rb +1 -1
- data/lib/mutant/expression/method.rb +1 -1
- data/lib/mutant/expression/methods.rb +1 -1
- data/lib/mutant/expression/namespace.rb +1 -1
- data/lib/mutant/mutator/node/send.rb +18 -18
- data/lib/mutant/reporter/cli.rb +1 -6
- data/lib/mutant/reporter/cli/format.rb +2 -2
- data/lib/mutant/reporter/cli/printer.rb +5 -500
- data/lib/mutant/reporter/cli/printer/config.rb +32 -0
- data/lib/mutant/reporter/cli/printer/env_progress.rb +66 -0
- data/lib/mutant/reporter/cli/printer/env_result.rb +23 -0
- data/lib/mutant/reporter/cli/printer/mutation_progress_result.rb +37 -0
- data/lib/mutant/reporter/cli/printer/mutation_result.rb +151 -0
- data/lib/mutant/reporter/cli/printer/status.rb +60 -0
- data/lib/mutant/reporter/cli/printer/status_progressive.rb +52 -0
- data/lib/mutant/reporter/cli/printer/subject_progress.rb +90 -0
- data/lib/mutant/reporter/cli/printer/subject_result.rb +28 -0
- data/lib/mutant/reporter/cli/printer/test_result.rb +33 -0
- data/lib/mutant/require_highjack.rb +11 -50
- data/lib/mutant/version.rb +1 -1
- data/lib/mutant/zombifier.rb +79 -37
- data/meta/send.rb +1 -1
- data/mutant-rspec.gemspec +1 -1
- data/mutant.gemspec +3 -3
- data/spec/integration/mutant/corpus_spec.rb +2 -2
- data/spec/integration/mutant/rspec_spec.rb +5 -15
- data/spec/integrations.yml +3 -3
- data/spec/spec_helper.rb +1 -0
- data/spec/support/corpus.rb +233 -220
- data/spec/support/file_system.rb +60 -0
- data/spec/support/rb_bug.rb +1 -1
- data/spec/support/ruby_vm.rb +82 -0
- data/spec/support/shared_context.rb +19 -10
- data/spec/unit/mutant/ast_spec.rb +2 -2
- data/spec/unit/mutant/cache_spec.rb +22 -0
- data/spec/unit/mutant/cli_spec.rb +1 -30
- data/spec/unit/mutant/context_spec.rb +1 -0
- data/spec/unit/mutant/expression/method_spec.rb +6 -4
- data/spec/unit/mutant/parallel/master_spec.rb +1 -1
- data/spec/unit/mutant/reporter/cli/printer/config_spec.rb +33 -0
- data/spec/unit/mutant/reporter/cli/printer/env_progress_spec.rb +76 -0
- data/spec/unit/mutant/reporter/cli/printer/env_result_spec.rb +35 -0
- data/spec/unit/mutant/reporter/cli/printer/mutation_progress_result_spec.rb +23 -0
- data/spec/unit/mutant/reporter/cli/printer/mutation_result_spec.rb +110 -0
- data/spec/unit/mutant/reporter/cli/printer/status_progressive_spec.rb +51 -0
- data/spec/unit/mutant/reporter/cli/printer/status_spec.rb +145 -0
- data/spec/unit/mutant/reporter/cli/printer/subject_progress_spec.rb +37 -0
- data/spec/unit/mutant/reporter/cli/printer/subject_result_spec.rb +37 -0
- data/spec/unit/mutant/reporter/cli/printer/test_result_spec.rb +14 -0
- data/spec/unit/mutant/reporter/cli/printer_spec.rb +140 -0
- data/spec/unit/mutant/reporter/cli_spec.rb +69 -313
- data/spec/unit/mutant/reporter/trace_spec.rb +12 -0
- data/spec/unit/mutant/require_highjack_spec.rb +25 -28
- data/spec/unit/mutant/warning_filter_spec.rb +7 -0
- data/spec/unit/mutant/zombifier_spec.rb +120 -0
- data/spec/unit/mutant_spec.rb +0 -43
- data/test_app/Gemfile.rspec3.3 +6 -0
- metadata +46 -17
- data/.travis.yml +0 -20
- data/lib/mutant/zombifier/file.rb +0 -100
- data/spec/integration/mutant/zombie_spec.rb +0 -6
@@ -0,0 +1,60 @@
|
|
1
|
+
module MutantSpec
|
2
|
+
class FileState
|
3
|
+
DEFAULTS = IceNine.deep_freeze(
|
4
|
+
file: false,
|
5
|
+
contents: nil,
|
6
|
+
requires: []
|
7
|
+
)
|
8
|
+
|
9
|
+
include Adamantium, Anima.new(*DEFAULTS.keys)
|
10
|
+
|
11
|
+
def self.new(attributes = DEFAULTS)
|
12
|
+
super(DEFAULTS.merge(attributes))
|
13
|
+
end
|
14
|
+
|
15
|
+
DOES_NOT_EXIST = new
|
16
|
+
|
17
|
+
alias_method :file?, :file
|
18
|
+
end # FileState
|
19
|
+
|
20
|
+
class FakePathname
|
21
|
+
include Adamantium, Concord.new(:file_system, :pathname)
|
22
|
+
|
23
|
+
def join(*arguments)
|
24
|
+
self.class.new(
|
25
|
+
file_system,
|
26
|
+
pathname.join(*arguments)
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
def read
|
31
|
+
state.contents
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_s
|
35
|
+
pathname.to_s
|
36
|
+
end
|
37
|
+
|
38
|
+
def file?
|
39
|
+
state.file?
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def state
|
45
|
+
file_system.state(pathname.to_s)
|
46
|
+
end
|
47
|
+
end # Pathname
|
48
|
+
|
49
|
+
class FileSystem
|
50
|
+
include Adamantium, Concord.new(:file_states)
|
51
|
+
|
52
|
+
def state(filename)
|
53
|
+
file_states.fetch(filename, FileState::DOES_NOT_EXIST)
|
54
|
+
end
|
55
|
+
|
56
|
+
def path(filename)
|
57
|
+
FakePathname.new(self, Pathname.new(filename))
|
58
|
+
end
|
59
|
+
end # FileSystem
|
60
|
+
end # MutantSpec
|
data/spec/support/rb_bug.rb
CHANGED
@@ -0,0 +1,82 @@
|
|
1
|
+
module MutantSpec
|
2
|
+
# Not a real VM, just kidding. It connects the require / eval triggers
|
3
|
+
# require semantics Zombifier relies on in a way we can avoid having to
|
4
|
+
# mock around everywhere to test every detail.
|
5
|
+
#
|
6
|
+
# rubocop:disable LineLength
|
7
|
+
class RubyVM
|
8
|
+
include Concord.new(:expected_events)
|
9
|
+
|
10
|
+
# An event being observed by the VM handlers
|
11
|
+
class EventObservation
|
12
|
+
include Concord::Public.new(:type, :payload)
|
13
|
+
end
|
14
|
+
|
15
|
+
# An event being expected, can advance the VM
|
16
|
+
class EventExpectation
|
17
|
+
include AbstractType, Anima.new(:expected_payload, :trigger_requires)
|
18
|
+
|
19
|
+
DEFAULTS = IceNine.deep_freeze(trigger_requires: [])
|
20
|
+
|
21
|
+
def initialize(attributes)
|
22
|
+
super(DEFAULTS.merge(attributes))
|
23
|
+
end
|
24
|
+
|
25
|
+
def handle(vm, observation)
|
26
|
+
unless match?(observation)
|
27
|
+
fail "Unexpected event observation: #{observation.inspect}, expected #{inspect}"
|
28
|
+
end
|
29
|
+
|
30
|
+
trigger_requires.each(&vm.method(:require))
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
abstract_method :advance_vm
|
36
|
+
|
37
|
+
def match?(observation)
|
38
|
+
observation.type.eql?(self.class) && observation.payload.eql?(expected_payload)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Expectation and advance on require calls
|
42
|
+
class Require < self
|
43
|
+
end
|
44
|
+
|
45
|
+
# Expectation and advance on eval calls
|
46
|
+
class Eval < self
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# A fake implementation of Kernel#require
|
51
|
+
def require(logical_name)
|
52
|
+
handle_event(EventObservation.new(EventExpectation::Require, logical_name: logical_name))
|
53
|
+
self
|
54
|
+
end
|
55
|
+
|
56
|
+
# A fake implementation of Kernel#eval
|
57
|
+
def eval(source, binding, location)
|
58
|
+
handle_event(
|
59
|
+
EventObservation.new(
|
60
|
+
EventExpectation::Eval,
|
61
|
+
binding: binding,
|
62
|
+
source: source,
|
63
|
+
source_location: location
|
64
|
+
)
|
65
|
+
)
|
66
|
+
self
|
67
|
+
end
|
68
|
+
|
69
|
+
# Test if VM events where fully processed
|
70
|
+
def done?
|
71
|
+
expected_events.empty?
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def handle_event(observation)
|
77
|
+
fail "Unexpected event: #{observation.type} / #{observation.payload}" if expected_events.empty?
|
78
|
+
|
79
|
+
expected_events.slice!(0).handle(self, observation)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end # MutantSpec
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# rubocop:disable ModuleLength
|
1
2
|
module SharedContext
|
2
3
|
def update(name, &block)
|
3
4
|
define_method(name) do
|
@@ -13,6 +14,14 @@ module SharedContext
|
|
13
14
|
end
|
14
15
|
end
|
15
16
|
|
17
|
+
def it_reports(expected_content)
|
18
|
+
it 'writes expected report to output' do
|
19
|
+
described_class.call(output, reportable)
|
20
|
+
output.rewind
|
21
|
+
expect(output.read).to eql(strip_indent(expected_content))
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
16
25
|
# rubocop:disable MethodLength
|
17
26
|
# rubocop:disable AbcSize
|
18
27
|
def setup_shared_context
|
@@ -21,11 +30,17 @@ module SharedContext
|
|
21
30
|
let(:job_b) { Mutant::Parallel::Job.new(index: 1, payload: mutation_b) }
|
22
31
|
let(:job_a_result) { Mutant::Runner::JobResult.new(job: job_a, result: mutation_a_result) }
|
23
32
|
let(:job_b_result) { Mutant::Runner::JobResult.new(job: job_b, result: mutation_b_result) }
|
24
|
-
let(:mutations) { [mutation_a, mutation_b] }
|
25
|
-
let(:matchable_scopes) { double('matchable scopes', length: 10) }
|
26
33
|
let(:test_a) { double('test a', identification: 'test-a') }
|
27
34
|
let(:test_b) { double('test b', identification: 'test-b') }
|
35
|
+
let(:output) { StringIO.new }
|
36
|
+
let(:matchable_scopes) { double('matchable scopes', length: 10) }
|
28
37
|
let(:message_sequence) { FakeActor::MessageSequence.new }
|
38
|
+
let(:mutations) { [mutation_a, mutation_b] }
|
39
|
+
let(:mutation_a_node) { s(:false) }
|
40
|
+
let(:mutation_b_node) { s(:nil) }
|
41
|
+
let(:mutation_b) { Mutant::Mutation::Evil.new(subject_a, mutation_b_node) }
|
42
|
+
let(:mutation_a) { Mutant::Mutation::Evil.new(subject_a, mutation_a_node) }
|
43
|
+
let(:subject_a_node) { s(:true) }
|
29
44
|
|
30
45
|
let(:status) do
|
31
46
|
Mutant::Parallel::Status.new(
|
@@ -45,8 +60,8 @@ module SharedContext
|
|
45
60
|
let(:subject_a) do
|
46
61
|
double(
|
47
62
|
'subject a',
|
48
|
-
node:
|
49
|
-
source:
|
63
|
+
node: subject_a_node,
|
64
|
+
source: Unparser.unparse(subject_a_node),
|
50
65
|
tests: [test_a],
|
51
66
|
identification: 'subject-a'
|
52
67
|
)
|
@@ -64,12 +79,6 @@ module SharedContext
|
|
64
79
|
)
|
65
80
|
end
|
66
81
|
|
67
|
-
let(:mutation_a_node) { s(:false) }
|
68
|
-
let(:mutation_b_node) { s(:nil) }
|
69
|
-
|
70
|
-
let(:mutation_b) { Mutant::Mutation::Evil.new(subject_a, mutation_b_node) }
|
71
|
-
let(:mutation_a) { Mutant::Mutation::Evil.new(subject_a, mutation_a_node) }
|
72
|
-
|
73
82
|
let(:mutation_a_result) do
|
74
83
|
Mutant::Result::Mutation.new(
|
75
84
|
mutation: mutation_a,
|
@@ -23,7 +23,7 @@ RSpec.describe Mutant::AST do
|
|
23
23
|
let(:block) { ->(node) { node.equal?(child_a) } }
|
24
24
|
|
25
25
|
it 'returns the full path' do
|
26
|
-
expect(path).to eql([
|
26
|
+
expect(path).to eql(%i[root parent child_a])
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
@@ -31,7 +31,7 @@ RSpec.describe Mutant::AST do
|
|
31
31
|
let(:block) { ->(node) { node.equal?(child_a) || node.equal?(child_b) } }
|
32
32
|
|
33
33
|
it 'returns the last full path' do
|
34
|
-
expect(path).to eql([
|
34
|
+
expect(path).to eql(%i[root parent child_b])
|
35
35
|
end
|
36
36
|
end
|
37
37
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
RSpec.describe Mutant::Cache do
|
2
|
+
let(:object) { described_class.new }
|
3
|
+
|
4
|
+
describe '#parse' do
|
5
|
+
let(:path) { double('Path') }
|
6
|
+
|
7
|
+
subject { object.parse(path) }
|
8
|
+
|
9
|
+
before do
|
10
|
+
allow(File).to receive(:read).with(path).and_return(':source')
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'returns parsed source' do
|
14
|
+
expect(subject).to eql(s(:sym, :source))
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'returns cached parsed source' do
|
18
|
+
source = object.parse(path)
|
19
|
+
expect(subject).to be(source)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -105,13 +105,6 @@ RSpec.describe Mutant::CLI do
|
|
105
105
|
it_should_behave_like 'an invalid cli run'
|
106
106
|
end
|
107
107
|
|
108
|
-
context 'with code filter and missing argument' do
|
109
|
-
let(:arguments) { %w[--code] }
|
110
|
-
let(:expected_message) { 'missing argument: --code' }
|
111
|
-
|
112
|
-
it_should_behave_like 'an invalid cli run'
|
113
|
-
end
|
114
|
-
|
115
108
|
context 'with include help flag' do
|
116
109
|
let(:flags) { %w[--help] }
|
117
110
|
|
@@ -133,10 +126,8 @@ Environment:
|
|
133
126
|
|
134
127
|
Options:
|
135
128
|
--expected-coverage COVERAGE Fail unless COVERAGE is not reached exactly, parsed via Rational()
|
136
|
-
--
|
137
|
-
--use STRATEGY Use STRATEGY for killing mutations
|
129
|
+
--use INTEGRATION Use INTEGRATION to kill mutations
|
138
130
|
--ignore-subject PATTERN Ignore subjects that match PATTERN
|
139
|
-
--code CODE Scope execution to subjects with CODE
|
140
131
|
--fail-fast Fail fast
|
141
132
|
--version Print mutants version
|
142
133
|
-d, --debug Enable debugging output
|
@@ -229,16 +220,6 @@ Options:
|
|
229
220
|
end
|
230
221
|
end
|
231
222
|
|
232
|
-
context 'with score flag' do
|
233
|
-
let(:flags) { %w[--score 50.0] }
|
234
|
-
|
235
|
-
it_should_behave_like 'a cli parser'
|
236
|
-
|
237
|
-
it 'configures expected coverage' do
|
238
|
-
expect(subject.config.expected_coverage).to eql(Rational(1, 2))
|
239
|
-
end
|
240
|
-
end
|
241
|
-
|
242
223
|
context 'with require flag' do
|
243
224
|
let(:flags) { %w[--require foo] }
|
244
225
|
|
@@ -288,15 +269,5 @@ Options:
|
|
288
269
|
expect(subject.config.zombie).to be(true)
|
289
270
|
end
|
290
271
|
end
|
291
|
-
|
292
|
-
context 'with subject code filter' do
|
293
|
-
let(:flags) { %w[--code faa --code bbb] }
|
294
|
-
|
295
|
-
let(:expected_matcher_config) do
|
296
|
-
default_matcher_config.update(subject_selects: [[:code, 'faa'], [:code, 'bbb']])
|
297
|
-
end
|
298
|
-
|
299
|
-
it_should_behave_like 'a cli parser'
|
300
|
-
end
|
301
272
|
end
|
302
273
|
end
|
@@ -30,10 +30,12 @@ RSpec.describe Mutant::Expression::Method do
|
|
30
30
|
let(:input) { instance_method }
|
31
31
|
|
32
32
|
it 'returns correct matcher' do
|
33
|
-
should eql(
|
34
|
-
|
35
|
-
|
36
|
-
|
33
|
+
should eql(
|
34
|
+
Mutant::Matcher::Method::Instance.new(
|
35
|
+
env,
|
36
|
+
TestApp::Literal, TestApp::Literal.instance_method(:string)
|
37
|
+
)
|
38
|
+
)
|
37
39
|
end
|
38
40
|
end
|
39
41
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
RSpec.describe Mutant::Parallel::Master do
|
2
2
|
let(:message_sequence) { FakeActor::MessageSequence.new }
|
3
|
-
let(:actor_names) { [
|
3
|
+
let(:actor_names) { %i[master worker_a worker_b] }
|
4
4
|
let(:status) { double('Status') }
|
5
5
|
let(:sink) { FakeSink.new }
|
6
6
|
let(:processor) { double('Processor') }
|
@@ -0,0 +1,33 @@
|
|
1
|
+
RSpec.describe Mutant::Reporter::CLI::Printer::Config do
|
2
|
+
setup_shared_context
|
3
|
+
|
4
|
+
let(:reportable) { config }
|
5
|
+
|
6
|
+
describe '.call' do
|
7
|
+
context 'on default config' do
|
8
|
+
it_reports(<<-REPORT)
|
9
|
+
Mutant configuration:
|
10
|
+
Matcher: #<Mutant::Matcher::Config match_expressions=[] subject_ignores=[] subject_selects=[]>
|
11
|
+
Integration: null
|
12
|
+
Expect Coverage: 100.00%
|
13
|
+
Jobs: 1
|
14
|
+
Includes: []
|
15
|
+
Requires: []
|
16
|
+
REPORT
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'with non default coverage expectation' do
|
20
|
+
update(:config) { { expected_coverage: 0.1r } }
|
21
|
+
|
22
|
+
it_reports(<<-REPORT)
|
23
|
+
Mutant configuration:
|
24
|
+
Matcher: #<Mutant::Matcher::Config match_expressions=[] subject_ignores=[] subject_selects=[]>
|
25
|
+
Integration: null
|
26
|
+
Expect Coverage: 10.00%
|
27
|
+
Jobs: 1
|
28
|
+
Includes: []
|
29
|
+
Requires: []
|
30
|
+
REPORT
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
RSpec.describe Mutant::Reporter::CLI::Printer::EnvProgress do
|
2
|
+
setup_shared_context
|
3
|
+
|
4
|
+
update(:config) { { expected_coverage: 0.1r } }
|
5
|
+
|
6
|
+
let(:reportable) { env_result }
|
7
|
+
|
8
|
+
describe '.call' do
|
9
|
+
context 'without progress' do
|
10
|
+
update(:subject_a_result) { { mutation_results: [] } }
|
11
|
+
|
12
|
+
it_reports <<-'STR'
|
13
|
+
Mutant configuration:
|
14
|
+
Matcher: #<Mutant::Matcher::Config match_expressions=[] subject_ignores=[] subject_selects=[]>
|
15
|
+
Integration: null
|
16
|
+
Expect Coverage: 10.00%
|
17
|
+
Jobs: 1
|
18
|
+
Includes: []
|
19
|
+
Requires: []
|
20
|
+
Subjects: 1
|
21
|
+
Mutations: 2
|
22
|
+
Kills: 0
|
23
|
+
Alive: 0
|
24
|
+
Runtime: 4.00s
|
25
|
+
Killtime: 0.00s
|
26
|
+
Overhead: Inf%
|
27
|
+
Coverage: 0.00%
|
28
|
+
Expected: 10.00%
|
29
|
+
STR
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'on full coverage' do
|
33
|
+
it_reports <<-'STR'
|
34
|
+
Mutant configuration:
|
35
|
+
Matcher: #<Mutant::Matcher::Config match_expressions=[] subject_ignores=[] subject_selects=[]>
|
36
|
+
Integration: null
|
37
|
+
Expect Coverage: 10.00%
|
38
|
+
Jobs: 1
|
39
|
+
Includes: []
|
40
|
+
Requires: []
|
41
|
+
Subjects: 1
|
42
|
+
Mutations: 2
|
43
|
+
Kills: 2
|
44
|
+
Alive: 0
|
45
|
+
Runtime: 4.00s
|
46
|
+
Killtime: 2.00s
|
47
|
+
Overhead: 100.00%
|
48
|
+
Coverage: 100.00%
|
49
|
+
Expected: 10.00%
|
50
|
+
STR
|
51
|
+
end
|
52
|
+
|
53
|
+
context 'on partial coverage' do
|
54
|
+
update(:mutation_a_test_result) { { passed: true } }
|
55
|
+
|
56
|
+
it_reports <<-'STR'
|
57
|
+
Mutant configuration:
|
58
|
+
Matcher: #<Mutant::Matcher::Config match_expressions=[] subject_ignores=[] subject_selects=[]>
|
59
|
+
Integration: null
|
60
|
+
Expect Coverage: 10.00%
|
61
|
+
Jobs: 1
|
62
|
+
Includes: []
|
63
|
+
Requires: []
|
64
|
+
Subjects: 1
|
65
|
+
Mutations: 2
|
66
|
+
Kills: 1
|
67
|
+
Alive: 1
|
68
|
+
Runtime: 4.00s
|
69
|
+
Killtime: 2.00s
|
70
|
+
Overhead: 100.00%
|
71
|
+
Coverage: 50.00%
|
72
|
+
Expected: 10.00%
|
73
|
+
STR
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|