mutant 0.8.7 → 0.8.8
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 +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
|