mutant 0.11.28 → 0.11.29

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/lib/mutant/ast.rb +8 -9
  3. data/lib/mutant/bootstrap.rb +39 -13
  4. data/lib/mutant/cli/command/environment/test.rb +38 -1
  5. data/lib/mutant/cli/command/environment.rb +11 -0
  6. data/lib/mutant/context.rb +31 -61
  7. data/lib/mutant/env.rb +8 -1
  8. data/lib/mutant/expression/method.rb +4 -1
  9. data/lib/mutant/expression/methods.rb +4 -1
  10. data/lib/mutant/expression/namespace.rb +4 -4
  11. data/lib/mutant/hooks.rb +1 -0
  12. data/lib/mutant/integration/null.rb +1 -0
  13. data/lib/mutant/integration.rb +5 -1
  14. data/lib/mutant/matcher/descendants.rb +1 -1
  15. data/lib/mutant/matcher/method/instance.rb +5 -4
  16. data/lib/mutant/matcher/method/metaclass.rb +1 -1
  17. data/lib/mutant/matcher/method/singleton.rb +1 -1
  18. data/lib/mutant/matcher/method.rb +30 -4
  19. data/lib/mutant/matcher/methods.rb +8 -7
  20. data/lib/mutant/matcher/namespace.rb +1 -1
  21. data/lib/mutant/meta/example.rb +12 -2
  22. data/lib/mutant/mutation/runner/sink.rb +7 -3
  23. data/lib/mutant/mutation/runner.rb +2 -3
  24. data/lib/mutant/parallel/connection.rb +178 -0
  25. data/lib/mutant/parallel/pipe.rb +39 -0
  26. data/lib/mutant/parallel/worker.rb +42 -14
  27. data/lib/mutant/parallel.rb +18 -7
  28. data/lib/mutant/reporter/cli/format.rb +19 -2
  29. data/lib/mutant/reporter/cli/printer/test.rb +138 -0
  30. data/lib/mutant/reporter/cli.rb +33 -4
  31. data/lib/mutant/reporter.rb +22 -1
  32. data/lib/mutant/result.rb +53 -2
  33. data/lib/mutant/scope.rb +41 -1
  34. data/lib/mutant/subject/method/instance.rb +3 -2
  35. data/lib/mutant/subject/method/metaclass.rb +1 -1
  36. data/lib/mutant/subject/method/singleton.rb +2 -2
  37. data/lib/mutant/subject/method.rb +1 -1
  38. data/lib/mutant/test/runner/sink.rb +51 -0
  39. data/lib/mutant/test/runner.rb +62 -0
  40. data/lib/mutant/timer.rb +9 -0
  41. data/lib/mutant/version.rb +1 -1
  42. data/lib/mutant/world.rb +2 -0
  43. data/lib/mutant.rb +7 -1
  44. metadata +9 -19
  45. data/lib/mutant/pipe.rb +0 -96
@@ -4,7 +4,7 @@ module Mutant
4
4
  class Reporter
5
5
  # Reporter that reports in human readable format
6
6
  class CLI < self
7
- include Anima.new(:output, :format)
7
+ include Anima.new(:print_warnings, :output, :format)
8
8
 
9
9
  # Build reporter
10
10
  #
@@ -13,8 +13,9 @@ module Mutant
13
13
  # @return [Reporter::CLI]
14
14
  def self.build(output)
15
15
  new(
16
- format: Format::Progressive.new(tty: output.respond_to?(:tty?) && output.tty?),
17
- output: output
16
+ format: Format::Progressive.new(tty: output.respond_to?(:tty?) && output.tty?),
17
+ print_warnings: false,
18
+ output: output
18
19
  )
19
20
  end
20
21
 
@@ -28,6 +29,16 @@ module Mutant
28
29
  self
29
30
  end
30
31
 
32
+ # Report test start
33
+ #
34
+ # @param [Env] env
35
+ #
36
+ # @return [self]
37
+ def test_start(env)
38
+ write(format.test_start(env))
39
+ self
40
+ end
41
+
31
42
  # Report progress object
32
43
  #
33
44
  # @param [Parallel::Status] status
@@ -38,6 +49,14 @@ module Mutant
38
49
  self
39
50
  end
40
51
 
52
+ # Report progress object
53
+ #
54
+ # @return [self]
55
+ def test_progress(status)
56
+ write(format.test_progress(status))
57
+ self
58
+ end
59
+
41
60
  # Report delay in seconds
42
61
  #
43
62
  # @return [Float]
@@ -51,7 +70,7 @@ module Mutant
51
70
  #
52
71
  # @return [self]
53
72
  def warn(message)
54
- output.puts(message)
73
+ output.puts(message) if print_warnings
55
74
  self
56
75
  end
57
76
 
@@ -65,6 +84,16 @@ module Mutant
65
84
  self
66
85
  end
67
86
 
87
+ # Report env
88
+ #
89
+ # @param [Result::Env] env
90
+ #
91
+ # @return [self]
92
+ def test_report(env)
93
+ Printer::Test::EnvResult.call(output: output, object: env)
94
+ self
95
+ end
96
+
68
97
  private
69
98
 
70
99
  def write(frame)
@@ -19,13 +19,27 @@ module Mutant
19
19
  # @return [self]
20
20
  abstract_method :start
21
21
 
22
- # Report collector state
22
+ # Report test start
23
+ #
24
+ # @param [Env] env
25
+ #
26
+ # @return [self]
27
+ abstract_method :test_start
28
+
29
+ # Report final state
23
30
  #
24
31
  # @param [Runner::Collector] collector
25
32
  #
26
33
  # @return [self]
27
34
  abstract_method :report
28
35
 
36
+ # Report final test state
37
+ #
38
+ # @param [Runner::Collector] collector
39
+ #
40
+ # @return [self]
41
+ abstract_method :test_report
42
+
29
43
  # Report progress on object
30
44
  #
31
45
  # @param [Object] object
@@ -33,6 +47,13 @@ module Mutant
33
47
  # @return [self]
34
48
  abstract_method :progress
35
49
 
50
+ # Report progress on object
51
+ #
52
+ # @param [Object] object
53
+ #
54
+ # @return [self]
55
+ abstract_method :test_progress
56
+
36
57
  # The reporter delay
37
58
  #
38
59
  # @return [Float]
data/lib/mutant/result.rb CHANGED
@@ -105,12 +105,62 @@ module Mutant
105
105
  def stop?
106
106
  env.config.fail_fast && !subject_results.all?(&:success?)
107
107
  end
108
-
109
108
  end # Env
110
109
 
110
+ # TestEnv result object
111
+ class TestEnv
112
+ include Result, Anima.new(
113
+ :env,
114
+ :runtime,
115
+ :test_results
116
+ )
117
+
118
+ # Test if run is successful
119
+ #
120
+ # @return [Boolean]
121
+ def success?
122
+ amount_tests_failed.equal?(0)
123
+ end
124
+ memoize :success?
125
+
126
+ # Failed subject results
127
+ #
128
+ # @return [Array<Result::Test>]
129
+ def failed_test_results
130
+ test_results.reject(&:success?)
131
+ end
132
+ memoize :failed_test_results
133
+
134
+ def stop?
135
+ env.config.fail_fast && !test_results.all?(&:success?)
136
+ end
137
+
138
+ def testtime
139
+ test_results.map(&:runtime).sum(0.0)
140
+ end
141
+
142
+ def amount_tests
143
+ env.integration.all_tests.length
144
+ end
145
+
146
+ def amount_test_results
147
+ test_results.length
148
+ end
149
+
150
+ def amount_tests_failed
151
+ failed_test_results.length
152
+ end
153
+
154
+ def amount_tests_success
155
+ test_results.count(&:passed)
156
+ end
157
+ end # TestEnv
158
+
111
159
  # Test result
112
160
  class Test
113
- include Anima.new(:passed, :runtime)
161
+ include Anima.new(:passed, :runtime, :output)
162
+
163
+ alias_method :success?, :passed
114
164
 
115
165
  class VoidValue < self
116
166
  include Singleton
@@ -120,6 +170,7 @@ module Mutant
120
170
  # @return [undefined]
121
171
  def initialize
122
172
  super(
173
+ output: '',
123
174
  passed: false,
124
175
  runtime: 0.0
125
176
  )
data/lib/mutant/scope.rb CHANGED
@@ -3,6 +3,46 @@
3
3
  module Mutant
4
4
  # Class or Module bound to an exact expression
5
5
  class Scope
6
- include Anima.new(:raw, :expression)
6
+ include Adamantium, Anima.new(:raw, :expression)
7
+
8
+ NAMESPACE_DELIMITER = '::'
9
+
10
+ # Nesting of scope
11
+ #
12
+ # @return [Enumerable<Class,Module>]
13
+ def nesting
14
+ const = Object
15
+ name_nesting.map do |name|
16
+ const = const.const_get(name)
17
+ end
18
+ end
19
+ memoize :nesting
20
+
21
+ # Unqualified name of scope
22
+ #
23
+ # @return [String]
24
+ def unqualified_name
25
+ name_nesting.last
26
+ end
27
+
28
+ # Match expressions for scope
29
+ #
30
+ # @return [Enumerable<Expression>]
31
+ def match_expressions
32
+ name_nesting.each_index.reverse_each.map do |index|
33
+ Expression::Namespace::Recursive.new(
34
+ scope_name: name_nesting.take(index.succ).join(NAMESPACE_DELIMITER)
35
+ )
36
+ end
37
+ end
38
+ memoize :match_expressions
39
+
40
+ private
41
+
42
+ def name_nesting
43
+ raw.name.split(NAMESPACE_DELIMITER)
44
+ end
45
+ memoize :name_nesting
46
+
7
47
  end # Scope
8
48
  end # Mutant
@@ -13,12 +13,12 @@ module Mutant
13
13
  #
14
14
  # @return [self]
15
15
  def prepare
16
- scope.undef_method(name)
16
+ scope.raw.undef_method(name)
17
17
  self
18
18
  end
19
19
 
20
20
  def post_insert
21
- scope.__send__(visibility, name)
21
+ scope.raw.__send__(visibility, name)
22
22
  self
23
23
  end
24
24
 
@@ -31,6 +31,7 @@ module Mutant
31
31
  # @return [self]
32
32
  def prepare
33
33
  scope
34
+ .raw
34
35
  .instance_variable_get(:@memoized_methods)
35
36
  .delete(name)
36
37
 
@@ -15,7 +15,7 @@ module Mutant
15
15
  #
16
16
  # @return [self]
17
17
  def prepare
18
- scope.singleton_class.public_send(:undef_method, name)
18
+ scope.raw.singleton_class.undef_method(name)
19
19
  self
20
20
  end
21
21
 
@@ -13,12 +13,12 @@ module Mutant
13
13
  #
14
14
  # @return [self]
15
15
  def prepare
16
- scope.singleton_class.__send__(:undef_method, name)
16
+ scope.raw.singleton_class.undef_method(name)
17
17
  self
18
18
  end
19
19
 
20
20
  def post_insert
21
- scope.singleton_class.__send__(visibility, name)
21
+ scope.raw.singleton_class.__send__(visibility, name)
22
22
  self
23
23
  end
24
24
 
@@ -20,7 +20,7 @@ module Mutant
20
20
  Expression::Method.new(
21
21
  method_name: name.to_s,
22
22
  scope_symbol: self.class::SYMBOL,
23
- scope_name: scope.name
23
+ scope_name: scope.raw.name
24
24
  )
25
25
  end
26
26
  memoize :expression
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ class Test
5
+ module Runner
6
+ class Sink
7
+ include Anima.new(:env)
8
+
9
+ # Initialize object
10
+ #
11
+ # @return [undefined]
12
+ def initialize(*)
13
+ super
14
+ @start = env.world.timer.now
15
+ @test_results = []
16
+ end
17
+
18
+ # Runner status
19
+ #
20
+ # @return [Result::Env]
21
+ def status
22
+ Result::TestEnv.new(
23
+ env: env,
24
+ runtime: env.world.timer.now - @start,
25
+ test_results: @test_results
26
+ )
27
+ end
28
+
29
+ # Test if scheduling stopped
30
+ #
31
+ # @return [Boolean]
32
+ def stop?
33
+ status.stop?
34
+ end
35
+
36
+ # Handle mutation finish
37
+ #
38
+ # @return [self]
39
+ def response(response)
40
+ if response.error
41
+ env.world.stderr.puts(response.log)
42
+ fail response.error
43
+ end
44
+
45
+ @test_results << response.result.with(output: response.log)
46
+ self
47
+ end
48
+ end # Sink
49
+ end # Runner
50
+ end # Test
51
+ end # Mutant
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ class Test
5
+ module Runner
6
+ # Run against env
7
+ #
8
+ # @return [Either<String, Result>]
9
+ def self.call(env)
10
+ reporter(env).test_start(env)
11
+
12
+ Either::Right.new(run_tests(env))
13
+ end
14
+
15
+ def self.run_tests(env)
16
+ reporter = reporter(env)
17
+
18
+ env
19
+ .record(:tests) { run_driver(reporter, async_driver(env)) }
20
+ .tap { |result| env.record(:report) { reporter.test_report(result) } }
21
+ end
22
+ private_class_method :run_tests
23
+
24
+ def self.async_driver(env)
25
+ Parallel.async(world: env.world, config: test_config(env))
26
+ end
27
+ private_class_method :async_driver
28
+
29
+ def self.run_driver(reporter, driver)
30
+ Signal.trap('INT') do
31
+ driver.stop
32
+ end
33
+
34
+ loop do
35
+ status = driver.wait_timeout(reporter.delay)
36
+ break status.payload if status.done?
37
+ reporter.test_progress(status)
38
+ end
39
+ end
40
+ private_class_method :run_driver
41
+
42
+ def self.test_config(env)
43
+ Parallel::Config.new(
44
+ block: env.method(:run_test_index),
45
+ jobs: env.config.jobs,
46
+ on_process_start: env.method(:emit_test_worker_process_start),
47
+ process_name: 'mutant-test-runner-process',
48
+ sink: Sink.new(env: env),
49
+ source: Parallel::Source::Array.new(jobs: env.integration.all_tests.each_index.to_a),
50
+ thread_name: 'mutant-test-runner-thread',
51
+ timeout: nil
52
+ )
53
+ end
54
+ private_class_method :test_config
55
+
56
+ def self.reporter(env)
57
+ env.config.reporter
58
+ end
59
+ private_class_method :reporter
60
+ end # Runner
61
+ end # Test
62
+ end # Mutant
data/lib/mutant/timer.rb CHANGED
@@ -4,6 +4,15 @@ module Mutant
4
4
  class Timer
5
5
  include Anima.new(:process)
6
6
 
7
+ # Monotonic elapsed time of block execution
8
+ #
9
+ # @return [Float]
10
+ def elapsed
11
+ start = now
12
+ yield
13
+ now - start
14
+ end
15
+
7
16
  # The now monotonic time
8
17
  #
9
18
  # @return [Float]
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Mutant
4
4
  # Current mutant version
5
- VERSION = '0.11.28'
5
+ VERSION = '0.11.29'
6
6
  end # Mutant
data/lib/mutant/world.rb CHANGED
@@ -22,7 +22,9 @@ module Mutant
22
22
  :recorder,
23
23
  :stderr,
24
24
  :stdout,
25
+ :tempfile,
25
26
  :thread,
27
+ :time,
26
28
  :timer
27
29
  )
28
30
 
data/lib/mutant.rb CHANGED
@@ -74,7 +74,6 @@ module Mutant
74
74
  require 'mutant/bootstrap'
75
75
  require 'mutant/version'
76
76
  require 'mutant/env'
77
- require 'mutant/pipe'
78
77
  require 'mutant/util'
79
78
  require 'mutant/registry'
80
79
  require 'mutant/ast'
@@ -102,6 +101,8 @@ module Mutant
102
101
  require 'mutant/isolation/fork'
103
102
  require 'mutant/isolation/none'
104
103
  require 'mutant/parallel'
104
+ require 'mutant/parallel/connection'
105
+ require 'mutant/parallel/pipe'
105
106
  require 'mutant/parallel/driver'
106
107
  require 'mutant/parallel/source'
107
108
  require 'mutant/parallel/worker'
@@ -204,6 +205,8 @@ module Mutant
204
205
  require 'mutant/expression/namespace'
205
206
  require 'mutant/expression/parser'
206
207
  require 'mutant/test'
208
+ require 'mutant/test/runner'
209
+ require 'mutant/test/runner/sink'
207
210
  require 'mutant/timer'
208
211
  require 'mutant/integration'
209
212
  require 'mutant/integration/null'
@@ -243,6 +246,7 @@ module Mutant
243
246
  require 'mutant/reporter/cli/printer/mutation_result'
244
247
  require 'mutant/reporter/cli/printer/status_progressive'
245
248
  require 'mutant/reporter/cli/printer/subject_result'
249
+ require 'mutant/reporter/cli/printer/test'
246
250
  require 'mutant/reporter/cli/format'
247
251
  require 'mutant/repository'
248
252
  require 'mutant/repository/diff'
@@ -327,7 +331,9 @@ module Mutant
327
331
  recorder: recorder,
328
332
  stderr: $stderr,
329
333
  stdout: $stdout,
334
+ tempfile: Tempfile,
330
335
  thread: Thread,
336
+ time: Time,
331
337
  timer: timer
332
338
  )
333
339
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mutant
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.28
4
+ version: 0.11.29
5
5
  platform: ruby
6
6
  authors:
7
7
  - Markus Schirp
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-02-09 00:00:00.000000000 Z
11
+ date: 2024-03-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: diff-lcs
@@ -44,14 +44,14 @@ dependencies:
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 2.8.2
47
+ version: 2.9.0
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: 2.8.2
54
+ version: 2.9.0
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: sorbet-runtime
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -80,20 +80,6 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: 0.6.9
83
- - !ruby/object:Gem::Dependency
84
- name: parallel
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - "~>"
88
- - !ruby/object:Gem::Version
89
- version: '1.3'
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - "~>"
95
- - !ruby/object:Gem::Version
96
- version: '1.3'
97
83
  - !ruby/object:Gem::Dependency
98
84
  name: rspec
99
85
  requirement: !ruby/object:Gem::Requirement
@@ -305,11 +291,12 @@ files:
305
291
  - lib/mutant/mutator/util/array.rb
306
292
  - lib/mutant/mutator/util/symbol.rb
307
293
  - lib/mutant/parallel.rb
294
+ - lib/mutant/parallel/connection.rb
308
295
  - lib/mutant/parallel/driver.rb
296
+ - lib/mutant/parallel/pipe.rb
309
297
  - lib/mutant/parallel/source.rb
310
298
  - lib/mutant/parallel/worker.rb
311
299
  - lib/mutant/parser.rb
312
- - lib/mutant/pipe.rb
313
300
  - lib/mutant/procto.rb
314
301
  - lib/mutant/range.rb
315
302
  - lib/mutant/registry.rb
@@ -327,6 +314,7 @@ files:
327
314
  - lib/mutant/reporter/cli/printer/mutation_result.rb
328
315
  - lib/mutant/reporter/cli/printer/status_progressive.rb
329
316
  - lib/mutant/reporter/cli/printer/subject_result.rb
317
+ - lib/mutant/reporter/cli/printer/test.rb
330
318
  - lib/mutant/reporter/null.rb
331
319
  - lib/mutant/reporter/sequence.rb
332
320
  - lib/mutant/repository.rb
@@ -347,6 +335,8 @@ files:
347
335
  - lib/mutant/subject/method/metaclass.rb
348
336
  - lib/mutant/subject/method/singleton.rb
349
337
  - lib/mutant/test.rb
338
+ - lib/mutant/test/runner.rb
339
+ - lib/mutant/test/runner/sink.rb
350
340
  - lib/mutant/timer.rb
351
341
  - lib/mutant/transform.rb
352
342
  - lib/mutant/util.rb
data/lib/mutant/pipe.rb DELETED
@@ -1,96 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Mutant
4
- # Pipe abstraction
5
- class Pipe
6
- include Adamantium, Anima.new(:reader, :writer)
7
-
8
- # Run block with pipe in binmode
9
- #
10
- # @return [undefined]
11
- def self.with(io)
12
- io.pipe(binmode: true) do |(reader, writer)|
13
- yield new(reader: reader, writer: writer)
14
- end
15
- end
16
-
17
- def self.from_io(io)
18
- reader, writer = io.pipe(binmode: true)
19
- new(reader: reader, writer: writer)
20
- end
21
-
22
- # Writer end of the pipe
23
- #
24
- # @return [IO]
25
- def to_writer
26
- reader.close
27
- writer
28
- end
29
-
30
- # Parent reader end of the pipe
31
- #
32
- # @return [IO]
33
- def to_reader
34
- writer.close
35
- reader
36
- end
37
-
38
- class Connection
39
- include Anima.new(:marshal, :reader, :writer)
40
-
41
- Error = Class.new(RuntimeError)
42
-
43
- class Frame
44
- include Anima.new(:io)
45
-
46
- HEADER_FORMAT = 'N'
47
- MAX_BYTES = (2**32).pred
48
- HEADER_SIZE = 4
49
-
50
- def receive_value
51
- header = read(HEADER_SIZE)
52
- read(Util.one(header.unpack(HEADER_FORMAT)))
53
- end
54
-
55
- def send_value(body)
56
- bytesize = body.bytesize
57
-
58
- fail Error, 'message to big' if bytesize > MAX_BYTES
59
-
60
- io.binmode
61
- io.write([bytesize].pack(HEADER_FORMAT))
62
- io.write(body)
63
- end
64
-
65
- private
66
-
67
- def read(bytes)
68
- io.binmode
69
- io.read(bytes) or fail Error, 'Unexpected EOF'
70
- end
71
- end
72
-
73
- def call(payload)
74
- send_value(payload)
75
- receive_value
76
- end
77
-
78
- def receive_value
79
- marshal.load(reader.receive_value)
80
- end
81
-
82
- def send_value(value)
83
- writer.send_value(marshal.dump(value))
84
- self
85
- end
86
-
87
- def self.from_pipes(marshal:, reader:, writer:)
88
- new(
89
- marshal: marshal,
90
- reader: Frame.new(io: reader.to_reader),
91
- writer: Frame.new(io: writer.to_writer)
92
- )
93
- end
94
- end
95
- end # Pipe
96
- end # Mutant