mutant 0.8.7 → 0.8.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Changelog.md +5 -0
- data/README.md +64 -3
- data/config/flay.yml +1 -1
- data/lib/mutant.rb +2 -0
- data/lib/mutant/cli.rb +1 -1
- data/lib/mutant/env/bootstrap.rb +36 -8
- data/lib/mutant/expression/method.rb +3 -5
- data/lib/mutant/expression/methods.rb +2 -4
- data/lib/mutant/expression/namespace.rb +4 -8
- data/lib/mutant/matcher.rb +3 -18
- data/lib/mutant/matcher/chain.rb +7 -13
- data/lib/mutant/matcher/compiler.rb +2 -13
- data/lib/mutant/matcher/filter.rb +6 -19
- data/lib/mutant/matcher/method.rb +124 -104
- data/lib/mutant/matcher/method/instance.rb +40 -34
- data/lib/mutant/matcher/method/singleton.rb +80 -61
- data/lib/mutant/matcher/methods.rb +19 -29
- data/lib/mutant/matcher/namespace.rb +22 -16
- data/lib/mutant/matcher/null.rb +4 -7
- data/lib/mutant/matcher/scope.rb +23 -13
- data/lib/mutant/matcher/static.rb +17 -0
- data/lib/mutant/mutation.rb +0 -5
- data/lib/mutant/reporter/cli/format.rb +2 -3
- data/lib/mutant/reporter/cli/printer/env_progress.rb +37 -11
- data/lib/mutant/reporter/cli/printer/status_progressive.rb +1 -1
- data/lib/mutant/scope.rb +6 -0
- data/lib/mutant/subject/method.rb +0 -7
- data/lib/mutant/subject/method/instance.rb +0 -10
- data/lib/mutant/subject/method/singleton.rb +0 -10
- data/lib/mutant/version.rb +1 -1
- data/lib/mutant/zombifier.rb +2 -1
- data/mutant-rspec.gemspec +1 -1
- data/spec/integration/mutant/rspec_spec.rb +1 -1
- data/spec/shared/method_matcher_behavior.rb +21 -14
- data/spec/spec_helper.rb +6 -0
- data/spec/unit/mutant/env/boostrap_spec.rb +88 -26
- data/spec/unit/mutant/env_spec.rb +0 -1
- data/spec/unit/mutant/expression/method_spec.rb +3 -3
- data/spec/unit/mutant/expression/methods_spec.rb +3 -4
- data/spec/unit/mutant/expression/namespace/flat_spec.rb +2 -3
- data/spec/unit/mutant/expression/namespace/recursive_spec.rb +2 -4
- data/spec/unit/mutant/matcher/chain_spec.rb +21 -29
- data/spec/unit/mutant/matcher/compiler/subject_prefix_spec.rb +16 -13
- data/spec/unit/mutant/matcher/compiler_spec.rb +49 -60
- data/spec/unit/mutant/matcher/filter_spec.rb +15 -31
- data/spec/unit/mutant/matcher/method/instance_spec.rb +84 -128
- data/spec/unit/mutant/matcher/method/singleton_spec.rb +48 -52
- data/spec/unit/mutant/matcher/methods/instance_spec.rb +21 -24
- data/spec/unit/mutant/matcher/methods/singleton_spec.rb +18 -21
- data/spec/unit/mutant/matcher/namespace_spec.rb +30 -38
- data/spec/unit/mutant/matcher/null_spec.rb +5 -20
- data/spec/unit/mutant/matcher/scope_spec.rb +33 -0
- data/spec/unit/mutant/matcher/static_spec.rb +11 -0
- data/spec/unit/mutant/mutation_spec.rb +30 -10
- data/spec/unit/mutant/reporter/cli/printer/env_progress_spec.rb +6 -0
- data/spec/unit/mutant/reporter/cli/printer/env_result_spec.rb +2 -0
- data/spec/unit/mutant/reporter/cli/printer/status_spec.rb +10 -0
- data/spec/unit/mutant/reporter/cli_spec.rb +4 -0
- data/spec/unit/mutant/subject/method/instance_spec.rb +0 -28
- data/spec/unit/mutant/subject/method/singleton_spec.rb +0 -28
- data/test_app/Gemfile.rspec3.4 +7 -0
- data/test_app/lib/test_app.rb +16 -12
- data/test_app/lib/test_app/literal.rb +3 -0
- metadata +9 -2
@@ -3,62 +3,68 @@ module Mutant
|
|
3
3
|
class Method
|
4
4
|
# Matcher for instance methods
|
5
5
|
class Instance < self
|
6
|
-
SUBJECT_CLASS = Subject::Method::Instance
|
7
6
|
|
8
7
|
# Dispatching builder, detects memoizable case
|
9
8
|
#
|
10
|
-
# @param [Env::Boostrap] env
|
11
9
|
# @param [Class, Module] scope
|
12
10
|
# @param [UnboundMethod] method
|
13
11
|
#
|
14
12
|
# @return [Matcher::Method::Instance]
|
15
13
|
#
|
16
14
|
# @api private
|
17
|
-
def self.
|
15
|
+
def self.new(scope, target_method)
|
18
16
|
name = target_method.name
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
NAME_INDEX = 0
|
26
|
-
|
27
|
-
private
|
17
|
+
evaluator =
|
18
|
+
if scope.include?(Memoizable) && scope.memoized?(name)
|
19
|
+
Evaluator::Memoized
|
20
|
+
else
|
21
|
+
Evaluator
|
22
|
+
end
|
28
23
|
|
29
|
-
|
30
|
-
#
|
31
|
-
# @param [Parser::AST::Node] node
|
32
|
-
#
|
33
|
-
# @return [Boolean]
|
34
|
-
#
|
35
|
-
# @api private
|
36
|
-
def match?(node)
|
37
|
-
location = node.location || return
|
38
|
-
expression = location.expression || return
|
39
|
-
|
40
|
-
expression.line.equal?(source_line) &&
|
41
|
-
node.type.equal?(:def) &&
|
42
|
-
node.children[NAME_INDEX].equal?(method_name)
|
24
|
+
super(scope, target_method, evaluator)
|
43
25
|
end
|
44
26
|
|
45
|
-
#
|
46
|
-
class
|
47
|
-
SUBJECT_CLASS = Subject::Method::Instance
|
27
|
+
# Instance method specific evaluator
|
28
|
+
class Evaluator < Evaluator
|
29
|
+
SUBJECT_CLASS = Subject::Method::Instance
|
30
|
+
NAME_INDEX = 0
|
48
31
|
|
49
32
|
private
|
50
33
|
|
51
|
-
#
|
34
|
+
# Check if node is matched
|
35
|
+
#
|
36
|
+
# @param [Parser::AST::Node] node
|
52
37
|
#
|
53
|
-
# @return [
|
38
|
+
# @return [Boolean]
|
54
39
|
#
|
55
40
|
# @api private
|
56
|
-
def
|
57
|
-
|
41
|
+
def match?(node)
|
42
|
+
n_def?(node) &&
|
43
|
+
node.location.line.equal?(source_line) &&
|
44
|
+
node.children.fetch(NAME_INDEX).equal?(method_name)
|
58
45
|
end
|
59
46
|
|
60
|
-
|
47
|
+
# Evaluator specialized for memoized instance mthods
|
48
|
+
class Memoized < self
|
49
|
+
SUBJECT_CLASS = Subject::Method::Instance::Memoized
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
# Source location
|
54
|
+
#
|
55
|
+
# @return [Array{String,Fixnum}]
|
56
|
+
#
|
57
|
+
# @api private
|
58
|
+
def source_location
|
59
|
+
scope
|
60
|
+
.unmemoized_instance_method(method_name)
|
61
|
+
.source_location
|
62
|
+
end
|
63
|
+
|
64
|
+
end # Memoized
|
65
|
+
end # Evaluator
|
61
66
|
|
67
|
+
private_constant(*constants(false))
|
62
68
|
end # Instance
|
63
69
|
end # Method
|
64
70
|
end # Matcher
|
@@ -3,79 +3,98 @@ module Mutant
|
|
3
3
|
class Method
|
4
4
|
# Matcher for singleton methods
|
5
5
|
class Singleton < self
|
6
|
-
SUBJECT_CLASS = Subject::Method::Singleton
|
7
|
-
RECEIVER_INDEX = 0
|
8
|
-
NAME_INDEX = 1
|
9
6
|
|
10
|
-
|
11
|
-
|
12
|
-
# Test for node match
|
7
|
+
# New singleton method matcher
|
13
8
|
#
|
14
|
-
# @param [
|
9
|
+
# @param [Class, Module] scope
|
10
|
+
# @param [Symbol] method_name
|
15
11
|
#
|
16
|
-
# @return [
|
12
|
+
# @return [Matcher::Method::Singleton]
|
17
13
|
#
|
18
14
|
# @api private
|
19
|
-
def
|
20
|
-
|
15
|
+
def self.new(scope, method_name)
|
16
|
+
super(scope, method_name, Evaluator)
|
21
17
|
end
|
22
18
|
|
23
|
-
#
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
#
|
29
|
-
# @api private
|
30
|
-
def line?(node)
|
31
|
-
expression = node.location.expression
|
32
|
-
return false unless expression
|
33
|
-
expression.line.equal?(source_line)
|
34
|
-
end
|
19
|
+
# Singleton method evaluator
|
20
|
+
class Evaluator < Evaluator
|
21
|
+
SUBJECT_CLASS = Subject::Method::Singleton
|
22
|
+
RECEIVER_INDEX = 0
|
23
|
+
NAME_INDEX = 1
|
35
24
|
|
36
|
-
|
37
|
-
#
|
38
|
-
# @param [Parser::AST::Node] node
|
39
|
-
#
|
40
|
-
# @return [Boolean]
|
41
|
-
#
|
42
|
-
# @api private
|
43
|
-
def name?(node)
|
44
|
-
node.children[NAME_INDEX].equal?(method_name)
|
45
|
-
end
|
25
|
+
private
|
46
26
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
case receiver.type
|
57
|
-
when :self
|
58
|
-
true
|
59
|
-
when :const
|
60
|
-
receiver_name?(receiver)
|
61
|
-
else
|
62
|
-
env.warn(format('Can only match :defs on :self or :const got %s unable to match', receiver.type.inspect))
|
63
|
-
false
|
27
|
+
# Test for node match
|
28
|
+
#
|
29
|
+
# @param [Parser::AST::Node] node
|
30
|
+
#
|
31
|
+
# @return [Boolean]
|
32
|
+
#
|
33
|
+
# @api private
|
34
|
+
def match?(node)
|
35
|
+
n_defs?(node) && line?(node) && name?(node) && receiver?(node)
|
64
36
|
end
|
65
|
-
end
|
66
37
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
38
|
+
# Test for line match
|
39
|
+
#
|
40
|
+
# @param [Parser::AST::Node] node
|
41
|
+
#
|
42
|
+
# @return [Boolean]
|
43
|
+
#
|
44
|
+
# @api private
|
45
|
+
def line?(node)
|
46
|
+
node
|
47
|
+
.location
|
48
|
+
.line
|
49
|
+
.equal?(source_line)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Test for name match
|
53
|
+
#
|
54
|
+
# @param [Parser::AST::Node] node
|
55
|
+
#
|
56
|
+
# @return [Boolean]
|
57
|
+
#
|
58
|
+
# @api private
|
59
|
+
def name?(node)
|
60
|
+
node.children.fetch(NAME_INDEX).equal?(method_name)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Test for receiver match
|
64
|
+
#
|
65
|
+
# @param [Parser::AST::Node] node
|
66
|
+
#
|
67
|
+
# @return [Boolean]
|
68
|
+
#
|
69
|
+
# @api private
|
70
|
+
def receiver?(node)
|
71
|
+
receiver = node.children.fetch(RECEIVER_INDEX)
|
72
|
+
case receiver.type
|
73
|
+
when :self
|
74
|
+
true
|
75
|
+
when :const
|
76
|
+
receiver_name?(receiver)
|
77
|
+
else
|
78
|
+
env.warn(format('Can only match :defs on :self or :const got %s unable to match', receiver.type.inspect))
|
79
|
+
nil
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Test if receiver name matches context
|
84
|
+
#
|
85
|
+
# @param [Parser::AST::Node] node
|
86
|
+
#
|
87
|
+
# @return [Boolean]
|
88
|
+
#
|
89
|
+
# @api private
|
90
|
+
def receiver_name?(node)
|
91
|
+
name = node.children.fetch(NAME_INDEX)
|
92
|
+
name.to_s.eql?(context.unqualified_name)
|
93
|
+
end
|
94
|
+
|
95
|
+
end # Evaluator
|
78
96
|
|
97
|
+
private_constant(*constants(false))
|
79
98
|
end # Singleton
|
80
99
|
end # Method
|
81
100
|
end # Matcher
|
@@ -2,23 +2,27 @@ 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
|
5
|
+
include AbstractType, Concord.new(:scope)
|
6
|
+
|
7
|
+
CANDIDATE_NAMES = IceNine.deep_freeze(%i[
|
8
|
+
public_instance_methods
|
9
|
+
private_instance_methods
|
10
|
+
protected_instance_methods
|
11
|
+
])
|
12
|
+
|
13
|
+
private_constant(*constants(false))
|
6
14
|
|
7
15
|
# Enumerate subjects
|
8
16
|
#
|
9
|
-
# @
|
10
|
-
# if block given
|
17
|
+
# @param [Env] env
|
11
18
|
#
|
12
|
-
# @return [
|
13
|
-
# otherwise
|
19
|
+
# @return [Enumerable<Subject>]
|
14
20
|
#
|
15
21
|
# @api private
|
16
|
-
def
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
self
|
22
|
+
def call(env)
|
23
|
+
Chain.new(
|
24
|
+
methods.map { |method| matcher.new(scope, method) }
|
25
|
+
).call(env)
|
22
26
|
end
|
23
27
|
|
24
28
|
private
|
@@ -45,29 +49,16 @@ module Mutant
|
|
45
49
|
end
|
46
50
|
memoize :methods
|
47
51
|
|
48
|
-
# Subjects detected on scope
|
49
|
-
#
|
50
|
-
# @return [Array<Subject>]
|
51
|
-
#
|
52
|
-
# @api private
|
53
|
-
def subjects
|
54
|
-
methods.map do |method|
|
55
|
-
matcher.build(env, scope, method)
|
56
|
-
end.flat_map(&:to_a)
|
57
|
-
end
|
58
|
-
memoize :subjects
|
59
|
-
|
60
52
|
# Candidate method names on target scope
|
61
53
|
#
|
62
54
|
# @return [Enumerable<Symbol>]
|
63
55
|
#
|
64
56
|
# @api private
|
65
57
|
def candidate_names
|
66
|
-
|
67
|
-
candidate_scope.
|
68
|
-
|
69
|
-
|
70
|
-
).sort
|
58
|
+
CANDIDATE_NAMES
|
59
|
+
.map(&candidate_scope.method(:public_send))
|
60
|
+
.reduce(:+)
|
61
|
+
.sort
|
71
62
|
end
|
72
63
|
|
73
64
|
# Candidate scope
|
@@ -133,7 +124,6 @@ module Mutant
|
|
133
124
|
end
|
134
125
|
|
135
126
|
end # Instance
|
136
|
-
|
137
127
|
end # Methods
|
138
128
|
end # Matcher
|
139
129
|
end # Mutant
|
@@ -1,34 +1,40 @@
|
|
1
1
|
module Mutant
|
2
2
|
class Matcher
|
3
|
-
|
4
3
|
# Matcher for specific namespace
|
5
4
|
class Namespace < self
|
6
|
-
include Concord::Public.new(:
|
5
|
+
include Concord::Public.new(:expression)
|
7
6
|
|
8
7
|
# Enumerate subjects
|
9
8
|
#
|
10
|
-
# @
|
11
|
-
# if block given
|
9
|
+
# @param [Env] env
|
12
10
|
#
|
13
|
-
# @return [
|
14
|
-
# otherwise
|
11
|
+
# @return [Enumerable<Subject>]
|
15
12
|
#
|
16
13
|
# @api private
|
17
|
-
def
|
18
|
-
|
19
|
-
|
20
|
-
env
|
21
|
-
scope.each(&block) if match?(scope)
|
22
|
-
end
|
23
|
-
|
24
|
-
self
|
14
|
+
def call(env)
|
15
|
+
Chain.new(
|
16
|
+
matched_scopes(env).map { |scope| Scope.new(scope.raw) }
|
17
|
+
).call(env)
|
25
18
|
end
|
26
19
|
|
27
20
|
private
|
28
21
|
|
29
|
-
#
|
22
|
+
# The matched scopes
|
23
|
+
#
|
24
|
+
# @param [Env] env
|
25
|
+
#
|
26
|
+
# @return [Enumerable<Scope>]
|
27
|
+
#
|
28
|
+
# @api private
|
29
|
+
def matched_scopes(env)
|
30
|
+
env
|
31
|
+
.matchable_scopes
|
32
|
+
.select(&method(:match?))
|
33
|
+
end
|
34
|
+
|
35
|
+
# Test scope if matches expression
|
30
36
|
#
|
31
|
-
# @param [
|
37
|
+
# @param [Scope] scope
|
32
38
|
#
|
33
39
|
# @return [Boolean]
|
34
40
|
#
|
data/lib/mutant/matcher/null.rb
CHANGED
@@ -6,16 +6,13 @@ module Mutant
|
|
6
6
|
|
7
7
|
# Enumerate subjects
|
8
8
|
#
|
9
|
-
# @
|
10
|
-
# if no block given
|
9
|
+
# @param [Env] env
|
11
10
|
#
|
12
|
-
# @return [
|
13
|
-
# otherwise
|
11
|
+
# @return [Enumerable<Subject>]
|
14
12
|
#
|
15
13
|
# @api private
|
16
|
-
def
|
17
|
-
|
18
|
-
self
|
14
|
+
def call(_env)
|
15
|
+
EMPTY_ARRAY
|
19
16
|
end
|
20
17
|
|
21
18
|
end # Null
|
data/lib/mutant/matcher/scope.rb
CHANGED
@@ -1,31 +1,41 @@
|
|
1
1
|
module Mutant
|
2
2
|
class Matcher
|
3
|
-
# Matcher
|
3
|
+
# Matcher expanding Mutant::Scope objects into method matches
|
4
|
+
# at singleton or instance level
|
5
|
+
#
|
6
|
+
# If we *ever* get other subjects than methods, its likely the place
|
7
|
+
# to hook in custom matchers. In that case the scope matchers to expand
|
8
|
+
# should be passed as arguments to the constructor.
|
4
9
|
class Scope < self
|
5
|
-
include Concord
|
10
|
+
include Concord.new(:scope)
|
6
11
|
|
7
12
|
MATCHERS = [
|
8
13
|
Matcher::Methods::Singleton,
|
9
14
|
Matcher::Methods::Instance
|
10
15
|
].freeze
|
11
16
|
|
12
|
-
|
17
|
+
private_constant(*constants(false))
|
18
|
+
|
19
|
+
# Matched subjects
|
13
20
|
#
|
14
|
-
# @
|
15
|
-
# if block given
|
21
|
+
# @param [Env] env
|
16
22
|
#
|
17
|
-
# @return [
|
18
|
-
# otherwise
|
23
|
+
# @return [Enumerable<Subject>]
|
19
24
|
#
|
20
25
|
# @api private
|
21
|
-
def
|
22
|
-
|
26
|
+
def call(env)
|
27
|
+
Chain.new(effective_matchers).call(env)
|
28
|
+
end
|
23
29
|
|
24
|
-
|
25
|
-
matcher.new(env, scope).each(&block)
|
26
|
-
end
|
30
|
+
private
|
27
31
|
|
28
|
-
|
32
|
+
# Effective matchers
|
33
|
+
#
|
34
|
+
# @return [Enumerable<Matcher>]
|
35
|
+
#
|
36
|
+
# @api private
|
37
|
+
def effective_matchers
|
38
|
+
MATCHERS.map { |matcher| matcher.new(scope) }
|
29
39
|
end
|
30
40
|
|
31
41
|
end # Scope
|