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,28 @@
|
|
1
|
+
module Mutant
|
2
|
+
class Reporter
|
3
|
+
class CLI
|
4
|
+
class Printer
|
5
|
+
# Subject result printer
|
6
|
+
class SubjectResult < self
|
7
|
+
|
8
|
+
delegate :subject, :alive_mutation_results, :tests
|
9
|
+
|
10
|
+
# Run report printer
|
11
|
+
#
|
12
|
+
# @return [undefined]
|
13
|
+
#
|
14
|
+
# @api private
|
15
|
+
#
|
16
|
+
def run
|
17
|
+
status(subject.identification)
|
18
|
+
tests.each do |test|
|
19
|
+
puts("- #{test.identification}")
|
20
|
+
end
|
21
|
+
visit_collection(MutationResult, alive_mutation_results)
|
22
|
+
end
|
23
|
+
|
24
|
+
end # SubjectResult
|
25
|
+
end # Printer
|
26
|
+
end # CLI
|
27
|
+
end # Reporter
|
28
|
+
end # Mutant
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Mutant
|
2
|
+
class Reporter
|
3
|
+
class CLI
|
4
|
+
class Printer
|
5
|
+
# Test result reporter
|
6
|
+
class TestResult < self
|
7
|
+
|
8
|
+
delegate :tests, :runtime
|
9
|
+
|
10
|
+
STATUS_FORMAT = '- %d @ runtime: %s'.freeze
|
11
|
+
OUTPUT_HEADER = 'Test Output:'.freeze
|
12
|
+
TEST_FORMAT = ' - %s'.freeze
|
13
|
+
|
14
|
+
# Run test result reporter
|
15
|
+
#
|
16
|
+
# @return [undefined]
|
17
|
+
#
|
18
|
+
# @api private
|
19
|
+
#
|
20
|
+
def run
|
21
|
+
info(STATUS_FORMAT, tests.length, runtime)
|
22
|
+
tests.each do |test|
|
23
|
+
info(TEST_FORMAT, test.identification)
|
24
|
+
end
|
25
|
+
puts(OUTPUT_HEADER)
|
26
|
+
puts(object.output)
|
27
|
+
end
|
28
|
+
|
29
|
+
end # TestResult
|
30
|
+
end # Printer
|
31
|
+
end # CLI
|
32
|
+
end # Reporter
|
33
|
+
end # Mutant
|
@@ -1,62 +1,23 @@
|
|
1
1
|
module Mutant
|
2
2
|
# Require highjack
|
3
|
-
|
4
|
-
include Concord.new(:target, :callback)
|
3
|
+
module RequireHighjack
|
5
4
|
|
6
|
-
#
|
5
|
+
# Install require callback
|
7
6
|
#
|
8
|
-
# @
|
9
|
-
#
|
10
|
-
# @api private
|
11
|
-
#
|
12
|
-
attr_reader :original
|
13
|
-
|
14
|
-
# Run block with highjacked require
|
15
|
-
#
|
16
|
-
# @return [self]
|
17
|
-
#
|
18
|
-
# @api private
|
19
|
-
#
|
20
|
-
def run
|
21
|
-
infect
|
22
|
-
yield
|
23
|
-
self
|
24
|
-
ensure
|
25
|
-
disinfect
|
26
|
-
end
|
27
|
-
|
28
|
-
# Infect kernel with highjack
|
29
|
-
#
|
30
|
-
# @return [self]
|
31
|
-
#
|
32
|
-
# @api private
|
33
|
-
#
|
34
|
-
def infect
|
35
|
-
callback = @callback
|
36
|
-
@original = target.method(:require)
|
37
|
-
target.module_eval do
|
38
|
-
undef :require
|
39
|
-
define_method(:require) do |logical_name|
|
40
|
-
callback.call(logical_name)
|
41
|
-
end
|
42
|
-
module_function :require
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
# Imperfectly disinfect kernel from highjack
|
7
|
+
# @param [Module] target
|
8
|
+
# @param [#call] callback
|
47
9
|
#
|
48
|
-
# @return [
|
10
|
+
# @return [#call]
|
11
|
+
# the original implementation on singleton
|
49
12
|
#
|
50
13
|
# @api private
|
51
14
|
#
|
52
|
-
def
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
original.call(logical_name)
|
15
|
+
def self.call(target, callback)
|
16
|
+
target.method(:require).tap do
|
17
|
+
target.module_eval do
|
18
|
+
define_method(:require, &callback)
|
19
|
+
public :require
|
58
20
|
end
|
59
|
-
module_function :require
|
60
21
|
end
|
61
22
|
end
|
62
23
|
|
data/lib/mutant/version.rb
CHANGED
data/lib/mutant/zombifier.rb
CHANGED
@@ -1,20 +1,19 @@
|
|
1
1
|
module Mutant
|
2
2
|
# Zombifier namespace
|
3
3
|
class Zombifier
|
4
|
-
include
|
4
|
+
include Anima.new(
|
5
|
+
:includes,
|
6
|
+
:namespace,
|
7
|
+
:load_path,
|
8
|
+
:kernel,
|
9
|
+
:require_highjack,
|
10
|
+
:root_require,
|
11
|
+
:pathname
|
12
|
+
)
|
5
13
|
|
6
|
-
|
7
|
-
includes = %w[
|
8
|
-
mutant
|
9
|
-
unparser
|
10
|
-
morpher
|
11
|
-
adamantium
|
12
|
-
equalizer
|
13
|
-
anima
|
14
|
-
concord
|
15
|
-
]
|
14
|
+
include AST::Sexp
|
16
15
|
|
17
|
-
|
16
|
+
LoadError = Class.new(::LoadError)
|
18
17
|
|
19
18
|
# Initialize object
|
20
19
|
#
|
@@ -24,37 +23,28 @@ module Mutant
|
|
24
23
|
#
|
25
24
|
# @api private
|
26
25
|
#
|
27
|
-
def initialize(
|
26
|
+
def initialize(*)
|
27
|
+
super
|
28
|
+
@includes = %r{\A#{Regexp.union(includes)}(?:/.*)?\z}
|
28
29
|
@zombified = Set.new
|
29
|
-
@highjack = RequireHighjack.new(Kernel, method(:require))
|
30
|
-
super(namespace)
|
31
30
|
end
|
32
31
|
|
33
|
-
|
34
|
-
|
35
|
-
# @param [String] logical_name
|
36
|
-
# @param [Symbol] namespace
|
37
|
-
#
|
38
|
-
# @return [self]
|
39
|
-
#
|
40
|
-
# @api private
|
41
|
-
#
|
42
|
-
def self.run(logical_name, namespace)
|
43
|
-
new(namespace).run(logical_name)
|
32
|
+
def self.call(*args)
|
33
|
+
new(*args).__send__(:call)
|
44
34
|
self
|
45
35
|
end
|
46
36
|
|
37
|
+
private
|
38
|
+
|
47
39
|
# Run zombifier
|
48
40
|
#
|
49
|
-
# @param [String] logical_name
|
50
|
-
#
|
51
41
|
# @return [undefined]
|
52
42
|
#
|
53
43
|
# @api private
|
54
44
|
#
|
55
|
-
def
|
56
|
-
@
|
57
|
-
require(
|
45
|
+
def call
|
46
|
+
@original = require_highjack.call(method(:require))
|
47
|
+
require(root_require)
|
58
48
|
end
|
59
49
|
|
60
50
|
# Test if logical name is subjected to zombification
|
@@ -64,25 +54,77 @@ module Mutant
|
|
64
54
|
# @api private
|
65
55
|
#
|
66
56
|
def include?(logical_name)
|
67
|
-
!@zombified.include?(logical_name) &&
|
57
|
+
!@zombified.include?(logical_name) && @includes =~ logical_name
|
68
58
|
end
|
69
59
|
|
70
60
|
# Require file in zombie namespace
|
71
61
|
#
|
72
62
|
# @param [#to_s] logical_name
|
73
63
|
#
|
74
|
-
# @return [
|
64
|
+
# @return [undefined]
|
75
65
|
#
|
76
66
|
# @api private
|
77
67
|
#
|
78
68
|
def require(logical_name)
|
79
69
|
logical_name = logical_name.to_s
|
80
|
-
@
|
70
|
+
@original.call(logical_name)
|
81
71
|
return unless include?(logical_name)
|
82
72
|
@zombified << logical_name
|
83
|
-
|
84
|
-
|
85
|
-
|
73
|
+
zombify(find(logical_name))
|
74
|
+
end
|
75
|
+
|
76
|
+
# Find file by logical path
|
77
|
+
#
|
78
|
+
# @param [String] logical_name
|
79
|
+
#
|
80
|
+
# @return [File]
|
81
|
+
#
|
82
|
+
# @raise [LoadError]
|
83
|
+
# otherwise
|
84
|
+
#
|
85
|
+
# @api private
|
86
|
+
#
|
87
|
+
def find(logical_name)
|
88
|
+
file_name = "#{logical_name}.rb"
|
89
|
+
|
90
|
+
load_path.each do |path|
|
91
|
+
path = pathname.new(path).join(file_name)
|
92
|
+
return path if path.file?
|
93
|
+
end
|
94
|
+
|
95
|
+
fail LoadError, "Cannot find file #{file_name.inspect} in load path"
|
96
|
+
end
|
97
|
+
|
98
|
+
# Zombify contents of file
|
99
|
+
#
|
100
|
+
# Probably the 2nd valid use of eval ever. (First one is inserting mutants!).
|
101
|
+
#
|
102
|
+
# @param [Pathname] source_path
|
103
|
+
#
|
104
|
+
# @return [undefined]
|
105
|
+
#
|
106
|
+
# @api private
|
107
|
+
#
|
108
|
+
# rubocop:disable Lint/Eval
|
109
|
+
#
|
110
|
+
def zombify(source_path)
|
111
|
+
kernel.eval(
|
112
|
+
Unparser.unparse(namespaced_node(source_path)),
|
113
|
+
TOPLEVEL_BINDING,
|
114
|
+
source_path.to_s
|
115
|
+
)
|
116
|
+
end
|
117
|
+
|
118
|
+
# Return namespaced root
|
119
|
+
#
|
120
|
+
# @param [Symbol] namespace
|
121
|
+
#
|
122
|
+
# @return [Parser::AST::Node]
|
123
|
+
#
|
124
|
+
# @api private
|
125
|
+
#
|
126
|
+
def namespaced_node(source_path)
|
127
|
+
s(:module, s(:const, nil, namespace), Parser::CurrentRuby.parse(source_path.read))
|
86
128
|
end
|
87
129
|
|
88
130
|
end # Zombifier
|
data/meta/send.rb
CHANGED
@@ -408,7 +408,7 @@ Mutant::Meta::Example.add do
|
|
408
408
|
mutation 'self[*bar]'
|
409
409
|
end
|
410
410
|
|
411
|
-
(Mutant::AST::Types::BINARY_METHOD_OPERATORS - [
|
411
|
+
(Mutant::AST::Types::BINARY_METHOD_OPERATORS - %i[<= >= < > == eql?]).each do |operator|
|
412
412
|
Mutant::Meta::Example.add do
|
413
413
|
source "true #{operator} false"
|
414
414
|
|
data/mutant-rspec.gemspec
CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |gem|
|
|
18
18
|
gem.extra_rdoc_files = %w[TODO LICENSE]
|
19
19
|
|
20
20
|
gem.add_runtime_dependency('mutant', "~> #{gem.version}")
|
21
|
-
gem.add_runtime_dependency('rspec-core', '>= 3.
|
21
|
+
gem.add_runtime_dependency('rspec-core', '>= 3.2.0', '< 3.4.0')
|
22
22
|
|
23
23
|
gem.add_development_dependency('bundler', '~> 1.3', '>= 1.3.5')
|
24
24
|
end
|
data/mutant.gemspec
CHANGED
@@ -21,16 +21,16 @@ Gem::Specification.new do |gem|
|
|
21
21
|
gem.extra_rdoc_files = %w[TODO LICENSE]
|
22
22
|
gem.executables = %w[mutant]
|
23
23
|
|
24
|
-
gem.required_ruby_version = '>= 1
|
24
|
+
gem.required_ruby_version = '>= 2.1'
|
25
25
|
|
26
|
-
gem.add_runtime_dependency('parser', '~> 2.2
|
26
|
+
gem.add_runtime_dependency('parser', '~> 2.2.2')
|
27
27
|
gem.add_runtime_dependency('ast', '~> 2.0')
|
28
28
|
gem.add_runtime_dependency('diff-lcs', '~> 1.2')
|
29
29
|
gem.add_runtime_dependency('parallel', '~> 1.3')
|
30
30
|
gem.add_runtime_dependency('morpher', '~> 0.2.3')
|
31
31
|
gem.add_runtime_dependency('procto', '~> 0.0.2')
|
32
32
|
gem.add_runtime_dependency('abstract_type', '~> 0.0.7')
|
33
|
-
gem.add_runtime_dependency('unparser', '~> 0.2.
|
33
|
+
gem.add_runtime_dependency('unparser', '~> 0.2.4')
|
34
34
|
gem.add_runtime_dependency('ice_nine', '~> 0.11.1')
|
35
35
|
gem.add_runtime_dependency('adamantium', '~> 0.2.0')
|
36
36
|
gem.add_runtime_dependency('memoizable', '~> 0.4.2')
|
@@ -5,13 +5,13 @@ RSpec.describe 'Mutant on ruby corpus', mutant: false do
|
|
5
5
|
skip 'Corpus test is deactivated on RBX' if RUBY_ENGINE.eql?('rbx')
|
6
6
|
end
|
7
7
|
|
8
|
-
Corpus::Project::ALL.select(&:mutation_generation).each do |project|
|
8
|
+
MutantSpec::Corpus::Project::ALL.select(&:mutation_generation).each do |project|
|
9
9
|
specify "#{project.name} does not fail on mutation generation" do
|
10
10
|
project.verify_mutation_generation
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
-
Corpus::Project::ALL.select(&:mutation_coverage).each do |project|
|
14
|
+
MutantSpec::Corpus::Project::ALL.select(&:mutation_coverage).each do |project|
|
15
15
|
specify "#{project.name} does have expected mutation coverage" do
|
16
16
|
project.verify_mutation_coverage
|
17
17
|
end
|
@@ -2,21 +2,11 @@ RSpec.describe 'rspec integration', mutant: false do
|
|
2
2
|
|
3
3
|
let(:base_cmd) { 'bundle exec mutant -I lib --require test_app --use rspec' }
|
4
4
|
|
5
|
-
|
6
|
-
|
5
|
+
%w[3.2 3.3].each do |version|
|
6
|
+
context "RSpec #{version}" do
|
7
|
+
let(:gemfile) { "Gemfile.rspec#{version}" }
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
context 'RSpec 3.1' do
|
12
|
-
let(:gemfile) { 'Gemfile.rspec3.1' }
|
13
|
-
|
14
|
-
it_behaves_like 'framework integration'
|
15
|
-
end
|
16
|
-
|
17
|
-
context 'RSpec 3.2' do
|
18
|
-
let(:gemfile) { 'Gemfile.rspec3.2' }
|
19
|
-
|
20
|
-
it_behaves_like 'framework integration'
|
9
|
+
it_behaves_like 'framework integration'
|
10
|
+
end
|
21
11
|
end
|
22
12
|
end
|
data/spec/integrations.yml
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
repo_uri: 'https://github.com/rubyspec/rubyspec.git'
|
5
5
|
mutation_coverage: false
|
6
6
|
mutation_generation: true
|
7
|
-
expect_coverage: 0
|
7
|
+
expect_coverage: 0 # not run
|
8
8
|
exclude:
|
9
9
|
# Binary encoded source subjected to limitations see README of unparser
|
10
10
|
- core/array/pack/{b,h,u}_spec.rb
|
@@ -31,11 +31,11 @@
|
|
31
31
|
mutation_coverage: true
|
32
32
|
mutation_generation: true
|
33
33
|
exclude: []
|
34
|
-
expect_coverage:
|
34
|
+
expect_coverage: 1
|
35
35
|
- name: axiom
|
36
36
|
namespace: Axiom
|
37
37
|
repo_uri: 'https://github.com/dkubb/axiom.git'
|
38
38
|
mutation_coverage: false
|
39
39
|
mutation_generation: true
|
40
40
|
exclude: []
|
41
|
-
expect_coverage:
|
41
|
+
expect_coverage: 1
|
data/spec/spec_helper.rb
CHANGED
data/spec/support/corpus.rb
CHANGED
@@ -2,240 +2,253 @@ require 'morpher'
|
|
2
2
|
require 'anima'
|
3
3
|
require 'mutant'
|
4
4
|
|
5
|
-
|
6
|
-
module
|
7
|
-
#
|
8
|
-
# rubocop:disable
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
5
|
+
module MutantSpec
|
6
|
+
# Namespace module for corpus testing
|
7
|
+
#
|
8
|
+
# rubocop:disable MethodLength
|
9
|
+
module Corpus
|
10
|
+
# Project under corpus test
|
11
|
+
# rubocop:disable ClassLength
|
12
|
+
ROOT = Pathname.new(__FILE__).parent.parent.parent
|
13
|
+
TMP = ROOT.join('tmp').freeze
|
14
|
+
|
15
|
+
class Project
|
16
|
+
MUTEX = Mutex.new
|
17
|
+
include Adamantium, Anima.new(
|
18
|
+
:name,
|
19
|
+
:repo_uri,
|
20
|
+
:exclude,
|
21
|
+
:mutation_coverage,
|
22
|
+
:mutation_generation,
|
23
|
+
:namespace,
|
24
|
+
:expect_coverage
|
25
|
+
)
|
26
|
+
|
27
|
+
# Verify mutation coverage
|
28
|
+
#
|
29
|
+
# @return [self]
|
30
|
+
# if successful
|
31
|
+
#
|
32
|
+
# @raise [Exception]
|
33
|
+
#
|
34
|
+
def verify_mutation_coverage
|
35
|
+
checkout
|
36
|
+
Dir.chdir(repo_path) do
|
37
|
+
Bundler.with_clean_env do
|
38
|
+
install_mutant
|
39
|
+
system(
|
40
|
+
%W[
|
41
|
+
bundle exec mutant
|
42
|
+
--use rspec
|
43
|
+
--include lib
|
44
|
+
--require #{name}
|
45
|
+
--expected-coverage #{expect_coverage}
|
46
|
+
#{namespace}*
|
47
|
+
]
|
48
|
+
)
|
49
|
+
end
|
37
50
|
end
|
38
51
|
end
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
52
|
+
|
53
|
+
# Verify mutation generation
|
54
|
+
#
|
55
|
+
# @return [self]
|
56
|
+
# if successful
|
57
|
+
#
|
58
|
+
# @raise [Exception]
|
59
|
+
# otherwise
|
60
|
+
#
|
61
|
+
# rubocop:disable AbcSize
|
62
|
+
#
|
63
|
+
def verify_mutation_generation
|
64
|
+
checkout
|
65
|
+
start = Time.now
|
66
|
+
paths = Pathname.glob(repo_path.join('**/*.rb')).sort_by(&:size).reverse
|
67
|
+
options = {
|
68
|
+
finish: method(:finish),
|
69
|
+
start: method(:start),
|
70
|
+
in_processes: parallel_processes
|
71
|
+
}
|
72
|
+
total = Parallel.map(paths, options) do |path|
|
73
|
+
count = 0
|
74
|
+
node =
|
75
|
+
begin
|
76
|
+
Parser::CurrentRuby.parse(path.read)
|
77
|
+
rescue EncodingError, ArgumentError
|
78
|
+
nil # Make rubocop happy
|
79
|
+
end
|
80
|
+
if node
|
81
|
+
Mutant::Mutator::Node.each(node) do
|
82
|
+
count += 1
|
83
|
+
end
|
68
84
|
end
|
69
|
-
|
70
|
-
|
71
|
-
|
85
|
+
count
|
86
|
+
end.inject(0, :+)
|
87
|
+
took = Time.now - start
|
88
|
+
puts format(
|
89
|
+
'Total Mutations/Time/Parse-Errors: %s/%0.2fs - %0.2f/s',
|
90
|
+
total,
|
91
|
+
took,
|
92
|
+
total / took
|
93
|
+
)
|
94
|
+
self
|
95
|
+
end
|
96
|
+
|
97
|
+
# Checkout repository
|
98
|
+
#
|
99
|
+
# @return [self]
|
100
|
+
#
|
101
|
+
# @api private
|
102
|
+
#
|
103
|
+
def checkout
|
104
|
+
return self if noinstall?
|
105
|
+
TMP.mkdir unless TMP.directory?
|
106
|
+
if repo_path.exist?
|
107
|
+
Dir.chdir(repo_path) do
|
108
|
+
system(%w[git fetch origin])
|
109
|
+
system(%w[git reset --hard])
|
110
|
+
system(%w[git clean -f -d -x])
|
111
|
+
system(%w[git checkout origin/master])
|
112
|
+
system(%w[git reset --hard])
|
113
|
+
system(%w[git clean -f -d -x])
|
72
114
|
end
|
115
|
+
else
|
116
|
+
system(%W[git clone #{repo_uri} #{repo_path}])
|
73
117
|
end
|
74
|
-
|
75
|
-
end
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
118
|
+
self
|
119
|
+
end
|
120
|
+
memoize :checkout
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
# Install mutant
|
125
|
+
#
|
126
|
+
# @return [undefined]
|
127
|
+
#
|
128
|
+
# @api private
|
129
|
+
#
|
130
|
+
def install_mutant
|
131
|
+
return if noinstall?
|
132
|
+
relative = ROOT.relative_path_from(repo_path)
|
133
|
+
repo_path.join('Gemfile').open('a') do |file|
|
134
|
+
file << "gem 'mutant', path: '#{relative}'\n"
|
135
|
+
file << "gem 'mutant-rspec', path: '#{relative}'\n"
|
136
|
+
end
|
137
|
+
lockfile = repo_path.join('Gemfile.lock')
|
138
|
+
lockfile.delete if lockfile.exist?
|
139
|
+
system('bundle install')
|
140
|
+
end
|
141
|
+
|
142
|
+
# Not in the docs. Number from chatting with their support.
|
143
|
+
CIRCLE_CI_CONTAINER_PROCESSES = 2
|
144
|
+
|
145
|
+
# Return number of parallel processes to use
|
146
|
+
#
|
147
|
+
# @return [Fixnum]
|
148
|
+
#
|
149
|
+
# @api private
|
150
|
+
#
|
151
|
+
def parallel_processes
|
152
|
+
if ENV['CI']
|
153
|
+
Mutant::Config::DEFAULT.jobs
|
154
|
+
else
|
155
|
+
Parallel.processor_count
|
103
156
|
end
|
104
|
-
else
|
105
|
-
system(%W[git clone #{repo_uri} #{repo_path}])
|
106
157
|
end
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
#
|
117
|
-
# @api private
|
118
|
-
#
|
119
|
-
def install_mutant
|
120
|
-
return if noinstall?
|
121
|
-
relative = ROOT.relative_path_from(repo_path)
|
122
|
-
repo_path.join('Gemfile').open('a') do |file|
|
123
|
-
file << "gem 'mutant', path: '#{relative}'\n"
|
124
|
-
file << "gem 'mutant-rspec', path: '#{relative}'\n"
|
158
|
+
|
159
|
+
# Return repository path
|
160
|
+
#
|
161
|
+
# @return [Pathname]
|
162
|
+
#
|
163
|
+
# @api private
|
164
|
+
#
|
165
|
+
def repo_path
|
166
|
+
TMP.join(name)
|
125
167
|
end
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
#
|
136
|
-
# @return [Fixnum]
|
137
|
-
#
|
138
|
-
# @api private
|
139
|
-
#
|
140
|
-
def parallel_processes
|
141
|
-
if ENV['CI']
|
142
|
-
Mutant::Config::DEFAULT.jobs
|
143
|
-
else
|
144
|
-
Parallel.processor_count
|
168
|
+
|
169
|
+
# Test if installation should be skipped
|
170
|
+
#
|
171
|
+
# @return [Boolean]
|
172
|
+
#
|
173
|
+
# @api private
|
174
|
+
#
|
175
|
+
def noinstall?
|
176
|
+
ENV.key?('NOINSTALL')
|
145
177
|
end
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
# Test if installation should be skipped
|
159
|
-
#
|
160
|
-
# @return [Boolean]
|
161
|
-
#
|
162
|
-
# @api private
|
163
|
-
#
|
164
|
-
def noinstall?
|
165
|
-
ENV.key?('NOINSTALL')
|
166
|
-
end
|
167
|
-
|
168
|
-
# Print start progress
|
169
|
-
#
|
170
|
-
# @param [Pathname] path
|
171
|
-
# @param [Fixnum] _index
|
172
|
-
#
|
173
|
-
# @return [undefined]
|
174
|
-
#
|
175
|
-
def start(path, _index)
|
176
|
-
MUTEX.synchronize do
|
177
|
-
puts format('Starting - %s', path)
|
178
|
+
|
179
|
+
# Print start progress
|
180
|
+
#
|
181
|
+
# @param [Pathname] path
|
182
|
+
# @param [Fixnum] _index
|
183
|
+
#
|
184
|
+
# @return [undefined]
|
185
|
+
#
|
186
|
+
def start(path, _index)
|
187
|
+
MUTEX.synchronize do
|
188
|
+
puts format('Starting - %s', path)
|
189
|
+
end
|
178
190
|
end
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
191
|
+
|
192
|
+
# Print finish progress
|
193
|
+
#
|
194
|
+
# @param [Pathname] path
|
195
|
+
# @param [Fixnum] _index
|
196
|
+
# @param [Fixnum] count
|
197
|
+
#
|
198
|
+
# @return [undefined]
|
199
|
+
#
|
200
|
+
def finish(path, _index, count)
|
201
|
+
MUTEX.synchronize do
|
202
|
+
puts format('Mutations - %4i - %s', count, path)
|
203
|
+
end
|
192
204
|
end
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
205
|
+
|
206
|
+
# Helper method to execute system commands
|
207
|
+
#
|
208
|
+
# @param [Array<String>] arguments
|
209
|
+
#
|
210
|
+
# @api private
|
211
|
+
#
|
212
|
+
def system(arguments)
|
213
|
+
return if Kernel.system(*arguments)
|
214
|
+
if block_given?
|
215
|
+
yield
|
216
|
+
else
|
217
|
+
fail 'System command failed!'
|
218
|
+
end
|
207
219
|
end
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
220
|
+
|
221
|
+
# rubocop:disable ClosingParenthesisIndentation
|
222
|
+
LOADER = Morpher.build do
|
223
|
+
s(:block,
|
224
|
+
s(:guard, s(:primitive, Array)),
|
225
|
+
s(:map,
|
226
|
+
s(:block,
|
227
|
+
s(:guard, s(:primitive, Hash)),
|
228
|
+
s(:hash_transform,
|
229
|
+
s(:key_symbolize, :repo_uri, s(:guard, s(:primitive, String))),
|
230
|
+
s(:key_symbolize, :name, s(:guard, s(:primitive, String))),
|
231
|
+
s(:key_symbolize, :namespace, s(:guard, s(:primitive, String))),
|
232
|
+
s(:key_symbolize, :expect_coverage, s(:guard, s(:primitive, Fixnum))),
|
233
|
+
s(:key_symbolize, :mutation_coverage,
|
234
|
+
s(:guard, s(:or, s(:primitive, TrueClass), s(:primitive, FalseClass)))),
|
235
|
+
s(:key_symbolize, :mutation_generation,
|
236
|
+
s(:guard, s(:or, s(:primitive, TrueClass), s(:primitive, FalseClass)))),
|
237
|
+
s(:key_symbolize, :exclude, s(:map, s(:guard, s(:primitive, String))))
|
238
|
+
),
|
239
|
+
s(:load_attribute_hash,
|
240
|
+
# NOTE: The domain param has no DSL currently!
|
241
|
+
Morpher::Evaluator::Transformer::Domain::Param.new(
|
242
|
+
Project,
|
243
|
+
%i[repo_uri name exclude mutation_coverage mutation_generation]
|
244
|
+
)
|
232
245
|
)
|
233
246
|
)
|
234
247
|
)
|
235
248
|
)
|
236
|
-
|
237
|
-
end
|
249
|
+
end
|
238
250
|
|
239
|
-
|
240
|
-
|
241
|
-
end # Corpus
|
251
|
+
ALL = LOADER.call(YAML.load_file(ROOT.join('spec', 'integrations.yml')))
|
252
|
+
end # Project
|
253
|
+
end # Corpus
|
254
|
+
end # MutantSpec
|