mutant 0.10.24 → 0.10.29
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 +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
|