mutant 0.10.1 → 0.10.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/bin/mutant +0 -2
  3. data/lib/mutant.rb +7 -5
  4. data/lib/mutant/cli/command.rb +8 -6
  5. data/lib/mutant/cli/command/run.rb +23 -10
  6. data/lib/mutant/config.rb +80 -77
  7. data/lib/mutant/env.rb +14 -4
  8. data/lib/mutant/integration.rb +7 -10
  9. data/lib/mutant/integration/null.rb +0 -1
  10. data/lib/mutant/isolation.rb +11 -48
  11. data/lib/mutant/isolation/fork.rb +107 -40
  12. data/lib/mutant/isolation/none.rb +18 -5
  13. data/lib/mutant/license/subscription.rb +1 -1
  14. data/lib/mutant/license/subscription/commercial.rb +2 -3
  15. data/lib/mutant/license/subscription/opensource.rb +2 -2
  16. data/lib/mutant/matcher/config.rb +13 -0
  17. data/lib/mutant/matcher/method/instance.rb +0 -2
  18. data/lib/mutant/mutator/node/send.rb +1 -1
  19. data/lib/mutant/parallel.rb +0 -1
  20. data/lib/mutant/parallel/worker.rb +0 -2
  21. data/lib/mutant/reporter/cli.rb +0 -2
  22. data/lib/mutant/reporter/cli/printer/config.rb +9 -5
  23. data/lib/mutant/reporter/cli/printer/coverage_result.rb +19 -0
  24. data/lib/mutant/reporter/cli/printer/env_progress.rb +2 -0
  25. data/lib/mutant/reporter/cli/printer/isolation_result.rb +19 -35
  26. data/lib/mutant/reporter/cli/printer/mutation_result.rb +4 -9
  27. data/lib/mutant/reporter/cli/printer/subject_result.rb +2 -2
  28. data/lib/mutant/result.rb +91 -30
  29. data/lib/mutant/runner/sink.rb +12 -5
  30. data/lib/mutant/timer.rb +60 -11
  31. data/lib/mutant/transform.rb +25 -21
  32. data/lib/mutant/version.rb +1 -1
  33. data/lib/mutant/warnings.rb +0 -1
  34. data/lib/mutant/world.rb +67 -0
  35. metadata +12 -14
  36. data/lib/mutant/minitest/coverage.rb +0 -51
  37. data/lib/mutant/reporter/cli/printer/mutation_progress_result.rb +0 -28
  38. data/lib/mutant/reporter/cli/printer/subject_progress.rb +0 -58
  39. data/lib/mutant/reporter/cli/printer/test_result.rb +0 -32
@@ -99,7 +99,7 @@ module Mutant
99
99
 
100
100
  dynamic_selector, *actual_arguments = *arguments
101
101
 
102
- return unless n_sym?(dynamic_selector)
102
+ return unless dynamic_selector && n_sym?(dynamic_selector)
103
103
 
104
104
  method_name = AST::Meta::Symbol.new(dynamic_selector).name
105
105
 
@@ -41,7 +41,6 @@ module Mutant
41
41
  end
42
42
  private_class_method :threads
43
43
 
44
- # ignore :reek:LongParameterList
45
44
  def self.shared(klass, config, **attributes)
46
45
  klass.new(
47
46
  condition_variable: config.condition_variable,
@@ -17,8 +17,6 @@ module Mutant
17
17
  # Run worker payload
18
18
  #
19
19
  # @return [self]
20
- #
21
- # ignore :reek:TooManyStatements
22
20
  def call
23
21
  loop do
24
22
  job = next_job or break
@@ -11,8 +11,6 @@ module Mutant
11
11
  # @param [IO] output
12
12
  #
13
13
  # @return [Reporter::CLI]
14
- #
15
- # :reek:ManualDispatch
16
14
  def self.build(output)
17
15
  new(
18
16
  output,
@@ -12,13 +12,17 @@ module Mutant
12
12
  # @param [Mutant::Config] config
13
13
  #
14
14
  # @return [undefined]
15
+ #
16
+ # rubocop:disable Metrics/AbcSize
15
17
  def run
16
- info 'Matcher: %s', object.matcher.inspect
17
- info 'Integration: %s', object.integration
18
- info 'Jobs: %d', object.jobs
19
- info 'Includes: %s', object.includes
20
- info 'Requires: %s', object.requires
18
+ info 'Matcher: %s', object.matcher.inspect
19
+ info 'Integration: %s', object.integration || 'null'
20
+ info 'Jobs: %s', object.jobs || 'auto'
21
+ info 'Includes: %s', object.includes
22
+ info 'Requires: %s', object.requires
23
+ info 'MutationTimeout: %0.9g', object.mutation_timeout if object.mutation_timeout
21
24
  end
25
+ # rubocop:enable Metrics/AbcSize
22
26
 
23
27
  end # Config
24
28
  end # Printer
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ class Reporter
5
+ class CLI
6
+ class Printer
7
+ # Reporter for mutation coverage results
8
+ class CoverageResult < self
9
+ # Run report printer
10
+ #
11
+ # @return [undefined]
12
+ def run
13
+ visit(MutationResult, object.mutation_result)
14
+ end
15
+ end # Printer
16
+ end # Coverage
17
+ end # CLI
18
+ end # Reporter
19
+ end # Mutant
@@ -10,6 +10,7 @@ module Mutant
10
10
  :amount_mutation_results,
11
11
  :amount_mutations_alive,
12
12
  :amount_mutations_killed,
13
+ :amount_timeouts,
13
14
  :coverage,
14
15
  :env,
15
16
  :killtime,
@@ -21,6 +22,7 @@ module Mutant
21
22
  [:info, 'Results: %s', :amount_mutation_results],
22
23
  [:info, 'Kills: %s', :amount_mutations_killed],
23
24
  [:info, 'Alive: %s', :amount_mutations_alive ],
25
+ [:info, 'Timeouts: %s', :amount_timeouts ],
24
26
  [:info, 'Runtime: %0.2fs', :runtime ],
25
27
  [:info, 'Killtime: %0.2fs', :killtime ],
26
28
  [:info, 'Overhead: %0.2f%%', :overhead_percent ],
@@ -6,7 +6,7 @@ module Mutant
6
6
  class Printer
7
7
  # Reporter for mutation results
8
8
  class IsolationResult < self
9
- CHILD_ERROR_MESSAGE = <<~'MESSAGE'
9
+ PROCESS_ERROR_MESSAGE = <<~'MESSAGE'
10
10
  Killfork exited nonzero. Its result (if any) was ignored.
11
11
  Process status:
12
12
  %s
@@ -36,40 +36,24 @@ module Mutant
36
36
  ```
37
37
  MESSAGE
38
38
 
39
- FORK_ERROR_MESSAGE = <<~'MESSAGE'
40
- Forking the child process to isolate the mutation in failed.
41
- This meant that either the RubyVM or your OS was under too much
42
- pressure to add another child process.
43
-
44
- Possible solutions are:
45
- * Reduce concurrency
46
- * Reduce locks
39
+ TIMEOUT_ERROR_MESSAGE =<<~'MESSAGE'
40
+ Mutation analysis ran into the configured timeout of %0.9<timeout>g seconds.
47
41
  MESSAGE
48
42
 
49
- MAP = {
50
- Isolation::Fork::ChildError => :visit_child_error,
51
- Isolation::Fork::ForkError => :visit_fork_error,
52
- Isolation::Result::ErrorChain => :visit_chain,
53
- Isolation::Result::Exception => :visit_exception,
54
- Isolation::Result::Success => :visit_success
55
- }.freeze
56
-
57
43
  private_constant(*constants(false))
58
44
 
59
45
  # Run report printer
60
46
  #
61
47
  # @return [undefined]
62
48
  def run
49
+ print_timeout
50
+ print_process_status
63
51
  print_log_messages
64
- __send__(MAP.fetch(object.class))
52
+ print_exception
65
53
  end
66
54
 
67
55
  private
68
56
 
69
- def visit_success
70
- visit(TestResult, object.value)
71
- end
72
-
73
57
  def print_log_messages
74
58
  log = object.log
75
59
 
@@ -82,16 +66,23 @@ module Mutant
82
66
  end
83
67
  end
84
68
 
85
- def visit_child_error
86
- puts(CHILD_ERROR_MESSAGE % object.value.inspect)
69
+ def print_process_status
70
+ process_status = object.process_status or return
71
+
72
+ if process_status.success?
73
+ puts("Killfork: #{process_status.inspect}")
74
+ else
75
+ puts(PROCESS_ERROR_MESSAGE % process_status.inspect)
76
+ end
87
77
  end
88
78
 
89
- def visit_fork_error
90
- puts(FORK_ERROR_MESSAGE)
79
+ def print_timeout
80
+ timeout = object.timeout or return
81
+ puts(TIMEOUT_ERROR_MESSAGE % { timeout: timeout })
91
82
  end
92
83
 
93
- def visit_exception
94
- exception = object.value
84
+ def print_exception
85
+ exception = object.exception or return
95
86
 
96
87
  puts(
97
88
  EXCEPTION_ERROR_MESSAGE % [
@@ -100,13 +91,6 @@ module Mutant
100
91
  ]
101
92
  )
102
93
  end
103
-
104
- def visit_chain
105
- printer = self.class
106
-
107
- visit(printer, object.value)
108
- visit(printer, object.next)
109
- end
110
94
  end # IsolationResult
111
95
  end # Printer
112
96
  end # CLI
@@ -46,24 +46,23 @@ module Mutant
46
46
  This is typically a problem of your specs not passing unmutated.
47
47
  MESSAGE
48
48
 
49
- FOOTER = '-----------------------'
49
+ SEPARATOR = '-----------------------'
50
50
 
51
51
  # Run report printer
52
52
  #
53
53
  # @return [undefined]
54
54
  def run
55
55
  puts(mutation.identification)
56
+ puts(SEPARATOR)
56
57
  print_details
57
- puts(FOOTER)
58
+ puts(SEPARATOR)
58
59
  end
59
60
 
60
61
  private
61
62
 
62
63
  def print_details
64
+ visit(IsolationResult, isolation_result)
63
65
  __send__(MAP.fetch(mutation.class))
64
-
65
- puts(FOOTER)
66
- visit_isolation_result
67
66
  end
68
67
 
69
68
  def evil_details
@@ -94,10 +93,6 @@ module Mutant
94
93
  info(NEUTRAL_MESSAGE, original_node.inspect, mutation.source)
95
94
  end
96
95
 
97
- def visit_isolation_result
98
- visit(IsolationResult, isolation_result)
99
- end
100
-
101
96
  def original_node
102
97
  mutation.subject.node
103
98
  end
@@ -7,7 +7,7 @@ module Mutant
7
7
  # Subject result printer
8
8
  class SubjectResult < self
9
9
 
10
- delegate :subject, :alive_mutation_results, :tests
10
+ delegate :subject, :uncovered_results, :tests
11
11
 
12
12
  # Run report printer
13
13
  #
@@ -17,7 +17,7 @@ module Mutant
17
17
  tests.each do |test|
18
18
  puts("- #{test.identification}")
19
19
  end
20
- visit_collection(MutationResult, alive_mutation_results)
20
+ visit_collection(CoverageResult, uncovered_results)
21
21
  end
22
22
 
23
23
  end # SubjectResult
@@ -4,8 +4,8 @@ module Mutant
4
4
  # Namespace and mixin module for results
5
5
  module Result
6
6
 
7
- # Coverage mixin
8
- module Coverage
7
+ # CoverageMetric mixin
8
+ module CoverageMetric
9
9
  FULL_COVERAGE = Rational(1).freeze
10
10
  private_constant(*constants(false))
11
11
 
@@ -19,7 +19,7 @@ module Mutant
19
19
  Rational(amount_mutations_killed, amount_mutation_results)
20
20
  end
21
21
  end
22
- end # Coverage
22
+ end # CoverageMetric
23
23
 
24
24
  # Class level mixin
25
25
  module ClassMethods
@@ -39,6 +39,13 @@ module Mutant
39
39
  end
40
40
  memoize(name)
41
41
  end
42
+
43
+ # Delegate a method to child
44
+ def delegate(name, target)
45
+ define_method(name) do
46
+ public_send(target).public_send(name)
47
+ end
48
+ end
42
49
  end # ClassMethods
43
50
 
44
51
  private_constant(*constants(false))
@@ -68,7 +75,7 @@ module Mutant
68
75
 
69
76
  # Env result object
70
77
  class Env
71
- include Coverage, Result, Anima.new(
78
+ include CoverageMetric, Result, Anima.new(
72
79
  :env,
73
80
  :runtime,
74
81
  :subject_results
@@ -92,6 +99,7 @@ module Mutant
92
99
  sum :amount_mutation_results, :subject_results
93
100
  sum :amount_mutations_alive, :subject_results
94
101
  sum :amount_mutations_killed, :subject_results
102
+ sum :amount_timeouts, :subject_results
95
103
  sum :killtime, :subject_results
96
104
 
97
105
  # Amount of mutations
@@ -114,7 +122,6 @@ module Mutant
114
122
  # Test result
115
123
  class Test
116
124
  include Result, Anima.new(
117
- :output,
118
125
  :passed,
119
126
  :runtime,
120
127
  :tests
@@ -128,7 +135,6 @@ module Mutant
128
135
  # @return [undefined]
129
136
  def initialize
130
137
  super(
131
- output: '',
132
138
  passed: false,
133
139
  runtime: 0.0,
134
140
  tests: []
@@ -139,35 +145,42 @@ module Mutant
139
145
 
140
146
  # Subject result
141
147
  class Subject
142
- include Coverage, Result, Anima.new(
143
- :mutation_results,
148
+ include CoverageMetric, Result, Anima.new(
149
+ :coverage_results,
144
150
  :subject,
145
151
  :tests
146
152
  )
147
153
 
148
- sum :killtime, :mutation_results
149
- sum :runtime, :mutation_results
154
+ sum :killtime, :coverage_results
155
+ sum :runtime, :coverage_results
150
156
 
151
157
  # Test if subject was processed successful
152
158
  #
153
159
  # @return [Boolean]
154
160
  def success?
155
- alive_mutation_results.empty?
161
+ uncovered_results.empty?
156
162
  end
157
163
 
158
164
  # Alive mutations
159
165
  #
160
- # @return [Array<Result::Mutation>]
161
- def alive_mutation_results
162
- mutation_results.reject(&:success?)
166
+ # @return [Array<Result::Coverage>]
167
+ def uncovered_results
168
+ coverage_results.reject(&:success?)
163
169
  end
164
- memoize :alive_mutation_results
170
+ memoize :uncovered_results
165
171
 
166
172
  # Amount of mutations
167
173
  #
168
174
  # @return [Integer]
169
175
  def amount_mutation_results
170
- mutation_results.length
176
+ coverage_results.length
177
+ end
178
+
179
+ # Amount of mutations
180
+ #
181
+ # @return [Integer]
182
+ def amount_timeouts
183
+ coverage_results.count(&:timeout?)
171
184
  end
172
185
 
173
186
  # Amount of mutations
@@ -181,25 +194,49 @@ module Mutant
181
194
  #
182
195
  # @return [Integer]
183
196
  def amount_mutations_killed
184
- killed_mutation_results.length
197
+ covered_results.length
185
198
  end
186
199
 
187
200
  # Number of alive mutations
188
201
  #
189
202
  # @return [Integer]
190
203
  def amount_mutations_alive
191
- alive_mutation_results.length
204
+ uncovered_results.length
192
205
  end
193
206
 
194
207
  private
195
208
 
196
- def killed_mutation_results
197
- mutation_results.select(&:success?)
209
+ def covered_results
210
+ coverage_results.select(&:success?)
198
211
  end
199
- memoize :killed_mutation_results
212
+ memoize :covered_results
200
213
 
201
214
  end # Subject
202
215
 
216
+ # Coverage of a mutation against criteria
217
+ class Coverage
218
+ include Result, Anima.new(
219
+ :mutation_result,
220
+ :criteria_result
221
+ )
222
+
223
+ delegate :killtime, :mutation_result
224
+ delegate :runtime, :mutation_result
225
+ delegate :success?, :criteria_result
226
+ delegate :timeout?, :mutation_result
227
+ end # Coverage
228
+
229
+ class CoverageCriteria
230
+ include Result, Anima.new(*Config::CoverageCriteria.anima.attribute_names)
231
+
232
+ # Test if one coverage criteria indicates success
233
+ #
234
+ # @return [Boolean]
235
+ def success?
236
+ process_abort || test_result || timeout
237
+ end
238
+ end
239
+
203
240
  # Mutation result
204
241
  class Mutation
205
242
  include Result, Anima.new(
@@ -208,25 +245,49 @@ module Mutant
208
245
  :runtime
209
246
  )
210
247
 
248
+ # Create mutation criteria results
249
+ #
250
+ # @praam [Result::CoverageCriteria]
251
+ def criteria_result(coverage_criteria)
252
+ CoverageCriteria.new(
253
+ process_abort: coverage_criteria.process_abort && process_abort?,
254
+ test_result: coverage_criteria.test_result && test_result_success?,
255
+ timeout: coverage_criteria.timeout && timeout?
256
+ )
257
+ end
258
+
211
259
  # Time the tests had been running
212
260
  #
213
261
  # @return [Float]
214
262
  def killtime
215
- if isolation_result.success?
216
- isolation_result.value.runtime
217
- else
218
- 0.0
219
- end
263
+ isolation_result.value&.runtime || 0.0
264
+ end
265
+
266
+ # Test for timeout
267
+ #
268
+ # @return [Boolean]
269
+ def timeout?
270
+ !isolation_result.timeout.nil?
271
+ end
272
+
273
+ # Test for unexpected process abort
274
+ #
275
+ # @return [Boolean]
276
+ def process_abort?
277
+ process_status = isolation_result.process_status or return false
278
+
279
+ !timeout? && !process_status.exited?
220
280
  end
221
281
 
282
+ private
283
+
222
284
  # Test if mutation was handled successfully
223
285
  #
224
286
  # @return [Boolean]
225
- def success?
226
- isolation_result.success? &&
227
- mutation.class.success?(isolation_result.value)
287
+ def test_result_success?
288
+ isolation_result.valid_value? && mutation.class.success?(isolation_result.value)
228
289
  end
229
- memoize :success?
290
+ memoize :test_result_success?
230
291
 
231
292
  end # Mutation
232
293
  end # Result