mutant 0.11.28 → 0.11.30

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/lib/mutant/ast.rb +8 -9
  3. data/lib/mutant/bootstrap.rb +45 -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 +3 -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.30'
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.30
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-11 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