mutant 0.5.26 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rspec +1 -0
- data/.travis.yml +1 -0
- data/Changelog.md +16 -3
- data/Gemfile +0 -2
- data/Gemfile.devtools +2 -2
- data/README.md +9 -15
- data/bin/mutant +0 -1
- data/config/flay.yml +1 -1
- data/config/flog.yml +1 -1
- data/config/mutant.yml +1 -1
- data/config/reek.yml +14 -11
- data/config/rubocop.yml +1 -1
- data/lib/mutant.rb +22 -21
- data/lib/mutant/ast.rb +47 -0
- data/lib/mutant/cli.rb +7 -4
- data/lib/mutant/config.rb +1 -0
- data/lib/mutant/context.rb +1 -1
- data/lib/mutant/diff.rb +38 -7
- data/lib/mutant/env.rb +22 -3
- data/lib/mutant/expression.rb +15 -4
- data/lib/mutant/integration.rb +1 -1
- data/lib/mutant/isolation.rb +2 -4
- data/lib/mutant/matcher.rb +1 -1
- data/lib/mutant/matcher/method.rb +1 -1
- data/lib/mutant/matcher/method/singleton.rb +1 -1
- data/lib/mutant/matcher/methods.rb +0 -2
- data/lib/mutant/meta/example.rb +0 -2
- data/lib/mutant/meta/example/dsl.rb +1 -1
- data/lib/mutant/mutator.rb +1 -1
- data/lib/mutant/mutator/node.rb +3 -3
- data/lib/mutant/mutator/node/begin.rb +1 -1
- data/lib/mutant/mutator/node/block.rb +16 -3
- data/lib/mutant/mutator/node/if.rb +1 -1
- data/lib/mutant/mutator/node/literal/fixnum.rb +1 -1
- data/lib/mutant/mutator/node/resbody.rb +0 -2
- data/lib/mutant/mutator/node/send.rb +17 -7
- data/lib/mutant/mutator/node/send/index.rb +0 -2
- data/lib/mutant/mutator/registry.rb +1 -1
- data/lib/mutant/mutator/util.rb +1 -1
- data/lib/mutant/mutator/util/array.rb +1 -1
- data/lib/mutant/reporter.rb +13 -3
- data/lib/mutant/reporter/cli.rb +54 -8
- data/lib/mutant/reporter/cli/format.rb +197 -0
- data/lib/mutant/reporter/cli/printer.rb +402 -22
- data/lib/mutant/reporter/cli/tput.rb +27 -0
- data/lib/mutant/reporter/null.rb +4 -34
- data/lib/mutant/reporter/trace.rb +6 -38
- data/lib/mutant/result.rb +44 -56
- data/lib/mutant/runner.rb +99 -52
- data/lib/mutant/runner/collector.rb +134 -0
- data/lib/mutant/subject/method/instance.rb +12 -4
- data/lib/mutant/version.rb +1 -1
- data/lib/mutant/warning_filter.rb +0 -2
- data/lib/mutant/zombifier/file.rb +1 -1
- data/meta/block.rb +17 -1
- data/meta/send.rb +123 -1
- data/mutant-rspec.gemspec +3 -3
- data/mutant.gemspec +1 -1
- data/spec/integration/mutant/corpus_spec.rb +4 -195
- data/spec/integration/mutant/null_spec.rb +1 -3
- data/spec/integration/mutant/rspec_spec.rb +1 -3
- data/spec/integration/mutant/test_mutator_handles_types_spec.rb +1 -3
- data/spec/integration/mutant/zombie_spec.rb +1 -3
- data/spec/integrations.yml +7 -0
- data/spec/shared/method_matcher_behavior.rb +1 -1
- data/spec/spec_helper.rb +1 -0
- data/spec/support/compress_helper.rb +1 -0
- data/spec/support/corpus.rb +239 -0
- data/spec/support/mutation_verifier.rb +2 -4
- data/spec/unit/mutant/cli_spec.rb +20 -13
- data/spec/unit/mutant/context/root_spec.rb +1 -3
- data/spec/unit/mutant/context/scope/root_spec.rb +1 -3
- data/spec/unit/mutant/context/scope/unqualified_name_spec.rb +1 -3
- data/spec/unit/mutant/diff_spec.rb +37 -19
- data/spec/unit/mutant/expression/method_spec.rb +5 -7
- data/spec/unit/mutant/expression/methods_spec.rb +5 -7
- data/spec/unit/mutant/expression/namespace/flat_spec.rb +6 -8
- data/spec/unit/mutant/expression/namespace/recursive_spec.rb +6 -7
- data/spec/unit/mutant/expression_spec.rb +14 -5
- data/spec/unit/mutant/integration_spec.rb +14 -3
- data/spec/unit/mutant/isolation_spec.rb +2 -4
- data/spec/unit/mutant/loader/eval_spec.rb +1 -3
- data/spec/unit/mutant/matcher/chain_spec.rb +1 -3
- data/spec/unit/mutant/matcher/compiler/subject_prefix_spec.rb +21 -0
- data/spec/unit/mutant/matcher/compiler_spec.rb +28 -3
- data/spec/unit/mutant/matcher/filter_spec.rb +1 -3
- data/spec/unit/mutant/matcher/method/instance_spec.rb +3 -5
- data/spec/unit/mutant/matcher/method/singleton_spec.rb +22 -4
- data/spec/unit/mutant/matcher/methods/instance_spec.rb +7 -6
- data/spec/unit/mutant/matcher/methods/singleton_spec.rb +4 -6
- data/spec/unit/mutant/matcher/namespace_spec.rb +1 -3
- data/spec/unit/mutant/matcher/null_spec.rb +1 -3
- data/spec/unit/mutant/mutation_spec.rb +1 -3
- data/spec/unit/mutant/mutator/node_spec.rb +1 -3
- data/spec/unit/mutant/reporter/cli_spec.rb +444 -206
- data/spec/unit/mutant/reporter/null_spec.rb +1 -3
- data/spec/unit/mutant/require_highjack_spec.rb +1 -3
- data/spec/unit/mutant/runner_spec.rb +42 -28
- data/spec/unit/mutant/subject/context_spec.rb +1 -3
- data/spec/unit/mutant/subject/method/instance_spec.rb +27 -19
- data/spec/unit/mutant/subject/method/singleton_spec.rb +49 -17
- data/spec/unit/mutant/subject_spec.rb +1 -3
- data/spec/unit/mutant/test_spec.rb +1 -3
- data/spec/unit/mutant/warning_expectation.rb +1 -3
- data/spec/unit/mutant/warning_filter_spec.rb +1 -3
- data/spec/unit/mutant_spec.rb +13 -3
- data/test_app/Gemfile.devtools +2 -2
- data/test_app/spec/unit/test_app/literal/string_spec.rb +1 -1
- metadata +10 -21
- data/lib/mutant/matcher/method/finder.rb +0 -72
- data/lib/mutant/reporter/cli/progress.rb +0 -10
- data/lib/mutant/reporter/cli/progress/config.rb +0 -30
- data/lib/mutant/reporter/cli/progress/env.rb +0 -30
- data/lib/mutant/reporter/cli/progress/noop.rb +0 -27
- data/lib/mutant/reporter/cli/progress/result.rb +0 -12
- data/lib/mutant/reporter/cli/progress/result/mutation.rb +0 -45
- data/lib/mutant/reporter/cli/progress/result/subject.rb +0 -54
- data/lib/mutant/reporter/cli/progress/subject.rb +0 -27
- data/lib/mutant/reporter/cli/registry.rb +0 -81
- data/lib/mutant/reporter/cli/report.rb +0 -10
- data/lib/mutant/reporter/cli/report/env.rb +0 -92
- data/lib/mutant/reporter/cli/report/mutation.rb +0 -103
- data/lib/mutant/reporter/cli/report/subject.rb +0 -32
- data/lib/mutant/reporter/cli/report/test.rb +0 -28
- data/lib/mutant/walker.rb +0 -53
- data/spec/shared/mutator_behavior.rb +0 -55
@@ -1,6 +1,4 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
describe 'as a zombie' do
|
1
|
+
RSpec.describe 'as a zombie' do
|
4
2
|
specify 'it allows to create zombie from mutant' do
|
5
3
|
expect { Mutant.zombify }.to change { defined?(Zombie) }.from(nil).to('constant')
|
6
4
|
expect(Zombie.constants).to include(:Mutant)
|
data/spec/integrations.yml
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -0,0 +1,239 @@
|
|
1
|
+
require 'morpher'
|
2
|
+
require 'anima'
|
3
|
+
|
4
|
+
# Namespace module for corpus testing
|
5
|
+
module Corpus
|
6
|
+
# Project under corpus test
|
7
|
+
# rubocop:disable ClassLength
|
8
|
+
ROOT = Pathname.new(__FILE__).parent.parent.parent
|
9
|
+
TMP = ROOT.join('tmp').freeze
|
10
|
+
|
11
|
+
class Project
|
12
|
+
MUTEX = Mutex.new
|
13
|
+
include Adamantium, Anima.new(
|
14
|
+
:name,
|
15
|
+
:repo_uri,
|
16
|
+
:exclude,
|
17
|
+
:mutation_coverage,
|
18
|
+
:mutation_generation,
|
19
|
+
:namespace,
|
20
|
+
:expect_coverage
|
21
|
+
)
|
22
|
+
|
23
|
+
# Verify mutation coverage
|
24
|
+
#
|
25
|
+
# @return [self]
|
26
|
+
# if successful
|
27
|
+
#
|
28
|
+
# @raise [Exception]
|
29
|
+
#
|
30
|
+
def verify_mutation_coverage
|
31
|
+
checkout
|
32
|
+
Dir.chdir(repo_path) do
|
33
|
+
Bundler.with_clean_env do
|
34
|
+
install_mutant
|
35
|
+
system(%W[bundle exec mutant --use rspec -I lib -r #{name} --score #{expect_coverage} #{namespace}*])
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Verify mutation generation
|
41
|
+
#
|
42
|
+
# @return [self]
|
43
|
+
# if successful
|
44
|
+
#
|
45
|
+
# @raise [Exception]
|
46
|
+
# otherwise
|
47
|
+
#
|
48
|
+
# rubocop:disable MethodLength
|
49
|
+
def verify_mutation_generation
|
50
|
+
checkout
|
51
|
+
start = Time.now
|
52
|
+
paths = Pathname.glob(repo_path.join('**/*.rb')).sort_by(&:size).reverse
|
53
|
+
options = {
|
54
|
+
finish: method(:finish),
|
55
|
+
start: method(:start),
|
56
|
+
in_processes: parallel_processes
|
57
|
+
}
|
58
|
+
total = Parallel.map(paths, options) do |path|
|
59
|
+
count = 0
|
60
|
+
node =
|
61
|
+
begin
|
62
|
+
Parser::CurrentRuby.parse(path.read)
|
63
|
+
rescue EncodingError, ArgumentError
|
64
|
+
nil # Make rubocop happy
|
65
|
+
end
|
66
|
+
if node
|
67
|
+
Mutant::Mutator::Node.each(node) do
|
68
|
+
count += 1
|
69
|
+
end
|
70
|
+
end
|
71
|
+
count
|
72
|
+
end.inject(0, :+)
|
73
|
+
took = Time.now - start
|
74
|
+
puts format(
|
75
|
+
'Total Mutations/Time/Parse-Errors: %s/%0.2fs - %0.2f/s',
|
76
|
+
total,
|
77
|
+
took,
|
78
|
+
total / took
|
79
|
+
)
|
80
|
+
self
|
81
|
+
end
|
82
|
+
|
83
|
+
# Checkout repository
|
84
|
+
#
|
85
|
+
# @return [self]
|
86
|
+
#
|
87
|
+
# @api private
|
88
|
+
#
|
89
|
+
def checkout
|
90
|
+
return self if noinstall?
|
91
|
+
TMP.mkdir unless TMP.directory?
|
92
|
+
if repo_path.exist?
|
93
|
+
Dir.chdir(repo_path) do
|
94
|
+
system(%w[git fetch])
|
95
|
+
system(%w[git reset --hard])
|
96
|
+
system(%w[git clean -f -d -x])
|
97
|
+
system(%w[git checkout origin/master])
|
98
|
+
system(%w[git reset --hard])
|
99
|
+
system(%w[git clean -f -d -x])
|
100
|
+
end
|
101
|
+
else
|
102
|
+
system(%W[git clone #{repo_uri} #{repo_path}])
|
103
|
+
end
|
104
|
+
self
|
105
|
+
end
|
106
|
+
memoize :checkout
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
# Install mutant
|
111
|
+
#
|
112
|
+
# @return [undefined]
|
113
|
+
#
|
114
|
+
# @api private
|
115
|
+
#
|
116
|
+
def install_mutant
|
117
|
+
return if noinstall?
|
118
|
+
relative = ROOT.relative_path_from(repo_path)
|
119
|
+
devtools = ROOT.join('Gemfile.devtools').read
|
120
|
+
devtools << "gem 'mutant', path: '#{relative}'\n"
|
121
|
+
devtools << "gem 'mutant-rspec', path: '#{relative}'\n"
|
122
|
+
File.write(repo_path.join('Gemfile.devtools'), devtools)
|
123
|
+
lockfile = repo_path.join('Gemfile.lock')
|
124
|
+
lockfile.delete if lockfile.exist?
|
125
|
+
system('bundle install')
|
126
|
+
end
|
127
|
+
|
128
|
+
# Not in the docs. Number from chatting with their support.
|
129
|
+
CIRCLE_CI_CONTAINER_PROCESSES = 2
|
130
|
+
|
131
|
+
# Return number of parallel processes to use
|
132
|
+
#
|
133
|
+
# @return [Fixnum]
|
134
|
+
#
|
135
|
+
# @api private
|
136
|
+
#
|
137
|
+
def parallel_processes
|
138
|
+
case
|
139
|
+
when Devtools.circle_ci?
|
140
|
+
CIRCLE_CI_CONTAINER_PROCESSES
|
141
|
+
else
|
142
|
+
Parallel.processor_count
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
# Return repository path
|
147
|
+
#
|
148
|
+
# @return [Pathname]
|
149
|
+
#
|
150
|
+
# @api private
|
151
|
+
#
|
152
|
+
def repo_path
|
153
|
+
TMP.join(name)
|
154
|
+
end
|
155
|
+
|
156
|
+
# Test if installation should be skipped
|
157
|
+
#
|
158
|
+
# @return [Boolean]
|
159
|
+
#
|
160
|
+
# @api private
|
161
|
+
#
|
162
|
+
def noinstall?
|
163
|
+
ENV.key?('NOINSTALL')
|
164
|
+
end
|
165
|
+
|
166
|
+
# Print start progress
|
167
|
+
#
|
168
|
+
# @param [Pathname] path
|
169
|
+
# @param [Fixnum] _index
|
170
|
+
#
|
171
|
+
# @return [undefined]
|
172
|
+
#
|
173
|
+
def start(path, _index)
|
174
|
+
MUTEX.synchronize do
|
175
|
+
puts format('Starting - %s', path)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
# Print finish progress
|
180
|
+
#
|
181
|
+
# @param [Pathname] path
|
182
|
+
# @param [Fixnum] _index
|
183
|
+
# @param [Fixnum] count
|
184
|
+
#
|
185
|
+
# @return [undefined]
|
186
|
+
#
|
187
|
+
def finish(path, _index, count)
|
188
|
+
MUTEX.synchronize do
|
189
|
+
puts format('Mutations - %4i - %s', count, path)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# Helper method to execute system commands
|
194
|
+
#
|
195
|
+
# @param [Array<String>] arguments
|
196
|
+
#
|
197
|
+
# @api private
|
198
|
+
#
|
199
|
+
def system(arguments)
|
200
|
+
return if Kernel.system(*arguments)
|
201
|
+
if block_given?
|
202
|
+
yield
|
203
|
+
else
|
204
|
+
fail 'System command failed!'
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
LOADER = Morpher.build do
|
209
|
+
s(:block,
|
210
|
+
s(:guard, s(:primitive, Array)),
|
211
|
+
s(:map,
|
212
|
+
s(:block,
|
213
|
+
s(:guard, s(:primitive, Hash)),
|
214
|
+
s(:hash_transform,
|
215
|
+
s(:key_symbolize, :repo_uri, s(:guard, s(:primitive, String))),
|
216
|
+
s(:key_symbolize, :name, s(:guard, s(:primitive, String))),
|
217
|
+
s(:key_symbolize, :namespace, s(:guard, s(:primitive, String))),
|
218
|
+
s(:key_symbolize, :expect_coverage, s(:guard, s(:primitive, Float))),
|
219
|
+
s(:key_symbolize, :mutation_coverage,
|
220
|
+
s(:guard, s(:or, s(:primitive, TrueClass), s(:primitive, FalseClass)))),
|
221
|
+
s(:key_symbolize, :mutation_generation,
|
222
|
+
s(:guard, s(:or, s(:primitive, TrueClass), s(:primitive, FalseClass)))),
|
223
|
+
s(:key_symbolize, :exclude, s(:map, s(:guard, s(:primitive, String))))
|
224
|
+
),
|
225
|
+
s(:load_attribute_hash,
|
226
|
+
# NOTE: The domain param has no DSL currently!
|
227
|
+
Morpher::Evaluator::Transformer::Domain::Param.new(
|
228
|
+
Project,
|
229
|
+
[:repo_uri, :name, :exclude, :mutation_coverage, :mutation_generation]
|
230
|
+
)
|
231
|
+
)
|
232
|
+
)
|
233
|
+
)
|
234
|
+
)
|
235
|
+
end
|
236
|
+
|
237
|
+
ALL = LOADER.call(YAML.load_file(ROOT.join('spec', 'integrations.yml')))
|
238
|
+
end # Project
|
239
|
+
end # Corpus
|
@@ -1,5 +1,3 @@
|
|
1
|
-
# encoding: UTF-8
|
2
|
-
|
3
1
|
class MutationVerifier
|
4
2
|
include Adamantium::Flat, Concord.new(:original_node, :expected, :generated)
|
5
3
|
|
@@ -28,7 +26,7 @@ class MutationVerifier
|
|
28
26
|
|
29
27
|
private
|
30
28
|
|
31
|
-
# Return unexpected
|
29
|
+
# Return unexpected mutations
|
32
30
|
#
|
33
31
|
# @return [Array<Parser::AST::Node>]
|
34
32
|
#
|
@@ -71,7 +69,7 @@ private
|
|
71
69
|
].join("\n")
|
72
70
|
end
|
73
71
|
|
74
|
-
# Return missing
|
72
|
+
# Return missing mutations
|
75
73
|
#
|
76
74
|
# @return [Array<Parser::AST::Node>]
|
77
75
|
#
|
@@ -1,6 +1,4 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
shared_examples_for 'an invalid cli run' do
|
1
|
+
RSpec.shared_examples_for 'an invalid cli run' do
|
4
2
|
it 'raises error' do
|
5
3
|
expect do
|
6
4
|
subject
|
@@ -8,13 +6,13 @@ shared_examples_for 'an invalid cli run' do
|
|
8
6
|
end
|
9
7
|
end
|
10
8
|
|
11
|
-
shared_examples_for 'a cli parser' do
|
9
|
+
RSpec.shared_examples_for 'a cli parser' do
|
12
10
|
it { expect(subject.config.integration).to eql(expected_integration) }
|
13
11
|
it { expect(subject.config.reporter).to eql(expected_reporter) }
|
14
12
|
it { expect(subject.config.matcher_config).to eql(expected_matcher_config) }
|
15
13
|
end
|
16
14
|
|
17
|
-
describe Mutant::CLI do
|
15
|
+
RSpec.describe Mutant::CLI do
|
18
16
|
let(:object) { described_class }
|
19
17
|
|
20
18
|
describe '.run' do
|
@@ -38,7 +36,7 @@ describe Mutant::CLI do
|
|
38
36
|
end
|
39
37
|
end
|
40
38
|
|
41
|
-
context 'when report
|
39
|
+
context 'when report signals error' do
|
42
40
|
let(:report_success) { false }
|
43
41
|
|
44
42
|
it 'exits failure' do
|
@@ -67,18 +65,16 @@ describe Mutant::CLI do
|
|
67
65
|
subject { object.new(arguments) }
|
68
66
|
|
69
67
|
# Defaults
|
70
|
-
let(:expected_filter) { Morpher.evaluator(s(:true))
|
71
|
-
let(:expected_integration) { Mutant::Integration::Null.new
|
72
|
-
let(:expected_reporter) { Mutant::
|
73
|
-
let(:expected_matcher_config) { default_matcher_config
|
68
|
+
let(:expected_filter) { Morpher.evaluator(s(:true)) }
|
69
|
+
let(:expected_integration) { Mutant::Integration::Null.new }
|
70
|
+
let(:expected_reporter) { Mutant::Config::DEFAULT.reporter }
|
71
|
+
let(:expected_matcher_config) { default_matcher_config }
|
74
72
|
|
75
73
|
let(:default_matcher_config) do
|
76
74
|
Mutant::Matcher::Config::DEFAULT
|
77
75
|
.update(match_expressions: expressions.map(&Mutant::Expression.method(:parse)))
|
78
76
|
end
|
79
77
|
|
80
|
-
let(:ns) { Mutant::Matcher }
|
81
|
-
|
82
78
|
let(:flags) { [] }
|
83
79
|
let(:expressions) { %w[TestApp*] }
|
84
80
|
|
@@ -132,6 +128,7 @@ Environment:
|
|
132
128
|
--zombie Run mutant zombified
|
133
129
|
-I, --include DIRECTORY Add DIRECTORY to $LOAD_PATH
|
134
130
|
-r, --require NAME Require file with NAME
|
131
|
+
-j, --jobs NUMBER Number of kill processes. Defaults to number of processors.
|
135
132
|
|
136
133
|
Options:
|
137
134
|
--score COVERAGE Fail unless COVERAGE is not reached exactly
|
@@ -161,7 +158,7 @@ Options:
|
|
161
158
|
|
162
159
|
it_should_behave_like 'a cli parser'
|
163
160
|
|
164
|
-
let(:expected_integration) { Mutant::Integration::
|
161
|
+
let(:expected_integration) { Mutant::Integration::Rspec.build }
|
165
162
|
end
|
166
163
|
|
167
164
|
context 'with version flag' do
|
@@ -175,6 +172,16 @@ Options:
|
|
175
172
|
it_should_behave_like 'a cli parser'
|
176
173
|
end
|
177
174
|
|
175
|
+
context 'with jobs flag' do
|
176
|
+
let(:flags) { %w[--jobs 0] }
|
177
|
+
|
178
|
+
it_should_behave_like 'a cli parser'
|
179
|
+
|
180
|
+
it 'configures expected coverage' do
|
181
|
+
expect(subject.config.processes).to eql(0)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
178
185
|
context 'with score flag' do
|
179
186
|
let(:flags) { %w[--score 99.5] }
|
180
187
|
|