mutant 0.10.24 → 0.10.29
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 +15 -11
- data/lib/mutant/ast/find_metaclass_containing.rb +1 -1
- data/lib/mutant/ast/regexp.rb +17 -0
- data/lib/mutant/ast/regexp/transformer.rb +2 -2
- data/lib/mutant/ast/regexp/transformer/quantifier.rb +4 -2
- data/lib/mutant/bootstrap.rb +1 -1
- data/lib/mutant/cli/command.rb +4 -0
- 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 +1 -1
- data/lib/mutant/context.rb +1 -1
- data/lib/mutant/env.rb +2 -2
- 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 +1 -1
- data/lib/mutant/matcher/config.rb +6 -3
- data/lib/mutant/matcher/method.rb +12 -10
- 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 +0 -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.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/literal/float.rb +1 -3
- data/lib/mutant/mutator/node/literal/integer.rb +3 -6
- data/lib/mutant/mutator/node/literal/range.rb +1 -1
- data/lib/mutant/mutator/node/literal/regex.rb +3 -17
- 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 +0 -11
- data/lib/mutant/mutator/node/regexp/beginning_of_line_anchor.rb +20 -0
- data/lib/mutant/mutator/node/regexp/character_type.rb +1 -1
- 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 +1 -1
- data/lib/mutant/subject/method/instance.rb +5 -42
- 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 +1 -1
- metadata +13 -160
- data/lib/mutant/mutator/node/regexp/greedy_zero_or_more.rb +0 -24
data/lib/mutant/matcher.rb
CHANGED
@@ -16,17 +16,20 @@ module Mutant
|
|
16
16
|
ATTRIBUTE_FORMAT = '%s: [%s]'
|
17
17
|
ENUM_DELIMITER = ','
|
18
18
|
EMPTY_ATTRIBUTES = 'empty'
|
19
|
-
PRESENTATIONS =
|
19
|
+
PRESENTATIONS = {
|
20
20
|
ignore: :syntax,
|
21
21
|
start_expressions: :syntax,
|
22
22
|
subject_filters: :inspect,
|
23
23
|
subjects: :syntax
|
24
|
-
|
24
|
+
}.freeze
|
25
|
+
|
25
26
|
private_constant(*constants(false))
|
26
27
|
|
27
28
|
DEFAULT = new(Hash[anima.attribute_names.map { |name| [name, []] }])
|
28
29
|
|
29
|
-
expression =
|
30
|
+
expression = Transform::Block.capture(:expression) do |input|
|
31
|
+
Mutant::Config::DEFAULT.expression_parser.call(input)
|
32
|
+
end
|
30
33
|
|
31
34
|
expression_array = Transform::Array.new(expression)
|
32
35
|
|
@@ -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)
|
@@ -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
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
@@ -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
|
@@ -28,11 +28,10 @@ module Mutant
|
|
28
28
|
emit_type(s(:str, NULL_REGEXP_SOURCE), options)
|
29
29
|
end
|
30
30
|
|
31
|
-
# NOTE: will only mutate parts of regexp body if the
|
32
|
-
# body is composed of only strings. Regular expressions
|
33
|
-
# with interpolation are skipped
|
34
31
|
def mutate_body
|
35
|
-
|
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))
|
36
35
|
|
37
36
|
Mutator.mutate(body_ast).each do |mutation|
|
38
37
|
source = AST::Regexp.to_expression(mutation).to_s
|
@@ -40,19 +39,6 @@ module Mutant
|
|
40
39
|
end
|
41
40
|
end
|
42
41
|
|
43
|
-
def body_ast
|
44
|
-
AST::Regexp.to_ast(body_expression)
|
45
|
-
end
|
46
|
-
|
47
|
-
def body_expression
|
48
|
-
AST::Regexp.parse(body.map(&:children).join)
|
49
|
-
end
|
50
|
-
memoize :body_expression
|
51
|
-
|
52
|
-
def body
|
53
|
-
children.slice(0...-1)
|
54
|
-
end
|
55
|
-
|
56
42
|
end # Regex
|
57
43
|
end # Literal
|
58
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
|
|
@@ -14,17 +14,6 @@ module Mutant
|
|
14
14
|
children.each_index(&method(:mutate_child))
|
15
15
|
end
|
16
16
|
end # RootExpression
|
17
|
-
|
18
|
-
# Mutator for beginning of line anchor `^`
|
19
|
-
class BeginningOfLineAnchor < Node
|
20
|
-
handle(:regexp_bol_anchor)
|
21
|
-
|
22
|
-
private
|
23
|
-
|
24
|
-
def dispatch
|
25
|
-
emit(s(:regexp_bos_anchor))
|
26
|
-
end
|
27
|
-
end # BeginningOfLineAnchor
|
28
17
|
end # Regexp
|
29
18
|
end # Node
|
30
19
|
end # Mutator
|
@@ -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
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mutant
|
4
|
+
class Mutator
|
5
|
+
class Node
|
6
|
+
module Regexp
|
7
|
+
# Mutator for regexp named capture groups, such as `/(?<foo>bar)/`
|
8
|
+
class NamedGroup < Node
|
9
|
+
handle(:regexp_named_group)
|
10
|
+
|
11
|
+
children :name, :group
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def dispatch
|
16
|
+
return unless group
|
17
|
+
|
18
|
+
emit_group_mutations
|
19
|
+
|
20
|
+
# Allows unused captures to be kept and named if they are explicitly prefixed with an
|
21
|
+
# underscore, like we allow with unused local variables.
|
22
|
+
return if name_underscored?
|
23
|
+
|
24
|
+
emit(s(:regexp_passive_group, group))
|
25
|
+
emit_name_underscore_mutation
|
26
|
+
end
|
27
|
+
|
28
|
+
def emit_name_underscore_mutation
|
29
|
+
emit_type("_#{name}", group)
|
30
|
+
end
|
31
|
+
|
32
|
+
def name_underscored?
|
33
|
+
name.start_with?('_')
|
34
|
+
end
|
35
|
+
end # EndOfLineAnchor
|
36
|
+
end # Regexp
|
37
|
+
end # Node
|
38
|
+
end # Mutator
|
39
|
+
end # Mutant
|