mutant 0.8.24 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.circleci/config.yml +3 -3
- data/Changelog.md +14 -654
- data/Gemfile +13 -0
- data/Gemfile.lock +59 -64
- data/LICENSE +271 -20
- data/README.md +73 -140
- data/Rakefile +0 -21
- data/bin/mutant +7 -2
- data/config/reek.yml +2 -1
- data/config/rubocop.yml +5 -9
- data/docs/incremental.md +76 -0
- data/docs/known-problems.md +0 -14
- data/docs/mutant-minitest.md +1 -1
- data/docs/mutant-rspec.md +2 -24
- data/lib/mutant.rb +45 -53
- data/lib/mutant/ast/nodes.rb +0 -2
- data/lib/mutant/ast/types.rb +1 -117
- data/lib/mutant/base.rb +192 -0
- data/lib/mutant/bootstrap.rb +145 -0
- data/lib/mutant/cli.rb +68 -54
- data/lib/mutant/config.rb +119 -6
- data/lib/mutant/env.rb +94 -8
- data/lib/mutant/expression.rb +6 -1
- data/lib/mutant/expression/parser.rb +9 -31
- data/lib/mutant/integration.rb +64 -36
- data/lib/mutant/isolation.rb +16 -1
- data/lib/mutant/isolation/fork.rb +105 -40
- data/lib/mutant/license.rb +34 -0
- data/lib/mutant/license/subscription.rb +47 -0
- data/lib/mutant/license/subscription/commercial.rb +57 -0
- data/lib/mutant/license/subscription/opensource.rb +77 -0
- data/lib/mutant/loader.rb +27 -4
- data/lib/mutant/matcher.rb +48 -1
- data/lib/mutant/matcher/chain.rb +1 -1
- data/lib/mutant/matcher/config.rb +0 -2
- data/lib/mutant/matcher/filter.rb +1 -1
- data/lib/mutant/matcher/method.rb +11 -7
- data/lib/mutant/matcher/methods.rb +1 -1
- data/lib/mutant/matcher/namespace.rb +1 -1
- data/lib/mutant/matcher/null.rb +1 -1
- data/lib/mutant/matcher/scope.rb +1 -1
- data/lib/mutant/meta/example/dsl.rb +0 -8
- data/lib/mutant/mutation.rb +1 -2
- data/lib/mutant/mutator/node.rb +2 -9
- data/lib/mutant/mutator/node/arguments.rb +1 -1
- data/lib/mutant/mutator/node/class.rb +0 -8
- data/lib/mutant/mutator/node/define.rb +0 -12
- data/lib/mutant/mutator/node/generic.rb +30 -44
- data/lib/mutant/mutator/node/index.rb +4 -4
- data/lib/mutant/mutator/node/literal/regex.rb +0 -39
- data/lib/mutant/mutator/node/send.rb +13 -12
- data/lib/mutant/parallel.rb +61 -40
- data/lib/mutant/parallel/driver.rb +59 -0
- data/lib/mutant/parallel/source.rb +6 -2
- data/lib/mutant/parallel/worker.rb +63 -45
- data/lib/mutant/range.rb +15 -0
- data/lib/mutant/reporter/cli.rb +5 -11
- data/lib/mutant/reporter/cli/format.rb +3 -46
- data/lib/mutant/reporter/cli/printer/config.rb +5 -6
- data/lib/mutant/reporter/cli/printer/env.rb +40 -0
- data/lib/mutant/reporter/cli/printer/env_progress.rb +13 -17
- data/lib/mutant/reporter/cli/printer/isolation_result.rb +17 -3
- data/lib/mutant/reporter/cli/printer/mutation_result.rb +2 -3
- data/lib/mutant/reporter/cli/printer/status_progressive.rb +19 -10
- data/lib/mutant/repository.rb +0 -65
- data/lib/mutant/repository/diff.rb +104 -0
- data/lib/mutant/repository/diff/ranges.rb +52 -0
- data/lib/mutant/result.rb +16 -7
- data/lib/mutant/runner.rb +38 -47
- data/lib/mutant/runner/sink.rb +1 -1
- data/lib/mutant/selector/null.rb +19 -0
- data/lib/mutant/subject.rb +3 -1
- data/lib/mutant/subject/method/instance.rb +3 -1
- data/lib/mutant/transform.rb +511 -0
- data/lib/mutant/variable.rb +282 -0
- data/lib/mutant/version.rb +1 -1
- data/lib/mutant/warnings.rb +113 -0
- data/meta/case.rb +1 -0
- data/meta/class.rb +0 -9
- data/meta/def.rb +1 -26
- data/meta/regexp.rb +10 -20
- data/meta/send.rb +14 -46
- data/mutant-minitest.gemspec +1 -1
- data/mutant-rspec.gemspec +2 -2
- data/mutant.gemspec +15 -16
- data/mutant.yml +6 -0
- data/spec/integration/mutant/isolation/fork_spec.rb +22 -5
- data/spec/integration/mutant/minitest_spec.rb +3 -2
- data/spec/integration/mutant/rspec_spec.rb +4 -3
- data/spec/integrations.yml +16 -13
- data/spec/shared/base_behavior.rb +45 -0
- data/spec/shared/framework_integration_behavior.rb +43 -14
- data/spec/spec_helper.rb +21 -17
- data/spec/support/corpus.rb +56 -95
- data/spec/support/shared_context.rb +37 -14
- data/spec/support/xspec.rb +7 -3
- data/spec/unit/mutant/bootstrap_spec.rb +216 -0
- data/spec/unit/mutant/cli_spec.rb +173 -117
- data/spec/unit/mutant/config_spec.rb +126 -0
- data/spec/unit/mutant/either_spec.rb +247 -0
- data/spec/unit/mutant/env_spec.rb +162 -40
- data/spec/unit/mutant/expression/method_spec.rb +16 -0
- data/spec/unit/mutant/expression/parser_spec.rb +29 -33
- data/spec/unit/mutant/expression_spec.rb +5 -7
- data/spec/unit/mutant/integration_spec.rb +100 -9
- data/spec/unit/mutant/isolation/fork_spec.rb +125 -67
- data/spec/unit/mutant/isolation/result_spec.rb +33 -1
- data/spec/unit/mutant/license_spec.rb +257 -0
- data/spec/unit/mutant/loader_spec.rb +50 -11
- data/spec/unit/mutant/matcher/compiler_spec.rb +0 -78
- data/spec/unit/mutant/matcher/method/instance_spec.rb +55 -11
- data/spec/unit/mutant/matcher/method/singleton_spec.rb +12 -2
- data/spec/unit/mutant/matcher_spec.rb +102 -0
- data/spec/unit/mutant/maybe_spec.rb +60 -0
- data/spec/unit/mutant/meta/example/dsl_spec.rb +1 -17
- data/spec/unit/mutant/mutation_spec.rb +13 -6
- data/spec/unit/mutant/parallel/driver_spec.rb +112 -14
- data/spec/unit/mutant/parallel/source/array_spec.rb +25 -17
- data/spec/unit/mutant/parallel/worker_spec.rb +182 -44
- data/spec/unit/mutant/parallel_spec.rb +105 -8
- data/spec/unit/mutant/range_spec.rb +141 -0
- data/spec/unit/mutant/reporter/cli/printer/config_spec.rb +7 -21
- data/spec/unit/mutant/reporter/cli/printer/env_progress_spec.rb +15 -6
- data/spec/unit/mutant/reporter/cli/printer/env_result_spec.rb +10 -2
- data/spec/unit/mutant/reporter/cli/printer/isolation_result_spec.rb +12 -4
- data/spec/unit/mutant/reporter/cli/printer/mutation_result_spec.rb +31 -2
- data/spec/unit/mutant/reporter/cli/printer/status_progressive_spec.rb +4 -4
- data/spec/unit/mutant/reporter/cli/printer/subject_result_spec.rb +5 -0
- data/spec/unit/mutant/reporter/cli_spec.rb +46 -123
- data/spec/unit/mutant/repository/diff/ranges_spec.rb +180 -0
- data/spec/unit/mutant/repository/diff_spec.rb +84 -71
- data/spec/unit/mutant/require_highjack_spec.rb +1 -1
- data/spec/unit/mutant/result/env_spec.rb +39 -9
- data/spec/unit/mutant/result/test_spec.rb +14 -0
- data/spec/unit/mutant/runner_spec.rb +88 -41
- data/spec/unit/mutant/selector/expression_spec.rb +11 -10
- data/spec/unit/mutant/selector/null_spec.rb +17 -0
- data/spec/unit/mutant/subject/method/instance_spec.rb +44 -5
- data/spec/unit/mutant/subject/method/singleton_spec.rb +9 -2
- data/spec/unit/mutant/subject_spec.rb +9 -1
- data/spec/unit/mutant/transform/array_spec.rb +92 -0
- data/spec/unit/mutant/transform/bool_spec.rb +63 -0
- data/spec/unit/mutant/transform/error_spec.rb +132 -0
- data/spec/unit/mutant/transform/exception_spec.rb +44 -0
- data/spec/unit/mutant/transform/hash_spec.rb +236 -0
- data/spec/unit/mutant/transform/index_spec.rb +92 -0
- data/spec/unit/mutant/transform/named_spec.rb +49 -0
- data/spec/unit/mutant/transform/primitive_spec.rb +56 -0
- data/spec/unit/mutant/transform/sequence_spec.rb +98 -0
- data/spec/unit/mutant/variable_spec.rb +618 -0
- data/spec/unit/mutant/warnings_spec.rb +89 -0
- data/spec/unit/mutant/world_spec.rb +63 -0
- data/test_app/Gemfile.minitest +0 -2
- metadata +79 -113
- data/.gitattributes +0 -1
- data/.ruby-gemset +0 -1
- data/config/triage.yml +0 -2
- data/lib/mutant/actor.rb +0 -57
- data/lib/mutant/actor/env.rb +0 -31
- data/lib/mutant/actor/mailbox.rb +0 -34
- data/lib/mutant/actor/receiver.rb +0 -42
- data/lib/mutant/actor/sender.rb +0 -26
- data/lib/mutant/ast/meta/restarg.rb +0 -19
- data/lib/mutant/ast/regexp.rb +0 -42
- data/lib/mutant/ast/regexp/transformer.rb +0 -187
- data/lib/mutant/ast/regexp/transformer/direct.rb +0 -123
- data/lib/mutant/ast/regexp/transformer/named_group.rb +0 -59
- data/lib/mutant/ast/regexp/transformer/options_group.rb +0 -83
- data/lib/mutant/ast/regexp/transformer/quantifier.rb +0 -114
- data/lib/mutant/ast/regexp/transformer/recursive.rb +0 -58
- data/lib/mutant/ast/regexp/transformer/root.rb +0 -31
- data/lib/mutant/ast/regexp/transformer/text.rb +0 -60
- data/lib/mutant/env/bootstrap.rb +0 -160
- data/lib/mutant/matcher/compiler.rb +0 -60
- data/lib/mutant/mutator/node/regexp.rb +0 -35
- data/lib/mutant/mutator/node/regexp/alternation_meta.rb +0 -23
- data/lib/mutant/mutator/node/regexp/capture_group.rb +0 -28
- data/lib/mutant/mutator/node/regexp/character_type.rb +0 -32
- data/lib/mutant/mutator/node/regexp/end_of_line_anchor.rb +0 -23
- data/lib/mutant/mutator/node/regexp/end_of_string_or_before_end_of_line_anchor.rb +0 -23
- data/lib/mutant/mutator/node/regexp/greedy_zero_or_more.rb +0 -27
- data/lib/mutant/parallel/master.rb +0 -181
- data/lib/mutant/reporter/cli/printer/status.rb +0 -53
- data/lib/mutant/reporter/cli/tput.rb +0 -46
- data/lib/mutant/warning_filter.rb +0 -61
- data/meta/regexp/character_types.rb +0 -23
- data/meta/regexp/regexp_alternation_meta.rb +0 -13
- data/meta/regexp/regexp_bol_anchor.rb +0 -10
- data/meta/regexp/regexp_bos_anchor.rb +0 -18
- data/meta/regexp/regexp_capture_group.rb +0 -19
- data/meta/regexp/regexp_eol_anchor.rb +0 -10
- data/meta/regexp/regexp_eos_anchor.rb +0 -8
- data/meta/regexp/regexp_eos_ob_eol_anchor.rb +0 -10
- data/meta/regexp/regexp_greedy_zero_or_more.rb +0 -12
- data/meta/regexp/regexp_root_expression.rb +0 -10
- data/meta/restarg.rb +0 -10
- data/spec/support/fake_actor.rb +0 -111
- data/spec/support/warning.rb +0 -66
- data/spec/unit/mutant/actor/binding_spec.rb +0 -34
- data/spec/unit/mutant/actor/env_spec.rb +0 -31
- data/spec/unit/mutant/actor/mailbox_spec.rb +0 -28
- data/spec/unit/mutant/actor/message_spec.rb +0 -25
- data/spec/unit/mutant/actor/receiver_spec.rb +0 -58
- data/spec/unit/mutant/actor/sender_spec.rb +0 -24
- data/spec/unit/mutant/ast/regexp/parse_spec.rb +0 -19
- data/spec/unit/mutant/ast/regexp/transformer/lookup_table/table_spec.rb +0 -21
- data/spec/unit/mutant/ast/regexp/transformer/lookup_table_spec.rb +0 -35
- data/spec/unit/mutant/ast/regexp/transformer_spec.rb +0 -21
- data/spec/unit/mutant/ast/regexp_spec.rb +0 -704
- data/spec/unit/mutant/env/bootstrap_spec.rb +0 -188
- data/spec/unit/mutant/matcher/compiler/subject_prefix_spec.rb +0 -26
- data/spec/unit/mutant/parallel/master_spec.rb +0 -338
- data/spec/unit/mutant/reporter/cli/printer/status_spec.rb +0 -121
- data/spec/unit/mutant/reporter/cli/tput_spec.rb +0 -50
- data/spec/unit/mutant/warning_filter_spec.rb +0 -106
- data/spec/unit/mutant_spec.rb +0 -17
- data/test_app/Gemfile.rspec3.7 +0 -7
data/lib/mutant/env.rb
CHANGED
@@ -4,20 +4,39 @@ module Mutant
|
|
4
4
|
# Abstract base class for mutant environments
|
5
5
|
class Env
|
6
6
|
include Adamantium::Flat, Anima.new(
|
7
|
-
:actor_env,
|
8
7
|
:config,
|
9
8
|
:integration,
|
10
9
|
:matchable_scopes,
|
11
10
|
:mutations,
|
12
11
|
:parser,
|
13
12
|
:selector,
|
14
|
-
:subjects
|
13
|
+
:subjects,
|
14
|
+
:world
|
15
15
|
)
|
16
16
|
|
17
17
|
SEMANTICS_MESSAGE =
|
18
18
|
"Fix your lib to follow normal ruby semantics!\n" \
|
19
19
|
'{Module,Class}#name should return resolvable constant name as String or nil'
|
20
20
|
|
21
|
+
# Construct minimal empty env
|
22
|
+
#
|
23
|
+
# @param [World] world
|
24
|
+
# @param [Config] config
|
25
|
+
#
|
26
|
+
# @return [Env]
|
27
|
+
def self.empty(world, config)
|
28
|
+
new(
|
29
|
+
config: config,
|
30
|
+
integration: Integration::Null.new(config),
|
31
|
+
matchable_scopes: EMPTY_ARRAY,
|
32
|
+
mutations: EMPTY_ARRAY,
|
33
|
+
parser: Parser.new,
|
34
|
+
selector: Selector::Null.new,
|
35
|
+
subjects: EMPTY_ARRAY,
|
36
|
+
world: world
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
21
40
|
# Kill mutation
|
22
41
|
#
|
23
42
|
# @param [Mutation] mutation
|
@@ -26,8 +45,10 @@ module Mutant
|
|
26
45
|
def kill(mutation)
|
27
46
|
start = Timer.now
|
28
47
|
|
48
|
+
tests = selections.fetch(mutation.subject)
|
49
|
+
|
29
50
|
Result::Mutation.new(
|
30
|
-
isolation_result: run_mutation_tests(mutation),
|
51
|
+
isolation_result: run_mutation_tests(mutation, tests),
|
31
52
|
mutation: mutation,
|
32
53
|
runtime: Timer.now - start
|
33
54
|
)
|
@@ -43,18 +64,83 @@ module Mutant
|
|
43
64
|
end
|
44
65
|
memoize :selections
|
45
66
|
|
67
|
+
# Emit warning
|
68
|
+
#
|
69
|
+
# @param [String] warning
|
70
|
+
#
|
71
|
+
# @return [self]
|
72
|
+
def warn(message)
|
73
|
+
config.reporter.warn(message)
|
74
|
+
self
|
75
|
+
end
|
76
|
+
|
77
|
+
# Selected tests
|
78
|
+
#
|
79
|
+
# @return [Set<Test>]
|
80
|
+
def selected_tests
|
81
|
+
selections.values.flatten.to_set
|
82
|
+
end
|
83
|
+
memoize :selected_tests
|
84
|
+
|
85
|
+
# Amount of mutations
|
86
|
+
#
|
87
|
+
# @return [Integer]
|
88
|
+
def amount_mutations
|
89
|
+
mutations.length
|
90
|
+
end
|
91
|
+
memoize :amount_mutations
|
92
|
+
|
93
|
+
# Amount of tests reachable by integration
|
94
|
+
#
|
95
|
+
# @return [Integer]
|
96
|
+
def amount_total_tests
|
97
|
+
integration.all_tests.length
|
98
|
+
end
|
99
|
+
memoize :amount_total_tests
|
100
|
+
|
101
|
+
# Amount of selected subjects
|
102
|
+
#
|
103
|
+
# @return [Integer]
|
104
|
+
def amount_subjects
|
105
|
+
subjects.length
|
106
|
+
end
|
107
|
+
memoize :amount_subjects
|
108
|
+
|
109
|
+
# Amount of selected tests
|
110
|
+
#
|
111
|
+
# @return [Integer]
|
112
|
+
def amount_selected_tests
|
113
|
+
selected_tests.length
|
114
|
+
end
|
115
|
+
memoize :amount_selected_tests
|
116
|
+
|
117
|
+
# Ratio between selected tests and subjects
|
118
|
+
#
|
119
|
+
# @return [Rational]
|
120
|
+
def test_subject_ratio
|
121
|
+
return Rational(0) if amount_subjects.zero?
|
122
|
+
|
123
|
+
Rational(amount_selected_tests, amount_subjects)
|
124
|
+
end
|
125
|
+
memoize :test_subject_ratio
|
126
|
+
|
46
127
|
private
|
47
128
|
|
48
129
|
# Kill mutation under isolation with integration
|
49
130
|
#
|
50
|
-
# @param [
|
51
|
-
# @param [
|
131
|
+
# @param [Mutation] mutation
|
132
|
+
# @param [Array<Test>] test
|
52
133
|
#
|
53
134
|
# @return [Result::Isolation]
|
54
|
-
def run_mutation_tests(mutation)
|
135
|
+
def run_mutation_tests(mutation, tests)
|
55
136
|
config.isolation.call do
|
56
|
-
mutation.insert(
|
57
|
-
|
137
|
+
result = mutation.insert(world.kernel)
|
138
|
+
|
139
|
+
if result.equal?(Loader::Result::VoidValue.instance)
|
140
|
+
Result::Test::VoidValue.instance
|
141
|
+
else
|
142
|
+
integration.call(tests)
|
143
|
+
end
|
58
144
|
end
|
59
145
|
end
|
60
146
|
|
data/lib/mutant/expression.rb
CHANGED
@@ -12,6 +12,11 @@ module Mutant
|
|
12
12
|
|
13
13
|
private_constant(*constants(false))
|
14
14
|
|
15
|
+
# Syntax of expression
|
16
|
+
#
|
17
|
+
# @return [Matcher]
|
18
|
+
abstract_method :matcher
|
19
|
+
|
15
20
|
# Syntax of expression
|
16
21
|
#
|
17
22
|
# @return [String]
|
@@ -23,7 +28,7 @@ module Mutant
|
|
23
28
|
#
|
24
29
|
# @return [Integer]
|
25
30
|
def match_length(other)
|
26
|
-
if eql?(other)
|
31
|
+
if syntax.eql?(other.syntax)
|
27
32
|
syntax.length
|
28
33
|
else
|
29
34
|
0
|
@@ -5,45 +5,24 @@ module Mutant
|
|
5
5
|
class Parser
|
6
6
|
include Concord.new(:types)
|
7
7
|
|
8
|
-
|
9
|
-
include AbstractType
|
10
|
-
end # ParserError
|
11
|
-
|
12
|
-
# Error raised on invalid expressions
|
13
|
-
class InvalidExpressionError < ParserError; end
|
14
|
-
|
15
|
-
# Error raised on ambiguous expressions
|
16
|
-
class AmbiguousExpressionError < ParserError; end
|
17
|
-
|
18
|
-
# Parse input into expression or raise
|
19
|
-
#
|
20
|
-
# @param [String] syntax
|
21
|
-
#
|
22
|
-
# @return [Expression]
|
23
|
-
# if expression is valid
|
24
|
-
#
|
25
|
-
# @raise [ParserError]
|
26
|
-
# otherwise
|
27
|
-
def call(input)
|
28
|
-
try_parse(input) or fail InvalidExpressionError, "Expression: #{input.inspect} is not valid"
|
29
|
-
end
|
30
|
-
|
31
|
-
# Try to parse input into expression
|
8
|
+
# Apply expression parsing
|
32
9
|
#
|
33
10
|
# @param [String] input
|
34
11
|
#
|
35
|
-
# @return [Expression]
|
12
|
+
# @return [Either<String, Expression>]
|
36
13
|
# if expression is valid
|
37
14
|
#
|
38
15
|
# @return [nil]
|
39
16
|
# otherwise
|
40
|
-
def
|
17
|
+
def apply(input)
|
41
18
|
expressions = expressions(input)
|
42
19
|
case expressions.length
|
43
|
-
when 0
|
44
|
-
|
20
|
+
when 0
|
21
|
+
Either::Left.new("Expression: #{input.inspect} is invalid")
|
22
|
+
when 1
|
23
|
+
Either::Right.new(expressions.first)
|
45
24
|
else
|
46
|
-
|
25
|
+
Either::Left.new("Expression: #{input.inspect} is ambiguous")
|
47
26
|
end
|
48
27
|
end
|
49
28
|
|
@@ -57,8 +36,7 @@ module Mutant
|
|
57
36
|
# if expressions can be parsed from input
|
58
37
|
def expressions(input)
|
59
38
|
types.each_with_object([]) do |type, aggregate|
|
60
|
-
expression = type.try_parse(input)
|
61
|
-
aggregate << expression if expression
|
39
|
+
expression = type.try_parse(input) and aggregate << expression
|
62
40
|
end
|
63
41
|
end
|
64
42
|
|
data/lib/mutant/integration.rb
CHANGED
@@ -6,22 +6,76 @@ module Mutant
|
|
6
6
|
class Integration
|
7
7
|
include AbstractType, Adamantium::Flat, Concord.new(:config)
|
8
8
|
|
9
|
+
LOAD_MESSAGE = <<~'MESSAGE'
|
10
|
+
Unable to load integration mutant-%<integration_name>s:
|
11
|
+
%<exception>s
|
12
|
+
You may have to install the gem mutant-%<integration_name>s!
|
13
|
+
MESSAGE
|
14
|
+
|
15
|
+
CONST_MESSAGE = <<~'MESSAGE'
|
16
|
+
Unable to load integration mutant-%<integration_name>s:
|
17
|
+
%<exception>s
|
18
|
+
This is a bug in the integration you have to report.
|
19
|
+
The integration is supposed to define %<constant_name>s!
|
20
|
+
MESSAGE
|
21
|
+
|
22
|
+
private_constant(*constants(false))
|
23
|
+
|
9
24
|
# Setup integration
|
10
25
|
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
26
|
+
# @param env [Bootstrap]
|
27
|
+
#
|
28
|
+
# @return [Either<String, Integration>]
|
29
|
+
def self.setup(env)
|
30
|
+
attempt_require(env)
|
31
|
+
.apply { attempt_const_get(env) }
|
32
|
+
.fmap { |klass| klass.new(env.config).setup }
|
33
|
+
end
|
34
|
+
|
35
|
+
# Attempt to require integration
|
36
|
+
#
|
37
|
+
# @param env [Bootstrap]
|
38
|
+
#
|
39
|
+
# @return [Either<String, undefined>]
|
40
|
+
#
|
41
|
+
# @api private
|
42
|
+
#
|
43
|
+
# rubocop:disable Style/MultilineBlockChain
|
44
|
+
def self.attempt_require(env)
|
45
|
+
integration_name = env.config.integration
|
46
|
+
|
47
|
+
Either.wrap_error(LoadError) do
|
48
|
+
env.world.kernel.require("mutant/integration/#{integration_name}")
|
49
|
+
end.lmap do |exception|
|
50
|
+
LOAD_MESSAGE % {
|
51
|
+
exception: exception.inspect,
|
52
|
+
integration_name: integration_name
|
53
|
+
}
|
54
|
+
end
|
55
|
+
end
|
56
|
+
private_class_method :attempt_require
|
57
|
+
# rubocop:enable Style/MultilineBlockChain
|
58
|
+
|
59
|
+
# Attempt const get
|
14
60
|
#
|
15
|
-
#
|
61
|
+
# @param env [Boostrap]
|
16
62
|
#
|
17
|
-
# @
|
18
|
-
# @param name [String]
|
63
|
+
# @return [Either<String, Class<Integration>>]
|
19
64
|
#
|
20
|
-
# @
|
21
|
-
def self.
|
22
|
-
|
23
|
-
|
65
|
+
# @api private
|
66
|
+
def self.attempt_const_get(env)
|
67
|
+
integration_name = env.config.integration
|
68
|
+
constant_name = integration_name.capitalize
|
69
|
+
|
70
|
+
Either.wrap_error(NameError) { const_get(constant_name) }.lmap do |exception|
|
71
|
+
CONST_MESSAGE % {
|
72
|
+
constant_name: "#{self}::#{constant_name}",
|
73
|
+
exception: exception.inspect,
|
74
|
+
integration_name: integration_name
|
75
|
+
}
|
76
|
+
end
|
24
77
|
end
|
78
|
+
private_class_method :attempt_const_get
|
25
79
|
|
26
80
|
# Perform integration setup
|
27
81
|
#
|
@@ -50,31 +104,5 @@ module Mutant
|
|
50
104
|
def expression_parser
|
51
105
|
config.expression_parser
|
52
106
|
end
|
53
|
-
|
54
|
-
# Null integration that never kills a mutation
|
55
|
-
class Null < self
|
56
|
-
|
57
|
-
# Available tests for integration
|
58
|
-
#
|
59
|
-
# @return [Enumerable<Test>]
|
60
|
-
def all_tests
|
61
|
-
EMPTY_ARRAY
|
62
|
-
end
|
63
|
-
|
64
|
-
# Run a collection of tests
|
65
|
-
#
|
66
|
-
# @param [Enumerable<Mutant::Test>] tests
|
67
|
-
#
|
68
|
-
# @return [Result::Test]
|
69
|
-
def call(tests)
|
70
|
-
Result::Test.new(
|
71
|
-
output: '',
|
72
|
-
passed: true,
|
73
|
-
runtime: 0.0,
|
74
|
-
tests: tests
|
75
|
-
)
|
76
|
-
end
|
77
|
-
|
78
|
-
end # Null
|
79
107
|
end # Integration
|
80
108
|
end # Mutant
|
data/lib/mutant/isolation.rb
CHANGED
@@ -9,6 +9,10 @@ module Mutant
|
|
9
9
|
class Result
|
10
10
|
include AbstractType, Adamantium
|
11
11
|
|
12
|
+
NULL_LOG = ''
|
13
|
+
|
14
|
+
private_constant(*constants(false))
|
15
|
+
|
12
16
|
abstract_method :error
|
13
17
|
abstract_method :next
|
14
18
|
abstract_method :value
|
@@ -22,6 +26,13 @@ module Mutant
|
|
22
26
|
ErrorChain.new(error, self)
|
23
27
|
end
|
24
28
|
|
29
|
+
# The log captured from integration
|
30
|
+
#
|
31
|
+
# @return [String]
|
32
|
+
def log
|
33
|
+
NULL_LOG
|
34
|
+
end
|
35
|
+
|
25
36
|
# Test for success
|
26
37
|
#
|
27
38
|
# @return [Boolean]
|
@@ -31,7 +42,11 @@ module Mutant
|
|
31
42
|
|
32
43
|
# Succesful result producing value
|
33
44
|
class Success < self
|
34
|
-
include Concord::Public.new(:value)
|
45
|
+
include Concord::Public.new(:value, :log)
|
46
|
+
|
47
|
+
def self.new(_value, _log = '')
|
48
|
+
super
|
49
|
+
end
|
35
50
|
end # Success
|
36
51
|
|
37
52
|
# Unsuccessful result by unexpected exception
|
@@ -4,16 +4,15 @@ module Mutant
|
|
4
4
|
class Isolation
|
5
5
|
# Isolation via the fork(2) systemcall.
|
6
6
|
class Fork < self
|
7
|
-
include(
|
8
|
-
Adamantium::Flat,
|
9
|
-
Anima.new(:devnull, :io, :marshal, :process, :stderr, :stdout)
|
10
|
-
)
|
7
|
+
include(Adamantium::Flat, Concord.new(:world))
|
11
8
|
|
12
|
-
|
9
|
+
READ_SIZE = 4096
|
10
|
+
|
11
|
+
ATTRIBUTES = %i[block log_pipe result_pipe world].freeze
|
13
12
|
|
14
13
|
# Unsucessful result as child exited nonzero
|
15
14
|
class ChildError < Result
|
16
|
-
include Concord::Public.new(:value)
|
15
|
+
include Concord::Public.new(:value, :log)
|
17
16
|
end # ChildError
|
18
17
|
|
19
18
|
# Unsucessful result as fork failed
|
@@ -21,6 +20,36 @@ module Mutant
|
|
21
20
|
include Equalizer.new
|
22
21
|
end # ForkError
|
23
22
|
|
23
|
+
# Pipe abstraction
|
24
|
+
class Pipe
|
25
|
+
include Adamantium::Flat, Anima.new(:reader, :writer)
|
26
|
+
|
27
|
+
# Run block with pipe in binmode
|
28
|
+
#
|
29
|
+
# @return [undefined]
|
30
|
+
def self.with(io)
|
31
|
+
io.pipe(binmode: true) do |(reader, writer)|
|
32
|
+
yield new(reader: reader, writer: writer)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Child writer end of the pipe
|
37
|
+
#
|
38
|
+
# @return [IO]
|
39
|
+
def child
|
40
|
+
reader.close
|
41
|
+
writer
|
42
|
+
end
|
43
|
+
|
44
|
+
# Parent reader end of the pipe
|
45
|
+
#
|
46
|
+
# @return [IO]
|
47
|
+
def parent
|
48
|
+
writer.close
|
49
|
+
reader
|
50
|
+
end
|
51
|
+
end # Pipe
|
52
|
+
|
24
53
|
# ignore :reek:InstanceVariableAssumption
|
25
54
|
class Parent
|
26
55
|
include(
|
@@ -33,9 +62,6 @@ module Mutant
|
|
33
62
|
|
34
63
|
# Parent process
|
35
64
|
#
|
36
|
-
# @param [IO] reader
|
37
|
-
# @param [IO] writer
|
38
|
-
#
|
39
65
|
# @return [Result]
|
40
66
|
def call
|
41
67
|
pid = start_child or return ForkError.new
|
@@ -51,7 +77,14 @@ module Mutant
|
|
51
77
|
#
|
52
78
|
# @return [Integer]
|
53
79
|
def start_child
|
54
|
-
process.fork
|
80
|
+
world.process.fork do
|
81
|
+
Child.call(
|
82
|
+
to_h.merge(
|
83
|
+
log_pipe: log_pipe.child,
|
84
|
+
result_pipe: result_pipe.child
|
85
|
+
)
|
86
|
+
)
|
87
|
+
end
|
55
88
|
end
|
56
89
|
|
57
90
|
# Read child result
|
@@ -59,14 +92,46 @@ module Mutant
|
|
59
92
|
# @param [Integer] pid
|
60
93
|
#
|
61
94
|
# @return [undefined]
|
95
|
+
#
|
96
|
+
# rubocop:disable Metrics/MethodLength
|
62
97
|
def read_child_result(pid)
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
98
|
+
result_fragments = []
|
99
|
+
log_fragments = []
|
100
|
+
|
101
|
+
read_fragments(
|
102
|
+
log_pipe.parent => log_fragments,
|
103
|
+
result_pipe.parent => result_fragments
|
104
|
+
)
|
105
|
+
|
106
|
+
begin
|
107
|
+
result = world.marshal.load(result_fragments.join)
|
108
|
+
rescue ArgumentError => exception
|
109
|
+
add_result(Result::Exception.new(exception))
|
110
|
+
else
|
111
|
+
add_result(Result::Success.new(result, log_fragments.join))
|
112
|
+
end
|
68
113
|
ensure
|
69
|
-
wait_child(pid)
|
114
|
+
wait_child(pid, log_fragments)
|
115
|
+
end
|
116
|
+
# rubocop:enable Metrics/MethodLength
|
117
|
+
|
118
|
+
# Read fragments
|
119
|
+
#
|
120
|
+
# @param [Hash{FD => Array<String}] targets
|
121
|
+
#
|
122
|
+
# @return [undefined]
|
123
|
+
def read_fragments(targets)
|
124
|
+
until targets.empty?
|
125
|
+
ready, = world.io.select(targets.keys)
|
126
|
+
|
127
|
+
ready.each do |fd|
|
128
|
+
if fd.eof?
|
129
|
+
targets.delete(fd)
|
130
|
+
else
|
131
|
+
targets.fetch(fd) << fd.read_nonblock(READ_SIZE)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
70
135
|
end
|
71
136
|
|
72
137
|
# Wait for child process
|
@@ -74,10 +139,12 @@ module Mutant
|
|
74
139
|
# @param [Integer] pid
|
75
140
|
#
|
76
141
|
# @return [undefined]
|
77
|
-
def wait_child(pid)
|
78
|
-
_pid, status = process.wait2(pid)
|
142
|
+
def wait_child(pid, log_fragments)
|
143
|
+
_pid, status = world.process.wait2(pid)
|
79
144
|
|
80
|
-
|
145
|
+
unless status.success? # rubocop:disable Style/GuardClause
|
146
|
+
add_result(ChildError.new(status, log_fragments.join))
|
147
|
+
end
|
81
148
|
end
|
82
149
|
|
83
150
|
# Add a result
|
@@ -97,29 +164,14 @@ module Mutant
|
|
97
164
|
|
98
165
|
# Handle child process
|
99
166
|
#
|
100
|
-
# @param [IO] reader
|
101
|
-
# @param [IO] writer
|
102
|
-
#
|
103
167
|
# @return [undefined]
|
104
168
|
def call
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
169
|
+
world.stderr.reopen(log_pipe)
|
170
|
+
world.stdout.reopen(log_pipe)
|
171
|
+
result_pipe.syswrite(world.marshal.dump(block.call))
|
172
|
+
result_pipe.close
|
109
173
|
end
|
110
174
|
|
111
|
-
private
|
112
|
-
|
113
|
-
# The block result computed under silencing
|
114
|
-
#
|
115
|
-
# @return [Object]
|
116
|
-
def result
|
117
|
-
devnull.call do |null|
|
118
|
-
stderr.reopen(null)
|
119
|
-
stdout.reopen(null)
|
120
|
-
yield
|
121
|
-
end
|
122
|
-
end
|
123
175
|
end # Child
|
124
176
|
|
125
177
|
private_constant(*(constants(false) - %i[ChildError ForkError]))
|
@@ -128,11 +180,24 @@ module Mutant
|
|
128
180
|
#
|
129
181
|
# @return [Result]
|
130
182
|
# execution result
|
183
|
+
#
|
184
|
+
# ignore :reek:NestedIterators
|
185
|
+
#
|
186
|
+
# rubocop:disable Metrics/MethodLength
|
131
187
|
def call(&block)
|
132
|
-
io
|
133
|
-
|
188
|
+
io = world.io
|
189
|
+
Pipe.with(io) do |result|
|
190
|
+
Pipe.with(io) do |log|
|
191
|
+
Parent.call(
|
192
|
+
block: block,
|
193
|
+
log_pipe: log,
|
194
|
+
result_pipe: result,
|
195
|
+
world: world
|
196
|
+
)
|
197
|
+
end
|
134
198
|
end
|
135
199
|
end
|
200
|
+
# rubocop:enable Metrics/MethodLength
|
136
201
|
end # Fork
|
137
202
|
end # Isolation
|
138
203
|
end # Mutant
|