mutant 0.8.7 → 0.8.8
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 +5 -0
- data/README.md +64 -3
- data/config/flay.yml +1 -1
- data/lib/mutant.rb +2 -0
- data/lib/mutant/cli.rb +1 -1
- data/lib/mutant/env/bootstrap.rb +36 -8
- data/lib/mutant/expression/method.rb +3 -5
- data/lib/mutant/expression/methods.rb +2 -4
- data/lib/mutant/expression/namespace.rb +4 -8
- data/lib/mutant/matcher.rb +3 -18
- data/lib/mutant/matcher/chain.rb +7 -13
- data/lib/mutant/matcher/compiler.rb +2 -13
- data/lib/mutant/matcher/filter.rb +6 -19
- data/lib/mutant/matcher/method.rb +124 -104
- data/lib/mutant/matcher/method/instance.rb +40 -34
- data/lib/mutant/matcher/method/singleton.rb +80 -61
- data/lib/mutant/matcher/methods.rb +19 -29
- data/lib/mutant/matcher/namespace.rb +22 -16
- data/lib/mutant/matcher/null.rb +4 -7
- data/lib/mutant/matcher/scope.rb +23 -13
- data/lib/mutant/matcher/static.rb +17 -0
- data/lib/mutant/mutation.rb +0 -5
- data/lib/mutant/reporter/cli/format.rb +2 -3
- data/lib/mutant/reporter/cli/printer/env_progress.rb +37 -11
- data/lib/mutant/reporter/cli/printer/status_progressive.rb +1 -1
- data/lib/mutant/scope.rb +6 -0
- data/lib/mutant/subject/method.rb +0 -7
- data/lib/mutant/subject/method/instance.rb +0 -10
- data/lib/mutant/subject/method/singleton.rb +0 -10
- data/lib/mutant/version.rb +1 -1
- data/lib/mutant/zombifier.rb +2 -1
- data/mutant-rspec.gemspec +1 -1
- data/spec/integration/mutant/rspec_spec.rb +1 -1
- data/spec/shared/method_matcher_behavior.rb +21 -14
- data/spec/spec_helper.rb +6 -0
- data/spec/unit/mutant/env/boostrap_spec.rb +88 -26
- data/spec/unit/mutant/env_spec.rb +0 -1
- data/spec/unit/mutant/expression/method_spec.rb +3 -3
- data/spec/unit/mutant/expression/methods_spec.rb +3 -4
- data/spec/unit/mutant/expression/namespace/flat_spec.rb +2 -3
- data/spec/unit/mutant/expression/namespace/recursive_spec.rb +2 -4
- data/spec/unit/mutant/matcher/chain_spec.rb +21 -29
- data/spec/unit/mutant/matcher/compiler/subject_prefix_spec.rb +16 -13
- data/spec/unit/mutant/matcher/compiler_spec.rb +49 -60
- data/spec/unit/mutant/matcher/filter_spec.rb +15 -31
- data/spec/unit/mutant/matcher/method/instance_spec.rb +84 -128
- data/spec/unit/mutant/matcher/method/singleton_spec.rb +48 -52
- data/spec/unit/mutant/matcher/methods/instance_spec.rb +21 -24
- data/spec/unit/mutant/matcher/methods/singleton_spec.rb +18 -21
- data/spec/unit/mutant/matcher/namespace_spec.rb +30 -38
- data/spec/unit/mutant/matcher/null_spec.rb +5 -20
- data/spec/unit/mutant/matcher/scope_spec.rb +33 -0
- data/spec/unit/mutant/matcher/static_spec.rb +11 -0
- data/spec/unit/mutant/mutation_spec.rb +30 -10
- data/spec/unit/mutant/reporter/cli/printer/env_progress_spec.rb +6 -0
- data/spec/unit/mutant/reporter/cli/printer/env_result_spec.rb +2 -0
- data/spec/unit/mutant/reporter/cli/printer/status_spec.rb +10 -0
- data/spec/unit/mutant/reporter/cli_spec.rb +4 -0
- data/spec/unit/mutant/subject/method/instance_spec.rb +0 -28
- data/spec/unit/mutant/subject/method/singleton_spec.rb +0 -28
- data/test_app/Gemfile.rspec3.4 +7 -0
- data/test_app/lib/test_app.rb +16 -12
- data/test_app/lib/test_app/literal.rb +3 -0
- metadata +9 -2
@@ -0,0 +1,17 @@
|
|
1
|
+
module Mutant
|
2
|
+
class Matcher
|
3
|
+
# Matcher returning subjects already known at its creation time
|
4
|
+
class Static
|
5
|
+
include Concord.new(:subjects)
|
6
|
+
|
7
|
+
# Call matcher
|
8
|
+
#
|
9
|
+
# @return [Enumerable<Subject>]
|
10
|
+
#
|
11
|
+
# @api private
|
12
|
+
def call(_env)
|
13
|
+
subjects
|
14
|
+
end
|
15
|
+
end # Static
|
16
|
+
end # Matcher
|
17
|
+
end # Mutant
|
data/lib/mutant/mutation.rb
CHANGED
@@ -59,15 +59,10 @@ module Mutant
|
|
59
59
|
|
60
60
|
# Insert mutated node
|
61
61
|
#
|
62
|
-
# FIXME: Cache subject visibility in a better way! Ideally dont mutate it
|
63
|
-
# implicitly. Also subject.public? should NOT be a public interface it
|
64
|
-
# is a detail of method mutations.
|
65
|
-
#
|
66
62
|
# @return [self]
|
67
63
|
#
|
68
64
|
# @api private
|
69
65
|
def insert
|
70
|
-
subject.public?
|
71
66
|
subject.prepare
|
72
67
|
Loader::Eval.call(root, subject)
|
73
68
|
self
|
@@ -10,34 +10,51 @@ module Mutant
|
|
10
10
|
:amount_mutations,
|
11
11
|
:amount_mutations_alive,
|
12
12
|
:amount_mutations_killed,
|
13
|
+
:amount_mutation_results,
|
13
14
|
:runtime,
|
14
15
|
:killtime,
|
15
16
|
:overhead,
|
16
17
|
:env
|
17
18
|
)
|
18
19
|
|
20
|
+
# rubocop:disable SpaceInsideBrackets
|
21
|
+
FORMATS = IceNine.deep_freeze([
|
22
|
+
[:info, 'Subjects: %s', :amount_subjects ],
|
23
|
+
[:info, 'Mutations: %s', :amount_mutations ],
|
24
|
+
[:info, 'Results: %s', :amount_mutation_results ],
|
25
|
+
[:info, 'Kills: %s', :amount_mutations_killed ],
|
26
|
+
[:info, 'Alive: %s', :amount_mutations_alive ],
|
27
|
+
[:info, 'Runtime: %0.2fs', :runtime ],
|
28
|
+
[:info, 'Killtime: %0.2fs', :killtime ],
|
29
|
+
[:info, 'Overhead: %0.2f%%', :overhead_percent ],
|
30
|
+
[:info, 'Mutations/s: %0.2f', :mutations_per_second ],
|
31
|
+
[:status, 'Coverage: %0.2f%%', :coverage_percent ],
|
32
|
+
[:status, 'Expected: %0.2f%%', :expected_coverage_percent]
|
33
|
+
])
|
34
|
+
|
19
35
|
# Run printer
|
20
36
|
#
|
21
37
|
# @return [undefined]
|
22
38
|
#
|
23
|
-
# rubocop:disable AbcSize
|
24
|
-
#
|
25
39
|
# @api private
|
26
40
|
def run
|
27
41
|
visit(Config, env.config)
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
info 'Alive: %s', amount_mutations_alive
|
32
|
-
info 'Runtime: %0.2fs', runtime
|
33
|
-
info 'Killtime: %0.2fs', killtime
|
34
|
-
info 'Overhead: %0.2f%%', overhead_percent
|
35
|
-
status 'Coverage: %0.2f%%', coverage_percent
|
36
|
-
status 'Expected: %0.2f%%', (env.config.expected_coverage * 100)
|
42
|
+
FORMATS.each do |report, format, value|
|
43
|
+
__send__(report, format, __send__(value))
|
44
|
+
end
|
37
45
|
end
|
38
46
|
|
39
47
|
private
|
40
48
|
|
49
|
+
# Mutations processed per second
|
50
|
+
#
|
51
|
+
# @return [Float]
|
52
|
+
#
|
53
|
+
# @api private
|
54
|
+
def mutations_per_second
|
55
|
+
amount_mutation_results / runtime
|
56
|
+
end
|
57
|
+
|
41
58
|
# Coverage in percent
|
42
59
|
#
|
43
60
|
# @return [Float]
|
@@ -47,6 +64,15 @@ module Mutant
|
|
47
64
|
coverage * 100
|
48
65
|
end
|
49
66
|
|
67
|
+
# Expected coverage in percent
|
68
|
+
#
|
69
|
+
# @return [Float]
|
70
|
+
#
|
71
|
+
# @api private
|
72
|
+
def expected_coverage_percent
|
73
|
+
env.config.expected_coverage * 100
|
74
|
+
end
|
75
|
+
|
50
76
|
# Overhead in percent
|
51
77
|
#
|
52
78
|
# @return [Float]
|
data/lib/mutant/scope.rb
ADDED
@@ -7,16 +7,6 @@ module Mutant
|
|
7
7
|
NAME_INDEX = 0
|
8
8
|
SYMBOL = '#'.freeze
|
9
9
|
|
10
|
-
# Test if method is public
|
11
|
-
#
|
12
|
-
# @return [Boolean]
|
13
|
-
#
|
14
|
-
# @api private
|
15
|
-
def public?
|
16
|
-
scope.public_method_defined?(name)
|
17
|
-
end
|
18
|
-
memoize :public?
|
19
|
-
|
20
10
|
# Prepare subject for mutation insertion
|
21
11
|
#
|
22
12
|
# @return [self]
|
@@ -7,16 +7,6 @@ module Mutant
|
|
7
7
|
NAME_INDEX = 1
|
8
8
|
SYMBOL = '.'.freeze
|
9
9
|
|
10
|
-
# Test if method is public
|
11
|
-
#
|
12
|
-
# @return [Boolean]
|
13
|
-
#
|
14
|
-
# @api private
|
15
|
-
def public?
|
16
|
-
scope.singleton_class.public_method_defined?(name)
|
17
|
-
end
|
18
|
-
memoize :public?
|
19
|
-
|
20
10
|
# Prepare subject for mutation insertion
|
21
11
|
#
|
22
12
|
# @return [self]
|
data/lib/mutant/version.rb
CHANGED
data/lib/mutant/zombifier.rb
CHANGED
@@ -10,6 +10,7 @@ module Mutant
|
|
10
10
|
:root_require,
|
11
11
|
:pathname
|
12
12
|
)
|
13
|
+
private(*anima.attribute_names)
|
13
14
|
|
14
15
|
include AST::Sexp
|
15
16
|
|
@@ -56,7 +57,7 @@ module Mutant
|
|
56
57
|
#
|
57
58
|
# @api private
|
58
59
|
def include?(logical_name)
|
59
|
-
!@zombified.include?(logical_name) &&
|
60
|
+
!@zombified.include?(logical_name) && includes =~ logical_name
|
60
61
|
end
|
61
62
|
|
62
63
|
# Require file in zombie namespace
|
data/mutant-rspec.gemspec
CHANGED
@@ -16,7 +16,7 @@ Gem::Specification.new do |gem|
|
|
16
16
|
gem.extra_rdoc_files = %w[TODO LICENSE]
|
17
17
|
|
18
18
|
gem.add_runtime_dependency('mutant', "~> #{gem.version}")
|
19
|
-
gem.add_runtime_dependency('rspec-core', '>= 3.2.0', '< 3.
|
19
|
+
gem.add_runtime_dependency('rspec-core', '>= 3.2.0', '< 3.5.0')
|
20
20
|
|
21
21
|
gem.add_development_dependency('bundler', '~> 1.3', '>= 1.3.5')
|
22
22
|
end
|
@@ -2,7 +2,7 @@ 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
|
-
%w[3.2 3.3].each do |version|
|
5
|
+
%w[3.2 3.3 3.4].each do |version|
|
6
6
|
context "RSpec #{version}" do
|
7
7
|
let(:gemfile) { "Gemfile.rspec#{version}" }
|
8
8
|
|
@@ -1,38 +1,45 @@
|
|
1
1
|
RSpec.shared_examples_for 'a method matcher' do
|
2
|
-
|
3
|
-
before { subject }
|
4
|
-
|
5
2
|
let(:node) { mutation_subject.node }
|
6
3
|
let(:context) { mutation_subject.context }
|
7
|
-
let(:mutation_subject) {
|
4
|
+
let(:mutation_subject) { subject.first }
|
8
5
|
|
9
|
-
it '
|
10
|
-
expect(
|
6
|
+
it 'returns one subject' do
|
7
|
+
expect(subject.size).to be(1)
|
11
8
|
end
|
12
9
|
|
13
|
-
|
14
|
-
|
15
|
-
it 'should have correct method name' do
|
10
|
+
it 'has expected method name' do
|
16
11
|
expect(name).to eql(method_name)
|
17
12
|
end
|
18
13
|
|
19
|
-
it '
|
14
|
+
it 'has epxected line number' do
|
20
15
|
expect(node.location.expression.line).to eql(method_line)
|
21
16
|
end
|
22
17
|
|
23
|
-
it '
|
18
|
+
it 'has expected arity' do
|
24
19
|
expect(arguments.children.length).to eql(method_arity)
|
25
20
|
end
|
26
21
|
|
27
|
-
it '
|
22
|
+
it 'has expected scope in context' do
|
28
23
|
expect(context.scope).to eql(scope)
|
29
24
|
end
|
30
25
|
|
31
|
-
it '
|
26
|
+
it 'has source path in context' do
|
32
27
|
expect(context.source_path).to eql(source_path)
|
33
28
|
end
|
34
29
|
|
35
|
-
it '
|
30
|
+
it 'has the correct node type' do
|
36
31
|
expect(node.type).to be(type)
|
37
32
|
end
|
38
33
|
end
|
34
|
+
|
35
|
+
RSpec.shared_examples_for 'skipped candidate' do
|
36
|
+
it 'does not emit matcher' do
|
37
|
+
expect(subject).to eql([])
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'does warn' do
|
41
|
+
subject
|
42
|
+
|
43
|
+
expect(env.config.reporter.warn_calls).to eql(expected_warnings)
|
44
|
+
end
|
45
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -43,6 +43,12 @@ module ParserHelper
|
|
43
43
|
Unparser.unparse(node)
|
44
44
|
end
|
45
45
|
|
46
|
+
def test_env
|
47
|
+
Fixtures::TEST_ENV.with(
|
48
|
+
config: Mutant::Config::DEFAULT.with(reporter: Mutant::Reporter::Trace.new)
|
49
|
+
)
|
50
|
+
end
|
51
|
+
|
46
52
|
def parse(string)
|
47
53
|
Unparser::Preprocessor.run(Parser::CurrentRuby.parse(string))
|
48
54
|
end
|
@@ -1,16 +1,29 @@
|
|
1
|
+
# This spec is a good example for:
|
2
|
+
#
|
3
|
+
# If test look that ugly the class under test sucks.
|
4
|
+
#
|
5
|
+
# As the bootstrap needs to infect global VM state
|
6
|
+
# this is to some degree acceptable.
|
7
|
+
#
|
8
|
+
# Still the bootstrap needs to be cleaned up.
|
9
|
+
# And the change that added this warning did the groundwork.
|
1
10
|
RSpec.describe Mutant::Env::Bootstrap do
|
11
|
+
let(:matcher_config) { Mutant::Matcher::Config::DEFAULT }
|
12
|
+
let(:integration) { instance_double(Mutant::Integration) }
|
13
|
+
let(:integration_class) { instance_double(Class) }
|
14
|
+
let(:object_space_modules) { [] }
|
15
|
+
|
2
16
|
let(:config) do
|
3
17
|
Mutant::Config::DEFAULT.with(
|
4
|
-
jobs:
|
5
|
-
reporter:
|
6
|
-
includes:
|
7
|
-
requires:
|
8
|
-
|
18
|
+
jobs: 1,
|
19
|
+
reporter: Mutant::Reporter::Trace.new,
|
20
|
+
includes: [],
|
21
|
+
requires: [],
|
22
|
+
integration: integration_class,
|
23
|
+
matcher: matcher_config
|
9
24
|
)
|
10
25
|
end
|
11
26
|
|
12
|
-
let(:integration) { Mutant::Integration::Null.new(config) }
|
13
|
-
|
14
27
|
let(:expected_env) do
|
15
28
|
Mutant::Env.new(
|
16
29
|
cache: Mutant::Cache.new,
|
@@ -28,16 +41,40 @@ RSpec.describe Mutant::Env::Bootstrap do
|
|
28
41
|
it { should eql(expected_env) }
|
29
42
|
end
|
30
43
|
|
31
|
-
let(:object_space_modules) { [] }
|
32
|
-
|
33
44
|
before do
|
34
|
-
|
45
|
+
expect(integration_class).to receive(:new)
|
46
|
+
.with(config)
|
47
|
+
.and_return(integration)
|
48
|
+
|
49
|
+
expect(integration).to receive(:setup).and_return(integration)
|
50
|
+
|
51
|
+
expect(ObjectSpace).to receive(:each_object)
|
52
|
+
.with(Module)
|
53
|
+
.and_return(object_space_modules.each)
|
54
|
+
end
|
55
|
+
|
56
|
+
describe '#warn' do
|
57
|
+
let(:object) { described_class.new(config) }
|
58
|
+
let(:message) { instance_double(String) }
|
59
|
+
|
60
|
+
subject { object.warn(message) }
|
61
|
+
|
62
|
+
it 'reports a warning' do
|
63
|
+
expect { subject }
|
64
|
+
.to change { object.config.reporter.warn_calls }
|
65
|
+
.from([])
|
66
|
+
.to([message])
|
67
|
+
end
|
68
|
+
|
69
|
+
it_behaves_like 'a command method'
|
35
70
|
end
|
36
71
|
|
37
72
|
describe '.call' do
|
38
73
|
subject { described_class.call(config) }
|
39
74
|
|
40
75
|
context 'when Module#name calls result in exceptions' do
|
76
|
+
let(:object_space_modules) { [invalid_class] }
|
77
|
+
|
41
78
|
let(:invalid_class) do
|
42
79
|
Class.new do
|
43
80
|
def self.name
|
@@ -46,8 +83,6 @@ RSpec.describe Mutant::Env::Bootstrap do
|
|
46
83
|
end
|
47
84
|
end
|
48
85
|
|
49
|
-
let(:object_space_modules) { [invalid_class] }
|
50
|
-
|
51
86
|
after do
|
52
87
|
# Fix Class#name so other specs do not see this one
|
53
88
|
class << invalid_class
|
@@ -59,21 +94,41 @@ RSpec.describe Mutant::Env::Bootstrap do
|
|
59
94
|
|
60
95
|
it 'warns via reporter' do
|
61
96
|
expected_warnings = [
|
62
|
-
"Class#name from: #{invalid_class} raised an error:
|
97
|
+
"Class#name from: #{invalid_class} raised an error: " \
|
98
|
+
"RuntimeError. #{Mutant::Env::SEMANTICS_MESSAGE}"
|
63
99
|
]
|
64
100
|
|
65
|
-
expect { subject }
|
101
|
+
expect { subject }
|
102
|
+
.to change { config.reporter.warn_calls }
|
103
|
+
.from([])
|
104
|
+
.to(expected_warnings)
|
66
105
|
end
|
67
106
|
|
68
107
|
include_examples 'bootstrap call'
|
69
108
|
end
|
70
109
|
|
71
|
-
context 'when
|
110
|
+
context 'when requires are configured' do
|
111
|
+
let(:config) { super().with(requires: %w[foo bar]) }
|
112
|
+
|
113
|
+
before do
|
114
|
+
%w[foo bar].each do |component|
|
115
|
+
expect(Kernel).to receive(:require)
|
116
|
+
.with(component)
|
117
|
+
.and_return(true)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
include_examples 'bootstrap call'
|
122
|
+
end
|
123
|
+
|
124
|
+
context 'when includes are configured' do
|
72
125
|
let(:config) { super().with(includes: %w[foo bar]) }
|
73
126
|
|
74
127
|
before do
|
75
128
|
%w[foo bar].each do |component|
|
76
|
-
expect($LOAD_PATH).to receive(:<<)
|
129
|
+
expect($LOAD_PATH).to receive(:<<)
|
130
|
+
.with(component)
|
131
|
+
.and_return($LOAD_PATH)
|
77
132
|
end
|
78
133
|
end
|
79
134
|
|
@@ -81,6 +136,8 @@ RSpec.describe Mutant::Env::Bootstrap do
|
|
81
136
|
end
|
82
137
|
|
83
138
|
context 'when Module#name does not return a String or nil' do
|
139
|
+
let(:object_space_modules) { [invalid_class] }
|
140
|
+
|
84
141
|
let(:invalid_class) do
|
85
142
|
Class.new do
|
86
143
|
def self.name
|
@@ -89,8 +146,6 @@ RSpec.describe Mutant::Env::Bootstrap do
|
|
89
146
|
end
|
90
147
|
end
|
91
148
|
|
92
|
-
let(:object_space_modules) { [invalid_class] }
|
93
|
-
|
94
149
|
after do
|
95
150
|
# Fix Class#name so other specs do not see this one
|
96
151
|
class << invalid_class
|
@@ -101,29 +156,36 @@ RSpec.describe Mutant::Env::Bootstrap do
|
|
101
156
|
end
|
102
157
|
|
103
158
|
it 'warns via reporter' do
|
104
|
-
|
105
159
|
expected_warnings = [
|
106
160
|
"Class#name from: #{invalid_class.inspect} returned Object. #{Mutant::Env::SEMANTICS_MESSAGE}"
|
107
161
|
]
|
108
162
|
|
109
|
-
expect { subject }
|
163
|
+
expect { subject }
|
164
|
+
.to change { config.reporter.warn_calls }
|
165
|
+
.from([]).to(expected_warnings)
|
110
166
|
end
|
111
167
|
|
112
168
|
include_examples 'bootstrap call'
|
113
169
|
end
|
114
170
|
|
115
171
|
context 'when scope matches expression' do
|
116
|
-
let(:
|
117
|
-
let(:
|
172
|
+
let(:object_space_modules) { [TestApp::Literal, TestApp::Empty] }
|
173
|
+
let(:match_expressions) { object_space_modules.map(&:name).map(&method(:parse_expression)) }
|
118
174
|
|
119
|
-
|
120
|
-
|
175
|
+
let(:matcher_config) do
|
176
|
+
super().with(match_expressions: match_expressions)
|
121
177
|
end
|
122
178
|
|
123
179
|
let(:expected_env) do
|
180
|
+
subjects = Mutant::Matcher::Scope.new(TestApp::Literal).call(Fixtures::TEST_ENV)
|
181
|
+
|
124
182
|
super().with(
|
125
|
-
|
126
|
-
|
183
|
+
matchable_scopes: [
|
184
|
+
Mutant::Scope.new(TestApp::Empty, match_expressions.last),
|
185
|
+
Mutant::Scope.new(TestApp::Literal, match_expressions.first)
|
186
|
+
],
|
187
|
+
subjects: subjects,
|
188
|
+
mutations: subjects.flat_map(&:mutations)
|
127
189
|
)
|
128
190
|
end
|
129
191
|
|