mutant 0.10.23 → 0.10.28
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 +34 -14
- 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/integration.rb +8 -2
- data/lib/mutant/isolation/exception.rb +22 -0
- data/lib/mutant/isolation/fork.rb +9 -12
- 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 +14 -13
- 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 -2
- 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.rb +0 -5
- data/lib/mutant/mutator/node/argument.rb +2 -2
- data/lib/mutant/mutator/node/dynamic_literal.rb +1 -1
- data/lib/mutant/mutator/node/index.rb +1 -0
- data/lib/mutant/mutator/node/kwargs.rb +2 -2
- data/lib/mutant/mutator/node/literal/float.rb +1 -3
- data/lib/mutant/mutator/node/literal/integer.rb +3 -6
- data/lib/mutant/mutator/node/literal/regex.rb +12 -0
- data/lib/mutant/mutator/node/module.rb +19 -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 +73 -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/reporter/cli/printer/isolation_result.rb +3 -1
- 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/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 +38 -149
- data/lib/mutant/warnings.rb +0 -106
data/lib/mutant/loader.rb
CHANGED
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,12 +5,9 @@ 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
|
-
# Source locations we cannot acces
|
12
|
-
BLACKLIST = %w[(eval) <internal:prelude>].to_set.freeze
|
13
|
-
|
14
11
|
SOURCE_LOCATION_WARNING_FORMAT =
|
15
12
|
'%s does not have a valid source location, unable to emit subject'
|
16
13
|
|
@@ -32,11 +29,13 @@ module Mutant
|
|
32
29
|
# logic would be implemented directly on the Matcher::Method
|
33
30
|
# instance
|
34
31
|
class Evaluator
|
35
|
-
include
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
32
|
+
include(
|
33
|
+
AbstractType,
|
34
|
+
Adamantium,
|
35
|
+
Concord.new(:scope, :target_method, :env),
|
36
|
+
Procto,
|
37
|
+
AST::NodePredicates
|
38
|
+
)
|
40
39
|
|
41
40
|
# Matched subjects
|
42
41
|
#
|
@@ -51,7 +50,10 @@ module Mutant
|
|
51
50
|
|
52
51
|
def skip?
|
53
52
|
location = source_location
|
54
|
-
|
53
|
+
|
54
|
+
file = location&.first
|
55
|
+
|
56
|
+
if location.nil? || !file.end_with?('.rb')
|
55
57
|
env.warn(SOURCE_LOCATION_WARNING_FORMAT % target_method)
|
56
58
|
elsif matched_node_path.any?(&method(:n_block?))
|
57
59
|
env.warn(CLOSURE_WARNING_FORMAT % target_method)
|
@@ -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
|
|
@@ -79,7 +79,11 @@ module Mutant
|
|
79
79
|
|
80
80
|
def singleton_mutations
|
81
81
|
mutation('nil')
|
82
|
-
|
82
|
+
end
|
83
|
+
|
84
|
+
def regexp_mutations
|
85
|
+
mutation('//')
|
86
|
+
mutation('/nomatch\A/')
|
83
87
|
end
|
84
88
|
|
85
89
|
def node(input)
|
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)
|
data/lib/mutant/mutator/node.rb
CHANGED
@@ -28,7 +28,7 @@ module Mutant
|
|
28
28
|
def emit_argument_mutations
|
29
29
|
children.each_with_index do |child, index|
|
30
30
|
Mutator.mutate(child).each do |mutant|
|
31
|
-
|
31
|
+
unless forbid_argument?(mutant)
|
32
32
|
emit_child_update(index, mutant)
|
33
33
|
end
|
34
34
|
end
|
@@ -36,7 +36,7 @@ module Mutant
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def forbid_argument?(node)
|
39
|
-
|
39
|
+
n_pair?(node) && DISALLOW.include?(node.children.first.type)
|
40
40
|
end
|
41
41
|
end # Kwargs
|
42
42
|
end # Node
|
@@ -9,6 +9,8 @@ module Mutant
|
|
9
9
|
|
10
10
|
handle(:int)
|
11
11
|
|
12
|
+
children :value
|
13
|
+
|
12
14
|
private
|
13
15
|
|
14
16
|
def dispatch
|
@@ -17,12 +19,7 @@ module Mutant
|
|
17
19
|
end
|
18
20
|
|
19
21
|
def values
|
20
|
-
[0, 1,
|
21
|
-
end
|
22
|
-
|
23
|
-
def value
|
24
|
-
value, = children
|
25
|
-
value
|
22
|
+
[0, 1, value + 1, value - 1]
|
26
23
|
end
|
27
24
|
|
28
25
|
end # Integer
|
@@ -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
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mutant
|
4
|
+
class Mutator
|
5
|
+
class Node
|
6
|
+
class Module < self
|
7
|
+
handle :module
|
8
|
+
|
9
|
+
children :klass, :body
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def dispatch
|
14
|
+
emit_body_mutations if body
|
15
|
+
end
|
16
|
+
end # Module
|
17
|
+
end # Node
|
18
|
+
end # Mutator
|
19
|
+
end # Mutant
|
@@ -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
|
|