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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/lib/mutant/cli/command/root.rb +1 -1
  4. data/lib/mutant/cli/command/session.rb +281 -0
  5. data/lib/mutant/cli/command.rb +16 -2
  6. data/lib/mutant/config.rb +1 -1
  7. data/lib/mutant/expression/method.rb +0 -2
  8. data/lib/mutant/expression/methods.rb +0 -2
  9. data/lib/mutant/expression/namespace.rb +0 -2
  10. data/lib/mutant/isolation/fork.rb +2 -6
  11. data/lib/mutant/isolation.rb +31 -0
  12. data/lib/mutant/matcher/null.rb +1 -1
  13. data/lib/mutant/mutation/runner/sink.rb +23 -10
  14. data/lib/mutant/mutation/runner.rb +1 -0
  15. data/lib/mutant/mutation.rb +3 -20
  16. data/lib/mutant/mutator/node/literal/integer.rb +61 -0
  17. data/lib/mutant/parallel/connection.rb +0 -2
  18. data/lib/mutant/parallel/driver.rb +0 -2
  19. data/lib/mutant/reporter/cli/printer/alive_results.rb +27 -0
  20. data/lib/mutant/reporter/cli/printer/env_result.rb +52 -9
  21. data/lib/mutant/reporter/cli/printer/subject_result.rb +103 -5
  22. data/lib/mutant/reporter/cli/printer.rb +24 -1
  23. data/lib/mutant/repository/diff.rb +1 -2
  24. data/lib/mutant/result/exception.rb +29 -0
  25. data/lib/mutant/result/json_writer.rb +43 -0
  26. data/lib/mutant/result/process_status.rb +37 -0
  27. data/lib/mutant/result/session.rb +63 -0
  28. data/lib/mutant/result/test.rb +30 -0
  29. data/lib/mutant/result.rb +201 -96
  30. data/lib/mutant/segment/recorder.rb +0 -2
  31. data/lib/mutant/timer.rb +3 -1
  32. data/lib/mutant/transform/json.rb +68 -0
  33. data/lib/mutant/transform.rb +33 -25
  34. data/lib/mutant/world.rb +6 -0
  35. data/lib/mutant/zombifier.rb +0 -2
  36. data/lib/mutant.rb +13 -4
  37. metadata +33 -7
  38. data/lib/mutant/reporter/cli/printer/coverage_result.rb +0 -19
  39. 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(*constants(false))
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
- :mutation,
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
- # @praam [Result::CoverageCriteria]
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? && mutation.class.success?(isolation_result.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
@@ -12,8 +12,6 @@ module Mutant
12
12
  :timer
13
13
  )
14
14
 
15
- private(*anima.attribute_names)
16
-
17
15
  # rubocop:disable Metrics/MethodLength
18
16
  def record(name)
19
17
  start = timer.now
data/lib/mutant/timer.rb CHANGED
@@ -57,7 +57,9 @@ module Mutant
57
57
 
58
58
  # Deadline that never expires
59
59
  class None < self
60
- include Concord.new
60
+ include Equalizer.new
61
+
62
+ def initialize; end # rubocop:disable Lint/MissingSuper
61
63
 
62
64
  # The time left
63
65
  #
@@ -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
@@ -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 Concord.new
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 = 'Missing keys: %<missing>s, Unexpected keys: %<unexpected>s'
262
- PRIMITIVE = Primitive.new(primitive: ::Hash)
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
- transform_required(input).bind do |required|
317
- transform_optional(input).fmap(&required.public_method(:merge))
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 transform_keys(keys, input)
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 # Sequence
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 = Transform::Boolean.new
433
- FLOAT = Transform::Primitive.new(primitive: Float)
434
- INTEGER = Transform::Primitive.new(primitive: Integer)
435
- STRING = Transform::Primitive.new(primitive: String)
436
- STRING_ARRAY = Transform::Array.new(transform: STRING)
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
@@ -13,8 +13,6 @@ module Mutant
13
13
  :root_require
14
14
  )
15
15
 
16
- private(*anima.attribute_names)
17
-
18
16
  include AST::Sexp
19
17
 
20
18
  class LoadError < ::LoadError
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(