mutant 0.11.19 → 0.11.20

Sign up to get free protection for your applications and to get access to all the features.
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