mutant 0.15.1 → 0.16.2

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/lib/mutant/ast/pattern/lexer.rb +119 -47
  4. data/lib/mutant/cli/command/root.rb +1 -1
  5. data/lib/mutant/cli/command/session.rb +281 -0
  6. data/lib/mutant/cli/command.rb +16 -2
  7. data/lib/mutant/config.rb +1 -1
  8. data/lib/mutant/expression/method.rb +0 -2
  9. data/lib/mutant/expression/methods.rb +0 -2
  10. data/lib/mutant/expression/namespace.rb +0 -2
  11. data/lib/mutant/integration/null.rb +1 -1
  12. data/lib/mutant/isolation/fork.rb +3 -7
  13. data/lib/mutant/isolation/none.rb +1 -1
  14. data/lib/mutant/isolation.rb +31 -0
  15. data/lib/mutant/log_capture.rb +89 -0
  16. data/lib/mutant/matcher/null.rb +1 -1
  17. data/lib/mutant/mutation/runner/sink.rb +23 -10
  18. data/lib/mutant/mutation/runner.rb +1 -0
  19. data/lib/mutant/mutation.rb +3 -20
  20. data/lib/mutant/mutator/node/literal/integer.rb +61 -0
  21. data/lib/mutant/parallel/connection.rb +2 -4
  22. data/lib/mutant/parallel/driver.rb +0 -2
  23. data/lib/mutant/reporter/cli/printer/alive_results.rb +27 -0
  24. data/lib/mutant/reporter/cli/printer/env_result.rb +52 -9
  25. data/lib/mutant/reporter/cli/printer/isolation_result.rb +3 -6
  26. data/lib/mutant/reporter/cli/printer/subject_result.rb +103 -5
  27. data/lib/mutant/reporter/cli/printer/test.rb +1 -1
  28. data/lib/mutant/reporter/cli/printer.rb +24 -1
  29. data/lib/mutant/repository/diff.rb +1 -2
  30. data/lib/mutant/result/exception.rb +29 -0
  31. data/lib/mutant/result/json_writer.rb +43 -0
  32. data/lib/mutant/result/process_status.rb +37 -0
  33. data/lib/mutant/result/session.rb +63 -0
  34. data/lib/mutant/result/test.rb +57 -0
  35. data/lib/mutant/result.rb +201 -96
  36. data/lib/mutant/segment/recorder.rb +0 -2
  37. data/lib/mutant/test/runner/sink.rb +1 -1
  38. data/lib/mutant/timer.rb +3 -1
  39. data/lib/mutant/transform/codec.rb +45 -0
  40. data/lib/mutant/transform.rb +33 -25
  41. data/lib/mutant/world.rb +6 -0
  42. data/lib/mutant/zombifier.rb +0 -2
  43. data/lib/mutant.rb +14 -4
  44. metadata +34 -7
  45. data/lib/mutant/reporter/cli/printer/coverage_result.rb +0 -19
  46. data/lib/mutant/reporter/cli/printer/mutation_result.rb +0 -84
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ module Result
5
+ # Serializable exception data
6
+ class Exception
7
+ include Anima.new(
8
+ :backtrace,
9
+ :message,
10
+ :original_class
11
+ )
12
+
13
+ # Build from a Ruby exception
14
+ #
15
+ # @param [::Exception] exception
16
+ #
17
+ # @return [Exception]
18
+ def self.from_exception(exception)
19
+ new(
20
+ backtrace: exception.backtrace,
21
+ message: exception.message,
22
+ original_class: exception.class.name
23
+ )
24
+ end
25
+
26
+ CODEC = Transform::Codec.for_anima(self)
27
+ end # Exception
28
+ end # Result
29
+ end # Mutant
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ module Result
5
+ # Write result JSON to .mutant/results/
6
+ class JSONWriter
7
+ include Anima.new(:env, :result)
8
+
9
+ RESULTS_DIR = '.mutant/results'
10
+
11
+ # Write result JSON file
12
+ #
13
+ # @return [Pathname]
14
+ def call
15
+ dir = env.world.pathname.new(RESULTS_DIR)
16
+ dir.mkpath
17
+
18
+ path = dir.join("#{SESSION_ID}.json")
19
+ path.write(json)
20
+
21
+ path
22
+ end
23
+
24
+ private
25
+
26
+ def json
27
+ JSON.generate(Session::CODEC.dump(session).from_right)
28
+ end
29
+
30
+ def session
31
+ Session.new(
32
+ killtime: result.killtime,
33
+ mutant_version: VERSION,
34
+ pid: env.world.process.pid,
35
+ ruby_version: RUBY_VERSION,
36
+ runtime: result.runtime,
37
+ session_id: SESSION_ID,
38
+ subject_results: result.subject_results
39
+ )
40
+ end
41
+ end # JSONWriter
42
+ end # Result
43
+ end # Mutant
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ module Result
5
+ # Serializable process status
6
+ #
7
+ # Replaces Process::Status in the result tree with a
8
+ # round-trippable value object.
9
+ class ProcessStatus
10
+ include Anima.new(:exitstatus)
11
+
12
+ # Build from a Process::Status object
13
+ #
14
+ # @param [Process::Status] process_status
15
+ #
16
+ # @return [ProcessStatus]
17
+ def self.from_process_status(process_status)
18
+ new(exitstatus: process_status.exitstatus)
19
+ end
20
+
21
+ # Stable inspect without memory address for use in user-facing output
22
+ #
23
+ # @return [String]
24
+ def inspect
25
+ "#<#{self.class.name} exitstatus=#{exitstatus}>"
26
+ end
27
+
28
+ # Test for successful exit
29
+ #
30
+ # @return [Boolean]
31
+ def success?
32
+ exitstatus.equal?(0)
33
+ end
34
+ CODEC = Transform::Codec.for_anima(self)
35
+ end # ProcessStatus
36
+ end # Result
37
+ end # Mutant
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ module Result
5
+ # Top-level result object containing session metadata and subject results
6
+ class Session
7
+ # Extract timestamp from UUIDv7 session_id
8
+ module Timestamp
9
+ # @return [Time]
10
+ def timestamp
11
+ ms = session_id.delete('-')[0, 12].to_i(16)
12
+ Time.at(ms / 1000.0).utc
13
+ end
14
+ end
15
+
16
+ include Timestamp, Anima.new(
17
+ :killtime,
18
+ :mutant_version,
19
+ :pid,
20
+ :ruby_version,
21
+ :runtime,
22
+ :session_id,
23
+ :subject_results
24
+ )
25
+
26
+ dump = Transform::Success.new(
27
+ block: lambda do |object|
28
+ {
29
+ 'killtime' => object.killtime,
30
+ 'mutant_version' => object.mutant_version,
31
+ 'pid' => object.pid,
32
+ 'ruby_version' => object.ruby_version,
33
+ 'runtime' => object.runtime,
34
+ 'session_id' => object.session_id,
35
+ 'subject_results' => object.subject_results.map { |subject_result| Subject::CODEC.dump(subject_result).from_right }
36
+ }
37
+ end
38
+ )
39
+
40
+ load = Transform::Sequence.new(
41
+ steps: [
42
+ Transform::Hash.new(
43
+ required: [
44
+ Transform::Hash::Key.new(value: 'killtime', transform: Transform::FLOAT),
45
+ Transform::Hash::Key.new(value: 'mutant_version', transform: Transform::STRING),
46
+ Transform::Hash::Key.new(value: 'pid', transform: Transform::INTEGER),
47
+ Transform::Hash::Key.new(value: 'ruby_version', transform: Transform::STRING),
48
+ Transform::Hash::Key.new(value: 'runtime', transform: Transform::FLOAT),
49
+ Transform::Hash::Key.new(value: 'session_id', transform: Transform::STRING),
50
+ Transform::Hash::Key.new(value: 'subject_results', transform: Transform::Array.new(transform: Subject::CODEC.load_transform))
51
+ ],
52
+ optional: []
53
+ ),
54
+ Transform::Hash::Symbolize.new,
55
+ Transform::Success.new(block: method(:new).to_proc)
56
+ ]
57
+ )
58
+
59
+ CODEC = Transform::Codec.new(dump_transform: dump, load_transform: load)
60
+
61
+ end # Session
62
+ end # Result
63
+ end # Mutant
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ module Result
5
+ # Test result
6
+ class Test
7
+ include Anima.new(:job_index, :passed, :runtime, :output)
8
+
9
+ alias_method :success?, :passed
10
+
11
+ class VoidValue < self
12
+ include Singleton
13
+
14
+ # Initialize object
15
+ #
16
+ # @return [undefined]
17
+ def initialize
18
+ super(
19
+ job_index: nil,
20
+ output: LogCapture.from_binary(+''),
21
+ passed: false,
22
+ runtime: 0.0
23
+ )
24
+ end
25
+ end # VoidValue
26
+
27
+ dump = Transform::Success.new(
28
+ block: lambda do |object|
29
+ {
30
+ 'job_index' => object.job_index,
31
+ 'output' => LogCapture::CODEC.dump(object.output).from_right,
32
+ 'passed' => object.passed,
33
+ 'runtime' => object.runtime
34
+ }
35
+ end
36
+ )
37
+
38
+ load = Transform::Sequence.new(
39
+ steps: [
40
+ Transform::Hash.new(
41
+ required: [
42
+ Transform::Hash::Key.new(value: 'job_index', transform: Transform::Nullable.new(transform: Transform::INTEGER)),
43
+ Transform::Hash::Key.new(value: 'output', transform: LogCapture::CODEC.load_transform),
44
+ Transform::Hash::Key.new(value: 'passed', transform: Transform::BOOLEAN),
45
+ Transform::Hash::Key.new(value: 'runtime', transform: Transform::FLOAT)
46
+ ],
47
+ optional: []
48
+ ),
49
+ Transform::Hash::Symbolize.new,
50
+ Transform::Success.new(block: method(:new).to_proc)
51
+ ]
52
+ )
53
+
54
+ CODEC = Transform::Codec.new(dump_transform: dump, load_transform: load)
55
+ end # Test
56
+ end # Result
57
+ end # Mutant
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
+ CODEC = Transform::Codec.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::CODEC.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::CODEC.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
+ CODEC = Transform::Codec.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::CODEC.dump(object.mutation_result).from_right,
281
+ 'criteria_result' => CoverageCriteria::CODEC.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::CODEC.load_transform),
291
+ Transform::Hash::Key.new(value: 'criteria_result', transform: CoverageCriteria::CODEC.load_transform)
292
+ ],
293
+ optional: []
294
+ ),
295
+ Transform::Hash::Symbolize.new,
296
+ Transform::Success.new(block: method(:new).to_proc)
297
+ ]
298
+ )
299
+
300
+ CODEC = Transform::Codec.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::CODEC.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::CODEC.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
+ CODEC = Transform::Codec.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
@@ -38,7 +38,7 @@ module Mutant
38
38
  # @return [self]
39
39
  def response(response)
40
40
  if response.error
41
- env.world.stderr.puts(response.log)
41
+ env.world.stderr.puts(response.log.content)
42
42
  fail response.error
43
43
  end
44
44
 
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,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ class Transform
5
+ # Bidirectional codec over Ruby objects.
6
+ #
7
+ # Wraps a pair of dump/load transforms that convert between domain
8
+ # objects and a JSON-compatible Ruby structure (hashes, arrays,
9
+ # primitives). The outer layer that converts the structure to/from
10
+ # a JSON string is handled by callers as needed.
11
+ class Codec
12
+ include Anima.new(:dump_transform, :load_transform)
13
+
14
+ # Build a codec for simple Anima objects with primitive fields
15
+ #
16
+ # @param [Class] klass
17
+ #
18
+ # @return [Codec]
19
+ def self.for_anima(klass)
20
+ new(
21
+ dump_transform: Success.new(block: ->(object) { object.to_h.transform_keys(&:to_s) }),
22
+ load_transform: Success.new(block: ->(hash) { klass.new(**hash.transform_keys(&:to_sym)) })
23
+ )
24
+ end
25
+
26
+ # Dump object to Ruby structure
27
+ #
28
+ # @param [Object] object
29
+ #
30
+ # @return [Either<Error, Object>]
31
+ def dump(object)
32
+ dump_transform.call(object)
33
+ end
34
+
35
+ # Load object from Ruby structure
36
+ #
37
+ # @param [Object] input
38
+ #
39
+ # @return [Either<Error, Object>]
40
+ def load(input)
41
+ load_transform.call(input)
42
+ end
43
+ end # Codec
44
+ end # Transform
45
+ end # Mutant