mutant 0.3.6 → 0.5.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/.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
|