mutant 0.10.6 → 0.10.7
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.rb +4 -3
- data/lib/mutant/cli/command/run.rb +5 -2
- data/lib/mutant/config.rb +59 -34
- data/lib/mutant/env.rb +14 -4
- data/lib/mutant/integration.rb +7 -10
- data/lib/mutant/integration/null.rb +0 -1
- data/lib/mutant/isolation.rb +11 -48
- data/lib/mutant/isolation/fork.rb +107 -40
- data/lib/mutant/isolation/none.rb +18 -5
- data/lib/mutant/license/subscription/commercial.rb +2 -3
- data/lib/mutant/license/subscription/opensource.rb +0 -1
- data/lib/mutant/matcher/method/instance.rb +0 -2
- data/lib/mutant/mutator/node/send.rb +1 -1
- data/lib/mutant/parallel.rb +0 -1
- data/lib/mutant/parallel/worker.rb +0 -2
- data/lib/mutant/reporter/cli.rb +0 -2
- data/lib/mutant/reporter/cli/printer/config.rb +9 -5
- data/lib/mutant/reporter/cli/printer/coverage_result.rb +19 -0
- data/lib/mutant/reporter/cli/printer/env_progress.rb +2 -0
- data/lib/mutant/reporter/cli/printer/isolation_result.rb +19 -35
- data/lib/mutant/reporter/cli/printer/mutation_result.rb +4 -9
- data/lib/mutant/reporter/cli/printer/subject_result.rb +2 -2
- data/lib/mutant/result.rb +81 -30
- data/lib/mutant/runner/sink.rb +12 -5
- data/lib/mutant/timer.rb +60 -11
- data/lib/mutant/transform.rb +25 -21
- data/lib/mutant/version.rb +1 -1
- data/lib/mutant/warnings.rb +0 -1
- data/lib/mutant/world.rb +15 -0
- metadata +3 -5
- data/lib/mutant/reporter/cli/printer/mutation_progress_result.rb +0 -28
- data/lib/mutant/reporter/cli/printer/subject_progress.rb +0 -58
- data/lib/mutant/reporter/cli/printer/test_result.rb +0 -32
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: defe26105634d28a81b10e876713ab4e29a53a5d78ac8fe7e2d8a44d32b644c6
|
4
|
+
data.tar.gz: fad9645931798ed165a249e8e8d4a1c032896ceba6b823a1464ff2f60a7cadbb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 79da9f75f18510bbf81e43e2b98e43c4b92ddf2e6cb68b66828f9f4240f7d3e8747d6e475130f83a2d1edec8c1dda43a34f48524bd7439372e545a046099c28d
|
7
|
+
data.tar.gz: 7d8aac2776eca84aff051b7a146863bb271c5df4ec7dc344f041d3a22a17b4d87f9f48d5201e2dceec30f548adc7135d443ee78bc00764a9399fb677f2cf0aa5
|
data/lib/mutant.rb
CHANGED
@@ -179,16 +179,14 @@ require 'mutant/reporter/sequence'
|
|
179
179
|
require 'mutant/reporter/cli'
|
180
180
|
require 'mutant/reporter/cli/printer'
|
181
181
|
require 'mutant/reporter/cli/printer/config'
|
182
|
+
require 'mutant/reporter/cli/printer/coverage_result'
|
182
183
|
require 'mutant/reporter/cli/printer/env'
|
183
184
|
require 'mutant/reporter/cli/printer/env_progress'
|
184
185
|
require 'mutant/reporter/cli/printer/env_result'
|
185
186
|
require 'mutant/reporter/cli/printer/isolation_result'
|
186
|
-
require 'mutant/reporter/cli/printer/mutation_progress_result'
|
187
187
|
require 'mutant/reporter/cli/printer/mutation_result'
|
188
188
|
require 'mutant/reporter/cli/printer/status_progressive'
|
189
|
-
require 'mutant/reporter/cli/printer/subject_progress'
|
190
189
|
require 'mutant/reporter/cli/printer/subject_result'
|
191
|
-
require 'mutant/reporter/cli/printer/test_result'
|
192
190
|
require 'mutant/reporter/cli/format'
|
193
191
|
require 'mutant/repository'
|
194
192
|
require 'mutant/repository/diff'
|
@@ -219,12 +217,14 @@ module Mutant
|
|
219
217
|
stderr: $stderr,
|
220
218
|
stdout: $stdout,
|
221
219
|
thread: Thread,
|
220
|
+
timer: Timer.new(Process),
|
222
221
|
warnings: Warnings.new(Warning)
|
223
222
|
)
|
224
223
|
|
225
224
|
# Reopen class to initialize constant to avoid dep circle
|
226
225
|
class Config
|
227
226
|
DEFAULT = new(
|
227
|
+
coverage_criteria: Config::CoverageCriteria::DEFAULT,
|
228
228
|
expression_parser: Expression::Parser.new([
|
229
229
|
Expression::Method,
|
230
230
|
Expression::Methods,
|
@@ -237,6 +237,7 @@ module Mutant
|
|
237
237
|
isolation: Mutant::Isolation::Fork.new(WORLD),
|
238
238
|
jobs: nil,
|
239
239
|
matcher: Matcher::Config::DEFAULT,
|
240
|
+
mutation_timeout: nil,
|
240
241
|
reporter: Reporter::CLI.build(WORLD.stdout),
|
241
242
|
requires: EMPTY_ARRAY,
|
242
243
|
zombie: false
|
@@ -35,7 +35,7 @@ module Mutant
|
|
35
35
|
|
36
36
|
def initialize(attributes)
|
37
37
|
super(attributes)
|
38
|
-
@config = Config
|
38
|
+
@config = Config.env
|
39
39
|
end
|
40
40
|
|
41
41
|
def execute
|
@@ -49,7 +49,7 @@ module Mutant
|
|
49
49
|
end
|
50
50
|
|
51
51
|
def expand(file_config)
|
52
|
-
@config =
|
52
|
+
@config = @config.merge(file_config)
|
53
53
|
end
|
54
54
|
|
55
55
|
def soft_fail(result)
|
@@ -153,6 +153,9 @@ module Mutant
|
|
153
153
|
parser.on('-j', '--jobs NUMBER', 'Number of kill jobs. Defaults to number of processors.') do |number|
|
154
154
|
set(jobs: Integer(number))
|
155
155
|
end
|
156
|
+
parser.on('-t', '--mutation-timeout NUMBER', 'Per mutation analysis timeout') do |number|
|
157
|
+
set(mutation_timeout: Float(number))
|
158
|
+
end
|
156
159
|
end
|
157
160
|
end # Run
|
158
161
|
# rubocop:enable Metrics/ClassLength
|
data/lib/mutant/config.rb
CHANGED
@@ -7,6 +7,7 @@ module Mutant
|
|
7
7
|
# to current environment is being represented by the Mutant::Env object.
|
8
8
|
class Config
|
9
9
|
include Adamantium::Flat, Anima.new(
|
10
|
+
:coverage_criteria,
|
10
11
|
:expression_parser,
|
11
12
|
:fail_fast,
|
12
13
|
:includes,
|
@@ -14,6 +15,7 @@ module Mutant
|
|
14
15
|
:isolation,
|
15
16
|
:jobs,
|
16
17
|
:matcher,
|
18
|
+
:mutation_timeout,
|
17
19
|
:reporter,
|
18
20
|
:requires,
|
19
21
|
:zombie
|
@@ -23,30 +25,6 @@ module Mutant
|
|
23
25
|
define_method(:"#{name}?") { public_send(name) }
|
24
26
|
end
|
25
27
|
|
26
|
-
boolean = Transform::Boolean.new
|
27
|
-
integer = Transform::Primitive.new(Integer)
|
28
|
-
string = Transform::Primitive.new(String)
|
29
|
-
|
30
|
-
string_array = Transform::Array.new(string)
|
31
|
-
|
32
|
-
TRANSFORM = Transform::Sequence.new(
|
33
|
-
[
|
34
|
-
Transform::Exception.new(SystemCallError, :read.to_proc),
|
35
|
-
Transform::Exception.new(YAML::SyntaxError, YAML.method(:safe_load)),
|
36
|
-
Transform::Hash.new(
|
37
|
-
optional: [
|
38
|
-
Transform::Hash::Key.new('fail_fast', boolean),
|
39
|
-
Transform::Hash::Key.new('includes', string_array),
|
40
|
-
Transform::Hash::Key.new('integration', string),
|
41
|
-
Transform::Hash::Key.new('jobs', integer),
|
42
|
-
Transform::Hash::Key.new('requires', string_array)
|
43
|
-
],
|
44
|
-
required: []
|
45
|
-
),
|
46
|
-
Transform::Hash::Symbolize.new
|
47
|
-
]
|
48
|
-
)
|
49
|
-
|
50
28
|
MORE_THAN_ONE_CONFIG_FILE = <<~'MESSAGE'
|
51
29
|
Found more than one candidate for use as implicit config file: %s
|
52
30
|
MESSAGE
|
@@ -57,6 +35,32 @@ module Mutant
|
|
57
35
|
mutant.yml
|
58
36
|
].freeze
|
59
37
|
|
38
|
+
private_constant(*constants(false))
|
39
|
+
|
40
|
+
class CoverageCriteria
|
41
|
+
include Anima.new(:timeout, :test_result)
|
42
|
+
|
43
|
+
DEFAULT = new(
|
44
|
+
timeout: false,
|
45
|
+
test_result: true
|
46
|
+
)
|
47
|
+
|
48
|
+
TRANSFORM =
|
49
|
+
Transform::Sequence.new(
|
50
|
+
[
|
51
|
+
Transform::Hash.new(
|
52
|
+
optional: [
|
53
|
+
Transform::Hash::Key.new('timeout', Transform::BOOLEAN),
|
54
|
+
Transform::Hash::Key.new('test_result', Transform::BOOLEAN)
|
55
|
+
],
|
56
|
+
required: []
|
57
|
+
),
|
58
|
+
Transform::Hash::Symbolize.new,
|
59
|
+
->(value) { Either::Right.new(DEFAULT.with(**value)) }
|
60
|
+
]
|
61
|
+
)
|
62
|
+
end # CoverageCriteria
|
63
|
+
|
60
64
|
# Merge with other config
|
61
65
|
#
|
62
66
|
# @param [Config] other
|
@@ -64,18 +68,17 @@ module Mutant
|
|
64
68
|
# @return [Config]
|
65
69
|
def merge(other)
|
66
70
|
other.with(
|
67
|
-
fail_fast:
|
68
|
-
includes:
|
69
|
-
jobs:
|
70
|
-
integration:
|
71
|
-
|
72
|
-
|
73
|
-
|
71
|
+
fail_fast: fail_fast || other.fail_fast,
|
72
|
+
includes: other.includes + includes,
|
73
|
+
jobs: other.jobs || jobs,
|
74
|
+
integration: other.integration || integration,
|
75
|
+
mutation_timeout: other.mutation_timeout || mutation_timeout,
|
76
|
+
matcher: matcher.merge(other.matcher),
|
77
|
+
requires: other.requires + requires,
|
78
|
+
zombie: zombie || other.zombie
|
74
79
|
)
|
75
80
|
end
|
76
81
|
|
77
|
-
private_constant(*constants(false))
|
78
|
-
|
79
82
|
# Load config file
|
80
83
|
#
|
81
84
|
# @param [World] world
|
@@ -98,7 +101,7 @@ module Mutant
|
|
98
101
|
def self.load_contents(path)
|
99
102
|
Transform::Named
|
100
103
|
.new(path.to_s, TRANSFORM)
|
101
|
-
.
|
104
|
+
.call(path)
|
102
105
|
.lmap(&:compact_message)
|
103
106
|
end
|
104
107
|
private_class_method :load_contents
|
@@ -109,5 +112,27 @@ module Mutant
|
|
109
112
|
def self.env
|
110
113
|
DEFAULT.with(jobs: Etc.nprocessors)
|
111
114
|
end
|
115
|
+
|
116
|
+
TRANSFORM = Transform::Sequence.new(
|
117
|
+
[
|
118
|
+
Transform::Exception.new(SystemCallError, :read.to_proc),
|
119
|
+
Transform::Exception.new(YAML::SyntaxError, YAML.method(:safe_load)),
|
120
|
+
Transform::Hash.new(
|
121
|
+
optional: [
|
122
|
+
Transform::Hash::Key.new('coverage_criteria', CoverageCriteria::TRANSFORM),
|
123
|
+
Transform::Hash::Key.new('fail_fast', Transform::BOOLEAN),
|
124
|
+
Transform::Hash::Key.new('includes', Transform::STRING_ARRAY),
|
125
|
+
Transform::Hash::Key.new('integration', Transform::STRING),
|
126
|
+
Transform::Hash::Key.new('jobs', Transform::INTEGER),
|
127
|
+
Transform::Hash::Key.new('mutation_timeout', Transform::FLOAT),
|
128
|
+
Transform::Hash::Key.new('requires', Transform::STRING_ARRAY)
|
129
|
+
],
|
130
|
+
required: []
|
131
|
+
),
|
132
|
+
Transform::Hash::Symbolize.new
|
133
|
+
]
|
134
|
+
)
|
135
|
+
|
136
|
+
private_constant(:TRANSFORM)
|
112
137
|
end # Config
|
113
138
|
end # Mutant
|
data/lib/mutant/env.rb
CHANGED
@@ -24,10 +24,15 @@ module Mutant
|
|
24
24
|
# @param [Config] config
|
25
25
|
#
|
26
26
|
# @return [Env]
|
27
|
+
#
|
28
|
+
# rubocop:disable Metrics/MethodLength
|
27
29
|
def self.empty(world, config)
|
28
30
|
new(
|
29
31
|
config: config,
|
30
|
-
integration: Integration::Null.new(
|
32
|
+
integration: Integration::Null.new(
|
33
|
+
expression_parser: config.expression_parser,
|
34
|
+
timer: world.timer
|
35
|
+
),
|
31
36
|
matchable_scopes: EMPTY_ARRAY,
|
32
37
|
mutations: EMPTY_ARRAY,
|
33
38
|
parser: Parser.new,
|
@@ -36,6 +41,7 @@ module Mutant
|
|
36
41
|
world: world
|
37
42
|
)
|
38
43
|
end
|
44
|
+
# rubocop:enable Metrics/MethodLength
|
39
45
|
|
40
46
|
# Kill mutation
|
41
47
|
#
|
@@ -43,14 +49,14 @@ module Mutant
|
|
43
49
|
#
|
44
50
|
# @return [Result::Mutation]
|
45
51
|
def kill(mutation)
|
46
|
-
start =
|
52
|
+
start = timer.now
|
47
53
|
|
48
54
|
tests = selections.fetch(mutation.subject)
|
49
55
|
|
50
56
|
Result::Mutation.new(
|
51
57
|
isolation_result: run_mutation_tests(mutation, tests),
|
52
58
|
mutation: mutation,
|
53
|
-
runtime:
|
59
|
+
runtime: timer.now - start
|
54
60
|
)
|
55
61
|
end
|
56
62
|
|
@@ -127,7 +133,7 @@ module Mutant
|
|
127
133
|
private
|
128
134
|
|
129
135
|
def run_mutation_tests(mutation, tests)
|
130
|
-
config.isolation.call do
|
136
|
+
config.isolation.call(config.mutation_timeout) do
|
131
137
|
result = mutation.insert(world.kernel)
|
132
138
|
|
133
139
|
if result.equal?(Loader::Result::VoidValue.instance)
|
@@ -138,5 +144,9 @@ module Mutant
|
|
138
144
|
end
|
139
145
|
end
|
140
146
|
|
147
|
+
def timer
|
148
|
+
world.timer
|
149
|
+
end
|
150
|
+
|
141
151
|
end # Env
|
142
152
|
end # Mutant
|
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::Flat,
|
7
|
+
include AbstractType, Adamantium::Flat, Anima.new(:expression_parser, :timer)
|
8
8
|
|
9
9
|
LOAD_MESSAGE = <<~'MESSAGE'
|
10
10
|
Unable to load integration mutant-%<integration_name>s:
|
@@ -27,9 +27,12 @@ module Mutant
|
|
27
27
|
#
|
28
28
|
# @return [Either<String, Integration>]
|
29
29
|
def self.setup(env)
|
30
|
-
attempt_require(env)
|
31
|
-
.
|
32
|
-
|
30
|
+
attempt_require(env).bind { attempt_const_get(env) }.fmap do |klass|
|
31
|
+
klass.new(
|
32
|
+
expression_parser: env.config.expression_parser,
|
33
|
+
timer: env.world.timer
|
34
|
+
).setup
|
35
|
+
end
|
33
36
|
end
|
34
37
|
|
35
38
|
# rubocop:disable Style/MultilineBlockChain
|
@@ -80,11 +83,5 @@ module Mutant
|
|
80
83
|
#
|
81
84
|
# @return [Enumerable<Test>]
|
82
85
|
abstract_method :all_tests
|
83
|
-
|
84
|
-
private
|
85
|
-
|
86
|
-
def expression_parser
|
87
|
-
config.expression_parser
|
88
|
-
end
|
89
86
|
end # Integration
|
90
87
|
end # Mutant
|
data/lib/mutant/isolation.rb
CHANGED
@@ -7,57 +7,20 @@ module Mutant
|
|
7
7
|
|
8
8
|
# Isolated computation result
|
9
9
|
class Result
|
10
|
-
include
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
# Add error on top of current result
|
21
|
-
#
|
22
|
-
# @param [Result] error
|
23
|
-
#
|
24
|
-
# @return [Result]
|
25
|
-
def add_error(error)
|
26
|
-
ErrorChain.new(error, self)
|
27
|
-
end
|
28
|
-
|
29
|
-
# The log captured from integration
|
30
|
-
#
|
31
|
-
# @return [String]
|
32
|
-
def log
|
33
|
-
NULL_LOG
|
34
|
-
end
|
35
|
-
|
36
|
-
# Test for success
|
10
|
+
include Anima.new(
|
11
|
+
:exception,
|
12
|
+
:log,
|
13
|
+
:process_status,
|
14
|
+
:timeout,
|
15
|
+
:value
|
16
|
+
)
|
17
|
+
|
18
|
+
# Test for successful result
|
37
19
|
#
|
38
20
|
# @return [Boolean]
|
39
|
-
def
|
40
|
-
|
21
|
+
def valid_value?
|
22
|
+
timeout.nil? && exception.nil? && (process_status.nil? || process_status.success?)
|
41
23
|
end
|
42
|
-
|
43
|
-
# Successful result producing value
|
44
|
-
class Success < self
|
45
|
-
include Concord::Public.new(:value, :log)
|
46
|
-
|
47
|
-
def self.new(_value, _log = '')
|
48
|
-
super
|
49
|
-
end
|
50
|
-
end # Success
|
51
|
-
|
52
|
-
# Unsuccessful result by unexpected exception
|
53
|
-
class Exception < self
|
54
|
-
include Concord::Public.new(:value)
|
55
|
-
end # Error
|
56
|
-
|
57
|
-
# Result when there where many results
|
58
|
-
class ErrorChain < Result
|
59
|
-
include Concord::Public.new(:value, :next)
|
60
|
-
end # ChainError
|
61
24
|
end # Result
|
62
25
|
|
63
26
|
# Call block in isolation
|
@@ -3,22 +3,36 @@
|
|
3
3
|
module Mutant
|
4
4
|
class Isolation
|
5
5
|
# Isolation via the fork(2) systemcall.
|
6
|
+
#
|
7
|
+
# Communication between parent and child process is done
|
8
|
+
# via anonymous pipes.
|
9
|
+
#
|
10
|
+
# Timeouts are initially handled relatively efficiently via IO.select
|
11
|
+
# but once the child process pipes are on eof via busy looping on
|
12
|
+
# waitpid2 with Process::WNOHANG set.
|
13
|
+
#
|
14
|
+
# Handling timeouts this way is not the conceptually most
|
15
|
+
# efficient solution. But its cross platform.
|
16
|
+
#
|
17
|
+
# Design constraints:
|
18
|
+
#
|
19
|
+
# * Support Linux
|
20
|
+
# * Support MacOSX
|
21
|
+
# * Avoid platform specific APIs and code.
|
22
|
+
# * Only use ruby corelib.
|
23
|
+
# * Do not use any named resource.
|
24
|
+
# * Never block on latency inducing systemcall without a
|
25
|
+
# timeout.
|
26
|
+
# * Child process freezing before closing the pipes needs to
|
27
|
+
# be detected by parent process.
|
28
|
+
# * Child process freezing after closing the pipes needs to be
|
29
|
+
# detected by parent process.
|
6
30
|
class Fork < self
|
7
31
|
include(Adamantium::Flat, Concord.new(:world))
|
8
32
|
|
9
33
|
READ_SIZE = 4096
|
10
34
|
|
11
|
-
ATTRIBUTES = %i[block log_pipe result_pipe world].freeze
|
12
|
-
|
13
|
-
# Unsuccessful result as child exited nonzero
|
14
|
-
class ChildError < Result
|
15
|
-
include Concord::Public.new(:value, :log)
|
16
|
-
end # ChildError
|
17
|
-
|
18
|
-
# Unsuccessful result as fork failed
|
19
|
-
class ForkError < Result
|
20
|
-
include Equalizer.new
|
21
|
-
end # ForkError
|
35
|
+
ATTRIBUTES = %i[block deadline log_pipe result_pipe world].freeze
|
22
36
|
|
23
37
|
# Pipe abstraction
|
24
38
|
class Pipe
|
@@ -50,7 +64,6 @@ module Mutant
|
|
50
64
|
end
|
51
65
|
end # Pipe
|
52
66
|
|
53
|
-
# ignore :reek:InstanceVariableAssumption
|
54
67
|
class Parent
|
55
68
|
include(
|
56
69
|
Anima.new(*ATTRIBUTES),
|
@@ -64,15 +77,28 @@ module Mutant
|
|
64
77
|
#
|
65
78
|
# @return [Result]
|
66
79
|
def call
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
@
|
80
|
+
@exception = nil
|
81
|
+
@log_fragments = []
|
82
|
+
@timeout = nil
|
83
|
+
@value = nil
|
84
|
+
@pid = start_child
|
85
|
+
|
86
|
+
read_child_result
|
87
|
+
result
|
72
88
|
end
|
73
89
|
|
74
90
|
private
|
75
91
|
|
92
|
+
def result
|
93
|
+
Result.new(
|
94
|
+
exception: @exception,
|
95
|
+
log: @log_fragments.join,
|
96
|
+
process_status: @process_status,
|
97
|
+
timeout: @timeout,
|
98
|
+
value: @value
|
99
|
+
)
|
100
|
+
end
|
101
|
+
|
76
102
|
def start_child
|
77
103
|
world.process.fork do
|
78
104
|
Child.call(
|
@@ -85,30 +111,43 @@ module Mutant
|
|
85
111
|
end
|
86
112
|
|
87
113
|
# rubocop:disable Metrics/MethodLength
|
88
|
-
def read_child_result
|
114
|
+
def read_child_result
|
89
115
|
result_fragments = []
|
90
|
-
log_fragments = []
|
91
116
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
117
|
+
targets =
|
118
|
+
{
|
119
|
+
log_pipe.parent => @log_fragments,
|
120
|
+
result_pipe.parent => result_fragments
|
121
|
+
}
|
96
122
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
123
|
+
read_targets(targets)
|
124
|
+
|
125
|
+
if targets.empty?
|
126
|
+
load_result(result_fragments)
|
127
|
+
terminate_graceful
|
101
128
|
else
|
102
|
-
|
129
|
+
@timeout = deadline.allowed_time
|
130
|
+
terminate_ungraceful
|
103
131
|
end
|
104
|
-
ensure
|
105
|
-
wait_child(pid, log_fragments)
|
106
132
|
end
|
107
133
|
# rubocop:enable Metrics/MethodLength
|
108
134
|
|
109
|
-
def
|
135
|
+
def load_result(result_fragments)
|
136
|
+
@value = world.marshal.load(result_fragments.join)
|
137
|
+
rescue ArgumentError => exception
|
138
|
+
@exception = exception
|
139
|
+
end
|
140
|
+
|
141
|
+
# rubocop:disable Metrics/MethodLength
|
142
|
+
def read_targets(targets)
|
110
143
|
until targets.empty?
|
111
|
-
|
144
|
+
status = deadline.status
|
145
|
+
|
146
|
+
break unless status.ok?
|
147
|
+
|
148
|
+
ready, = world.io.select(targets.keys, [], [], status.time_left)
|
149
|
+
|
150
|
+
break unless ready
|
112
151
|
|
113
152
|
ready.each do |fd|
|
114
153
|
if fd.eof?
|
@@ -119,14 +158,42 @@ module Mutant
|
|
119
158
|
end
|
120
159
|
end
|
121
160
|
end
|
161
|
+
# rubocop:enable Metrics/MethodLength
|
122
162
|
|
123
|
-
|
124
|
-
|
163
|
+
# rubocop:disable Metrics/MethodLength
|
164
|
+
def terminate_graceful
|
165
|
+
status = nil
|
166
|
+
|
167
|
+
loop do
|
168
|
+
status = peek_child
|
169
|
+
break if status || deadline.expired?
|
170
|
+
world.kernel.sleep(0.1)
|
171
|
+
end
|
125
172
|
|
126
|
-
|
127
|
-
|
173
|
+
if status
|
174
|
+
handle_status(status)
|
175
|
+
else
|
176
|
+
terminate_ungraceful
|
128
177
|
end
|
129
178
|
end
|
179
|
+
# rubocop:enable Metrics/MethodLength
|
180
|
+
|
181
|
+
def terminate_ungraceful
|
182
|
+
world.process.kill('KILL', @pid)
|
183
|
+
|
184
|
+
_pid, status = world.process.wait2(@pid)
|
185
|
+
|
186
|
+
handle_status(status)
|
187
|
+
end
|
188
|
+
|
189
|
+
def handle_status(status)
|
190
|
+
@process_status = status
|
191
|
+
end
|
192
|
+
|
193
|
+
def peek_child
|
194
|
+
_pid, status = world.process.wait2(@pid, Process::WNOHANG)
|
195
|
+
status
|
196
|
+
end
|
130
197
|
|
131
198
|
def add_result(result)
|
132
199
|
@result = defined?(@result) ? @result.add_error(result) : result
|
@@ -157,17 +224,16 @@ module Mutant
|
|
157
224
|
# Call block in isolation
|
158
225
|
#
|
159
226
|
# @return [Result]
|
160
|
-
# execution result
|
161
|
-
#
|
162
|
-
# ignore :reek:NestedIterators
|
163
227
|
#
|
164
228
|
# rubocop:disable Metrics/MethodLength
|
165
|
-
def call(&block)
|
229
|
+
def call(timeout, &block)
|
230
|
+
deadline = world.deadline(timeout)
|
166
231
|
io = world.io
|
167
232
|
Pipe.with(io) do |result|
|
168
233
|
Pipe.with(io) do |log|
|
169
234
|
Parent.call(
|
170
235
|
block: block,
|
236
|
+
deadline: deadline,
|
171
237
|
log_pipe: log,
|
172
238
|
result_pipe: result,
|
173
239
|
world: world
|
@@ -176,6 +242,7 @@ module Mutant
|
|
176
242
|
end
|
177
243
|
end
|
178
244
|
# rubocop:enable Metrics/MethodLength
|
245
|
+
|
179
246
|
end # Fork
|
180
247
|
end # Isolation
|
181
248
|
end # Mutant
|