mutant 0.10.20 → 0.10.25
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/mutant.rb +23 -4
- data/lib/mutant/ast/meta/send.rb +0 -1
- data/lib/mutant/ast/regexp.rb +37 -0
- data/lib/mutant/ast/regexp/transformer.rb +150 -0
- data/lib/mutant/ast/regexp/transformer/direct.rb +121 -0
- data/lib/mutant/ast/regexp/transformer/named_group.rb +50 -0
- data/lib/mutant/ast/regexp/transformer/options_group.rb +68 -0
- data/lib/mutant/ast/regexp/transformer/quantifier.rb +90 -0
- data/lib/mutant/ast/regexp/transformer/recursive.rb +56 -0
- data/lib/mutant/ast/regexp/transformer/root.rb +28 -0
- data/lib/mutant/ast/regexp/transformer/text.rb +58 -0
- data/lib/mutant/ast/types.rb +115 -11
- data/lib/mutant/cli/command/environment.rb +9 -3
- data/lib/mutant/config.rb +8 -54
- data/lib/mutant/config/coverage_criteria.rb +61 -0
- data/lib/mutant/env.rb +8 -6
- data/lib/mutant/expression.rb +0 -12
- data/lib/mutant/expression/namespace.rb +1 -1
- data/lib/mutant/isolation/fork.rb +1 -1
- data/lib/mutant/loader.rb +1 -1
- data/lib/mutant/matcher.rb +2 -2
- data/lib/mutant/matcher/config.rb +27 -6
- data/lib/mutant/matcher/method.rb +2 -3
- data/lib/mutant/matcher/method/metaclass.rb +1 -1
- data/lib/mutant/meta/example/dsl.rb +6 -1
- data/lib/mutant/mutator/node/arguments.rb +0 -2
- data/lib/mutant/mutator/node/block.rb +5 -1
- data/lib/mutant/mutator/node/dynamic_literal.rb +1 -1
- data/lib/mutant/mutator/node/kwargs.rb +44 -0
- data/lib/mutant/mutator/node/literal/regex.rb +26 -0
- data/lib/mutant/mutator/node/literal/symbol.rb +0 -2
- data/lib/mutant/mutator/node/regexp.rb +20 -0
- data/lib/mutant/mutator/node/regexp/alternation_meta.rb +20 -0
- data/lib/mutant/mutator/node/regexp/beginning_of_line_anchor.rb +20 -0
- data/lib/mutant/mutator/node/regexp/capture_group.rb +25 -0
- data/lib/mutant/mutator/node/regexp/character_type.rb +31 -0
- data/lib/mutant/mutator/node/regexp/end_of_line_anchor.rb +20 -0
- data/lib/mutant/mutator/node/regexp/end_of_string_or_before_end_of_line_anchor.rb +20 -0
- data/lib/mutant/mutator/node/regexp/zero_or_more.rb +34 -0
- data/lib/mutant/mutator/node/sclass.rb +1 -1
- data/lib/mutant/mutator/node/send.rb +36 -19
- data/lib/mutant/parallel.rb +43 -28
- data/lib/mutant/parallel/driver.rb +9 -3
- data/lib/mutant/parallel/worker.rb +60 -2
- data/lib/mutant/pipe.rb +94 -0
- data/lib/mutant/reporter/cli/printer/env.rb +1 -1
- data/lib/mutant/reporter/cli/printer/isolation_result.rb +1 -6
- data/lib/mutant/result.rb +8 -0
- data/lib/mutant/runner.rb +7 -10
- data/lib/mutant/runner/sink.rb +12 -2
- data/lib/mutant/subject.rb +1 -3
- data/lib/mutant/subject/method/instance.rb +2 -4
- data/lib/mutant/timer.rb +2 -4
- data/lib/mutant/transform.rb +25 -2
- data/lib/mutant/version.rb +1 -1
- data/lib/mutant/world.rb +1 -2
- metadata +48 -9
- data/lib/mutant/warnings.rb +0 -106
@@ -29,13 +29,19 @@ module Mutant
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def expand(file_config)
|
32
|
+
if @config.matcher.subjects.any?
|
33
|
+
file_config = file_config.with(
|
34
|
+
matcher: file_config.matcher.with(subjects: [])
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
32
38
|
@config = Config.env.merge(file_config).merge(@config).expand_defaults
|
33
39
|
end
|
34
40
|
|
35
41
|
def parse_remaining_arguments(arguments)
|
36
42
|
Mutant.traverse(@config.expression_parser, arguments)
|
37
|
-
.fmap do |
|
38
|
-
matcher(
|
43
|
+
.fmap do |expressions|
|
44
|
+
matcher(subjects: expressions)
|
39
45
|
self
|
40
46
|
end
|
41
47
|
end
|
@@ -82,7 +88,7 @@ module Mutant
|
|
82
88
|
parser.separator('Matcher:')
|
83
89
|
|
84
90
|
parser.on('--ignore-subject EXPRESSION', 'Ignore subjects that match EXPRESSION as prefix') do |pattern|
|
85
|
-
add_matcher(:
|
91
|
+
add_matcher(:ignore, @config.expression_parser.call(pattern).from_right)
|
86
92
|
end
|
87
93
|
parser.on('--start-subject EXPRESSION', 'Start mutation testing at a specific subject') do |pattern|
|
88
94
|
add_matcher(:start_expressions, @config.expression_parser.call(pattern).from_right)
|
data/lib/mutant/config.rb
CHANGED
@@ -37,54 +37,6 @@ module Mutant
|
|
37
37
|
|
38
38
|
private_constant(*constants(false))
|
39
39
|
|
40
|
-
class CoverageCriteria
|
41
|
-
include Anima.new(:process_abort, :test_result, :timeout)
|
42
|
-
|
43
|
-
EMPTY = new(
|
44
|
-
process_abort: nil,
|
45
|
-
test_result: nil,
|
46
|
-
timeout: nil
|
47
|
-
)
|
48
|
-
|
49
|
-
DEFAULT = new(
|
50
|
-
process_abort: false,
|
51
|
-
test_result: true,
|
52
|
-
timeout: false
|
53
|
-
)
|
54
|
-
|
55
|
-
TRANSFORM =
|
56
|
-
Transform::Sequence.new(
|
57
|
-
[
|
58
|
-
Transform::Hash.new(
|
59
|
-
optional: [
|
60
|
-
Transform::Hash::Key.new('process_abort', Transform::BOOLEAN),
|
61
|
-
Transform::Hash::Key.new('test_result', Transform::BOOLEAN),
|
62
|
-
Transform::Hash::Key.new('timeout', Transform::BOOLEAN)
|
63
|
-
],
|
64
|
-
required: []
|
65
|
-
),
|
66
|
-
Transform::Hash::Symbolize.new,
|
67
|
-
->(value) { Either::Right.new(DEFAULT.with(**value)) }
|
68
|
-
]
|
69
|
-
)
|
70
|
-
|
71
|
-
def merge(other)
|
72
|
-
self.class.new(
|
73
|
-
process_abort: overwrite(other, :process_abort),
|
74
|
-
test_result: overwrite(other, :test_result),
|
75
|
-
timeout: overwrite(other, :timeout)
|
76
|
-
)
|
77
|
-
end
|
78
|
-
|
79
|
-
private
|
80
|
-
|
81
|
-
def overwrite(other, attribute_name)
|
82
|
-
other_value = other.public_send(attribute_name)
|
83
|
-
|
84
|
-
other_value.nil? ? public_send(attribute_name) : other_value
|
85
|
-
end
|
86
|
-
end # CoverageCriteria
|
87
|
-
|
88
40
|
# Merge with other config
|
89
41
|
#
|
90
42
|
# @param [Config] other
|
@@ -116,13 +68,14 @@ module Mutant
|
|
116
68
|
#
|
117
69
|
# @return [Either<String,Config>]
|
118
70
|
def self.load_config_file(world)
|
119
|
-
|
120
|
-
|
71
|
+
files = CANDIDATES
|
72
|
+
.map(&world.pathname.public_method(:new))
|
73
|
+
.select(&:readable?)
|
121
74
|
|
122
75
|
if files.one?
|
123
|
-
load_contents(files.first).fmap(&
|
76
|
+
load_contents(files.first).fmap(&DEFAULT.public_method(:with))
|
124
77
|
elsif files.empty?
|
125
|
-
Either::Right.new(
|
78
|
+
Either::Right.new(DEFAULT)
|
126
79
|
else
|
127
80
|
Either::Left.new(MORE_THAN_ONE_CONFIG_FILE % files.join(', '))
|
128
81
|
end
|
@@ -159,13 +112,14 @@ module Mutant
|
|
159
112
|
Transform::Exception.new(YAML::SyntaxError, YAML.method(:safe_load)),
|
160
113
|
Transform::Hash.new(
|
161
114
|
optional: [
|
162
|
-
Transform::Hash::Key.new('coverage_criteria', CoverageCriteria::TRANSFORM),
|
115
|
+
Transform::Hash::Key.new('coverage_criteria', ->(value) { CoverageCriteria::TRANSFORM.call(value) }),
|
163
116
|
Transform::Hash::Key.new('fail_fast', Transform::BOOLEAN),
|
164
117
|
Transform::Hash::Key.new('includes', Transform::STRING_ARRAY),
|
165
118
|
Transform::Hash::Key.new('integration', Transform::STRING),
|
166
119
|
Transform::Hash::Key.new('jobs', Transform::INTEGER),
|
167
120
|
Transform::Hash::Key.new('mutation_timeout', Transform::FLOAT),
|
168
|
-
Transform::Hash::Key.new('requires', Transform::STRING_ARRAY)
|
121
|
+
Transform::Hash::Key.new('requires', Transform::STRING_ARRAY),
|
122
|
+
Transform::Hash::Key.new('matcher', Matcher::Config::LOADER)
|
169
123
|
],
|
170
124
|
required: []
|
171
125
|
),
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mutant
|
4
|
+
class Config
|
5
|
+
# Configuration of coverge conditions
|
6
|
+
class CoverageCriteria
|
7
|
+
include Anima.new(:process_abort, :test_result, :timeout)
|
8
|
+
|
9
|
+
EMPTY = new(
|
10
|
+
process_abort: nil,
|
11
|
+
test_result: nil,
|
12
|
+
timeout: nil
|
13
|
+
)
|
14
|
+
|
15
|
+
DEFAULT = new(
|
16
|
+
process_abort: false,
|
17
|
+
test_result: true,
|
18
|
+
timeout: false
|
19
|
+
)
|
20
|
+
|
21
|
+
TRANSFORM =
|
22
|
+
Transform::Sequence.new(
|
23
|
+
[
|
24
|
+
Transform::Hash.new(
|
25
|
+
optional: [
|
26
|
+
Transform::Hash::Key.new('process_abort', Transform::BOOLEAN),
|
27
|
+
Transform::Hash::Key.new('test_result', Transform::BOOLEAN),
|
28
|
+
Transform::Hash::Key.new('timeout', Transform::BOOLEAN)
|
29
|
+
],
|
30
|
+
required: []
|
31
|
+
),
|
32
|
+
Transform::Hash::Symbolize.new,
|
33
|
+
->(value) { Either::Right.new(DEFAULT.with(**value)) }
|
34
|
+
]
|
35
|
+
)
|
36
|
+
|
37
|
+
# Merge coverage criteria with other instance
|
38
|
+
#
|
39
|
+
# Values from the other instance have precedence.
|
40
|
+
#
|
41
|
+
# @param [CoverageCriteria] other
|
42
|
+
#
|
43
|
+
# @return [CoverageCriteria]
|
44
|
+
def merge(other)
|
45
|
+
self.class.new(
|
46
|
+
process_abort: overwrite(other, :process_abort),
|
47
|
+
test_result: overwrite(other, :test_result),
|
48
|
+
timeout: overwrite(other, :timeout)
|
49
|
+
)
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def overwrite(other, attribute_name)
|
55
|
+
other_value = other.public_send(attribute_name)
|
56
|
+
|
57
|
+
other_value.nil? ? public_send(attribute_name) : other_value
|
58
|
+
end
|
59
|
+
end # CoverageCriteria
|
60
|
+
end # Config
|
61
|
+
end # Mutant
|
data/lib/mutant/env.rb
CHANGED
@@ -43,19 +43,21 @@ module Mutant
|
|
43
43
|
end
|
44
44
|
# rubocop:enable Metrics/MethodLength
|
45
45
|
|
46
|
-
#
|
46
|
+
# Cover mutation with specific index
|
47
47
|
#
|
48
|
-
# @param [
|
48
|
+
# @param [Integer] mutation_index
|
49
49
|
#
|
50
|
-
# @return [Result::
|
51
|
-
def
|
50
|
+
# @return [Result::MutationIndex]
|
51
|
+
def cover_index(mutation_index)
|
52
|
+
mutation = mutations.fetch(mutation_index)
|
53
|
+
|
52
54
|
start = timer.now
|
53
55
|
|
54
56
|
tests = selections.fetch(mutation.subject)
|
55
57
|
|
56
|
-
Result::
|
58
|
+
Result::MutationIndex.new(
|
57
59
|
isolation_result: run_mutation_tests(mutation, tests),
|
58
|
-
|
60
|
+
mutation_index: mutation_index,
|
59
61
|
runtime: timer.now - start
|
60
62
|
)
|
61
63
|
end
|
data/lib/mutant/expression.rb
CHANGED
@@ -4,8 +4,6 @@ module Mutant
|
|
4
4
|
|
5
5
|
# Abstract base class for match expression
|
6
6
|
class Expression
|
7
|
-
include AbstractType
|
8
|
-
|
9
7
|
fragment = /[A-Za-z][A-Za-z\d_]*/.freeze
|
10
8
|
SCOPE_NAME_PATTERN = /(?<scope_name>#{fragment}(?:#{SCOPE_OPERATOR}#{fragment})*)/.freeze
|
11
9
|
SCOPE_SYMBOL_PATTERN = '(?<scope_symbol>[.#])'
|
@@ -16,16 +14,6 @@ module Mutant
|
|
16
14
|
super.freeze
|
17
15
|
end
|
18
16
|
|
19
|
-
# Syntax of expression
|
20
|
-
#
|
21
|
-
# @return [Matcher]
|
22
|
-
abstract_method :matcher
|
23
|
-
|
24
|
-
# Syntax of expression
|
25
|
-
#
|
26
|
-
# @return [String]
|
27
|
-
abstract_method :syntax
|
28
|
-
|
29
17
|
# Match length with other expression
|
30
18
|
#
|
31
19
|
# @param [Expression] other
|
data/lib/mutant/loader.rb
CHANGED
data/lib/mutant/matcher.rb
CHANGED
@@ -20,7 +20,7 @@ module Mutant
|
|
20
20
|
# @return [Matcher]
|
21
21
|
def self.from_config(config)
|
22
22
|
Filter.new(
|
23
|
-
Chain.new(config.
|
23
|
+
Chain.new(config.subjects.map(&:matcher)),
|
24
24
|
method(:allowed_subject?).curry.call(config)
|
25
25
|
)
|
26
26
|
end
|
@@ -42,7 +42,7 @@ module Mutant
|
|
42
42
|
#
|
43
43
|
# @return [Boolean]
|
44
44
|
def self.ignore_subject?(config, subject)
|
45
|
-
config.
|
45
|
+
config.ignore.any? do |expression|
|
46
46
|
expression.prefix?(subject.expression)
|
47
47
|
end
|
48
48
|
end
|
@@ -5,8 +5,8 @@ module Mutant
|
|
5
5
|
# Subject matcher configuration
|
6
6
|
class Config
|
7
7
|
include Adamantium, Anima.new(
|
8
|
-
:
|
9
|
-
:
|
8
|
+
:ignore,
|
9
|
+
:subjects,
|
10
10
|
:start_expressions,
|
11
11
|
:subject_filters
|
12
12
|
)
|
@@ -17,15 +17,36 @@ module Mutant
|
|
17
17
|
ENUM_DELIMITER = ','
|
18
18
|
EMPTY_ATTRIBUTES = 'empty'
|
19
19
|
PRESENTATIONS = IceNine.deep_freeze(
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
20
|
+
ignore: :syntax,
|
21
|
+
start_expressions: :syntax,
|
22
|
+
subject_filters: :inspect,
|
23
|
+
subjects: :syntax
|
24
24
|
)
|
25
25
|
private_constant(*constants(false))
|
26
26
|
|
27
27
|
DEFAULT = new(Hash[anima.attribute_names.map { |name| [name, []] }])
|
28
28
|
|
29
|
+
expression = Transform::Block.capture(:expression) do |input|
|
30
|
+
Mutant::Config::DEFAULT.expression_parser.call(input)
|
31
|
+
end
|
32
|
+
|
33
|
+
expression_array = Transform::Array.new(expression)
|
34
|
+
|
35
|
+
LOADER =
|
36
|
+
Transform::Sequence.new(
|
37
|
+
[
|
38
|
+
Transform::Hash.new(
|
39
|
+
optional: [
|
40
|
+
Transform::Hash::Key.new('subjects', expression_array),
|
41
|
+
Transform::Hash::Key.new('ignore', expression_array)
|
42
|
+
],
|
43
|
+
required: []
|
44
|
+
),
|
45
|
+
Transform::Hash::Symbolize.new,
|
46
|
+
->(attributes) { Either::Right.new(DEFAULT.with(attributes)) }
|
47
|
+
]
|
48
|
+
)
|
49
|
+
|
29
50
|
# Inspection string
|
30
51
|
#
|
31
52
|
# @return [String]
|
@@ -37,7 +37,7 @@ module Mutant
|
|
37
37
|
# @return [Example]
|
38
38
|
#
|
39
39
|
# @raise [RuntimeError]
|
40
|
-
# in case example cannot be
|
40
|
+
# in case the example cannot be built
|
41
41
|
def example
|
42
42
|
fail 'source not defined' unless @source
|
43
43
|
|
@@ -82,6 +82,11 @@ module Mutant
|
|
82
82
|
mutation('self')
|
83
83
|
end
|
84
84
|
|
85
|
+
def regexp_mutations
|
86
|
+
mutation('//')
|
87
|
+
mutation('/nomatch\A/')
|
88
|
+
end
|
89
|
+
|
85
90
|
def node(input)
|
86
91
|
case input
|
87
92
|
when String
|
@@ -21,7 +21,7 @@ module Mutant
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def mutate_body
|
24
|
-
emit_body(nil)
|
24
|
+
emit_body(nil) unless unconditional_loop?
|
25
25
|
emit_body(N_RAISE)
|
26
26
|
|
27
27
|
return unless body
|
@@ -31,6 +31,10 @@ module Mutant
|
|
31
31
|
mutate_body_receiver
|
32
32
|
end
|
33
33
|
|
34
|
+
def unconditional_loop?
|
35
|
+
send.eql?(s(:send, nil, :loop))
|
36
|
+
end
|
37
|
+
|
34
38
|
def body_has_control?
|
35
39
|
AST.find_last_path(body) do |node|
|
36
40
|
n_break?(node) || n_next?(node)
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mutant
|
4
|
+
class Mutator
|
5
|
+
class Node
|
6
|
+
# Mutator for kwargs node
|
7
|
+
class Kwargs < self
|
8
|
+
|
9
|
+
DISALLOW = %i[nil self].freeze
|
10
|
+
|
11
|
+
private_constant(*constants(false))
|
12
|
+
|
13
|
+
handle(:kwargs)
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def dispatch
|
18
|
+
emit_argument_presence
|
19
|
+
emit_argument_mutations
|
20
|
+
end
|
21
|
+
|
22
|
+
def emit_argument_presence
|
23
|
+
Util::Array::Presence.call(children).each do |children|
|
24
|
+
emit_type(*children) unless children.empty?
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def emit_argument_mutations
|
29
|
+
children.each_with_index do |child, index|
|
30
|
+
Mutator.mutate(child).each do |mutant|
|
31
|
+
unless forbid_argument?(mutant)
|
32
|
+
emit_child_update(index, mutant)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def forbid_argument?(node)
|
39
|
+
n_pair?(node) && DISALLOW.include?(node.children.first.type)
|
40
|
+
end
|
41
|
+
end # Kwargs
|
42
|
+
end # Node
|
43
|
+
end # Mutator
|
44
|
+
end # Mutant
|