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 +4 -4
- data/lib/mutant/bootstrap.rb +2 -2
- data/lib/mutant/cli/command/environment/run.rb +3 -2
- data/lib/mutant/cli/command/environment/test.rb +1 -1
- data/lib/mutant/cli/command/environment.rb +1 -5
- data/lib/mutant/env.rb +17 -5
- data/lib/mutant/hooks.rb +4 -3
- data/lib/mutant/integration.rb +11 -1
- data/lib/mutant/matcher/config.rb +3 -3
- data/lib/mutant/matcher/method.rb +9 -0
- data/lib/mutant/matcher.rb +3 -1
- data/lib/mutant/mutation/runner.rb +7 -6
- data/lib/mutant/parallel/worker.rb +58 -54
- data/lib/mutant/parallel.rb +6 -5
- data/lib/mutant/reporter/cli/printer/env.rb +8 -6
- data/lib/mutant/repository/diff.rb +11 -3
- data/lib/mutant/repository.rb +0 -14
- data/lib/mutant/selector/expression.rb +1 -1
- data/lib/mutant/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 28479ac15f5cf3d79de8ffd27e87601db35a642c09a9d717df6d34bfb41bc0c0
|
4
|
+
data.tar.gz: 1aa5fc440f828ab7a3b21fbb65534899be91281bfd238e86cf3e7e7098f3c4ba
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9a7bb108c85e012d4ba53444e612ff955f6ceafbac159f1d4a1561c62f8f693279d1673e2cde0899ef2356ac42bad99143405d2ab482c35215553548c734e662
|
7
|
+
data.tar.gz: 143cc0c327d18f4203a6769606ceb976aaf0fb748168680d80bc0d4d2099d203a4271cf2a3644307050421c841a9019441947250c21fdefaf7883c198f0887b8
|
data/lib/mutant/bootstrap.rb
CHANGED
@@ -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
|
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.
|
45
|
+
if environment.integration.available_tests.empty?
|
45
46
|
Either::Left.new(NO_TESTS_MESSAGE)
|
46
47
|
else
|
47
48
|
Either::Right.new(environment)
|
@@ -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
|
107
|
+
# Amount of all tests the integration provides
|
104
108
|
#
|
105
109
|
# @return [Integer]
|
106
|
-
def
|
110
|
+
def amount_all_tests
|
107
111
|
integration.all_tests.length
|
108
112
|
end
|
109
|
-
memoize :
|
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
|
data/lib/mutant/integration.rb
CHANGED
@@ -88,11 +88,21 @@ module Mutant
|
|
88
88
|
# @return [Result::Test]
|
89
89
|
abstract_method :call
|
90
90
|
|
91
|
-
#
|
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
|
-
:
|
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
|
-
|
23
|
-
|
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.
|
data/lib/mutant/matcher.rb
CHANGED
@@ -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.
|
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:
|
45
|
-
jobs:
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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
|
-
|
24
|
-
|
28
|
+
def self.start_config(config)
|
29
|
+
world = config.world
|
25
30
|
io = world.io
|
26
|
-
|
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
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
-
|
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
|
-
|
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
|
data/lib/mutant/parallel.rb
CHANGED
@@ -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:
|
28
|
-
index:
|
29
|
-
|
30
|
-
|
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
|
-
:
|
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, '
|
21
|
-
[:info, '
|
22
|
-
[:info, 'Tests
|
23
|
-
[:info, '
|
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
|
-
|
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
|
data/lib/mutant/repository.rb
CHANGED
@@ -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.
|
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
|
data/lib/mutant/version.rb
CHANGED
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.
|
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-
|
11
|
+
date: 2023-05-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: diff-lcs
|