mutant 0.10.21 → 0.10.26
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 +32 -13
- data/lib/mutant/ast/find_metaclass_containing.rb +1 -1
- data/lib/mutant/ast/regexp.rb +54 -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 +92 -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 -2
- data/lib/mutant/bootstrap.rb +1 -1
- data/lib/mutant/cli/command.rb +4 -0
- data/lib/mutant/cli/command/environment.rb +9 -3
- data/lib/mutant/cli/command/environment/subject.rb +0 -4
- data/lib/mutant/cli/command/environment/test.rb +36 -0
- data/lib/mutant/cli/command/root.rb +1 -1
- data/lib/mutant/config.rb +9 -55
- data/lib/mutant/config/coverage_criteria.rb +61 -0
- data/lib/mutant/context.rb +1 -1
- data/lib/mutant/env.rb +2 -2
- data/lib/mutant/expression.rb +0 -12
- data/lib/mutant/expression/method.rb +4 -4
- data/lib/mutant/expression/methods.rb +5 -4
- data/lib/mutant/expression/namespace.rb +1 -1
- data/lib/mutant/integration.rb +8 -2
- data/lib/mutant/isolation/fork.rb +4 -11
- data/lib/mutant/loader.rb +1 -1
- data/lib/mutant/matcher.rb +3 -3
- data/lib/mutant/matcher/config.rb +30 -8
- data/lib/mutant/matcher/method.rb +10 -9
- data/lib/mutant/matcher/method/instance.rb +6 -2
- data/lib/mutant/matcher/method/metaclass.rb +1 -1
- data/lib/mutant/matcher/methods.rb +2 -4
- data/lib/mutant/meta/example.rb +1 -1
- data/lib/mutant/meta/example/dsl.rb +6 -1
- data/lib/mutant/meta/example/verification.rb +1 -1
- data/lib/mutant/mutation.rb +1 -1
- data/lib/mutant/mutator.rb +8 -1
- data/lib/mutant/mutator/node/argument.rb +2 -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 +12 -0
- data/lib/mutant/mutator/node/named_value/variable_assignment.rb +3 -3
- 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/named_group.rb +39 -0
- data/lib/mutant/mutator/node/regexp/zero_or_more.rb +34 -0
- data/lib/mutant/mutator/node/regopt.rb +1 -1
- data/lib/mutant/mutator/node/sclass.rb +1 -1
- data/lib/mutant/mutator/node/send.rb +55 -6
- data/lib/mutant/parallel.rb +2 -2
- data/lib/mutant/parallel/driver.rb +1 -1
- data/lib/mutant/parallel/worker.rb +1 -1
- data/lib/mutant/parser.rb +1 -1
- data/lib/mutant/pipe.rb +1 -1
- data/lib/mutant/procto.rb +23 -0
- data/lib/mutant/reporter/cli/printer.rb +10 -4
- data/lib/mutant/reporter/cli/printer/env.rb +3 -3
- data/lib/mutant/reporter/cli/printer/env_progress.rb +2 -2
- data/lib/mutant/selector.rb +1 -1
- data/lib/mutant/subject.rb +2 -4
- data/lib/mutant/subject/method/instance.rb +6 -45
- data/lib/mutant/timer.rb +2 -2
- data/lib/mutant/transform.rb +25 -0
- data/lib/mutant/variable.rb +322 -0
- data/lib/mutant/version.rb +1 -1
- data/lib/mutant/world.rb +2 -3
- metadata +39 -151
- data/lib/mutant/warnings.rb +0 -106
data/lib/mutant/matcher.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
module Mutant
|
4
4
|
# Abstract matcher to find subjects to mutate
|
5
5
|
class Matcher
|
6
|
-
include Adamantium
|
6
|
+
include Adamantium, AbstractType
|
7
7
|
|
8
8
|
# Call matcher
|
9
9
|
#
|
@@ -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
|
)
|
@@ -16,16 +16,38 @@ module Mutant
|
|
16
16
|
ATTRIBUTE_FORMAT = '%s: [%s]'
|
17
17
|
ENUM_DELIMITER = ','
|
18
18
|
EMPTY_ATTRIBUTES = 'empty'
|
19
|
-
PRESENTATIONS =
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
19
|
+
PRESENTATIONS = {
|
20
|
+
ignore: :syntax,
|
21
|
+
start_expressions: :syntax,
|
22
|
+
subject_filters: :inspect,
|
23
|
+
subjects: :syntax
|
24
|
+
}.freeze
|
25
|
+
|
25
26
|
private_constant(*constants(false))
|
26
27
|
|
27
28
|
DEFAULT = new(Hash[anima.attribute_names.map { |name| [name, []] }])
|
28
29
|
|
30
|
+
expression = Transform::Block.capture(:expression) do |input|
|
31
|
+
Mutant::Config::DEFAULT.expression_parser.call(input)
|
32
|
+
end
|
33
|
+
|
34
|
+
expression_array = Transform::Array.new(expression)
|
35
|
+
|
36
|
+
LOADER =
|
37
|
+
Transform::Sequence.new(
|
38
|
+
[
|
39
|
+
Transform::Hash.new(
|
40
|
+
optional: [
|
41
|
+
Transform::Hash::Key.new('subjects', expression_array),
|
42
|
+
Transform::Hash::Key.new('ignore', expression_array)
|
43
|
+
],
|
44
|
+
required: []
|
45
|
+
),
|
46
|
+
Transform::Hash::Symbolize.new,
|
47
|
+
->(attributes) { Either::Right.new(DEFAULT.with(attributes)) }
|
48
|
+
]
|
49
|
+
)
|
50
|
+
|
29
51
|
# Inspection string
|
30
52
|
#
|
31
53
|
# @return [String]
|
@@ -5,7 +5,7 @@ module Mutant
|
|
5
5
|
# Abstract base class for method matchers
|
6
6
|
class Method < self
|
7
7
|
include AbstractType,
|
8
|
-
Adamantium
|
8
|
+
Adamantium,
|
9
9
|
Concord::Public.new(:scope, :target_method, :evaluator)
|
10
10
|
|
11
11
|
# Source locations we cannot acces
|
@@ -32,11 +32,13 @@ module Mutant
|
|
32
32
|
# logic would be implemented directly on the Matcher::Method
|
33
33
|
# instance
|
34
34
|
class Evaluator
|
35
|
-
include
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
35
|
+
include(
|
36
|
+
AbstractType,
|
37
|
+
Adamantium,
|
38
|
+
Concord.new(:scope, :target_method, :env),
|
39
|
+
Procto,
|
40
|
+
AST::NodePredicates
|
41
|
+
)
|
40
42
|
|
41
43
|
# Matched subjects
|
42
44
|
#
|
@@ -87,9 +89,8 @@ module Mutant
|
|
87
89
|
node = matched_node_path.last || return
|
88
90
|
|
89
91
|
self.class::SUBJECT_CLASS.new(
|
90
|
-
context:
|
91
|
-
node:
|
92
|
-
warnings: env.world.warnings
|
92
|
+
context: context,
|
93
|
+
node: node
|
93
94
|
)
|
94
95
|
end
|
95
96
|
memoize :subject
|
@@ -13,9 +13,8 @@ module Mutant
|
|
13
13
|
#
|
14
14
|
# @return [Matcher::Method::Instance]
|
15
15
|
def self.new(scope, target_method)
|
16
|
-
name = target_method.name
|
17
16
|
evaluator =
|
18
|
-
if
|
17
|
+
if memoized_method?(scope, target_method.name)
|
19
18
|
Evaluator::Memoized
|
20
19
|
else
|
21
20
|
Evaluator
|
@@ -24,6 +23,11 @@ module Mutant
|
|
24
23
|
super(scope, target_method, evaluator)
|
25
24
|
end
|
26
25
|
|
26
|
+
def self.memoized_method?(scope, method_name)
|
27
|
+
scope < Adamantium && scope.memoized?(method_name)
|
28
|
+
end
|
29
|
+
private_class_method :memoized_method?
|
30
|
+
|
27
31
|
# Instance method specific evaluator
|
28
32
|
class Evaluator < Evaluator
|
29
33
|
SUBJECT_CLASS = Subject::Method::Instance
|
@@ -6,11 +6,11 @@ module Mutant
|
|
6
6
|
class Methods < self
|
7
7
|
include AbstractType, Concord.new(:scope)
|
8
8
|
|
9
|
-
CANDIDATE_NAMES =
|
9
|
+
CANDIDATE_NAMES = %i[
|
10
10
|
public_instance_methods
|
11
11
|
private_instance_methods
|
12
12
|
protected_instance_methods
|
13
|
-
]
|
13
|
+
].freeze
|
14
14
|
|
15
15
|
private_constant(*constants(false))
|
16
16
|
|
@@ -62,7 +62,6 @@ module Mutant
|
|
62
62
|
def candidate_scope
|
63
63
|
scope.singleton_class
|
64
64
|
end
|
65
|
-
memoize :candidate_scope, freezer: :noop
|
66
65
|
|
67
66
|
end # Singleton
|
68
67
|
|
@@ -79,7 +78,6 @@ module Mutant
|
|
79
78
|
def candidate_scope
|
80
79
|
scope.singleton_class
|
81
80
|
end
|
82
|
-
memoize :candidate_scope, freezer: :noop
|
83
81
|
end # Metaclass
|
84
82
|
|
85
83
|
# Matcher for instance methods
|
data/lib/mutant/meta/example.rb
CHANGED
@@ -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
|
data/lib/mutant/mutation.rb
CHANGED
data/lib/mutant/mutator.rb
CHANGED
@@ -6,7 +6,12 @@ module Mutant
|
|
6
6
|
|
7
7
|
REGISTRY = Registry.new
|
8
8
|
|
9
|
-
include
|
9
|
+
include(
|
10
|
+
Adamantium,
|
11
|
+
Concord.new(:input, :parent),
|
12
|
+
AbstractType,
|
13
|
+
Procto
|
14
|
+
)
|
10
15
|
|
11
16
|
# Lookup and invoke dedicated AST mutator
|
12
17
|
#
|
@@ -30,6 +35,8 @@ module Mutant
|
|
30
35
|
# @return [Set<Parser::AST::Node>]
|
31
36
|
attr_reader :output
|
32
37
|
|
38
|
+
alias_method :call, :output
|
39
|
+
|
33
40
|
private
|
34
41
|
|
35
42
|
def initialize(_input, _parent = nil)
|
@@ -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
|
@@ -19,6 +19,7 @@ module Mutant
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def dispatch
|
22
|
+
mutate_body
|
22
23
|
emit_singletons unless parent_node
|
23
24
|
children.each_with_index do |child, index|
|
24
25
|
mutate_child(index) unless n_str?(child)
|
@@ -27,6 +28,17 @@ module Mutant
|
|
27
28
|
emit_type(s(:str, NULL_REGEXP_SOURCE), options)
|
28
29
|
end
|
29
30
|
|
31
|
+
def mutate_body
|
32
|
+
# NOTE: will only mutate parts of regexp body if the body is composed of only strings.
|
33
|
+
# Regular expressions with interpolation are skipped.
|
34
|
+
return unless (body_ast = AST::Regexp.expand_regexp_ast(input))
|
35
|
+
|
36
|
+
Mutator.mutate(body_ast).each do |mutation|
|
37
|
+
source = AST::Regexp.to_expression(mutation).to_s
|
38
|
+
emit_type(s(:str, source), options)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
30
42
|
end # Regex
|
31
43
|
end # Literal
|
32
44
|
end # Node
|
@@ -17,9 +17,9 @@ module Mutant
|
|
17
17
|
lvasgn: EMPTY_STRING
|
18
18
|
}
|
19
19
|
|
20
|
-
MAP =
|
21
|
-
|
22
|
-
|
20
|
+
MAP = map
|
21
|
+
.transform_values { |prefix| [prefix, /^#{::Regexp.escape(prefix)}/] }
|
22
|
+
.freeze
|
23
23
|
|
24
24
|
handle(*MAP.keys)
|
25
25
|
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mutant
|
4
|
+
class Mutator
|
5
|
+
class Node
|
6
|
+
module Regexp
|
7
|
+
# Mutator for root expression regexp wrapper
|
8
|
+
class RootExpression < Node
|
9
|
+
handle(:regexp_root_expression)
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def dispatch
|
14
|
+
children.each_index(&method(:mutate_child))
|
15
|
+
end
|
16
|
+
end # RootExpression
|
17
|
+
end # Regexp
|
18
|
+
end # Node
|
19
|
+
end # Mutator
|
20
|
+
end # Mutant
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mutant
|
4
|
+
class Mutator
|
5
|
+
class Node
|
6
|
+
module Regexp
|
7
|
+
# Mutator for pipe in `/foo|bar/` regexp
|
8
|
+
class AlternationMeta < Node
|
9
|
+
handle(:regexp_alternation_meta)
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def dispatch
|
14
|
+
children.each_index(&method(:delete_child))
|
15
|
+
end
|
16
|
+
end # AlternationMeta
|
17
|
+
end # Regexp
|
18
|
+
end # Node
|
19
|
+
end # Mutator
|
20
|
+
end # Mutant
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mutant
|
4
|
+
class Mutator
|
5
|
+
class Node
|
6
|
+
module Regexp
|
7
|
+
# Mutator for beginning of line anchor `^`
|
8
|
+
class BeginningOfLineAnchor < Node
|
9
|
+
handle(:regexp_bol_anchor)
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def dispatch
|
14
|
+
emit(s(:regexp_bos_anchor))
|
15
|
+
end
|
16
|
+
end # BeginningOfLineAnchor
|
17
|
+
end # Regexp
|
18
|
+
end # Node
|
19
|
+
end # Mutator
|
20
|
+
end # Mutant
|