mutant 0.5.23 → 0.5.24
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/Changelog.md +10 -0
- data/config/flay.yml +1 -1
- data/config/reek.yml +19 -19
- data/lib/mutant.rb +12 -39
- data/lib/mutant/ast.rb +5 -0
- data/lib/mutant/ast/meta.rb +131 -0
- data/lib/mutant/ast/named_children.rb +98 -0
- data/lib/mutant/ast/node_predicates.rb +19 -0
- data/lib/mutant/ast/nodes.rb +21 -0
- data/lib/mutant/ast/sexp.rb +34 -0
- data/lib/mutant/ast/types.rb +48 -0
- data/lib/mutant/cache.rb +3 -2
- data/lib/mutant/cli.rb +11 -151
- data/lib/mutant/config.rb +22 -2
- data/lib/mutant/context/scope.rb +11 -21
- data/lib/mutant/delegator.rb +2 -0
- data/lib/mutant/diff.rb +7 -3
- data/lib/mutant/env.rb +49 -0
- data/lib/mutant/expression.rb +36 -8
- data/lib/mutant/expression/methods.rb +62 -0
- data/lib/mutant/expression/namespace.rb +41 -28
- data/lib/mutant/{strategy.rb → integration.rb} +12 -31
- data/lib/mutant/isolation.rb +1 -1
- data/lib/mutant/matcher.rb +1 -21
- data/lib/mutant/matcher/builder.rb +142 -0
- data/lib/mutant/matcher/method.rb +3 -7
- data/lib/mutant/matcher/method/instance.rb +6 -5
- data/lib/mutant/matcher/method/singleton.rb +2 -7
- data/lib/mutant/matcher/methods.rb +11 -14
- data/lib/mutant/matcher/namespace.rb +31 -39
- data/lib/mutant/matcher/scope.rb +13 -2
- data/lib/mutant/meta.rb +0 -1
- data/lib/mutant/meta/example/dsl.rb +5 -1
- data/lib/mutant/mutator/node.rb +16 -44
- data/lib/mutant/mutator/node/or_asgn.rb +1 -1
- data/lib/mutant/mutator/node/send.rb +5 -60
- data/lib/mutant/mutator/node/super.rb +2 -5
- data/lib/mutant/mutator/registry.rb +1 -1
- data/lib/mutant/reporter.rb +10 -0
- data/lib/mutant/reporter/cli.rb +13 -0
- data/lib/mutant/reporter/cli/printer.rb +2 -0
- data/lib/mutant/reporter/cli/progress/config.rb +1 -1
- data/lib/mutant/reporter/cli/progress/noop.rb +2 -0
- data/lib/mutant/reporter/cli/registry.rb +2 -0
- data/lib/mutant/reporter/null.rb +12 -0
- data/lib/mutant/reporter/trace.rb +4 -0
- data/lib/mutant/require_highjack.rb +2 -2
- data/lib/mutant/rspec.rb +0 -0
- data/lib/mutant/runner.rb +2 -0
- data/lib/mutant/runner/config.rb +8 -8
- data/lib/mutant/runner/killer.rb +5 -0
- data/lib/mutant/runner/subject.rb +1 -1
- data/lib/mutant/subject.rb +8 -8
- data/lib/mutant/subject/method.rb +3 -2
- data/lib/mutant/subject/method/instance.rb +1 -1
- data/lib/mutant/test.rb +7 -65
- data/lib/mutant/test/report.rb +59 -0
- data/lib/mutant/version.rb +1 -1
- data/lib/mutant/warning_filter.rb +2 -0
- data/lib/mutant/zombifier.rb +3 -0
- data/lib/mutant/zombifier/file.rb +1 -1
- data/meta/or_asgn.rb +11 -0
- data/meta/send.rb +1 -1
- data/mutant-rspec.gemspec +1 -1
- data/mutant.gemspec +1 -1
- data/spec/integration/mutant/corpus_spec.rb +2 -0
- data/spec/integration/mutant/test_mutator_handles_types_spec.rb +2 -2
- data/spec/spec_helper.rb +4 -3
- data/spec/unit/mutant/cli_new_spec.rb +11 -11
- data/spec/unit/mutant/expression/methods_spec.rb +43 -0
- data/spec/unit/mutant/expression/namespace/flat_spec.rb +1 -1
- data/spec/unit/mutant/expression/namespace/recursive_spec.rb +19 -5
- data/spec/unit/mutant/{strategy_spec.rb → integration_spec.rb} +1 -1
- data/spec/unit/mutant/isolation_spec.rb +3 -1
- data/spec/unit/mutant/matcher/method/instance_spec.rb +5 -5
- data/spec/unit/mutant/matcher/method/singleton_spec.rb +8 -8
- data/spec/unit/mutant/matcher/methods/instance_spec.rb +5 -8
- data/spec/unit/mutant/matcher/methods/singleton_spec.rb +5 -5
- data/spec/unit/mutant/matcher/namespace_spec.rb +18 -23
- data/spec/unit/mutant/mutation_spec.rb +1 -1
- data/spec/unit/mutant/runner/config_spec.rb +4 -5
- data/spec/unit/mutant/runner/mutation_spec.rb +21 -21
- data/spec/unit/mutant/runner/subject_spec.rb +6 -6
- data/spec/unit/mutant/subject/method/instance_spec.rb +0 -4
- data/spec/unit/mutant/subject/method/singleton_spec.rb +0 -1
- data/spec/unit/mutant/subject_spec.rb +3 -3
- metadata +20 -6
- data/lib/mutant/node_helpers.rb +0 -52
@@ -0,0 +1,142 @@
|
|
1
|
+
module Mutant
|
2
|
+
class Matcher
|
3
|
+
# Builder for complex matchers
|
4
|
+
class Builder
|
5
|
+
include Concord.new(:env), AST::Sexp
|
6
|
+
|
7
|
+
# Initalize object
|
8
|
+
#
|
9
|
+
# @param [Cache] env
|
10
|
+
#
|
11
|
+
# @return [undefined]
|
12
|
+
#
|
13
|
+
# @api private
|
14
|
+
#
|
15
|
+
def initialize(env)
|
16
|
+
super
|
17
|
+
@matchers = []
|
18
|
+
@subject_ignores = []
|
19
|
+
@subject_selectors = []
|
20
|
+
end
|
21
|
+
|
22
|
+
# Add a subject ignore
|
23
|
+
#
|
24
|
+
# @param [Expression] expression
|
25
|
+
#
|
26
|
+
# @return [self]
|
27
|
+
#
|
28
|
+
# @api private
|
29
|
+
#
|
30
|
+
def add_subject_ignore(expression)
|
31
|
+
@subject_ignores << expression.matcher(env)
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
# Add a subject selector
|
36
|
+
#
|
37
|
+
# @param [#call] selector
|
38
|
+
#
|
39
|
+
# @return [self]
|
40
|
+
#
|
41
|
+
# @api private
|
42
|
+
#
|
43
|
+
def add_subject_selector(attribute, value)
|
44
|
+
@subject_selectors << Morpher.compile(s(:eql, s(:attribute, attribute), s(:static, value)))
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
# Add a match expression
|
49
|
+
#
|
50
|
+
# @param [Expression] expression
|
51
|
+
#
|
52
|
+
# @return [self]
|
53
|
+
#
|
54
|
+
# @api private
|
55
|
+
#
|
56
|
+
def add_match_expression(expression)
|
57
|
+
@matchers << expression.matcher(env)
|
58
|
+
self
|
59
|
+
end
|
60
|
+
|
61
|
+
# Return generated matcher
|
62
|
+
#
|
63
|
+
# @return [Mutant::Matcher]
|
64
|
+
#
|
65
|
+
# @api private
|
66
|
+
#
|
67
|
+
def matcher
|
68
|
+
if @matchers.empty?
|
69
|
+
return Matcher::Null.new
|
70
|
+
end
|
71
|
+
|
72
|
+
matcher = Matcher::Chain.build(@matchers)
|
73
|
+
|
74
|
+
if predicate
|
75
|
+
Matcher::Filter.new(matcher, predicate)
|
76
|
+
else
|
77
|
+
matcher
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
# Return subject selector
|
84
|
+
#
|
85
|
+
# @return [#call]
|
86
|
+
# if selector is present
|
87
|
+
#
|
88
|
+
# @return [nil]
|
89
|
+
# otherwise
|
90
|
+
#
|
91
|
+
# @api private
|
92
|
+
#
|
93
|
+
def subject_selector
|
94
|
+
Morpher::Evaluator::Predicate::Boolean::Or.new(@subject_selectors) if @subject_selectors.any?
|
95
|
+
end
|
96
|
+
|
97
|
+
# Return predicate
|
98
|
+
#
|
99
|
+
# @return [#call]
|
100
|
+
# if filter is needed
|
101
|
+
#
|
102
|
+
# @return [nil]
|
103
|
+
# othrwise
|
104
|
+
#
|
105
|
+
# @api private
|
106
|
+
#
|
107
|
+
def predicate
|
108
|
+
if subject_selector && subject_rejector
|
109
|
+
Morpher::Evaluator::Predicate::Boolean::And.new([
|
110
|
+
subject_selector,
|
111
|
+
Morpher::Evaluator::Predicate::Negation.new(subject_rejector)
|
112
|
+
])
|
113
|
+
elsif subject_selector
|
114
|
+
subject_selector
|
115
|
+
elsif subject_rejector
|
116
|
+
Morpher::Evaluator::Predicate::Negation.new(subject_rejector)
|
117
|
+
else
|
118
|
+
nil
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# Return subject rejector
|
123
|
+
#
|
124
|
+
# @return [#call]
|
125
|
+
# if there is a subject rejector
|
126
|
+
#
|
127
|
+
# @return [nil]
|
128
|
+
# otherwise
|
129
|
+
#
|
130
|
+
# @api private
|
131
|
+
#
|
132
|
+
def subject_rejector
|
133
|
+
rejectors = @subject_ignores.flat_map(&:to_a).map do |subject|
|
134
|
+
Morpher.compile(s(:eql, s(:attribute, :identification), s(:static, subject.identification)))
|
135
|
+
end
|
136
|
+
|
137
|
+
Morpher::Evaluator::Predicate::Boolean::Or.new(rejectors) if rejectors.any?
|
138
|
+
end
|
139
|
+
|
140
|
+
end # Builder
|
141
|
+
end # Matcher
|
142
|
+
end # Mutant
|
@@ -2,7 +2,7 @@ module Mutant
|
|
2
2
|
class Matcher
|
3
3
|
# Matcher for subjects that are a specific method
|
4
4
|
class Method < self
|
5
|
-
include Adamantium::Flat, Concord::Public.new(:
|
5
|
+
include Adamantium::Flat, Concord::Public.new(:env, :scope, :method)
|
6
6
|
include Equalizer.new(:identification)
|
7
7
|
|
8
8
|
# Methods within rbx kernel directory are precompiled and their source
|
@@ -40,11 +40,7 @@ module Mutant
|
|
40
40
|
def skip?
|
41
41
|
location = source_location
|
42
42
|
if location.nil? || BLACKLIST.match(location.first)
|
43
|
-
|
44
|
-
'%s does not have valid source location unable to emit matcher',
|
45
|
-
method.inspect
|
46
|
-
)
|
47
|
-
$stderr.puts(message)
|
43
|
+
env.warn(format('%s does not have valid source location unable to emit matcher', method.inspect))
|
48
44
|
true
|
49
45
|
else
|
50
46
|
false
|
@@ -78,7 +74,7 @@ module Mutant
|
|
78
74
|
# @api private
|
79
75
|
#
|
80
76
|
def ast
|
81
|
-
cache.parse(source_path)
|
77
|
+
env.cache.parse(source_path)
|
82
78
|
end
|
83
79
|
|
84
80
|
# Return path to source
|
@@ -7,7 +7,7 @@ module Mutant
|
|
7
7
|
|
8
8
|
# Dispatching builder, detects memoizable case
|
9
9
|
#
|
10
|
-
# @param [
|
10
|
+
# @param [Env] env
|
11
11
|
# @param [Class, Module] scope
|
12
12
|
# @param [UnboundMethod] method
|
13
13
|
#
|
@@ -15,10 +15,10 @@ module Mutant
|
|
15
15
|
#
|
16
16
|
# @api private
|
17
17
|
#
|
18
|
-
def self.build(
|
18
|
+
def self.build(env, scope, method)
|
19
19
|
name = method.name
|
20
20
|
if scope.ancestors.include?(::Memoizable) && scope.memoized?(name)
|
21
|
-
return Memoized.new(
|
21
|
+
return Memoized.new(env, scope, method)
|
22
22
|
end
|
23
23
|
super
|
24
24
|
end
|
@@ -47,8 +47,9 @@ module Mutant
|
|
47
47
|
# @api private
|
48
48
|
#
|
49
49
|
def match?(node)
|
50
|
-
location
|
51
|
-
expression
|
50
|
+
location = node.location || return
|
51
|
+
expression = location.expression || return
|
52
|
+
|
52
53
|
expression.line.equal?(source_line) &&
|
53
54
|
node.type.equal?(:def) &&
|
54
55
|
node.children[NAME_INDEX].equal?(method_name)
|
@@ -18,7 +18,6 @@ module Mutant
|
|
18
18
|
|
19
19
|
RECEIVER_INDEX = 0
|
20
20
|
NAME_INDEX = 1
|
21
|
-
CONST_NAME_INDEX = 1
|
22
21
|
|
23
22
|
private
|
24
23
|
|
@@ -76,11 +75,7 @@ module Mutant
|
|
76
75
|
when :const
|
77
76
|
receiver_name?(receiver)
|
78
77
|
else
|
79
|
-
|
80
|
-
'Can only match :defs on :self or :const got %s unable to match',
|
81
|
-
receiver.type.inspect
|
82
|
-
)
|
83
|
-
$stderr.puts(message)
|
78
|
+
env.warn(format('Can only match :defs on :self or :const got %s unable to match', receiver.type.inspect))
|
84
79
|
false
|
85
80
|
end
|
86
81
|
end
|
@@ -94,7 +89,7 @@ module Mutant
|
|
94
89
|
# @api private
|
95
90
|
#
|
96
91
|
def receiver_name?(node)
|
97
|
-
name = node.children[
|
92
|
+
name = node.children[NAME_INDEX]
|
98
93
|
name.to_s.eql?(context.unqualified_name)
|
99
94
|
end
|
100
95
|
|
@@ -2,7 +2,7 @@ module Mutant
|
|
2
2
|
class Matcher
|
3
3
|
# Abstract base class for matcher that returns method subjects from scope
|
4
4
|
class Methods < self
|
5
|
-
include AbstractType, Concord::Public.new(:
|
5
|
+
include AbstractType, Concord::Public.new(:env, :scope)
|
6
6
|
|
7
7
|
# Enumerate subjects
|
8
8
|
#
|
@@ -17,9 +17,7 @@ module Mutant
|
|
17
17
|
def each(&block)
|
18
18
|
return to_enum unless block_given?
|
19
19
|
|
20
|
-
|
21
|
-
emit_matches(method, &block)
|
22
|
-
end
|
20
|
+
subjects.each(&block)
|
23
21
|
|
24
22
|
self
|
25
23
|
end
|
@@ -50,19 +48,18 @@ module Mutant
|
|
50
48
|
|
51
49
|
private
|
52
50
|
|
53
|
-
#
|
54
|
-
#
|
55
|
-
# @param [UnboundMethod, Method] method
|
51
|
+
# Return subjects
|
56
52
|
#
|
57
|
-
# @return [
|
53
|
+
# @return [Array<Subject>]
|
58
54
|
#
|
59
55
|
# @api private
|
60
56
|
#
|
61
|
-
def
|
62
|
-
|
63
|
-
|
64
|
-
end
|
57
|
+
def subjects
|
58
|
+
methods.map do |method|
|
59
|
+
matcher.build(env, scope, method)
|
60
|
+
end.flat_map(&:to_a)
|
65
61
|
end
|
62
|
+
memoize :subjects
|
66
63
|
|
67
64
|
# Return candidate names
|
68
65
|
#
|
@@ -73,11 +70,11 @@ module Mutant
|
|
73
70
|
# @api private
|
74
71
|
#
|
75
72
|
def candidate_names
|
76
|
-
|
73
|
+
(
|
77
74
|
candidate_scope.public_instance_methods(false) +
|
78
75
|
candidate_scope.private_instance_methods(false) +
|
79
76
|
candidate_scope.protected_instance_methods(false)
|
80
|
-
|
77
|
+
).sort
|
81
78
|
end
|
82
79
|
|
83
80
|
# Return candidate scope
|
@@ -2,8 +2,10 @@ module Mutant
|
|
2
2
|
class Matcher
|
3
3
|
|
4
4
|
# Matcher for specific namespace
|
5
|
+
#
|
6
|
+
# rubocop:disable LineLength
|
5
7
|
class Namespace < self
|
6
|
-
include Concord::Public.new(:
|
8
|
+
include Concord::Public.new(:env, :expression)
|
7
9
|
|
8
10
|
# Enumerate subjects
|
9
11
|
#
|
@@ -17,9 +19,8 @@ module Mutant
|
|
17
19
|
#
|
18
20
|
def each(&block)
|
19
21
|
return to_enum unless block_given?
|
20
|
-
|
21
22
|
scopes.each do |scope|
|
22
|
-
|
23
|
+
scope.each(&block)
|
23
24
|
end
|
24
25
|
|
25
26
|
self
|
@@ -27,30 +28,18 @@ module Mutant
|
|
27
28
|
|
28
29
|
private
|
29
30
|
|
30
|
-
# Return pattern
|
31
|
-
#
|
32
|
-
# @return [Regexp]
|
33
|
-
#
|
34
|
-
# @api private
|
35
|
-
#
|
36
|
-
def pattern
|
37
|
-
/\A#{Regexp.escape(namespace)}(?:\z|::)/
|
38
|
-
end
|
39
|
-
memoize :pattern
|
40
|
-
|
41
31
|
# Return scope enumerator
|
42
32
|
#
|
43
|
-
# @return [
|
33
|
+
# @return [Array<Class, Module>]
|
44
34
|
#
|
45
35
|
# @api private
|
46
36
|
#
|
47
|
-
def scopes
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
emit_scope(scope, &block)
|
52
|
-
end
|
37
|
+
def scopes
|
38
|
+
::ObjectSpace.each_object(Module).each_with_object([]) do |scope, aggregate|
|
39
|
+
aggregate << Scope.new(env, scope) if match?(scope)
|
40
|
+
end.sort_by(&:identification)
|
53
41
|
end
|
42
|
+
memoize :scopes
|
54
43
|
|
55
44
|
# Return scope name
|
56
45
|
#
|
@@ -64,36 +53,39 @@ module Mutant
|
|
64
53
|
#
|
65
54
|
# @api private
|
66
55
|
#
|
67
|
-
|
56
|
+
# rubocop:disable LineLength
|
57
|
+
#
|
58
|
+
def scope_name(scope)
|
68
59
|
scope.name
|
69
60
|
rescue => exception
|
70
|
-
|
71
|
-
WARNING:
|
72
|
-
While optaining #{scope.class}#name from: #{scope.inspect}
|
73
|
-
It raised an error: #{exception.inspect} fix your lib!
|
74
|
-
MESSAGE
|
61
|
+
env.warn("While optaining #{scope.class}#name from: #{scope.inspect} It raised an error: #{exception.inspect} fix your lib!")
|
75
62
|
nil
|
76
63
|
end
|
77
64
|
|
78
|
-
#
|
65
|
+
# Test scope if name matches expresion
|
79
66
|
#
|
80
67
|
# @param [Module,Class] scope
|
81
68
|
#
|
82
|
-
# @return [
|
69
|
+
# @return [Boolean]
|
83
70
|
#
|
84
71
|
# @api private
|
85
72
|
#
|
86
|
-
def
|
87
|
-
name =
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
Fix your lib to support normal ruby semantics!
|
93
|
-
MESSAGE
|
94
|
-
return
|
73
|
+
def match?(scope)
|
74
|
+
name = scope_name(scope) or return false
|
75
|
+
|
76
|
+
unless name.kind_of?(String)
|
77
|
+
env.warn("#{scope.class}#name from: #{scope.inspect} did not return a String or nil. Fix your lib to support normal ruby semantics!")
|
78
|
+
return false
|
95
79
|
end
|
96
|
-
|
80
|
+
|
81
|
+
scope_expression = Expression.try_parse(name)
|
82
|
+
|
83
|
+
unless scope_expression
|
84
|
+
$stderr.puts("WARNING: #{name.inspect} is not an identifiable ruby class name.")
|
85
|
+
return false
|
86
|
+
end
|
87
|
+
|
88
|
+
expression.prefix?(scope_expression)
|
97
89
|
end
|
98
90
|
|
99
91
|
end # Namespace
|
data/lib/mutant/matcher/scope.rb
CHANGED
@@ -2,13 +2,24 @@ module Mutant
|
|
2
2
|
class Matcher
|
3
3
|
# Matcher for specific namespace
|
4
4
|
class Scope < self
|
5
|
-
include Concord::Public.new(:
|
5
|
+
include Concord::Public.new(:env, :scope)
|
6
6
|
|
7
7
|
MATCHERS = [
|
8
8
|
Matcher::Methods::Singleton,
|
9
9
|
Matcher::Methods::Instance
|
10
10
|
].freeze
|
11
11
|
|
12
|
+
# Return identification
|
13
|
+
#
|
14
|
+
# @return [String]
|
15
|
+
#
|
16
|
+
# @api private
|
17
|
+
#
|
18
|
+
def identification
|
19
|
+
scope.name
|
20
|
+
end
|
21
|
+
memoize :identification
|
22
|
+
|
12
23
|
# Enumerate subjects
|
13
24
|
#
|
14
25
|
# @return [self]
|
@@ -23,7 +34,7 @@ module Mutant
|
|
23
34
|
return to_enum unless block_given?
|
24
35
|
|
25
36
|
MATCHERS.each do |matcher|
|
26
|
-
matcher.
|
37
|
+
matcher.new(env, scope).each(&block)
|
27
38
|
end
|
28
39
|
|
29
40
|
self
|