mutant 0.11.19 → 0.11.20

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4809cb65459d6270021f50359d6b799b420fdd142a32524a2ebd772485c7a70b
4
- data.tar.gz: b8284c5b2d2bd165b7ef78662910cb314a6a5b5cd619b5e411290a10a058ee57
3
+ metadata.gz: 28479ac15f5cf3d79de8ffd27e87601db35a642c09a9d717df6d34bfb41bc0c0
4
+ data.tar.gz: 1aa5fc440f828ab7a3b21fbb65534899be91281bfd238e86cf3e7e7098f3c4ba
5
5
  SHA512:
6
- metadata.gz: c68d2e51e917a1b347a036c1619ad3a54fefee4e6dec6710f1d6b488c707437bb58597822dcc2108f55c5e6caa4e3226e52a2eeaa7012d39685203e161ec235b
7
- data.tar.gz: 7894b0df9ce7e54757f04206f501e7e5a676a93694b53af09a2cb667a6fb0a02737747faddefeae0ff5808b629ef1b80b9a59bd0b1fee6fa13ef1984c72fad87
6
+ metadata.gz: 9a7bb108c85e012d4ba53444e612ff955f6ceafbac159f1d4a1561c62f8f693279d1673e2cde0899ef2356ac42bad99143405d2ab482c35215553548c734e662
7
+ data.tar.gz: 143cc0c327d18f4203a6769606ceb976aaf0fb748168680d80bc0d4d2099d203a4271cf2a3644307050421c841a9019441947250c21fdefaf7883c198f0887b8
@@ -88,7 +88,7 @@ module Mutant
88
88
  config, hooks, world = env.config, env.hooks, env.world
89
89
 
90
90
  env.record(:hooks_env_infection_pre) do
91
- hooks.run(:env_infection_pre, env)
91
+ hooks.run(:env_infection_pre, env: env)
92
92
  end
93
93
 
94
94
  env.record(:require_target) do
@@ -101,7 +101,7 @@ module Mutant
101
101
  end
102
102
 
103
103
  env.record(:hooks_env_infection_post) do
104
- hooks.run(:env_infection_post, env)
104
+ hooks.run(:env_infection_post, env: env)
105
105
  end
106
106
  end
107
107
  end
@@ -18,7 +18,8 @@ module Mutant
18
18
 
19
19
  NO_TESTS_MESSAGE = <<~'MESSAGE'
20
20
  ===============
21
- Mutant found no tests. Mutation testing cannot be started.
21
+ Mutant found no tests available for mutation testing.
22
+ Mutation testing cannot be started.
22
23
 
23
24
  This can have various reasons:
24
25
 
@@ -41,7 +42,7 @@ module Mutant
41
42
  end
42
43
 
43
44
  def validate_tests(environment)
44
- if environment.integration.all_tests.empty?
45
+ if environment.integration.available_tests.empty?
45
46
  Either::Left.new(NO_TESTS_MESSAGE)
46
47
  else
47
48
  Either::Right.new(environment)
@@ -21,7 +21,7 @@ module Mutant
21
21
 
22
22
  def list_tests(env)
23
23
  tests = env.integration.all_tests
24
- print('Tests in environment: %d' % tests.length)
24
+ print('All tests in environment: %d' % tests.length)
25
25
  tests.each do |test|
26
26
  print(test.identification)
27
27
  end
@@ -92,7 +92,6 @@ module Mutant
92
92
  end
93
93
  end
94
94
 
95
- # rubocop:disable Metrics/MethodLength
96
95
  def add_matcher_options(parser)
97
96
  parser.separator('Matcher:')
98
97
 
@@ -103,10 +102,7 @@ module Mutant
103
102
  add_matcher(:start_expressions, @config.expression_parser.call(pattern).from_right)
104
103
  end
105
104
  parser.on('--since REVISION', 'Only select subjects touched since REVISION') do |revision|
106
- add_matcher(
107
- :subject_filters,
108
- Repository::SubjectFilter.new(diff: Repository::Diff.new(to: revision, world: world))
109
- )
105
+ add_matcher(:diffs, Repository::Diff.new(to: revision, world: world))
110
106
  end
111
107
  end
112
108
 
data/lib/mutant/env.rb CHANGED
@@ -64,6 +64,10 @@ module Mutant
64
64
  )
65
65
  end
66
66
 
67
+ def emit_mutation_worker_process_start(index:)
68
+ hooks.run(:mutation_worker_process_start, index: index)
69
+ end
70
+
67
71
  # The test selections
68
72
  #
69
73
  # @return Hash{Mutation => Enumerable<Test>}
@@ -100,13 +104,21 @@ module Mutant
100
104
  end
101
105
  memoize :amount_mutations
102
106
 
103
- # Amount of tests reachable by integration
107
+ # Amount of all tests the integration provides
104
108
  #
105
109
  # @return [Integer]
106
- def amount_total_tests
110
+ def amount_all_tests
107
111
  integration.all_tests.length
108
112
  end
109
- memoize :amount_total_tests
113
+ memoize :amount_all_tests
114
+
115
+ # Amount of tests available for mutation testing
116
+ #
117
+ # @return [Integer]
118
+ def amount_available_tests
119
+ integration.available_tests.length
120
+ end
121
+ memoize :amount_available_tests
110
122
 
111
123
  # Amount of selected subjects
112
124
  #
@@ -147,9 +159,9 @@ module Mutant
147
159
 
148
160
  def run_mutation_tests(mutation, tests)
149
161
  config.isolation.call(config.mutation.timeout) do
150
- hooks.run(:mutation_insert_pre, mutation)
162
+ hooks.run(:mutation_insert_pre, mutation: mutation)
151
163
  result = mutation.insert(world.kernel)
152
- hooks.run(:mutation_insert_post, mutation)
164
+ hooks.run(:mutation_insert_post, mutation: mutation)
153
165
 
154
166
  result.either(
155
167
  ->(_) { Result::Test::VoidValue.instance },
data/lib/mutant/hooks.rb CHANGED
@@ -5,10 +5,11 @@ module Mutant
5
5
  include Adamantium, Anima.new(:map)
6
6
 
7
7
  DEFAULTS = %i[
8
- env_infection_pre
9
8
  env_infection_post
9
+ env_infection_pre
10
10
  mutation_insert_post
11
11
  mutation_insert_pre
12
+ mutation_worker_process_start
12
13
  ].product([EMPTY_ARRAY]).to_h.transform_values(&:freeze).freeze
13
14
 
14
15
  MESSAGE = 'Unknown hook %s'
@@ -32,10 +33,10 @@ module Mutant
32
33
  )
33
34
  end
34
35
 
35
- def run(name, payload)
36
+ def run(name, **payload)
36
37
  Hooks.assert_name(name)
37
38
 
38
- map.fetch(name).each { |block| block.call(payload) }
39
+ map.fetch(name).each { |block| block.call(**payload) }
39
40
 
40
41
  self
41
42
  end
@@ -88,11 +88,21 @@ module Mutant
88
88
  # @return [Result::Test]
89
89
  abstract_method :call
90
90
 
91
- # Available tests for integration
91
+ # All tests this integration can run
92
+ #
93
+ # Some tests may not be available for mutation testing.
94
+ # See #available_tests
92
95
  #
93
96
  # @return [Enumerable<Test>]
94
97
  abstract_method :all_tests
95
98
 
99
+ # All tests available for mutation testing
100
+ #
101
+ # Subset ofr #al_tests
102
+ #
103
+ # @return [Enumerable<Test>]
104
+ abstract_method :available_tests
105
+
96
106
  private
97
107
 
98
108
  def timer
@@ -8,7 +8,7 @@ module Mutant
8
8
  :ignore,
9
9
  :subjects,
10
10
  :start_expressions,
11
- :subject_filters
11
+ :diffs
12
12
  )
13
13
 
14
14
  INSPECT_FORMAT = "#<#{self} %s>"
@@ -19,8 +19,8 @@ module Mutant
19
19
  PRESENTATIONS = {
20
20
  ignore: :syntax,
21
21
  start_expressions: :syntax,
22
- subject_filters: :inspect,
23
- subjects: :syntax
22
+ subjects: :syntax,
23
+ diffs: :inspect
24
24
  }.freeze
25
25
 
26
26
  private_constant(*constants(false))
@@ -120,6 +120,11 @@ module Mutant
120
120
  def matched_view
121
121
  return if source_location.nil?
122
122
 
123
+ # This is a performance optimization when using --since to avoid the cost of parsing
124
+ # every source file that could possibly map to a subject. A more fine-grained filtering
125
+ # takes places later in the process.
126
+ return unless relevant_source_file?
127
+
123
128
  ast
124
129
  .on_line(source_line)
125
130
  .select { |view| view.node.type.eql?(self.class::MATCH_NODE_TYPE) && match?(view.node) }
@@ -127,6 +132,10 @@ module Mutant
127
132
  end
128
133
  memoize :matched_view
129
134
 
135
+ def relevant_source_file?
136
+ env.config.matcher.diffs.all? { |diff| diff.touches_path?(source_path) }
137
+ end
138
+
130
139
  def visibility
131
140
  # This can be cleaned up once we are on >ruby-3.0
132
141
  # Method#{public,private,protected}? exists there.
@@ -31,7 +31,9 @@ module Mutant
31
31
  private_class_method :allowed_subject?
32
32
 
33
33
  def self.select_subject?(config, subject)
34
- config.subject_filters.all? { |filter| filter.call(subject) }
34
+ config.diffs.all? do |diff|
35
+ diff.touches?(subject.source_path, subject.source_lines)
36
+ end
35
37
  end
36
38
  private_class_method :select_subject?
37
39
 
@@ -41,12 +41,13 @@ module Mutant
41
41
 
42
42
  def self.mutation_test_config(env)
43
43
  Parallel::Config.new(
44
- block: env.method(:cover_index),
45
- jobs: env.config.jobs,
46
- process_name: 'mutant-worker-process',
47
- sink: Sink.new(env: env),
48
- source: Parallel::Source::Array.new(jobs: env.mutations.each_index.to_a),
49
- thread_name: 'mutant-worker-thread'
44
+ block: env.method(:cover_index),
45
+ jobs: env.config.jobs,
46
+ on_process_start: env.method(:emit_mutation_worker_process_start),
47
+ process_name: 'mutant-worker-process',
48
+ sink: Sink.new(env: env),
49
+ source: Parallel::Source::Array.new(jobs: env.mutations.each_index.to_a),
50
+ thread_name: 'mutant-worker-thread'
50
51
  )
51
52
  end
52
53
  private_class_method :mutation_test_config
@@ -3,58 +3,69 @@
3
3
  module Mutant
4
4
  module Parallel
5
5
  class Worker
6
- include Adamantium, Anima.new(
7
- :connection,
8
- :index,
9
- :pid,
10
- :process,
11
- :var_active_jobs,
12
- :var_final,
13
- :var_running,
14
- :var_sink,
15
- :var_source
16
- )
17
-
18
- private(*anima.attribute_names)
19
-
20
- public :index
6
+ class Config
7
+ include Adamantium, Anima.new(
8
+ :block,
9
+ :index,
10
+ :on_process_start,
11
+ :process_name,
12
+ :var_active_jobs,
13
+ :var_final,
14
+ :var_running,
15
+ :var_sink,
16
+ :var_source,
17
+ :world
18
+ )
19
+ end
20
+
21
+ include Adamantium, Anima.new(:connection, :config, :pid)
22
+
23
+ def self.start(**attributes)
24
+ start_config(Config.new(**attributes))
25
+ end
21
26
 
22
27
  # rubocop:disable Metrics/MethodLength
23
- # rubocop:disable Metrics/ParameterLists
24
- def self.start(world:, block:, process_name:, **attributes)
28
+ def self.start_config(config)
29
+ world = config.world
25
30
  io = world.io
26
- process = world.process
31
+ marshal = world.marshal
27
32
 
28
33
  request = Pipe.from_io(io)
29
34
  response = Pipe.from_io(io)
30
35
 
31
- pid = process.fork do
32
- world.thread.current.name = process_name
33
- world.process.setproctitle(process_name)
34
-
35
- Child.new(
36
- block: block,
37
- connection: Pipe::Connection.from_pipes(
38
- marshal: world.marshal,
39
- reader: request,
40
- writer: response
41
- )
42
- ).call
36
+ pid = world.process.fork do
37
+ run_child(
38
+ config: config,
39
+ connection: Pipe::Connection.from_pipes(marshal: marshal, reader: request, writer: response)
40
+ )
43
41
  end
44
42
 
45
43
  new(
46
44
  pid: pid,
47
- process: process,
48
- connection: Pipe::Connection.from_pipes(
49
- marshal: world.marshal,
50
- reader: response,
51
- writer: request
52
- ),
53
- **attributes
45
+ config: config,
46
+ connection: Pipe::Connection.from_pipes(marshal: marshal, reader: response, writer: request)
54
47
  )
55
48
  end
49
+ private_class_method :start_config
56
50
  # rubocop:enable Metrics/MethodLength
57
- # rubocop:enable Metrics/ParameterLists
51
+
52
+ def self.run_child(config:, connection:)
53
+ world = config.world
54
+
55
+ world.thread.current.name = config.process_name
56
+ world.process.setproctitle(config.process_name)
57
+
58
+ config.on_process_start.call(index: config.index)
59
+
60
+ loop do
61
+ connection.send_value(config.block.call(connection.receive_value))
62
+ end
63
+ end
64
+ private_class_method :run_child
65
+
66
+ def index
67
+ config.index
68
+ end
58
69
 
59
70
  # Run worker payload
60
71
  #
@@ -89,45 +100,38 @@ module Mutant
89
100
 
90
101
  private
91
102
 
103
+ def process
104
+ config.world.process
105
+ end
106
+
92
107
  def next_job
93
- var_source.with do |source|
108
+ config.var_source.with do |source|
94
109
  source.next if source.next?
95
110
  end
96
111
  end
97
112
 
98
113
  def add_result(result)
99
- var_sink.with do |sink|
114
+ config.var_sink.with do |sink|
100
115
  sink.result(result)
101
116
  sink.stop?
102
117
  end
103
118
  end
104
119
 
105
120
  def job_start(job)
106
- var_active_jobs.with do |active_jobs|
121
+ config.var_active_jobs.with do |active_jobs|
107
122
  active_jobs << job
108
123
  end
109
124
  end
110
125
 
111
126
  def job_done(job)
112
- var_active_jobs.with do |active_jobs|
127
+ config.var_active_jobs.with do |active_jobs|
113
128
  active_jobs.delete(job)
114
129
  end
115
130
  end
116
131
 
117
132
  def finalize
118
- var_final.put(nil) if var_running.modify(&:pred).zero?
119
- end
120
-
121
- class Child
122
- include Anima.new(:block, :connection)
123
-
124
- def call
125
- loop do
126
- connection.send_value(block.call(connection.receive_value))
127
- end
128
- end
133
+ config.var_final.put(nil) if config.var_running.modify(&:pred).zero?
129
134
  end
130
- private_constant :Child
131
135
  end # Worker
132
136
  end # Parallel
133
137
  end # Mutant
@@ -3,7 +3,6 @@
3
3
  module Mutant
4
4
  # Parallel execution engine of arbitrary payloads
5
5
  module Parallel
6
-
7
6
  # Run async computation returning driver
8
7
  #
9
8
  # @param [World] world
@@ -24,10 +23,11 @@ module Mutant
24
23
  def self.workers(world, config, shared)
25
24
  Array.new(config.jobs) do |index|
26
25
  Worker.start(
27
- block: config.block,
28
- index: index,
29
- process_name: "#{config.process_name}-#{index}",
30
- world: world,
26
+ block: config.block,
27
+ index: index,
28
+ on_process_start: config.on_process_start,
29
+ process_name: "#{config.process_name}-#{index}",
30
+ world: world,
31
31
  **shared
32
32
  )
33
33
  end
@@ -93,6 +93,7 @@ module Mutant
93
93
  include Adamantium, Anima.new(
94
94
  :block,
95
95
  :jobs,
96
+ :on_process_start,
96
97
  :process_name,
97
98
  :sink,
98
99
  :source,
@@ -7,20 +7,22 @@ module Mutant
7
7
  # Env printer
8
8
  class Env < self
9
9
  delegate(
10
+ :amount_available_tests,
10
11
  :amount_mutations,
11
12
  :amount_selected_tests,
12
13
  :amount_subjects,
13
- :amount_total_tests,
14
+ :amount_all_tests,
14
15
  :config,
15
16
  :test_subject_ratio
16
17
  )
17
18
 
18
19
  FORMATS = [
19
- [:info, 'Subjects: %s', :amount_subjects ],
20
- [:info, 'Total-Tests: %s', :amount_total_tests ],
21
- [:info, 'Selected-Tests: %s', :amount_selected_tests],
22
- [:info, 'Tests/Subject: %0.2f avg', :test_subject_ratio ],
23
- [:info, 'Mutations: %s', :amount_mutations ]
20
+ [:info, 'Subjects: %s', :amount_subjects ],
21
+ [:info, 'All-Tests: %s', :amount_all_tests ],
22
+ [:info, 'Available-Tests: %s', :amount_available_tests],
23
+ [:info, 'Selected-Tests: %s', :amount_selected_tests ],
24
+ [:info, 'Tests/Subject: %0.2f avg', :test_subject_ratio ],
25
+ [:info, 'Mutations: %s', :amount_mutations ]
24
26
  ].each(&:freeze)
25
27
 
26
28
  # Run printer
@@ -22,12 +22,16 @@ module Mutant
22
22
  # @raise [RepositoryError]
23
23
  # when git command failed
24
24
  def touches?(path, line_range)
25
- touched_paths
26
- .from_right { |message| fail Error, message }
27
- .fetch(path) { return false }
25
+ touched_path(path) { return false }
28
26
  .touches?(line_range)
29
27
  end
30
28
 
29
+ def touches_path?(path)
30
+ touched_path(path) { return false }
31
+
32
+ true
33
+ end
34
+
31
35
  private
32
36
 
33
37
  def repository_root
@@ -37,6 +41,10 @@ module Mutant
37
41
  .fmap(&world.pathname.public_method(:new))
38
42
  end
39
43
 
44
+ def touched_path(path, &block)
45
+ touched_paths.from_right { |message| fail Error, message }.fetch(path, &block)
46
+ end
47
+
40
48
  def touched_paths
41
49
  repository_root.bind(&method(:diff_index))
42
50
  end
@@ -2,19 +2,5 @@
2
2
 
3
3
  module Mutant
4
4
  module Repository
5
- # Subject filter based on repository diff
6
- class SubjectFilter
7
- include Adamantium, Anima.new(:diff)
8
-
9
- # Test if subject was touched in diff
10
- #
11
- # @param [Subject] subject
12
- #
13
- # @return [Boolean]
14
- def call(subject)
15
- diff.touches?(subject.source_path, subject.source_lines)
16
- end
17
-
18
- end # SubjectFilter
19
5
  end # Repository
20
6
  end # Mutant
@@ -13,7 +13,7 @@ module Mutant
13
13
  # @return [Enumerable<Test>]
14
14
  def call(subject)
15
15
  subject.match_expressions.each do |match_expression|
16
- subject_tests = integration.all_tests.select do |test|
16
+ subject_tests = integration.available_tests.select do |test|
17
17
  test.expressions.any? do |test_expression|
18
18
  match_expression.prefix?(test_expression)
19
19
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Mutant
4
4
  # Current mutant version
5
- VERSION = '0.11.19'
5
+ VERSION = '0.11.20'
6
6
  end # Mutant
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.19
4
+ version: 0.11.20
5
5
  platform: ruby
6
6
  authors:
7
7
  - Markus Schirp
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-05-06 00:00:00.000000000 Z
11
+ date: 2023-05-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: diff-lcs