mutant 0.8.7 → 0.8.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog.md +5 -0
  3. data/README.md +64 -3
  4. data/config/flay.yml +1 -1
  5. data/lib/mutant.rb +2 -0
  6. data/lib/mutant/cli.rb +1 -1
  7. data/lib/mutant/env/bootstrap.rb +36 -8
  8. data/lib/mutant/expression/method.rb +3 -5
  9. data/lib/mutant/expression/methods.rb +2 -4
  10. data/lib/mutant/expression/namespace.rb +4 -8
  11. data/lib/mutant/matcher.rb +3 -18
  12. data/lib/mutant/matcher/chain.rb +7 -13
  13. data/lib/mutant/matcher/compiler.rb +2 -13
  14. data/lib/mutant/matcher/filter.rb +6 -19
  15. data/lib/mutant/matcher/method.rb +124 -104
  16. data/lib/mutant/matcher/method/instance.rb +40 -34
  17. data/lib/mutant/matcher/method/singleton.rb +80 -61
  18. data/lib/mutant/matcher/methods.rb +19 -29
  19. data/lib/mutant/matcher/namespace.rb +22 -16
  20. data/lib/mutant/matcher/null.rb +4 -7
  21. data/lib/mutant/matcher/scope.rb +23 -13
  22. data/lib/mutant/matcher/static.rb +17 -0
  23. data/lib/mutant/mutation.rb +0 -5
  24. data/lib/mutant/reporter/cli/format.rb +2 -3
  25. data/lib/mutant/reporter/cli/printer/env_progress.rb +37 -11
  26. data/lib/mutant/reporter/cli/printer/status_progressive.rb +1 -1
  27. data/lib/mutant/scope.rb +6 -0
  28. data/lib/mutant/subject/method.rb +0 -7
  29. data/lib/mutant/subject/method/instance.rb +0 -10
  30. data/lib/mutant/subject/method/singleton.rb +0 -10
  31. data/lib/mutant/version.rb +1 -1
  32. data/lib/mutant/zombifier.rb +2 -1
  33. data/mutant-rspec.gemspec +1 -1
  34. data/spec/integration/mutant/rspec_spec.rb +1 -1
  35. data/spec/shared/method_matcher_behavior.rb +21 -14
  36. data/spec/spec_helper.rb +6 -0
  37. data/spec/unit/mutant/env/boostrap_spec.rb +88 -26
  38. data/spec/unit/mutant/env_spec.rb +0 -1
  39. data/spec/unit/mutant/expression/method_spec.rb +3 -3
  40. data/spec/unit/mutant/expression/methods_spec.rb +3 -4
  41. data/spec/unit/mutant/expression/namespace/flat_spec.rb +2 -3
  42. data/spec/unit/mutant/expression/namespace/recursive_spec.rb +2 -4
  43. data/spec/unit/mutant/matcher/chain_spec.rb +21 -29
  44. data/spec/unit/mutant/matcher/compiler/subject_prefix_spec.rb +16 -13
  45. data/spec/unit/mutant/matcher/compiler_spec.rb +49 -60
  46. data/spec/unit/mutant/matcher/filter_spec.rb +15 -31
  47. data/spec/unit/mutant/matcher/method/instance_spec.rb +84 -128
  48. data/spec/unit/mutant/matcher/method/singleton_spec.rb +48 -52
  49. data/spec/unit/mutant/matcher/methods/instance_spec.rb +21 -24
  50. data/spec/unit/mutant/matcher/methods/singleton_spec.rb +18 -21
  51. data/spec/unit/mutant/matcher/namespace_spec.rb +30 -38
  52. data/spec/unit/mutant/matcher/null_spec.rb +5 -20
  53. data/spec/unit/mutant/matcher/scope_spec.rb +33 -0
  54. data/spec/unit/mutant/matcher/static_spec.rb +11 -0
  55. data/spec/unit/mutant/mutation_spec.rb +30 -10
  56. data/spec/unit/mutant/reporter/cli/printer/env_progress_spec.rb +6 -0
  57. data/spec/unit/mutant/reporter/cli/printer/env_result_spec.rb +2 -0
  58. data/spec/unit/mutant/reporter/cli/printer/status_spec.rb +10 -0
  59. data/spec/unit/mutant/reporter/cli_spec.rb +4 -0
  60. data/spec/unit/mutant/subject/method/instance_spec.rb +0 -28
  61. data/spec/unit/mutant/subject/method/singleton_spec.rb +0 -28
  62. data/test_app/Gemfile.rspec3.4 +7 -0
  63. data/test_app/lib/test_app.rb +16 -12
  64. data/test_app/lib/test_app/literal.rb +3 -0
  65. 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.build(env, scope, target_method)
15
+ def self.new(scope, target_method)
18
16
  name = target_method.name
19
- if scope.ancestors.include?(::Memoizable) && scope.memoized?(name)
20
- return Memoized.new(env, scope, target_method)
21
- end
22
- super
23
- end
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
- # Check if node is matched
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
- # Matcher for memoized instance methods
46
- class Memoized < self
47
- SUBJECT_CLASS = Subject::Method::Instance::Memoized
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
- # Source location
34
+ # Check if node is matched
35
+ #
36
+ # @param [Parser::AST::Node] node
52
37
  #
53
- # @return [Array{String,Fixnum}]
38
+ # @return [Boolean]
54
39
  #
55
40
  # @api private
56
- def source_location
57
- scope.unmemoized_instance_method(method_name).source_location
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
- end # Memoized
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
- private
11
-
12
- # Test for node match
7
+ # New singleton method matcher
13
8
  #
14
- # @param [Parser::AST::Node] node
9
+ # @param [Class, Module] scope
10
+ # @param [Symbol] method_name
15
11
  #
16
- # @return [Boolean]
12
+ # @return [Matcher::Method::Singleton]
17
13
  #
18
14
  # @api private
19
- def match?(node)
20
- line?(node) && name?(node) && receiver?(node)
15
+ def self.new(scope, method_name)
16
+ super(scope, method_name, Evaluator)
21
17
  end
22
18
 
23
- # Test for line match
24
- #
25
- # @param [Parser::AST::Node] node
26
- #
27
- # @return [Boolean]
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
- # Test for name match
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
- # Test for receiver match
48
- #
49
- # @param [Parser::AST::Node] node
50
- #
51
- # @return [Boolean]
52
- #
53
- # @api private
54
- def receiver?(node)
55
- receiver = node.children[RECEIVER_INDEX]
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
- # Test if receiver name matches context
68
- #
69
- # @param [Parser::AST::Node] node
70
- #
71
- # @return [Boolean]
72
- #
73
- # @api private
74
- def receiver_name?(node)
75
- name = node.children[NAME_INDEX]
76
- name.to_s.eql?(context.unqualified_name)
77
- end
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::Public.new(:env, :scope)
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
- # @return [self]
10
- # if block given
17
+ # @param [Env] env
11
18
  #
12
- # @return [Enumerator<Subject>]
13
- # otherwise
19
+ # @return [Enumerable<Subject>]
14
20
  #
15
21
  # @api private
16
- def each(&block)
17
- return to_enum unless block_given?
18
-
19
- subjects.each(&block)
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.public_instance_methods(false) +
68
- candidate_scope.private_instance_methods(false) +
69
- candidate_scope.protected_instance_methods(false)
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(:env, :expression)
5
+ include Concord::Public.new(:expression)
7
6
 
8
7
  # Enumerate subjects
9
8
  #
10
- # @return [self]
11
- # if block given
9
+ # @param [Env] env
12
10
  #
13
- # @return [Enumerator<Subject>]
14
- # otherwise
11
+ # @return [Enumerable<Subject>]
15
12
  #
16
13
  # @api private
17
- def each(&block)
18
- return to_enum unless block_given?
19
-
20
- env.matchable_scopes.select do |scope|
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
- # Test scope if name matches expression
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 [Module, Class] scope
37
+ # @param [Scope] scope
32
38
  #
33
39
  # @return [Boolean]
34
40
  #
@@ -6,16 +6,13 @@ module Mutant
6
6
 
7
7
  # Enumerate subjects
8
8
  #
9
- # @return [Enumerator<Subject]
10
- # if no block given
9
+ # @param [Env] env
11
10
  #
12
- # @return [self]
13
- # otherwise
11
+ # @return [Enumerable<Subject>]
14
12
  #
15
13
  # @api private
16
- def each
17
- return to_enum unless block_given?
18
- self
14
+ def call(_env)
15
+ EMPTY_ARRAY
19
16
  end
20
17
 
21
18
  end # Null
@@ -1,31 +1,41 @@
1
1
  module Mutant
2
2
  class Matcher
3
- # Matcher for specific namespace
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::Public.new(:env, :scope, :expression)
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
- # Enumerate subjects
17
+ private_constant(*constants(false))
18
+
19
+ # Matched subjects
13
20
  #
14
- # @return [self]
15
- # if block given
21
+ # @param [Env] env
16
22
  #
17
- # @return [Enumerator<Subject>]
18
- # otherwise
23
+ # @return [Enumerable<Subject>]
19
24
  #
20
25
  # @api private
21
- def each(&block)
22
- return to_enum unless block_given?
26
+ def call(env)
27
+ Chain.new(effective_matchers).call(env)
28
+ end
23
29
 
24
- MATCHERS.each do |matcher|
25
- matcher.new(env, scope).each(&block)
26
- end
30
+ private
27
31
 
28
- self
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