mutant 0.11.19 → 0.11.21
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/ast/meta/send.rb +1 -2
- 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 +18 -8
- data/lib/mutant/config.rb +36 -7
- data/lib/mutant/env.rb +20 -5
- data/lib/mutant/hooks.rb +4 -3
- data/lib/mutant/integration.rb +46 -7
- data/lib/mutant/isolation/fork.rb +0 -4
- data/lib/mutant/matcher/config.rb +3 -3
- data/lib/mutant/matcher/filter.rb +1 -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/config.rb +1 -1
- 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
- data/lib/mutant.rb +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 47d87c7b5cf12da4906d170d892be1d5121b4f65b86b39365db2d6424f1487c6
|
4
|
+
data.tar.gz: e8d9007fc9633eea5343b83282d331fab5e59aaf6f880aa9b52cb525b2f6db81
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 04e36404f3a210cfa4a7045e244a72509e69332dbf736e81f5069a7d632680931747b761fd5f61e2ba915e94a0f9545506c3db2369a44f62a21b7a0254264db6
|
7
|
+
data.tar.gz: 2e0db7e6d2ada9525ec054a197f8004e223a31c093ab7b603704044f92f8e1f4305048851b8a583172f3904a028a25a304a605e3e84d5a5e708eccabb6842e9c
|
data/lib/mutant/ast/meta/send.rb
CHANGED
@@ -33,8 +33,7 @@ module Mutant
|
|
33
33
|
#
|
34
34
|
# @return [Boolean]
|
35
35
|
def attribute_assignment?
|
36
|
-
!Types::METHOD_OPERATORS.include?(selector) &&
|
37
|
-
selector.to_s.end_with?(ATTRIBUTE_ASSIGNMENT_SELECTOR_SUFFIX)
|
36
|
+
!Types::METHOD_OPERATORS.include?(selector) && selector.end_with?(ATTRIBUTE_ASSIGNMENT_SELECTOR_SUFFIX)
|
38
37
|
end
|
39
38
|
|
40
39
|
# Test for binary operator implemented as method
|
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)
|
@@ -3,6 +3,7 @@
|
|
3
3
|
module Mutant
|
4
4
|
module CLI
|
5
5
|
class Command
|
6
|
+
# rubocop:disable Metrics/ClassLength
|
6
7
|
class Environment < self
|
7
8
|
NAME = 'environment'
|
8
9
|
SHORT_DESCRIPTION = 'Environment subcommands'
|
@@ -87,12 +88,24 @@ module Mutant
|
|
87
88
|
def add_integration_options(parser)
|
88
89
|
parser.separator('Integration:')
|
89
90
|
|
90
|
-
parser.on('--use INTEGRATION', '
|
91
|
-
|
92
|
-
|
91
|
+
parser.on('--use INTEGRATION', 'deprecated alias for --integration', &method(:assign_integration_name))
|
92
|
+
parser.on('--integration NAME', 'Use test integration with NAME', &method(:assign_integration_name))
|
93
|
+
|
94
|
+
parser.on(
|
95
|
+
'--integration-argument ARGUMENT', 'Pass ARGUMENT to integration',
|
96
|
+
&method(:add_integration_argument)
|
97
|
+
)
|
98
|
+
end
|
99
|
+
|
100
|
+
def add_integration_argument(value)
|
101
|
+
config = @config.integration
|
102
|
+
set(integration: config.with(arguments: config.arguments + [value]))
|
103
|
+
end
|
104
|
+
|
105
|
+
def assign_integration_name(name)
|
106
|
+
set(integration: @config.integration.with(name: name))
|
93
107
|
end
|
94
108
|
|
95
|
-
# rubocop:disable Metrics/MethodLength
|
96
109
|
def add_matcher_options(parser)
|
97
110
|
parser.separator('Matcher:')
|
98
111
|
|
@@ -103,10 +116,7 @@ module Mutant
|
|
103
116
|
add_matcher(:start_expressions, @config.expression_parser.call(pattern).from_right)
|
104
117
|
end
|
105
118
|
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
|
-
)
|
119
|
+
add_matcher(:diffs, Repository::Diff.new(to: revision, world: world))
|
110
120
|
end
|
111
121
|
end
|
112
122
|
|
data/lib/mutant/config.rb
CHANGED
@@ -52,6 +52,20 @@ module Mutant
|
|
52
52
|
```
|
53
53
|
MESSAGE
|
54
54
|
|
55
|
+
INTEGRATION_DEPRECATION = <<~'MESSAGE'
|
56
|
+
Deprecated configuration toplevel string key `integration` found.
|
57
|
+
|
58
|
+
This key will be removed in the next major version.
|
59
|
+
Instead place your integration configuration under the `integration.name` key
|
60
|
+
like this:
|
61
|
+
|
62
|
+
```
|
63
|
+
# mutant.yml
|
64
|
+
integration:
|
65
|
+
name: your_integration # typically rspec or minitest
|
66
|
+
```
|
67
|
+
MESSAGE
|
68
|
+
|
55
69
|
private_constant(*constants(false))
|
56
70
|
|
57
71
|
# Merge with other config
|
@@ -69,7 +83,7 @@ module Mutant
|
|
69
83
|
fail_fast: fail_fast || other.fail_fast,
|
70
84
|
hooks: hooks + other.hooks,
|
71
85
|
includes: includes + other.includes,
|
72
|
-
integration: other.integration
|
86
|
+
integration: integration.merge(other.integration),
|
73
87
|
jobs: other.jobs || jobs,
|
74
88
|
matcher: matcher.merge(other.matcher),
|
75
89
|
mutation: mutation.merge(other.mutation),
|
@@ -158,14 +172,29 @@ module Mutant
|
|
158
172
|
end
|
159
173
|
|
160
174
|
def self.deprecations(reporter, hash)
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
(hash['mutation'] ||= {})['timeout'] ||= hash.delete('mutation_timeout')
|
165
|
-
end
|
175
|
+
mutation_timeout_deprecation(reporter, hash)
|
176
|
+
integration_deprecation(reporter, hash)
|
166
177
|
|
167
178
|
hash
|
168
179
|
end
|
180
|
+
private_class_method :deprecations
|
181
|
+
|
182
|
+
def self.mutation_timeout_deprecation(reporter, hash)
|
183
|
+
return unless hash.key?('mutation_timeout')
|
184
|
+
reporter.warn(MUTATION_TIMEOUT_DEPRECATION)
|
185
|
+
|
186
|
+
(hash['mutation'] ||= {})['timeout'] ||= hash.delete('mutation_timeout')
|
187
|
+
end
|
188
|
+
private_class_method :mutation_timeout_deprecation
|
189
|
+
|
190
|
+
def self.integration_deprecation(reporter, hash)
|
191
|
+
value = hash['integration']
|
192
|
+
return unless value.instance_of?(String)
|
193
|
+
reporter.warn(INTEGRATION_DEPRECATION)
|
194
|
+
|
195
|
+
hash['integration'] = { 'name' => value }
|
196
|
+
end
|
197
|
+
private_class_method :integration_deprecation
|
169
198
|
|
170
199
|
TRANSFORMS = [
|
171
200
|
Transform::Hash.new(
|
@@ -196,7 +225,7 @@ module Mutant
|
|
196
225
|
value: 'includes'
|
197
226
|
),
|
198
227
|
Transform::Hash::Key.new(
|
199
|
-
transform:
|
228
|
+
transform: ->(value) { Integration::Config::TRANSFORM.call(value) },
|
200
229
|
value: 'integration'
|
201
230
|
),
|
202
231
|
Transform::Hash::Key.new(
|
data/lib/mutant/env.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
module Mutant
|
4
4
|
# Mutation testing execution environment
|
5
|
+
# rubocop:disable Metrics/ClassLength
|
5
6
|
class Env
|
6
7
|
include Adamantium, Anima.new(
|
7
8
|
:config,
|
@@ -32,6 +33,7 @@ module Mutant
|
|
32
33
|
config: config,
|
33
34
|
hooks: Hooks.empty,
|
34
35
|
integration: Integration::Null.new(
|
36
|
+
arguments: EMPTY_ARRAY,
|
35
37
|
expression_parser: config.expression_parser,
|
36
38
|
world: world
|
37
39
|
),
|
@@ -64,6 +66,10 @@ module Mutant
|
|
64
66
|
)
|
65
67
|
end
|
66
68
|
|
69
|
+
def emit_mutation_worker_process_start(index:)
|
70
|
+
hooks.run(:mutation_worker_process_start, index: index)
|
71
|
+
end
|
72
|
+
|
67
73
|
# The test selections
|
68
74
|
#
|
69
75
|
# @return Hash{Mutation => Enumerable<Test>}
|
@@ -100,13 +106,21 @@ module Mutant
|
|
100
106
|
end
|
101
107
|
memoize :amount_mutations
|
102
108
|
|
103
|
-
# Amount of tests
|
109
|
+
# Amount of all tests the integration provides
|
104
110
|
#
|
105
111
|
# @return [Integer]
|
106
|
-
def
|
112
|
+
def amount_all_tests
|
107
113
|
integration.all_tests.length
|
108
114
|
end
|
109
|
-
memoize :
|
115
|
+
memoize :amount_all_tests
|
116
|
+
|
117
|
+
# Amount of tests available for mutation testing
|
118
|
+
#
|
119
|
+
# @return [Integer]
|
120
|
+
def amount_available_tests
|
121
|
+
integration.available_tests.length
|
122
|
+
end
|
123
|
+
memoize :amount_available_tests
|
110
124
|
|
111
125
|
# Amount of selected subjects
|
112
126
|
#
|
@@ -147,9 +161,9 @@ module Mutant
|
|
147
161
|
|
148
162
|
def run_mutation_tests(mutation, tests)
|
149
163
|
config.isolation.call(config.mutation.timeout) do
|
150
|
-
hooks.run(:mutation_insert_pre, mutation)
|
164
|
+
hooks.run(:mutation_insert_pre, mutation: mutation)
|
151
165
|
result = mutation.insert(world.kernel)
|
152
|
-
hooks.run(:mutation_insert_post, mutation)
|
166
|
+
hooks.run(:mutation_insert_post, mutation: mutation)
|
153
167
|
|
154
168
|
result.either(
|
155
169
|
->(_) { Result::Test::VoidValue.instance },
|
@@ -163,4 +177,5 @@ module Mutant
|
|
163
177
|
end
|
164
178
|
|
165
179
|
end # Env
|
180
|
+
# rubocop:enable Metrics/ClassLength
|
166
181
|
end # Mutant
|
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
@@ -4,7 +4,7 @@ module Mutant
|
|
4
4
|
|
5
5
|
# Abstract base class mutant test framework integrations
|
6
6
|
class Integration
|
7
|
-
include AbstractType, Adamantium, Anima.new(:expression_parser, :world)
|
7
|
+
include AbstractType, Adamantium, Anima.new(:arguments, :expression_parser, :world)
|
8
8
|
|
9
9
|
LOAD_MESSAGE = <<~'MESSAGE'
|
10
10
|
Unable to load integration mutant-%<integration_name>s:
|
@@ -26,18 +26,47 @@ module Mutant
|
|
26
26
|
|
27
27
|
private_constant(*constants(false))
|
28
28
|
|
29
|
+
class Config
|
30
|
+
include Adamantium, Anima.new(:name, :arguments)
|
31
|
+
|
32
|
+
DEFAULT = new(arguments: EMPTY_ARRAY, name: nil)
|
33
|
+
|
34
|
+
TRANSFORM = Transform::Sequence.new(
|
35
|
+
steps: [
|
36
|
+
Transform::Primitive.new(primitive: Hash),
|
37
|
+
Transform::Hash.new(
|
38
|
+
optional: [
|
39
|
+
Transform::Hash::Key.new(transform: Transform::STRING, value: 'name'),
|
40
|
+
Transform::Hash::Key.new(transform: Transform::STRING_ARRAY, value: 'arguments')
|
41
|
+
],
|
42
|
+
required: []
|
43
|
+
),
|
44
|
+
Transform::Hash::Symbolize.new,
|
45
|
+
Transform::Success.new(block: DEFAULT.method(:with))
|
46
|
+
]
|
47
|
+
)
|
48
|
+
|
49
|
+
def merge(other)
|
50
|
+
self.class.new(
|
51
|
+
name: other.name || name,
|
52
|
+
arguments: arguments + other.arguments
|
53
|
+
)
|
54
|
+
end
|
55
|
+
end # Config
|
56
|
+
|
29
57
|
# Setup integration
|
30
58
|
#
|
31
59
|
# @param env [Bootstrap]
|
32
60
|
#
|
33
61
|
# @return [Either<String, Integration>]
|
34
62
|
def self.setup(env)
|
35
|
-
|
36
|
-
|
37
|
-
|
63
|
+
integration_config = env.config.integration
|
64
|
+
|
65
|
+
return Either::Left.new(INTEGRATION_MISSING) unless integration_config.name
|
38
66
|
|
39
67
|
attempt_require(env).bind { attempt_const_get(env) }.fmap do |klass|
|
40
68
|
klass.new(
|
69
|
+
arguments: integration_config.arguments,
|
41
70
|
expression_parser: env.config.expression_parser,
|
42
71
|
world: env.world
|
43
72
|
).setup
|
@@ -46,7 +75,7 @@ module Mutant
|
|
46
75
|
|
47
76
|
# rubocop:disable Style/MultilineBlockChain
|
48
77
|
def self.attempt_require(env)
|
49
|
-
integration_name = env.config.integration
|
78
|
+
integration_name = env.config.integration.name
|
50
79
|
|
51
80
|
Either.wrap_error(LoadError) do
|
52
81
|
env.world.kernel.require("mutant/integration/#{integration_name}")
|
@@ -61,7 +90,7 @@ module Mutant
|
|
61
90
|
# rubocop:enable Style/MultilineBlockChain
|
62
91
|
|
63
92
|
def self.attempt_const_get(env)
|
64
|
-
integration_name = env.config.integration
|
93
|
+
integration_name = env.config.integration.name
|
65
94
|
constant_name = integration_name.capitalize
|
66
95
|
|
67
96
|
Either.wrap_error(NameError) { const_get(constant_name) }.lmap do |exception|
|
@@ -88,11 +117,21 @@ module Mutant
|
|
88
117
|
# @return [Result::Test]
|
89
118
|
abstract_method :call
|
90
119
|
|
91
|
-
#
|
120
|
+
# All tests this integration can run
|
121
|
+
#
|
122
|
+
# Some tests may not be available for mutation testing.
|
123
|
+
# See #available_tests
|
92
124
|
#
|
93
125
|
# @return [Enumerable<Test>]
|
94
126
|
abstract_method :all_tests
|
95
127
|
|
128
|
+
# All tests available for mutation testing
|
129
|
+
#
|
130
|
+
# Subset ofr #al_tests
|
131
|
+
#
|
132
|
+
# @return [Enumerable<Test>]
|
133
|
+
abstract_method :available_tests
|
134
|
+
|
96
135
|
private
|
97
136
|
|
98
137
|
def timer
|
@@ -205,10 +205,6 @@ module Mutant
|
|
205
205
|
_pid, status = world.process.wait2(@pid, Process::WNOHANG)
|
206
206
|
status
|
207
207
|
end
|
208
|
-
|
209
|
-
def add_result(result)
|
210
|
-
@result = defined?(@result) ? @result.add_error(result) : result
|
211
|
-
end
|
212
208
|
end # Parent
|
213
209
|
# rubocop:enable Metrics/ClassLength
|
214
210
|
|
@@ -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,
|
@@ -16,7 +16,7 @@ module Mutant
|
|
16
16
|
# rubocop:disable Metrics/AbcSize
|
17
17
|
def run
|
18
18
|
info 'Matcher: %s', object.matcher.inspect
|
19
|
-
info 'Integration: %s', object.integration || 'null'
|
19
|
+
info 'Integration: %s', object.integration.name || 'null'
|
20
20
|
info 'Jobs: %s', object.jobs || 'auto'
|
21
21
|
info 'Includes: %s', object.includes
|
22
22
|
info 'Requires: %s', object.requires
|
@@ -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
data/lib/mutant.rb
CHANGED
@@ -346,7 +346,7 @@ module Mutant
|
|
346
346
|
fail_fast: false,
|
347
347
|
hooks: EMPTY_ARRAY,
|
348
348
|
includes: EMPTY_ARRAY,
|
349
|
-
integration:
|
349
|
+
integration: Integration::Config::DEFAULT,
|
350
350
|
isolation: Mutant::Isolation::Fork.new(world: WORLD),
|
351
351
|
jobs: nil,
|
352
352
|
matcher: Matcher::Config::DEFAULT,
|
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.21
|
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-
|
11
|
+
date: 2023-06-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: diff-lcs
|
@@ -72,14 +72,14 @@ dependencies:
|
|
72
72
|
requirements:
|
73
73
|
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: 0.6.
|
75
|
+
version: 0.6.8
|
76
76
|
type: :runtime
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: 0.6.
|
82
|
+
version: 0.6.8
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: parallel
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|