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.
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