mutant 0.7.9 → 0.8.0
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.
- 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
|