mutant 0.3.6 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -0
- data/Changelog.md +13 -0
- data/Gemfile +1 -1
- data/Guardfile +7 -25
- data/LICENSE +1 -1
- data/README.md +16 -10
- data/config/flay.yml +1 -1
- data/config/reek.yml +11 -11
- data/config/rubocop.yml +1 -1
- data/lib/mutant.rb +3 -10
- data/lib/mutant/cli.rb +199 -80
- data/lib/mutant/config.rb +3 -3
- data/lib/mutant/killer.rb +20 -0
- data/lib/mutant/matcher/filter.rb +3 -8
- data/lib/mutant/matcher/method/instance.rb +1 -1
- data/lib/mutant/matcher/namespace.rb +31 -2
- data/lib/mutant/matcher/null.rb +26 -0
- data/lib/mutant/mutation.rb +0 -1
- data/lib/mutant/reporter/cli/printer.rb +29 -0
- data/lib/mutant/reporter/cli/printer/config.rb +16 -116
- data/lib/mutant/runner/config.rb +47 -1
- data/lib/mutant/strategy.rb +39 -1
- data/lib/mutant/version.rb +1 -1
- data/lib/mutant/walker.rb +51 -0
- data/mutant-rspec.gemspec +24 -0
- data/mutant.gemspec +7 -3
- data/spec/integration/mutant/rspec_spec.rb +11 -6
- data/spec/integration/mutant/zombie_spec.rb +2 -2
- data/spec/shared/method_matcher_behavior.rb +6 -6
- data/spec/spec_helper.rb +2 -2
- data/spec/unit/mutant/cli_new_spec.rb +49 -34
- data/spec/unit/mutant/context/scope/root_spec.rb +1 -1
- data/spec/unit/mutant/loader/eval_spec.rb +2 -2
- data/spec/unit/mutant/matcher/chain_spec.rb +1 -1
- data/spec/unit/mutant/matcher/methods/instance_spec.rb +1 -1
- data/spec/unit/mutant/matcher/methods/singleton_spec.rb +1 -1
- data/spec/unit/mutant/matcher/namespace_spec.rb +1 -1
- data/spec/unit/mutant/mutation_spec.rb +1 -1
- data/spec/unit/mutant/{killer/rspec_spec.rb → rspec/killer_spec.rb} +2 -1
- data/spec/unit/mutant/runner/config_spec.rb +36 -21
- data/spec/unit/mutant_spec.rb +7 -9
- metadata +25 -22
- data/lib/mutant/cli/builder.rb +0 -167
- data/lib/mutant/killer/rspec.rb +0 -95
- data/lib/mutant/predicate.rb +0 -70
- data/lib/mutant/predicate/attribute.rb +0 -68
- data/lib/mutant/predicate/blacklist.rb +0 -27
- data/lib/mutant/predicate/matcher.rb +0 -38
- data/lib/mutant/predicate/whitelist.rb +0 -28
- data/lib/mutant/strategy/rspec.rb +0 -76
- data/spec/unit/mutant/cli/builder/rspec_spec.rb +0 -38
- data/spec/unit/mutant/matcher/filter_spec.rb +0 -19
- data/spec/unit/mutant/predicate_spec.rb +0 -135
data/lib/mutant/config.rb
CHANGED
@@ -8,10 +8,10 @@ module Mutant
|
|
8
8
|
:debug,
|
9
9
|
:strategy,
|
10
10
|
:matcher,
|
11
|
-
:subject_predicate,
|
12
11
|
:reporter,
|
13
12
|
:fail_fast,
|
14
|
-
:zombie
|
13
|
+
:zombie,
|
14
|
+
:expected_coverage
|
15
15
|
)
|
16
16
|
|
17
17
|
# Enumerate subjects
|
@@ -28,7 +28,7 @@ module Mutant
|
|
28
28
|
#
|
29
29
|
def subjects(&block)
|
30
30
|
return to_enum(__method__) unless block_given?
|
31
|
-
|
31
|
+
matcher.each(&block)
|
32
32
|
self
|
33
33
|
end
|
34
34
|
|
data/lib/mutant/killer.rb
CHANGED
@@ -99,5 +99,25 @@ module Mutant
|
|
99
99
|
#
|
100
100
|
abstract_method :run
|
101
101
|
|
102
|
+
# Null killer that never kills a mutation
|
103
|
+
class Null < self
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
# Run killer
|
108
|
+
#
|
109
|
+
# @return [true]
|
110
|
+
# when mutant was killed
|
111
|
+
#
|
112
|
+
# @return [false]
|
113
|
+
# otherwise
|
114
|
+
#
|
115
|
+
# @api private
|
116
|
+
#
|
117
|
+
def run
|
118
|
+
false
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
102
122
|
end # Killer
|
103
123
|
end # Mutant
|
@@ -4,7 +4,7 @@ module Mutant
|
|
4
4
|
class Matcher
|
5
5
|
# Matcher filter
|
6
6
|
class Filter < self
|
7
|
-
include Concord.new(:matcher, :
|
7
|
+
include Concord.new(:matcher, :predicate)
|
8
8
|
|
9
9
|
# Enumerate matches
|
10
10
|
#
|
@@ -16,14 +16,9 @@ module Mutant
|
|
16
16
|
#
|
17
17
|
# @api private
|
18
18
|
#
|
19
|
-
def each
|
19
|
+
def each(&block)
|
20
20
|
return to_enum unless block_given?
|
21
|
-
|
22
|
-
matcher.each do |subject|
|
23
|
-
next if filter.match?(subject)
|
24
|
-
yield subject
|
25
|
-
end
|
26
|
-
|
21
|
+
matcher.select(&predicate.method(:call)).each(&block)
|
27
22
|
self
|
28
23
|
end
|
29
24
|
|
@@ -54,6 +54,29 @@ module Mutant
|
|
54
54
|
end
|
55
55
|
end
|
56
56
|
|
57
|
+
# Return scope name
|
58
|
+
#
|
59
|
+
# @param [Class,Module] scope
|
60
|
+
#
|
61
|
+
# @return [String]
|
62
|
+
# if scope has a name and does not raise exceptions optaining it
|
63
|
+
#
|
64
|
+
# @return [nil]
|
65
|
+
# otherwise
|
66
|
+
#
|
67
|
+
# @api private
|
68
|
+
#
|
69
|
+
def self.scope_name(scope)
|
70
|
+
scope.name
|
71
|
+
rescue => exception
|
72
|
+
$stderr.puts <<-MESSAGE
|
73
|
+
WARNING:
|
74
|
+
While optaining #{scope.class}#name from: #{scope.inspect}
|
75
|
+
It raised an error: #{exception.inspect} fix your lib!
|
76
|
+
MESSAGE
|
77
|
+
nil
|
78
|
+
end
|
79
|
+
|
57
80
|
# Yield scope if name matches pattern
|
58
81
|
#
|
59
82
|
# @param [Module,Class] scope
|
@@ -63,9 +86,15 @@ module Mutant
|
|
63
86
|
# @api private
|
64
87
|
#
|
65
88
|
def emit_scope(scope)
|
66
|
-
name = scope
|
89
|
+
name = self.class.scope_name(scope)
|
67
90
|
# FIXME: Fix nokogiri to return a string here
|
68
|
-
|
91
|
+
unless name.nil? or name.kind_of?(String)
|
92
|
+
$stderr.puts <<-MESSAGE
|
93
|
+
WARNING:
|
94
|
+
#{scope.class}#name did not return a string or nil.
|
95
|
+
Fix your lib!
|
96
|
+
MESSAGE
|
97
|
+
end
|
69
98
|
if pattern =~ name
|
70
99
|
yield scope
|
71
100
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Mutant
|
4
|
+
class Matcher
|
5
|
+
# A null matcher, that does not match any subjects
|
6
|
+
class Null < self
|
7
|
+
include Equalizer.new
|
8
|
+
|
9
|
+
# Enumerate subjects
|
10
|
+
#
|
11
|
+
# @return [Enumerator<Subject]
|
12
|
+
# if no block given
|
13
|
+
#
|
14
|
+
# @return [self]
|
15
|
+
# otherwise
|
16
|
+
#
|
17
|
+
# @api private
|
18
|
+
#
|
19
|
+
def each(&block)
|
20
|
+
return to_enum unless block_given?
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
end # Null
|
25
|
+
end # Matcher
|
26
|
+
end # Mutant
|
data/lib/mutant/mutation.rb
CHANGED
@@ -10,6 +10,35 @@ module Mutant
|
|
10
10
|
|
11
11
|
REGISTRY = {}
|
12
12
|
|
13
|
+
# Create delegators to object
|
14
|
+
#
|
15
|
+
# @return [undefined]
|
16
|
+
#
|
17
|
+
# @api private
|
18
|
+
#
|
19
|
+
def self.delegate(*names)
|
20
|
+
names.each do |name|
|
21
|
+
define_delegator(name)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
private_class_method :delegate
|
25
|
+
|
26
|
+
# Create delegator to object
|
27
|
+
#
|
28
|
+
# @param [Symbol] name
|
29
|
+
#
|
30
|
+
# @return [undefined]
|
31
|
+
#
|
32
|
+
# @api private
|
33
|
+
#
|
34
|
+
def self.define_delegator(name)
|
35
|
+
define_method(name) do
|
36
|
+
object.public_send(name)
|
37
|
+
end
|
38
|
+
private name
|
39
|
+
end
|
40
|
+
private_class_method :define_delegator
|
41
|
+
|
13
42
|
# Registre handler for class
|
14
43
|
#
|
15
44
|
# @param [Class] klass
|
@@ -10,6 +10,8 @@ module Mutant
|
|
10
10
|
|
11
11
|
handle(Mutant::Config)
|
12
12
|
|
13
|
+
delegate :matcher, :strategy, :expected_coverage
|
14
|
+
|
13
15
|
# Report configuration
|
14
16
|
#
|
15
17
|
# @param [Mutant::Config] config
|
@@ -20,9 +22,9 @@ module Mutant
|
|
20
22
|
#
|
21
23
|
def run
|
22
24
|
info 'Mutant configuration:'
|
23
|
-
info 'Matcher:
|
24
|
-
info '
|
25
|
-
info '
|
25
|
+
info 'Matcher: %s', matcher.inspect
|
26
|
+
info 'Strategy: %s', strategy.inspect
|
27
|
+
info 'Expect Coverage: %02f%%', expected_coverage.inspect
|
26
28
|
self
|
27
29
|
end
|
28
30
|
|
@@ -31,6 +33,11 @@ module Mutant
|
|
31
33
|
|
32
34
|
handle(Mutant::Runner::Config)
|
33
35
|
|
36
|
+
delegate(
|
37
|
+
:amount_kills, :amount_mutations, :amount_kils,
|
38
|
+
:coverage, :subjects, :failed_subjects, :runtime, :mutations
|
39
|
+
)
|
40
|
+
|
34
41
|
# Run printer
|
35
42
|
#
|
36
43
|
# @return [self]
|
@@ -42,73 +49,18 @@ module Mutant
|
|
42
49
|
info 'Subjects: %s', amount_subjects
|
43
50
|
info 'Mutations: %s', amount_mutations
|
44
51
|
info 'Kills: %s', amount_kills
|
52
|
+
info 'Alive: %s', amount_alive
|
45
53
|
info 'Runtime: %0.2fs', runtime
|
46
54
|
info 'Killtime: %0.2fs', killtime
|
47
55
|
info 'Overhead: %0.2f%%', overhead
|
48
56
|
status 'Coverage: %0.2f%%', coverage
|
49
|
-
status '
|
57
|
+
status 'Expected: %0.2f%%', object.config.expected_coverage
|
50
58
|
print_generic_stats
|
51
59
|
self
|
52
60
|
end
|
53
61
|
|
54
62
|
private
|
55
63
|
|
56
|
-
# Return subjects
|
57
|
-
#
|
58
|
-
# @return [Array<Subject>]
|
59
|
-
#
|
60
|
-
# @api private
|
61
|
-
#
|
62
|
-
def subjects
|
63
|
-
object.subjects
|
64
|
-
end
|
65
|
-
|
66
|
-
# Walker for all ast nodes
|
67
|
-
class Walker
|
68
|
-
|
69
|
-
# Run walkter
|
70
|
-
#
|
71
|
-
# @param [Parser::AST::Node] root
|
72
|
-
#
|
73
|
-
# @return [self]
|
74
|
-
#
|
75
|
-
# @api private
|
76
|
-
#
|
77
|
-
def self.run(root, &block)
|
78
|
-
new(root, block)
|
79
|
-
self
|
80
|
-
end
|
81
|
-
|
82
|
-
private_class_method :new
|
83
|
-
|
84
|
-
# Initialize and run walker
|
85
|
-
#
|
86
|
-
# @param [Parser::AST::Node] root
|
87
|
-
# @param [#call(node)] block
|
88
|
-
#
|
89
|
-
# @return [undefined]
|
90
|
-
#
|
91
|
-
# @api private
|
92
|
-
#
|
93
|
-
def initialize(root, block)
|
94
|
-
@root, @block = root, block
|
95
|
-
dispatch(root)
|
96
|
-
end
|
97
|
-
|
98
|
-
private
|
99
|
-
|
100
|
-
# Perform dispatch
|
101
|
-
#
|
102
|
-
# @return [undefined]
|
103
|
-
#
|
104
|
-
# @api private
|
105
|
-
#
|
106
|
-
def dispatch(node)
|
107
|
-
@block.call(node)
|
108
|
-
node.children.grep(Parser::AST::Node).each(&method(:dispatch))
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
64
|
# Print generic stats
|
113
65
|
#
|
114
66
|
# @return [undefined]
|
@@ -131,7 +83,7 @@ module Mutant
|
|
131
83
|
# @api private
|
132
84
|
#
|
133
85
|
def generic_stats
|
134
|
-
|
86
|
+
subjects.each_with_object(Hash.new(0)) do |runner, stats|
|
135
87
|
Walker.run(runner.subject.node) do |node|
|
136
88
|
if Mutator::Registry.lookup(node) == Mutator::Node::Generic
|
137
89
|
stats[node.type] += 1
|
@@ -157,32 +109,11 @@ module Mutant
|
|
157
109
|
# @api private
|
158
110
|
#
|
159
111
|
def print_mutations
|
160
|
-
|
112
|
+
failed_subjects.each do |subject|
|
161
113
|
Subject::Runner::Details.run(subject, output)
|
162
114
|
end
|
163
115
|
end
|
164
116
|
|
165
|
-
# Return mutations
|
166
|
-
#
|
167
|
-
# @return [Array<Mutation>]
|
168
|
-
#
|
169
|
-
# @api private
|
170
|
-
#
|
171
|
-
def mutations
|
172
|
-
subjects.map(&:mutations).flatten
|
173
|
-
end
|
174
|
-
memoize :mutations
|
175
|
-
|
176
|
-
# Return amount of mutations
|
177
|
-
#
|
178
|
-
# @return [Fixnum]
|
179
|
-
#
|
180
|
-
# @api private
|
181
|
-
#
|
182
|
-
def amount_mutations
|
183
|
-
mutations.length
|
184
|
-
end
|
185
|
-
|
186
117
|
# Return amount of time in killers
|
187
118
|
#
|
188
119
|
# @return [Float]
|
@@ -194,16 +125,6 @@ module Mutant
|
|
194
125
|
end
|
195
126
|
memoize :killtime
|
196
127
|
|
197
|
-
# Return amount of kills
|
198
|
-
#
|
199
|
-
# @return [Fixnum]
|
200
|
-
#
|
201
|
-
# @api private
|
202
|
-
#
|
203
|
-
def amount_kills
|
204
|
-
mutations.select(&:success?).length
|
205
|
-
end
|
206
|
-
|
207
128
|
# Return mutant overhead
|
208
129
|
#
|
209
130
|
# @return [Float]
|
@@ -215,27 +136,6 @@ module Mutant
|
|
215
136
|
Rational(runtime - killtime, runtime) * 100
|
216
137
|
end
|
217
138
|
|
218
|
-
# Return runtime
|
219
|
-
#
|
220
|
-
# @return [Float]
|
221
|
-
#
|
222
|
-
# @api private
|
223
|
-
#
|
224
|
-
def runtime
|
225
|
-
object.runtime
|
226
|
-
end
|
227
|
-
|
228
|
-
# Return coverage
|
229
|
-
#
|
230
|
-
# @return [Float]
|
231
|
-
#
|
232
|
-
# @api private
|
233
|
-
#
|
234
|
-
def coverage
|
235
|
-
return 0 if amount_mutations.zero?
|
236
|
-
Rational(amount_kills, amount_mutations) * 100
|
237
|
-
end
|
238
|
-
|
239
139
|
# Return amount of alive mutations
|
240
140
|
#
|
241
141
|
# @return [Fixnum]
|
@@ -243,12 +143,12 @@ module Mutant
|
|
243
143
|
# @api private
|
244
144
|
#
|
245
145
|
def amount_alive
|
246
|
-
amount_mutations - amount_kills
|
146
|
+
object.amount_mutations - amount_kills
|
247
147
|
end
|
248
148
|
|
249
149
|
end # Runner
|
250
150
|
end # Config
|
251
151
|
end # Printer
|
252
|
-
end #
|
152
|
+
end # CLI
|
253
153
|
end # Reporter
|
254
154
|
end # Mutant
|
data/lib/mutant/runner/config.rb
CHANGED
@@ -40,6 +40,8 @@ module Mutant
|
|
40
40
|
end
|
41
41
|
memoize :failed_subjects
|
42
42
|
|
43
|
+
COVERAGE_PRECISION = 1
|
44
|
+
|
43
45
|
# Test if run was successful
|
44
46
|
#
|
45
47
|
# @return [true]
|
@@ -51,7 +53,7 @@ module Mutant
|
|
51
53
|
# @api private
|
52
54
|
#
|
53
55
|
def success?
|
54
|
-
|
56
|
+
coverage.round(COVERAGE_PRECISION) == config.expected_coverage.round(COVERAGE_PRECISION)
|
55
57
|
end
|
56
58
|
memoize :success?
|
57
59
|
|
@@ -65,6 +67,50 @@ module Mutant
|
|
65
67
|
config.strategy
|
66
68
|
end
|
67
69
|
|
70
|
+
# Return coverage
|
71
|
+
#
|
72
|
+
# @return [Float]
|
73
|
+
#
|
74
|
+
# @api private
|
75
|
+
#
|
76
|
+
def coverage
|
77
|
+
return 0.0 if amount_mutations.zero?
|
78
|
+
Rational(amount_kills, amount_mutations) * 100
|
79
|
+
end
|
80
|
+
memoize :coverage
|
81
|
+
|
82
|
+
# Return amount of kills
|
83
|
+
#
|
84
|
+
# @return [Fixnum]
|
85
|
+
#
|
86
|
+
# @api private
|
87
|
+
#
|
88
|
+
def amount_kills
|
89
|
+
mutations.select(&:success?).length
|
90
|
+
end
|
91
|
+
memoize :amount_kills
|
92
|
+
|
93
|
+
# Return mutations
|
94
|
+
#
|
95
|
+
# @return [Array<Mutation>]
|
96
|
+
#
|
97
|
+
# @api private
|
98
|
+
#
|
99
|
+
def mutations
|
100
|
+
subjects.map(&:mutations).flatten
|
101
|
+
end
|
102
|
+
memoize :mutations
|
103
|
+
|
104
|
+
# Return amount of mutations
|
105
|
+
#
|
106
|
+
# @return [Fixnum]
|
107
|
+
#
|
108
|
+
# @api private
|
109
|
+
#
|
110
|
+
def amount_mutations
|
111
|
+
mutations.length
|
112
|
+
end
|
113
|
+
|
68
114
|
private
|
69
115
|
|
70
116
|
# Run config
|