mutant 0.8.7 → 0.8.8
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|