mutant 0.15.1 → 0.16.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/VERSION +1 -1
- data/lib/mutant/cli/command/root.rb +1 -1
- data/lib/mutant/cli/command/session.rb +281 -0
- data/lib/mutant/cli/command.rb +16 -2
- data/lib/mutant/config.rb +1 -1
- data/lib/mutant/expression/method.rb +0 -2
- data/lib/mutant/expression/methods.rb +0 -2
- data/lib/mutant/expression/namespace.rb +0 -2
- data/lib/mutant/isolation/fork.rb +2 -6
- data/lib/mutant/isolation.rb +31 -0
- data/lib/mutant/matcher/null.rb +1 -1
- data/lib/mutant/mutation/runner/sink.rb +23 -10
- data/lib/mutant/mutation/runner.rb +1 -0
- data/lib/mutant/mutation.rb +3 -20
- data/lib/mutant/mutator/node/literal/integer.rb +61 -0
- data/lib/mutant/parallel/connection.rb +0 -2
- data/lib/mutant/parallel/driver.rb +0 -2
- data/lib/mutant/reporter/cli/printer/alive_results.rb +27 -0
- data/lib/mutant/reporter/cli/printer/env_result.rb +52 -9
- data/lib/mutant/reporter/cli/printer/subject_result.rb +103 -5
- data/lib/mutant/reporter/cli/printer.rb +24 -1
- data/lib/mutant/repository/diff.rb +1 -2
- data/lib/mutant/result/exception.rb +29 -0
- data/lib/mutant/result/json_writer.rb +43 -0
- data/lib/mutant/result/process_status.rb +37 -0
- data/lib/mutant/result/session.rb +63 -0
- data/lib/mutant/result/test.rb +30 -0
- data/lib/mutant/result.rb +201 -96
- data/lib/mutant/segment/recorder.rb +0 -2
- data/lib/mutant/timer.rb +3 -1
- data/lib/mutant/transform/json.rb +68 -0
- data/lib/mutant/transform.rb +33 -25
- data/lib/mutant/world.rb +6 -0
- data/lib/mutant/zombifier.rb +0 -2
- data/lib/mutant.rb +13 -4
- metadata +33 -7
- data/lib/mutant/reporter/cli/printer/coverage_result.rb +0 -19
- data/lib/mutant/reporter/cli/printer/mutation_result.rb +0 -84
data/lib/mutant/result.rb
CHANGED
|
@@ -48,7 +48,7 @@ module Mutant
|
|
|
48
48
|
end
|
|
49
49
|
end # ClassMethods
|
|
50
50
|
|
|
51
|
-
private_constant(
|
|
51
|
+
private_constant(:CoverageMetric, :ClassMethods)
|
|
52
52
|
|
|
53
53
|
# Hook called when module gets included
|
|
54
54
|
#
|
|
@@ -140,98 +140,6 @@ module Mutant
|
|
|
140
140
|
def amount_tests_success = test_results.count(&:passed)
|
|
141
141
|
end # TestEnv
|
|
142
142
|
|
|
143
|
-
# Test result
|
|
144
|
-
class Test
|
|
145
|
-
include Anima.new(:job_index, :passed, :runtime, :output)
|
|
146
|
-
|
|
147
|
-
alias_method :success?, :passed
|
|
148
|
-
|
|
149
|
-
class VoidValue < self
|
|
150
|
-
include Singleton
|
|
151
|
-
|
|
152
|
-
# Initialize object
|
|
153
|
-
#
|
|
154
|
-
# @return [undefined]
|
|
155
|
-
def initialize
|
|
156
|
-
super(
|
|
157
|
-
job_index: nil,
|
|
158
|
-
output: '',
|
|
159
|
-
passed: false,
|
|
160
|
-
runtime: 0.0
|
|
161
|
-
)
|
|
162
|
-
end
|
|
163
|
-
end # VoidValue
|
|
164
|
-
end # Test
|
|
165
|
-
|
|
166
|
-
# Subject result
|
|
167
|
-
class Subject
|
|
168
|
-
include CoverageMetric, Result, Anima.new(
|
|
169
|
-
:coverage_results,
|
|
170
|
-
:subject,
|
|
171
|
-
:tests
|
|
172
|
-
)
|
|
173
|
-
|
|
174
|
-
sum :killtime, :coverage_results
|
|
175
|
-
sum :runtime, :coverage_results
|
|
176
|
-
|
|
177
|
-
# Test if subject was processed successful
|
|
178
|
-
#
|
|
179
|
-
# @return [Boolean]
|
|
180
|
-
def success?
|
|
181
|
-
uncovered_results.empty?
|
|
182
|
-
end
|
|
183
|
-
|
|
184
|
-
# Alive mutations
|
|
185
|
-
#
|
|
186
|
-
# @return [Array<Result::Coverage>]
|
|
187
|
-
def uncovered_results = coverage_results.reject(&:success?)
|
|
188
|
-
memoize :uncovered_results
|
|
189
|
-
|
|
190
|
-
# Amount of mutations
|
|
191
|
-
#
|
|
192
|
-
# @return [Integer]
|
|
193
|
-
def amount_mutation_results = coverage_results.length
|
|
194
|
-
|
|
195
|
-
# Amount of mutations
|
|
196
|
-
#
|
|
197
|
-
# @return [Integer]
|
|
198
|
-
def amount_timeouts = coverage_results.count(&:timeout?)
|
|
199
|
-
|
|
200
|
-
# Amount of mutations
|
|
201
|
-
#
|
|
202
|
-
# @return [Integer]
|
|
203
|
-
def amount_mutations = subject.mutations.length
|
|
204
|
-
|
|
205
|
-
# Number of killed mutations
|
|
206
|
-
#
|
|
207
|
-
# @return [Integer]
|
|
208
|
-
def amount_mutations_killed = covered_results.length
|
|
209
|
-
|
|
210
|
-
# Number of alive mutations
|
|
211
|
-
#
|
|
212
|
-
# @return [Integer]
|
|
213
|
-
def amount_mutations_alive = uncovered_results.length
|
|
214
|
-
|
|
215
|
-
private
|
|
216
|
-
|
|
217
|
-
def covered_results = coverage_results.select(&:success?)
|
|
218
|
-
memoize :covered_results
|
|
219
|
-
|
|
220
|
-
end # Subject
|
|
221
|
-
|
|
222
|
-
# Coverage of a mutation against criteria
|
|
223
|
-
class Coverage
|
|
224
|
-
include Result, Anima.new(
|
|
225
|
-
:mutation_result,
|
|
226
|
-
:criteria_result
|
|
227
|
-
)
|
|
228
|
-
|
|
229
|
-
delegate :killtime, :mutation_result
|
|
230
|
-
delegate :runtime, :mutation_result
|
|
231
|
-
delegate :success?, :criteria_result
|
|
232
|
-
delegate :timeout?, :mutation_result
|
|
233
|
-
end # Coverage
|
|
234
|
-
|
|
235
143
|
class CoverageCriteria
|
|
236
144
|
include Result, Anima.new(*Config::CoverageCriteria.anima.attribute_names)
|
|
237
145
|
|
|
@@ -241,6 +149,8 @@ module Mutant
|
|
|
241
149
|
def success?
|
|
242
150
|
process_abort || test_result || timeout
|
|
243
151
|
end
|
|
152
|
+
|
|
153
|
+
JSON = Transform::JSON.for_anima(self)
|
|
244
154
|
end
|
|
245
155
|
|
|
246
156
|
class MutationIndex
|
|
@@ -255,13 +165,25 @@ module Mutant
|
|
|
255
165
|
class Mutation
|
|
256
166
|
include Result, Anima.new(
|
|
257
167
|
:isolation_result,
|
|
258
|
-
:
|
|
168
|
+
:mutation_diff,
|
|
169
|
+
:mutation_identification,
|
|
170
|
+
:mutation_node,
|
|
171
|
+
:mutation_source,
|
|
172
|
+
:mutation_type,
|
|
259
173
|
:runtime
|
|
260
174
|
)
|
|
261
175
|
|
|
176
|
+
TEST_PASS_SUCCESS = {
|
|
177
|
+
'evil' => false,
|
|
178
|
+
'neutral' => true,
|
|
179
|
+
'noop' => true
|
|
180
|
+
}.freeze
|
|
181
|
+
|
|
182
|
+
private_constant(:TEST_PASS_SUCCESS)
|
|
183
|
+
|
|
262
184
|
# Create mutation criteria results
|
|
263
185
|
#
|
|
264
|
-
# @
|
|
186
|
+
# @param [Result::CoverageCriteria]
|
|
265
187
|
def criteria_result(coverage_criteria)
|
|
266
188
|
CoverageCriteria.new(
|
|
267
189
|
process_abort: coverage_criteria.process_abort && process_abort?,
|
|
@@ -297,10 +219,193 @@ module Mutant
|
|
|
297
219
|
#
|
|
298
220
|
# @return [Boolean]
|
|
299
221
|
def test_result_success?
|
|
300
|
-
isolation_result.valid_value? &&
|
|
222
|
+
isolation_result.valid_value? && TEST_PASS_SUCCESS.fetch(mutation_type).equal?(isolation_result.value.passed)
|
|
301
223
|
end
|
|
302
224
|
memoize :test_result_success?
|
|
303
225
|
|
|
226
|
+
dump = Transform::Success.new(
|
|
227
|
+
block: lambda do |object|
|
|
228
|
+
{
|
|
229
|
+
'isolation_result' => Isolation::Result::JSON.dump(object.isolation_result).from_right,
|
|
230
|
+
'mutation_diff' => object.mutation_diff,
|
|
231
|
+
'mutation_identification' => object.mutation_identification,
|
|
232
|
+
'mutation_source' => object.mutation_source,
|
|
233
|
+
'mutation_type' => object.mutation_type,
|
|
234
|
+
'runtime' => object.runtime
|
|
235
|
+
}
|
|
236
|
+
end
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
derive_mutation_node = Transform::Success.new(
|
|
240
|
+
block: ->(hash) { hash.merge('mutation_node' => Unparser.parse(hash.fetch('mutation_source'))) }
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
load = Transform::Sequence.new(
|
|
244
|
+
steps: [
|
|
245
|
+
Transform::Hash.new(
|
|
246
|
+
required: [
|
|
247
|
+
Transform::Hash::Key.new(value: 'isolation_result', transform: Isolation::Result::JSON.load_transform),
|
|
248
|
+
Transform::Hash::Key.new(value: 'mutation_diff', transform: Transform::OPTIONAL_STRING),
|
|
249
|
+
Transform::Hash::Key.new(value: 'mutation_identification', transform: Transform::STRING),
|
|
250
|
+
Transform::Hash::Key.new(value: 'mutation_source', transform: Transform::STRING),
|
|
251
|
+
Transform::Hash::Key.new(value: 'mutation_type', transform: Transform::STRING),
|
|
252
|
+
Transform::Hash::Key.new(value: 'runtime', transform: Transform::FLOAT)
|
|
253
|
+
],
|
|
254
|
+
optional: []
|
|
255
|
+
),
|
|
256
|
+
derive_mutation_node,
|
|
257
|
+
Transform::Hash::Symbolize.new,
|
|
258
|
+
Transform::Success.new(block: method(:new).to_proc)
|
|
259
|
+
]
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
JSON = Transform::JSON.new(dump_transform: dump, load_transform: load)
|
|
304
263
|
end # Mutation
|
|
264
|
+
|
|
265
|
+
# Coverage of a mutation against criteria
|
|
266
|
+
class Coverage
|
|
267
|
+
include Result, Anima.new(
|
|
268
|
+
:mutation_result,
|
|
269
|
+
:criteria_result
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
delegate :killtime, :mutation_result
|
|
273
|
+
delegate :runtime, :mutation_result
|
|
274
|
+
delegate :success?, :criteria_result
|
|
275
|
+
delegate :timeout?, :mutation_result
|
|
276
|
+
|
|
277
|
+
dump = Transform::Success.new(
|
|
278
|
+
block: lambda do |object|
|
|
279
|
+
{
|
|
280
|
+
'mutation_result' => Mutation::JSON.dump(object.mutation_result).from_right,
|
|
281
|
+
'criteria_result' => CoverageCriteria::JSON.dump(object.criteria_result).from_right
|
|
282
|
+
}
|
|
283
|
+
end
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
load = Transform::Sequence.new(
|
|
287
|
+
steps: [
|
|
288
|
+
Transform::Hash.new(
|
|
289
|
+
required: [
|
|
290
|
+
Transform::Hash::Key.new(value: 'mutation_result', transform: Mutation::JSON.load_transform),
|
|
291
|
+
Transform::Hash::Key.new(value: 'criteria_result', transform: CoverageCriteria::JSON.load_transform)
|
|
292
|
+
],
|
|
293
|
+
optional: []
|
|
294
|
+
),
|
|
295
|
+
Transform::Hash::Symbolize.new,
|
|
296
|
+
Transform::Success.new(block: method(:new).to_proc)
|
|
297
|
+
]
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
JSON = Transform::JSON.new(dump_transform: dump, load_transform: load)
|
|
301
|
+
end # Coverage
|
|
302
|
+
|
|
303
|
+
# Subject result
|
|
304
|
+
class Subject
|
|
305
|
+
include CoverageMetric, Result, Anima.new(
|
|
306
|
+
:amount_mutations,
|
|
307
|
+
:coverage_results,
|
|
308
|
+
:expression_syntax,
|
|
309
|
+
:identification,
|
|
310
|
+
:node,
|
|
311
|
+
:source,
|
|
312
|
+
:source_path,
|
|
313
|
+
:tests
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
sum :killtime, :coverage_results
|
|
317
|
+
sum :runtime, :coverage_results
|
|
318
|
+
|
|
319
|
+
# Test if subject was processed successful
|
|
320
|
+
#
|
|
321
|
+
# @return [Boolean]
|
|
322
|
+
def success?
|
|
323
|
+
uncovered_results.empty?
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
# Alive mutations
|
|
327
|
+
#
|
|
328
|
+
# @return [Array<Result::Coverage>]
|
|
329
|
+
def uncovered_results = coverage_results.reject(&:success?)
|
|
330
|
+
memoize :uncovered_results
|
|
331
|
+
|
|
332
|
+
# Amount of mutations
|
|
333
|
+
#
|
|
334
|
+
# @return [Integer]
|
|
335
|
+
def amount_mutation_results = coverage_results.length
|
|
336
|
+
|
|
337
|
+
# Amount of mutations
|
|
338
|
+
#
|
|
339
|
+
# @return [Integer]
|
|
340
|
+
def amount_timeouts = coverage_results.count(&:timeout?)
|
|
341
|
+
|
|
342
|
+
# Number of killed mutations
|
|
343
|
+
#
|
|
344
|
+
# @return [Integer]
|
|
345
|
+
def amount_mutations_killed = covered_results.length
|
|
346
|
+
|
|
347
|
+
# Number of alive mutations
|
|
348
|
+
#
|
|
349
|
+
# @return [Integer]
|
|
350
|
+
def amount_mutations_alive = uncovered_results.length
|
|
351
|
+
|
|
352
|
+
private
|
|
353
|
+
|
|
354
|
+
def covered_results = coverage_results.select(&:success?)
|
|
355
|
+
memoize :covered_results
|
|
356
|
+
|
|
357
|
+
dump = Transform::Success.new(
|
|
358
|
+
block: lambda do |object|
|
|
359
|
+
{
|
|
360
|
+
'amount_mutations' => object.amount_mutations,
|
|
361
|
+
'coverage_results' => object.coverage_results
|
|
362
|
+
.map { |coverage_result| Coverage::JSON.dump(coverage_result).from_right },
|
|
363
|
+
'expression_syntax' => object.expression_syntax,
|
|
364
|
+
'identification' => object.identification,
|
|
365
|
+
'source' => object.source,
|
|
366
|
+
'source_path' => object.source_path,
|
|
367
|
+
'tests' => object.tests.map(&:identification)
|
|
368
|
+
}
|
|
369
|
+
end
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
Transform::Block.capture(:parse_node) do |source|
|
|
373
|
+
Either.wrap_error(::Parser::SyntaxError) { Unparser.parse(source) }
|
|
374
|
+
.lmap(&:message)
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
load_test = Transform::Success.new(
|
|
378
|
+
block: ->(id) { Mutant::Test.new(expressions: EMPTY_ARRAY, id:) }
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
derive_node = Transform::Success.new(
|
|
382
|
+
block: ->(hash) { hash.merge('node' => Unparser.parse(hash.fetch('source'))) }
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
load = Transform::Sequence.new(
|
|
386
|
+
steps: [
|
|
387
|
+
Transform::Hash.new(
|
|
388
|
+
required: [
|
|
389
|
+
Transform::Hash::Key.new(value: 'amount_mutations', transform: Transform::INTEGER),
|
|
390
|
+
Transform::Hash::Key.new(value: 'coverage_results', transform: Transform::Array.new(transform: Coverage::JSON.load_transform)),
|
|
391
|
+
Transform::Hash::Key.new(value: 'expression_syntax', transform: Transform::STRING),
|
|
392
|
+
Transform::Hash::Key.new(value: 'identification', transform: Transform::STRING),
|
|
393
|
+
Transform::Hash::Key.new(value: 'source', transform: Transform::STRING),
|
|
394
|
+
Transform::Hash::Key.new(value: 'source_path', transform: Transform::STRING),
|
|
395
|
+
Transform::Hash::Key.new(
|
|
396
|
+
value: 'tests',
|
|
397
|
+
transform: Transform::Array.new(transform: load_test)
|
|
398
|
+
)
|
|
399
|
+
],
|
|
400
|
+
optional: []
|
|
401
|
+
),
|
|
402
|
+
derive_node,
|
|
403
|
+
Transform::Hash::Symbolize.new,
|
|
404
|
+
Transform::Success.new(block: method(:new).to_proc)
|
|
405
|
+
]
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
JSON = Transform::JSON.new(dump_transform: dump, load_transform: load)
|
|
409
|
+
end # Subject
|
|
305
410
|
end # Result
|
|
306
411
|
end # Mutant
|
data/lib/mutant/timer.rb
CHANGED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mutant
|
|
4
|
+
class Transform
|
|
5
|
+
# Bidirectional JSON transform
|
|
6
|
+
#
|
|
7
|
+
# Wraps a pair of dump/load transforms and optionally adds
|
|
8
|
+
# JSON string serialization via .build
|
|
9
|
+
class JSON
|
|
10
|
+
include Anima.new(:dump_transform, :load_transform)
|
|
11
|
+
|
|
12
|
+
# Build a JSON transform that wraps raw transforms with JSON.parse/generate
|
|
13
|
+
#
|
|
14
|
+
# @param [Transform] dump
|
|
15
|
+
# @param [Transform] load
|
|
16
|
+
#
|
|
17
|
+
# @return [JSON]
|
|
18
|
+
# rubocop:disable Metrics/MethodLength
|
|
19
|
+
def self.build(dump:, load:)
|
|
20
|
+
new(
|
|
21
|
+
dump_transform: Sequence.new(
|
|
22
|
+
steps: [
|
|
23
|
+
dump,
|
|
24
|
+
Success.new(block: ::JSON.public_method(:generate))
|
|
25
|
+
]
|
|
26
|
+
),
|
|
27
|
+
load_transform: Sequence.new(
|
|
28
|
+
steps: [
|
|
29
|
+
Exception.new(error_class: ::JSON::ParserError, block: ::JSON.public_method(:parse)),
|
|
30
|
+
load
|
|
31
|
+
]
|
|
32
|
+
)
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
# rubocop:enable Metrics/MethodLength
|
|
36
|
+
|
|
37
|
+
# Build a JSON transform for simple Anima objects with JSON-primitive fields
|
|
38
|
+
#
|
|
39
|
+
# @param [Class] klass
|
|
40
|
+
#
|
|
41
|
+
# @return [JSON]
|
|
42
|
+
def self.for_anima(klass)
|
|
43
|
+
new(
|
|
44
|
+
dump_transform: Success.new(block: ->(object) { object.to_h.transform_keys(&:to_s) }),
|
|
45
|
+
load_transform: Success.new(block: ->(hash) { klass.new(**hash.transform_keys(&:to_sym)) })
|
|
46
|
+
)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Dump object to hash or JSON string
|
|
50
|
+
#
|
|
51
|
+
# @param [Object] object
|
|
52
|
+
#
|
|
53
|
+
# @return [Either<Error, Object>]
|
|
54
|
+
def dump(object)
|
|
55
|
+
dump_transform.call(object)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Load object from hash or JSON string
|
|
59
|
+
#
|
|
60
|
+
# @param [Object] input
|
|
61
|
+
#
|
|
62
|
+
# @return [Either<Error, Object>]
|
|
63
|
+
def load(input)
|
|
64
|
+
load_transform.call(input)
|
|
65
|
+
end
|
|
66
|
+
end # JSON
|
|
67
|
+
end # Transform
|
|
68
|
+
end # Mutant
|
data/lib/mutant/transform.rb
CHANGED
|
@@ -117,8 +117,6 @@ module Mutant
|
|
|
117
117
|
class Index < self
|
|
118
118
|
include Anima.new(:index, :transform)
|
|
119
119
|
|
|
120
|
-
private(*anima.attribute_names)
|
|
121
|
-
|
|
122
120
|
# Create error at specified index
|
|
123
121
|
#
|
|
124
122
|
# @param [Error] cause
|
|
@@ -185,7 +183,7 @@ module Mutant
|
|
|
185
183
|
|
|
186
184
|
# Transform guarding boolean primitives
|
|
187
185
|
class Boolean < self
|
|
188
|
-
include
|
|
186
|
+
include Equalizer.new
|
|
189
187
|
|
|
190
188
|
MESSAGE = 'Expected: boolean but got: %<actual>s'
|
|
191
189
|
|
|
@@ -258,8 +256,8 @@ module Mutant
|
|
|
258
256
|
class Hash < self
|
|
259
257
|
include Anima.new(:optional, :required)
|
|
260
258
|
|
|
261
|
-
KEY_MESSAGE
|
|
262
|
-
PRIMITIVE
|
|
259
|
+
KEY_MESSAGE = 'Missing keys: %<missing>s, Unexpected keys: %<unexpected>s'
|
|
260
|
+
PRIMITIVE = Primitive.new(primitive: ::Hash)
|
|
263
261
|
|
|
264
262
|
private_constant(*constants(false))
|
|
265
263
|
|
|
@@ -313,24 +311,16 @@ module Mutant
|
|
|
313
311
|
private
|
|
314
312
|
|
|
315
313
|
def transform(input)
|
|
316
|
-
|
|
317
|
-
|
|
314
|
+
coerce_keys(required, input).bind do |required|
|
|
315
|
+
coerce_keys(
|
|
316
|
+
optional.select { |key| input.key?(key.value) },
|
|
317
|
+
input
|
|
318
|
+
).fmap(&required.public_method(:merge))
|
|
318
319
|
end
|
|
319
320
|
end
|
|
320
321
|
|
|
321
|
-
def transform_required(input)
|
|
322
|
-
transform_keys(required, input)
|
|
323
|
-
end
|
|
324
|
-
|
|
325
|
-
def transform_optional(input)
|
|
326
|
-
transform_keys(
|
|
327
|
-
optional.select { |key| input.key?(key.value) },
|
|
328
|
-
input
|
|
329
|
-
)
|
|
330
|
-
end
|
|
331
|
-
|
|
332
322
|
# rubocop:disable Metrics/MethodLength
|
|
333
|
-
def
|
|
323
|
+
def coerce_keys(keys, input)
|
|
334
324
|
success(
|
|
335
325
|
keys
|
|
336
326
|
.to_h do |key|
|
|
@@ -375,6 +365,7 @@ module Mutant
|
|
|
375
365
|
|
|
376
366
|
def required_keys = required.map(&:value)
|
|
377
367
|
memoize :required_keys
|
|
368
|
+
|
|
378
369
|
end # Hash
|
|
379
370
|
|
|
380
371
|
# Sequence of transformations
|
|
@@ -411,7 +402,23 @@ module Mutant
|
|
|
411
402
|
def call(input)
|
|
412
403
|
success(block.call(input))
|
|
413
404
|
end
|
|
414
|
-
end #
|
|
405
|
+
end # Success
|
|
406
|
+
|
|
407
|
+
# Nullable wrapper: passes nil through, delegates non-nil to inner transform
|
|
408
|
+
class Nullable < self
|
|
409
|
+
include Anima.new(:transform)
|
|
410
|
+
|
|
411
|
+
# Apply transformation to input
|
|
412
|
+
#
|
|
413
|
+
# @param [Object]
|
|
414
|
+
#
|
|
415
|
+
# @return [Either<Error, Object>]
|
|
416
|
+
def call(input)
|
|
417
|
+
input.nil? ? success(nil) : transform.call(input)
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
def slug = "nullable(#{transform.slug})"
|
|
421
|
+
end # Nullable
|
|
415
422
|
|
|
416
423
|
# Generic exception transformer
|
|
417
424
|
class Exception < self
|
|
@@ -429,10 +436,11 @@ module Mutant
|
|
|
429
436
|
end
|
|
430
437
|
end # Exception
|
|
431
438
|
|
|
432
|
-
BOOLEAN
|
|
433
|
-
FLOAT
|
|
434
|
-
INTEGER
|
|
435
|
-
STRING
|
|
436
|
-
|
|
439
|
+
BOOLEAN = Transform::Boolean.new
|
|
440
|
+
FLOAT = Transform::Primitive.new(primitive: Float)
|
|
441
|
+
INTEGER = Transform::Primitive.new(primitive: Integer)
|
|
442
|
+
STRING = Transform::Primitive.new(primitive: String)
|
|
443
|
+
OPTIONAL_STRING = Nullable.new(transform: STRING)
|
|
444
|
+
STRING_ARRAY = Transform::Array.new(transform: STRING)
|
|
437
445
|
end # Transform
|
|
438
446
|
end # Mutant
|
data/lib/mutant/world.rb
CHANGED
|
@@ -89,6 +89,12 @@ module Mutant
|
|
|
89
89
|
recorder.record(name, &)
|
|
90
90
|
end
|
|
91
91
|
|
|
92
|
+
def parse_json(string)
|
|
93
|
+
record(:json_parse) do
|
|
94
|
+
Either.wrap_error(json::ParserError) { json.parse(string) }
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
92
98
|
def process_warmup
|
|
93
99
|
process.warmup if process.respond_to?(:warmup)
|
|
94
100
|
end
|
data/lib/mutant/zombifier.rb
CHANGED
data/lib/mutant.rb
CHANGED
|
@@ -52,7 +52,6 @@ module Mutant
|
|
|
52
52
|
AbstractType = Unparser::AbstractType
|
|
53
53
|
Adamantium = Unparser::Adamantium
|
|
54
54
|
Anima = Unparser::Anima
|
|
55
|
-
Concord = Unparser::Concord
|
|
56
55
|
Either = Unparser::Either
|
|
57
56
|
Equalizer = Unparser::Equalizer
|
|
58
57
|
|
|
@@ -70,6 +69,7 @@ module Mutant
|
|
|
70
69
|
record.call(:require_mutant_lib) do
|
|
71
70
|
require 'mutant/procto'
|
|
72
71
|
require 'mutant/transform'
|
|
72
|
+
require 'mutant/transform/json'
|
|
73
73
|
require 'mutant/variable'
|
|
74
74
|
require 'mutant/bootstrap'
|
|
75
75
|
require 'mutant/version'
|
|
@@ -96,6 +96,9 @@ module Mutant
|
|
|
96
96
|
require 'mutant/ast/pattern/token'
|
|
97
97
|
require 'mutant/ast/structure'
|
|
98
98
|
require 'mutant/parser'
|
|
99
|
+
require 'mutant/result/test'
|
|
100
|
+
require 'mutant/result/process_status'
|
|
101
|
+
require 'mutant/result/exception'
|
|
99
102
|
require 'mutant/isolation'
|
|
100
103
|
require 'mutant/isolation/exception'
|
|
101
104
|
require 'mutant/isolation/fork'
|
|
@@ -237,10 +240,11 @@ module Mutant
|
|
|
237
240
|
require 'mutant/cli/command/environment/subject'
|
|
238
241
|
require 'mutant/cli/command/environment/test'
|
|
239
242
|
require 'mutant/cli/command/util'
|
|
240
|
-
require 'mutant/cli/command/root'
|
|
241
243
|
require 'mutant/mutation/runner'
|
|
242
244
|
require 'mutant/mutation/runner/sink'
|
|
243
245
|
require 'mutant/result'
|
|
246
|
+
require 'mutant/result/session'
|
|
247
|
+
require 'mutant/result/json_writer'
|
|
244
248
|
require 'mutant/reporter'
|
|
245
249
|
require 'mutant/reporter/null'
|
|
246
250
|
require 'mutant/reporter/sequence'
|
|
@@ -248,17 +252,18 @@ module Mutant
|
|
|
248
252
|
require 'mutant/reporter/cli/progress_bar'
|
|
249
253
|
require 'mutant/reporter/cli/printer'
|
|
250
254
|
require 'mutant/reporter/cli/printer/config'
|
|
251
|
-
require 'mutant/reporter/cli/printer/coverage_result'
|
|
252
255
|
require 'mutant/reporter/cli/printer/env'
|
|
253
256
|
require 'mutant/reporter/cli/printer/env_progress'
|
|
257
|
+
require 'mutant/reporter/cli/printer/alive_results'
|
|
254
258
|
require 'mutant/reporter/cli/printer/env_result'
|
|
255
259
|
require 'mutant/reporter/cli/printer/isolation_result'
|
|
256
260
|
require 'mutant/reporter/cli/printer/mutation'
|
|
257
|
-
require 'mutant/reporter/cli/printer/mutation_result'
|
|
258
261
|
require 'mutant/reporter/cli/printer/status_progressive'
|
|
259
262
|
require 'mutant/reporter/cli/printer/subject_result'
|
|
260
263
|
require 'mutant/reporter/cli/printer/test'
|
|
261
264
|
require 'mutant/reporter/cli/format'
|
|
265
|
+
require 'mutant/cli/command/session'
|
|
266
|
+
require 'mutant/cli/command/root'
|
|
262
267
|
require 'mutant/repository'
|
|
263
268
|
require 'mutant/repository/diff'
|
|
264
269
|
require 'mutant/repository/diff/ranges'
|
|
@@ -343,6 +348,10 @@ module Mutant
|
|
|
343
348
|
timer:
|
|
344
349
|
)
|
|
345
350
|
|
|
351
|
+
# UUIDv7 identifying this mutant process invocation.
|
|
352
|
+
# Used as the JSON result filename and session identifier.
|
|
353
|
+
SESSION_ID = SecureRandom.uuid_v7
|
|
354
|
+
|
|
346
355
|
# Reopen class to initialize constant to avoid dep circle
|
|
347
356
|
class Config
|
|
348
357
|
DEFAULT = new(
|