mutant 0.8.19 → 0.8.20
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/.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
|