mutant 0.8.19 → 0.8.20
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.circleci/config.yml +46 -8
- data/Changelog.md +4 -0
- data/Gemfile.lock +10 -12
- data/README.md +22 -250
- data/config/flay.yml +1 -1
- data/docs/concurrency.md +39 -0
- data/docs/known-problems.md +44 -0
- data/docs/limitations.md +50 -0
- data/docs/mutant-minitest.md +147 -0
- data/docs/mutant-rspec.md +62 -0
- data/docs/nomenclature.md +82 -0
- data/docs/reading-reports.md +74 -0
- data/lib/mutant.rb +4 -3
- data/lib/mutant/env.rb +2 -2
- data/lib/mutant/expression/namespace.rb +3 -1
- data/lib/mutant/runner/sink.rb +2 -2
- data/lib/mutant/timer.rb +21 -0
- data/lib/mutant/version.rb +1 -1
- data/mutant-minitest.gemspec +22 -0
- data/mutant.gemspec +4 -5
- data/spec/integration/mutant/corpus_spec.rb +1 -7
- data/spec/integration/mutant/minitest_spec.rb +10 -0
- data/spec/integration/mutant/rspec_spec.rb +1 -1
- data/spec/integrations.yml +14 -0
- data/spec/shared/framework_integration_behavior.rb +8 -5
- data/spec/support/corpus.rb +20 -14
- data/spec/unit/mutant/clock_monotonic_spec.rb +52 -0
- data/spec/unit/mutant/env_spec.rb +2 -2
- data/spec/unit/mutant/expression/namespace/recursive_spec.rb +1 -1
- data/spec/unit/mutant/integration/rspec_spec.rb +1 -1
- data/spec/unit/mutant/reporter/cli_spec.rb +1 -1
- data/spec/unit/mutant/runner/sink_spec.rb +1 -1
- data/test_app/Gemfile.minitest +6 -0
- data/test_app/{Gemfile.rspec3.6 → Gemfile.rspec3.8} +2 -2
- data/test_app/test/unit/test_app/literal_test.rb +16 -0
- metadata +23 -26
- data/spec/rcov.opts +0 -7
- data/spec/support/rb_bug.rb +0 -18
- data/test_app/Gemfile.rspec3.4 +0 -7
- data/test_app/Gemfile.rspec3.5 +0 -7
data/lib/mutant.rb
CHANGED
@@ -4,15 +4,15 @@ require 'abstract_type'
|
|
4
4
|
require 'adamantium'
|
5
5
|
require 'anima'
|
6
6
|
require 'concord'
|
7
|
-
require 'digest/sha1'
|
8
7
|
require 'diff/lcs'
|
9
8
|
require 'diff/lcs/hunk'
|
9
|
+
require 'digest/sha1'
|
10
|
+
require 'etc'
|
10
11
|
require 'equalizer'
|
11
12
|
require 'ice_nine'
|
12
13
|
require 'morpher'
|
13
14
|
require 'open3'
|
14
15
|
require 'optparse'
|
15
|
-
require 'parallel'
|
16
16
|
require 'parser'
|
17
17
|
require 'parser/current'
|
18
18
|
require 'pathname'
|
@@ -177,6 +177,7 @@ require 'mutant/expression/method'
|
|
177
177
|
require 'mutant/expression/methods'
|
178
178
|
require 'mutant/expression/namespace'
|
179
179
|
require 'mutant/test'
|
180
|
+
require 'mutant/timer'
|
180
181
|
require 'mutant/integration'
|
181
182
|
require 'mutant/selector'
|
182
183
|
require 'mutant/selector/expression'
|
@@ -228,7 +229,7 @@ module Mutant
|
|
228
229
|
marshal: Marshal,
|
229
230
|
process: Process
|
230
231
|
),
|
231
|
-
jobs:
|
232
|
+
jobs: Etc.nprocessors,
|
232
233
|
kernel: Kernel,
|
233
234
|
load_path: $LOAD_PATH,
|
234
235
|
matcher: Matcher::Config::DEFAULT,
|
data/lib/mutant/env.rb
CHANGED
@@ -42,7 +42,7 @@ module Mutant
|
|
42
42
|
#
|
43
43
|
# rubocop:disable MethodLength
|
44
44
|
def run_mutation_tests(mutation)
|
45
|
-
start =
|
45
|
+
start = Timer.now
|
46
46
|
tests = selector.call(mutation.subject)
|
47
47
|
|
48
48
|
config.isolation.call do
|
@@ -53,7 +53,7 @@ module Mutant
|
|
53
53
|
Result::Test.new(
|
54
54
|
output: error.message,
|
55
55
|
passed: false,
|
56
|
-
runtime:
|
56
|
+
runtime: Timer.now - start,
|
57
57
|
tests: tests
|
58
58
|
)
|
59
59
|
end
|
data/lib/mutant/runner/sink.rb
CHANGED
@@ -10,7 +10,7 @@ module Mutant
|
|
10
10
|
# @return [undefined]
|
11
11
|
def initialize(*)
|
12
12
|
super
|
13
|
-
@start =
|
13
|
+
@start = Timer.now
|
14
14
|
@subject_results = {}
|
15
15
|
end
|
16
16
|
|
@@ -20,7 +20,7 @@ module Mutant
|
|
20
20
|
def status
|
21
21
|
Result::Env.new(
|
22
22
|
env: env,
|
23
|
-
runtime:
|
23
|
+
runtime: Timer.now - @start,
|
24
24
|
subject_results: @subject_results.values
|
25
25
|
)
|
26
26
|
end
|
data/lib/mutant/timer.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mutant
|
4
|
+
module Timer
|
5
|
+
# Monotonic elapsed time of block execution
|
6
|
+
#
|
7
|
+
# @return [Float]
|
8
|
+
def self.elapsed
|
9
|
+
start = now
|
10
|
+
yield
|
11
|
+
now - start
|
12
|
+
end
|
13
|
+
|
14
|
+
# The now monotonic time
|
15
|
+
#
|
16
|
+
# @return [Float]
|
17
|
+
def self.now
|
18
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
19
|
+
end
|
20
|
+
end # Timer
|
21
|
+
end # Mutant
|
data/lib/mutant/version.rb
CHANGED
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require File.expand_path('lib/mutant/version', __dir__)
|
4
|
+
|
5
|
+
Gem::Specification.new do |gem|
|
6
|
+
gem.name = 'mutant-minitest'
|
7
|
+
gem.version = Mutant::VERSION.dup
|
8
|
+
gem.authors = ['Markus Schirp']
|
9
|
+
gem.email = %w[mbj@schirp-dso.com]
|
10
|
+
gem.description = 'Minitest integration for mutant'
|
11
|
+
gem.summary = gem.description
|
12
|
+
gem.homepage = 'https://github.com/mbj/mutant'
|
13
|
+
gem.license = 'MIT'
|
14
|
+
|
15
|
+
gem.require_paths = %w[lib]
|
16
|
+
gem.files = `git ls-files -- lib/mutant/{minitest,/integration/minitest.rb}`.split("\n")
|
17
|
+
gem.test_files = `git ls-files -- spec/integration/mutant/minitest.rb`.split("\n")
|
18
|
+
gem.extra_rdoc_files = %w[LICENSE]
|
19
|
+
|
20
|
+
gem.add_runtime_dependency('minitest', '~> 5.11')
|
21
|
+
gem.add_runtime_dependency('mutant', "~> #{gem.version}")
|
22
|
+
end
|
data/mutant.gemspec
CHANGED
@@ -14,9 +14,9 @@ Gem::Specification.new do |gem|
|
|
14
14
|
|
15
15
|
gem.require_paths = %w[lib]
|
16
16
|
|
17
|
-
|
17
|
+
exclusion = `git ls-files -- lib/mutant/{minitest,integration}`.split("\n")
|
18
18
|
|
19
|
-
gem.files = `git ls-files`.split("\n") -
|
19
|
+
gem.files = `git ls-files`.split("\n") - exclusion
|
20
20
|
gem.test_files = `git ls-files -- spec/{unit,integration}`.split("\n")
|
21
21
|
gem.extra_rdoc_files = %w[LICENSE]
|
22
22
|
gem.executables = %w[mutant]
|
@@ -33,13 +33,12 @@ Gem::Specification.new do |gem|
|
|
33
33
|
gem.add_runtime_dependency('ice_nine', '~> 0.11.1')
|
34
34
|
gem.add_runtime_dependency('memoizable', '~> 0.4.2')
|
35
35
|
gem.add_runtime_dependency('morpher', '~> 0.2.6')
|
36
|
-
gem.add_runtime_dependency('parallel', '~> 1.3')
|
37
36
|
gem.add_runtime_dependency('parser', '~> 2.5.1')
|
38
37
|
gem.add_runtime_dependency('procto', '~> 0.0.2')
|
39
38
|
gem.add_runtime_dependency('regexp_parser', '~> 1.2')
|
40
|
-
gem.add_runtime_dependency('unparser', '~> 0.
|
39
|
+
gem.add_runtime_dependency('unparser', '~> 0.3.0')
|
41
40
|
|
42
41
|
gem.add_development_dependency('bundler', '~> 1.10')
|
43
42
|
gem.add_development_dependency('devtools', '~> 0.1.21')
|
44
|
-
gem.add_development_dependency('
|
43
|
+
gem.add_development_dependency('parallel', '~> 1.3')
|
45
44
|
end
|
@@ -1,12 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
RSpec.describe 'Mutant on ruby corpus', mutant: false do
|
4
|
-
|
5
|
-
before do
|
6
|
-
skip 'Corpus test is deactivated on < 2.1' if RUBY_VERSION < '2.1'
|
7
|
-
skip 'Corpus test is deactivated on RBX' if RUBY_ENGINE.eql?('rbx')
|
8
|
-
end
|
9
|
-
|
10
4
|
MutantSpec::Corpus::Project::ALL.select(&:mutation_generation).each do |project|
|
11
5
|
specify "#{project.name} does not fail on mutation generation" do
|
12
6
|
project.verify_mutation_generation
|
@@ -14,7 +8,7 @@ RSpec.describe 'Mutant on ruby corpus', mutant: false do
|
|
14
8
|
end
|
15
9
|
|
16
10
|
MutantSpec::Corpus::Project::ALL.select(&:mutation_coverage).each do |project|
|
17
|
-
specify "#{project.name} does have expected mutation coverage" do
|
11
|
+
specify "#{project.name} (#{project.integration}) does have expected mutation coverage" do
|
18
12
|
project.verify_mutation_coverage
|
19
13
|
end
|
20
14
|
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe 'minitest integration', mutant: false do
|
4
|
+
|
5
|
+
let(:base_cmd) { 'bundle exec mutant -I test -I lib --require test_app --use minitest' }
|
6
|
+
|
7
|
+
let(:gemfile) { 'Gemfile.minitest' }
|
8
|
+
|
9
|
+
it_behaves_like 'framework integration'
|
10
|
+
end
|
@@ -4,7 +4,7 @@ RSpec.describe 'rspec integration', mutant: false do
|
|
4
4
|
|
5
5
|
let(:base_cmd) { 'bundle exec mutant -I lib --require test_app --use rspec' }
|
6
6
|
|
7
|
-
%w[3.
|
7
|
+
%w[3.7 3.8].each do |version|
|
8
8
|
context "RSpec #{version}" do
|
9
9
|
let(:gemfile) { "Gemfile.rspec#{version}" }
|
10
10
|
|
data/spec/integrations.yml
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
namespace: Rubyspec
|
4
4
|
repo_uri: 'https://github.com/ruby/rubyspec.git'
|
5
5
|
repo_ref: 'origin/master'
|
6
|
+
integration: mspec
|
6
7
|
ruby_glob_pattern: '**/*_spec.rb'
|
7
8
|
mutation_coverage: false
|
8
9
|
mutation_generation: true
|
@@ -24,6 +25,7 @@
|
|
24
25
|
namespace: Regexp
|
25
26
|
repo_uri: 'https://github.com/ammar/regexp_parser.git'
|
26
27
|
repo_ref: 'v1.2.0'
|
28
|
+
integration: rspec
|
27
29
|
ruby_glob_pattern: '**/*.rb'
|
28
30
|
mutation_coverage: false
|
29
31
|
mutation_generation: true
|
@@ -34,6 +36,17 @@
|
|
34
36
|
repo_uri: 'https://github.com/mbj/auom.git'
|
35
37
|
repo_ref: 'origin/master'
|
36
38
|
ruby_glob_pattern: '**/*.rb'
|
39
|
+
integration: rspec
|
40
|
+
mutation_coverage: true
|
41
|
+
mutation_generation: true
|
42
|
+
expected_errors: {}
|
43
|
+
exclude: []
|
44
|
+
- name: auom
|
45
|
+
namespace: AUOM
|
46
|
+
repo_uri: 'https://github.com/mbj/auom.git'
|
47
|
+
repo_ref: 'origin/master'
|
48
|
+
ruby_glob_pattern: '**/*.rb'
|
49
|
+
integration: minitest
|
37
50
|
mutation_coverage: true
|
38
51
|
mutation_generation: true
|
39
52
|
expected_errors: {}
|
@@ -43,6 +56,7 @@
|
|
43
56
|
repo_uri: 'https://github.com/dkubb/axiom.git'
|
44
57
|
repo_ref: 'origin/master'
|
45
58
|
ruby_glob_pattern: '**/*.rb'
|
59
|
+
integration: rspec
|
46
60
|
mutation_coverage: false
|
47
61
|
mutation_generation: true
|
48
62
|
expected_errors: {}
|
@@ -6,22 +6,25 @@ RSpec.shared_examples_for 'framework integration' do
|
|
6
6
|
end
|
7
7
|
|
8
8
|
around do |example|
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
Bundler.with_clean_env do
|
10
|
+
Dir.chdir(TestApp.root) do
|
11
|
+
Kernel.system('bundle', 'install', '--gemfile', gemfile) || fail('Bundle install failed!')
|
12
|
+
example.run
|
13
|
+
end
|
12
14
|
end
|
13
15
|
end
|
14
16
|
|
15
17
|
specify 'it allows to kill mutations' do
|
16
|
-
expect(
|
18
|
+
expect(system_with_gemfile("#{base_cmd} TestApp::Literal#string")).to be(true)
|
17
19
|
end
|
18
20
|
|
19
21
|
specify 'it allows to exclude mutations' do
|
20
22
|
cli = <<-CMD.split("\n").join(' ')
|
21
23
|
#{base_cmd}
|
24
|
+
--ignore-subject TestApp::Literal#uncovered_string
|
25
|
+
--
|
22
26
|
TestApp::Literal#string
|
23
27
|
TestApp::Literal#uncovered_string
|
24
|
-
--ignore-subject TestApp::Literal#uncovered_string
|
25
28
|
CMD
|
26
29
|
expect(system_with_gemfile(cli)).to be(true)
|
27
30
|
end
|
data/spec/support/corpus.rb
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'morpher'
|
4
3
|
require 'anima'
|
4
|
+
require 'morpher'
|
5
5
|
require 'mutant'
|
6
|
+
require 'parallel'
|
6
7
|
|
7
8
|
# @api private
|
8
9
|
module MutantSpec
|
@@ -36,6 +37,7 @@ module MutantSpec
|
|
36
37
|
:expected_errors,
|
37
38
|
:mutation_coverage,
|
38
39
|
:mutation_generation,
|
40
|
+
:integration,
|
39
41
|
:name,
|
40
42
|
:namespace,
|
41
43
|
:repo_uri,
|
@@ -53,16 +55,18 @@ module MutantSpec
|
|
53
55
|
def verify_mutation_coverage
|
54
56
|
checkout
|
55
57
|
Dir.chdir(repo_path) do
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
58
|
+
Bundler.with_clean_env do
|
59
|
+
install_mutant
|
60
|
+
system(
|
61
|
+
%W[
|
62
|
+
bundle exec mutant
|
63
|
+
--use #{integration}
|
64
|
+
--include lib
|
65
|
+
--require #{name}
|
66
|
+
#{namespace}*
|
67
|
+
]
|
68
|
+
)
|
69
|
+
end
|
66
70
|
end
|
67
71
|
end
|
68
72
|
|
@@ -75,7 +79,7 @@ module MutantSpec
|
|
75
79
|
# otherwise
|
76
80
|
def verify_mutation_generation
|
77
81
|
checkout
|
78
|
-
start =
|
82
|
+
start = Mutant::Timer.now
|
79
83
|
|
80
84
|
options = {
|
81
85
|
finish: method(:finish),
|
@@ -86,7 +90,7 @@ module MutantSpec
|
|
86
90
|
total = Parallel.map(effective_ruby_paths, options, &method(:count_mutations_and_check_errors))
|
87
91
|
.inject(DEFAULT_MUTATION_COUNT, :+)
|
88
92
|
|
89
|
-
took =
|
93
|
+
took = Mutant::Timer.now - start
|
90
94
|
puts MUTATION_GENERATION_MESSAGE % [total, took, total / took]
|
91
95
|
self
|
92
96
|
end
|
@@ -163,6 +167,7 @@ module MutantSpec
|
|
163
167
|
repo_path.join('Gemfile').open('a') do |file|
|
164
168
|
file << "gem 'mutant', path: '#{relative}'\n"
|
165
169
|
file << "gem 'mutant-rspec', path: '#{relative}'\n"
|
170
|
+
file << "gem 'mutant-minitest', path: '#{relative}'\n"
|
166
171
|
file << "eval_gemfile File.expand_path('#{relative.join('Gemfile.shared')}')\n"
|
167
172
|
end
|
168
173
|
lockfile = repo_path.join('Gemfile.lock')
|
@@ -188,7 +193,7 @@ module MutantSpec
|
|
188
193
|
if ENV.key?('CI')
|
189
194
|
CIRCLE_CI_CONTAINER_PROCESSES
|
190
195
|
else
|
191
|
-
|
196
|
+
Etc.nprocessors
|
192
197
|
end
|
193
198
|
end
|
194
199
|
|
@@ -312,6 +317,7 @@ module MutantSpec
|
|
312
317
|
s(:key_symbolize, :ruby_glob_pattern, s(:guard, s(:primitive, String))),
|
313
318
|
s(:key_symbolize, :name, s(:guard, s(:primitive, String))),
|
314
319
|
s(:key_symbolize, :namespace, s(:guard, s(:primitive, String))),
|
320
|
+
s(:key_symbolize, :integration, s(:guard, s(:primitive, String))),
|
315
321
|
s(:key_symbolize, :mutation_coverage,
|
316
322
|
s(:guard, s(:or, s(:primitive, TrueClass), s(:primitive, FalseClass)))),
|
317
323
|
s(:key_symbolize, :mutation_generation,
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe Mutant::Timer do
|
4
|
+
let(:events) { [] }
|
5
|
+
|
6
|
+
let(:times) { [1.0, 2.0] }
|
7
|
+
|
8
|
+
before do
|
9
|
+
allow(Process).to receive(:clock_gettime) do |argument|
|
10
|
+
expect(argument).to be(Process::CLOCK_MONOTONIC)
|
11
|
+
|
12
|
+
events << :clock_gettime
|
13
|
+
|
14
|
+
times.fetch(events.count(:clock_gettime).pred)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '.elapsed' do
|
19
|
+
def apply
|
20
|
+
described_class.elapsed { events << :yield }
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'executes events in expected sequence' do
|
24
|
+
expect { apply }
|
25
|
+
.to change(events, :to_a)
|
26
|
+
.from([])
|
27
|
+
.to(%i[clock_gettime yield clock_gettime])
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'returns elapsed time' do
|
31
|
+
expect(apply).to be(1.0)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe '.now' do
|
36
|
+
def apply
|
37
|
+
described_class.now
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'returns current monotonic time' do
|
41
|
+
expect(apply).to be(1.0)
|
42
|
+
expect(apply).to be(2.0)
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'calls expected system API' do
|
46
|
+
expect { apply }
|
47
|
+
.to change(events, :to_a)
|
48
|
+
.from([])
|
49
|
+
.to(%i[clock_gettime])
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -57,7 +57,7 @@ RSpec.describe Mutant::Env do
|
|
57
57
|
.with(mutation_subject)
|
58
58
|
.and_return(tests)
|
59
59
|
|
60
|
-
allow(
|
60
|
+
allow(Mutant::Timer).to receive(:now).and_return(2.0, 3.0)
|
61
61
|
end
|
62
62
|
|
63
63
|
context 'when isolation does not raise error' do
|
@@ -91,7 +91,7 @@ RSpec.describe Mutant::Env do
|
|
91
91
|
Mutant::Result::Test.new(
|
92
92
|
output: 'test-error',
|
93
93
|
passed: false,
|
94
|
-
runtime:
|
94
|
+
runtime: 1.0,
|
95
95
|
tests: tests
|
96
96
|
)
|
97
97
|
end
|
@@ -22,7 +22,7 @@ RSpec.describe Mutant::Expression::Namespace::Recursive do
|
|
22
22
|
context 'when other is an equivalent expression' do
|
23
23
|
let(:other) { parse_expression(object.syntax) }
|
24
24
|
|
25
|
-
it { should be(
|
25
|
+
it { should be(object.syntax.length) }
|
26
26
|
end
|
27
27
|
|
28
28
|
context 'when other expression describes a shorter prefix' do
|