mutant 0.10.6 → 0.10.7

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 (34) hide show
  1. checksums.yaml +4 -4
  2. data/lib/mutant.rb +4 -3
  3. data/lib/mutant/cli/command/run.rb +5 -2
  4. data/lib/mutant/config.rb +59 -34
  5. data/lib/mutant/env.rb +14 -4
  6. data/lib/mutant/integration.rb +7 -10
  7. data/lib/mutant/integration/null.rb +0 -1
  8. data/lib/mutant/isolation.rb +11 -48
  9. data/lib/mutant/isolation/fork.rb +107 -40
  10. data/lib/mutant/isolation/none.rb +18 -5
  11. data/lib/mutant/license/subscription/commercial.rb +2 -3
  12. data/lib/mutant/license/subscription/opensource.rb +0 -1
  13. data/lib/mutant/matcher/method/instance.rb +0 -2
  14. data/lib/mutant/mutator/node/send.rb +1 -1
  15. data/lib/mutant/parallel.rb +0 -1
  16. data/lib/mutant/parallel/worker.rb +0 -2
  17. data/lib/mutant/reporter/cli.rb +0 -2
  18. data/lib/mutant/reporter/cli/printer/config.rb +9 -5
  19. data/lib/mutant/reporter/cli/printer/coverage_result.rb +19 -0
  20. data/lib/mutant/reporter/cli/printer/env_progress.rb +2 -0
  21. data/lib/mutant/reporter/cli/printer/isolation_result.rb +19 -35
  22. data/lib/mutant/reporter/cli/printer/mutation_result.rb +4 -9
  23. data/lib/mutant/reporter/cli/printer/subject_result.rb +2 -2
  24. data/lib/mutant/result.rb +81 -30
  25. data/lib/mutant/runner/sink.rb +12 -5
  26. data/lib/mutant/timer.rb +60 -11
  27. data/lib/mutant/transform.rb +25 -21
  28. data/lib/mutant/version.rb +1 -1
  29. data/lib/mutant/warnings.rb +0 -1
  30. data/lib/mutant/world.rb +15 -0
  31. metadata +3 -5
  32. data/lib/mutant/reporter/cli/printer/mutation_progress_result.rb +0 -28
  33. data/lib/mutant/reporter/cli/printer/subject_progress.rb +0 -58
  34. data/lib/mutant/reporter/cli/printer/test_result.rb +0 -32
@@ -12,12 +12,25 @@ module Mutant
12
12
  #
13
13
  # @return [Result]
14
14
  #
15
- # ignore :reek:UtilityFunction
16
- def call
17
- Result::Success.new(yield)
18
- rescue => exception
19
- Result::Exception.new(exception)
15
+ # rubocop:disable Lint/SuppressedException
16
+ # rubocop:disable Metrics/MethodLength
17
+ # ^^ it actually isn not suppressed, it assigns an lvar
18
+ def call(_timeout)
19
+ begin
20
+ value = yield
21
+ rescue => exception
22
+ end
23
+
24
+ Result.new(
25
+ exception: exception,
26
+ log: '',
27
+ process_status: nil,
28
+ timeout: nil,
29
+ value: value
30
+ )
20
31
  end
32
+ # rubocop:enable Lint/SuppressedException
33
+ # rubocop:enable Metrics/MethodLength
21
34
 
22
35
  end # None
23
36
  end # Isolation
@@ -12,7 +12,7 @@ module Mutant
12
12
  end
13
13
 
14
14
  def self.from_json(value)
15
- new(value.fetch('authors').map(&Author.method(:new)).to_set)
15
+ new(value.fetch('authors').map(&Author.public_method(:new)).to_set)
16
16
  end
17
17
 
18
18
  def apply(world)
@@ -39,12 +39,11 @@ module Mutant
39
39
  capture(world, %w[git show --quiet --pretty=format:%ae])
40
40
  end
41
41
 
42
- # ignore :reek:UtilityFunction
43
42
  def capture(world, command)
44
43
  world
45
44
  .capture_stdout(command)
46
45
  .fmap(&:chomp)
47
- .fmap(&Author.method(:new))
46
+ .fmap(&Author.public_method(:new))
48
47
  .fmap { |value| Set.new([value]) }
49
48
  .from_right { Set.new }
50
49
  end
@@ -67,7 +67,6 @@ module Mutant
67
67
  end
68
68
  end
69
69
 
70
- # ignore :reek:UtilityFunction
71
70
  def parse_remotes(input)
72
71
  input.lines.map(&Repository.method(:parse_remote)).to_set
73
72
  end
@@ -12,8 +12,6 @@ module Mutant
12
12
  # @param [UnboundMethod] method
13
13
  #
14
14
  # @return [Matcher::Method::Instance]
15
- #
16
- # :reek:ManualDispatch
17
15
  def self.new(scope, target_method)
18
16
  name = target_method.name
19
17
  evaluator =
@@ -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 || 'null'
18
- info 'Jobs: %s', object.jobs || 'auto'
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
+ 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,39 @@ 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
+ test_result: coverage_criteria.test_result && test_result_success?,
254
+ timeout: coverage_criteria.timeout && timeout?
255
+ )
256
+ end
257
+
211
258
  # Time the tests had been running
212
259
  #
213
260
  # @return [Float]
214
261
  def killtime
215
- if isolation_result.success?
216
- isolation_result.value.runtime
217
- else
218
- 0.0
219
- end
262
+ isolation_result.value&.runtime || 0.0
220
263
  end
221
264
 
265
+ # Test for timeout
266
+ #
267
+ # @return [Boolean]
268
+ def timeout?
269
+ !isolation_result.timeout.nil?
270
+ end
271
+
272
+ private
273
+
222
274
  # Test if mutation was handled successfully
223
275
  #
224
276
  # @return [Boolean]
225
- def success?
226
- isolation_result.success? &&
227
- mutation.class.success?(isolation_result.value)
277
+ def test_result_success?
278
+ isolation_result.valid_value? && mutation.class.success?(isolation_result.value)
228
279
  end
229
- memoize :success?
280
+ memoize :test_result_success?
230
281
 
231
282
  end # Mutation
232
283
  end # Result