mutant 0.3.6 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -0
  3. data/Changelog.md +13 -0
  4. data/Gemfile +1 -1
  5. data/Guardfile +7 -25
  6. data/LICENSE +1 -1
  7. data/README.md +16 -10
  8. data/config/flay.yml +1 -1
  9. data/config/reek.yml +11 -11
  10. data/config/rubocop.yml +1 -1
  11. data/lib/mutant.rb +3 -10
  12. data/lib/mutant/cli.rb +199 -80
  13. data/lib/mutant/config.rb +3 -3
  14. data/lib/mutant/killer.rb +20 -0
  15. data/lib/mutant/matcher/filter.rb +3 -8
  16. data/lib/mutant/matcher/method/instance.rb +1 -1
  17. data/lib/mutant/matcher/namespace.rb +31 -2
  18. data/lib/mutant/matcher/null.rb +26 -0
  19. data/lib/mutant/mutation.rb +0 -1
  20. data/lib/mutant/reporter/cli/printer.rb +29 -0
  21. data/lib/mutant/reporter/cli/printer/config.rb +16 -116
  22. data/lib/mutant/runner/config.rb +47 -1
  23. data/lib/mutant/strategy.rb +39 -1
  24. data/lib/mutant/version.rb +1 -1
  25. data/lib/mutant/walker.rb +51 -0
  26. data/mutant-rspec.gemspec +24 -0
  27. data/mutant.gemspec +7 -3
  28. data/spec/integration/mutant/rspec_spec.rb +11 -6
  29. data/spec/integration/mutant/zombie_spec.rb +2 -2
  30. data/spec/shared/method_matcher_behavior.rb +6 -6
  31. data/spec/spec_helper.rb +2 -2
  32. data/spec/unit/mutant/cli_new_spec.rb +49 -34
  33. data/spec/unit/mutant/context/scope/root_spec.rb +1 -1
  34. data/spec/unit/mutant/loader/eval_spec.rb +2 -2
  35. data/spec/unit/mutant/matcher/chain_spec.rb +1 -1
  36. data/spec/unit/mutant/matcher/methods/instance_spec.rb +1 -1
  37. data/spec/unit/mutant/matcher/methods/singleton_spec.rb +1 -1
  38. data/spec/unit/mutant/matcher/namespace_spec.rb +1 -1
  39. data/spec/unit/mutant/mutation_spec.rb +1 -1
  40. data/spec/unit/mutant/{killer/rspec_spec.rb → rspec/killer_spec.rb} +2 -1
  41. data/spec/unit/mutant/runner/config_spec.rb +36 -21
  42. data/spec/unit/mutant_spec.rb +7 -9
  43. metadata +25 -22
  44. data/lib/mutant/cli/builder.rb +0 -167
  45. data/lib/mutant/killer/rspec.rb +0 -95
  46. data/lib/mutant/predicate.rb +0 -70
  47. data/lib/mutant/predicate/attribute.rb +0 -68
  48. data/lib/mutant/predicate/blacklist.rb +0 -27
  49. data/lib/mutant/predicate/matcher.rb +0 -38
  50. data/lib/mutant/predicate/whitelist.rb +0 -28
  51. data/lib/mutant/strategy/rspec.rb +0 -76
  52. data/spec/unit/mutant/cli/builder/rspec_spec.rb +0 -38
  53. data/spec/unit/mutant/matcher/filter_spec.rb +0 -19
  54. 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
- Matcher::Filter.new(matcher, subject_predicate).each(&block)
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, :filter)
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
 
@@ -73,7 +73,7 @@ module Mutant
73
73
  # @api private
74
74
  #
75
75
  def source_location
76
- scope.original_instance_method(method.name).source_location
76
+ scope.unmemoized_instance_method(method.name).source_location
77
77
  end
78
78
 
79
79
  end # Memoized
@@ -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.name
89
+ name = self.class.scope_name(scope)
67
90
  # FIXME: Fix nokogiri to return a string here
68
- return unless name.kind_of?(String)
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
@@ -56,7 +56,6 @@ module Mutant
56
56
  def identification
57
57
  "#{subject.identification}:#{code}"
58
58
  end
59
- memoize :identification
60
59
 
61
60
  # Return mutation code
62
61
  #
@@ -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: %s', object.matcher.inspect
24
- info 'Subject Filter: %s', object.subject_predicate.inspect
25
- info 'Strategy: %s', object.strategy.inspect
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 'Alive: %s', amount_alive
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
- object.subjects.each_with_object(Hash.new(0)) do |runner, stats|
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
- object.failed_subjects.each do |subject|
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 # Cli
152
+ end # CLI
253
153
  end # Reporter
254
154
  end # Mutant
@@ -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
- failed_subjects.empty?
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