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.
Files changed (89) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog.md +10 -0
  3. data/config/flay.yml +1 -1
  4. data/config/reek.yml +19 -19
  5. data/lib/mutant.rb +12 -39
  6. data/lib/mutant/ast.rb +5 -0
  7. data/lib/mutant/ast/meta.rb +131 -0
  8. data/lib/mutant/ast/named_children.rb +98 -0
  9. data/lib/mutant/ast/node_predicates.rb +19 -0
  10. data/lib/mutant/ast/nodes.rb +21 -0
  11. data/lib/mutant/ast/sexp.rb +34 -0
  12. data/lib/mutant/ast/types.rb +48 -0
  13. data/lib/mutant/cache.rb +3 -2
  14. data/lib/mutant/cli.rb +11 -151
  15. data/lib/mutant/config.rb +22 -2
  16. data/lib/mutant/context/scope.rb +11 -21
  17. data/lib/mutant/delegator.rb +2 -0
  18. data/lib/mutant/diff.rb +7 -3
  19. data/lib/mutant/env.rb +49 -0
  20. data/lib/mutant/expression.rb +36 -8
  21. data/lib/mutant/expression/methods.rb +62 -0
  22. data/lib/mutant/expression/namespace.rb +41 -28
  23. data/lib/mutant/{strategy.rb → integration.rb} +12 -31
  24. data/lib/mutant/isolation.rb +1 -1
  25. data/lib/mutant/matcher.rb +1 -21
  26. data/lib/mutant/matcher/builder.rb +142 -0
  27. data/lib/mutant/matcher/method.rb +3 -7
  28. data/lib/mutant/matcher/method/instance.rb +6 -5
  29. data/lib/mutant/matcher/method/singleton.rb +2 -7
  30. data/lib/mutant/matcher/methods.rb +11 -14
  31. data/lib/mutant/matcher/namespace.rb +31 -39
  32. data/lib/mutant/matcher/scope.rb +13 -2
  33. data/lib/mutant/meta.rb +0 -1
  34. data/lib/mutant/meta/example/dsl.rb +5 -1
  35. data/lib/mutant/mutator/node.rb +16 -44
  36. data/lib/mutant/mutator/node/or_asgn.rb +1 -1
  37. data/lib/mutant/mutator/node/send.rb +5 -60
  38. data/lib/mutant/mutator/node/super.rb +2 -5
  39. data/lib/mutant/mutator/registry.rb +1 -1
  40. data/lib/mutant/reporter.rb +10 -0
  41. data/lib/mutant/reporter/cli.rb +13 -0
  42. data/lib/mutant/reporter/cli/printer.rb +2 -0
  43. data/lib/mutant/reporter/cli/progress/config.rb +1 -1
  44. data/lib/mutant/reporter/cli/progress/noop.rb +2 -0
  45. data/lib/mutant/reporter/cli/registry.rb +2 -0
  46. data/lib/mutant/reporter/null.rb +12 -0
  47. data/lib/mutant/reporter/trace.rb +4 -0
  48. data/lib/mutant/require_highjack.rb +2 -2
  49. data/lib/mutant/rspec.rb +0 -0
  50. data/lib/mutant/runner.rb +2 -0
  51. data/lib/mutant/runner/config.rb +8 -8
  52. data/lib/mutant/runner/killer.rb +5 -0
  53. data/lib/mutant/runner/subject.rb +1 -1
  54. data/lib/mutant/subject.rb +8 -8
  55. data/lib/mutant/subject/method.rb +3 -2
  56. data/lib/mutant/subject/method/instance.rb +1 -1
  57. data/lib/mutant/test.rb +7 -65
  58. data/lib/mutant/test/report.rb +59 -0
  59. data/lib/mutant/version.rb +1 -1
  60. data/lib/mutant/warning_filter.rb +2 -0
  61. data/lib/mutant/zombifier.rb +3 -0
  62. data/lib/mutant/zombifier/file.rb +1 -1
  63. data/meta/or_asgn.rb +11 -0
  64. data/meta/send.rb +1 -1
  65. data/mutant-rspec.gemspec +1 -1
  66. data/mutant.gemspec +1 -1
  67. data/spec/integration/mutant/corpus_spec.rb +2 -0
  68. data/spec/integration/mutant/test_mutator_handles_types_spec.rb +2 -2
  69. data/spec/spec_helper.rb +4 -3
  70. data/spec/unit/mutant/cli_new_spec.rb +11 -11
  71. data/spec/unit/mutant/expression/methods_spec.rb +43 -0
  72. data/spec/unit/mutant/expression/namespace/flat_spec.rb +1 -1
  73. data/spec/unit/mutant/expression/namespace/recursive_spec.rb +19 -5
  74. data/spec/unit/mutant/{strategy_spec.rb → integration_spec.rb} +1 -1
  75. data/spec/unit/mutant/isolation_spec.rb +3 -1
  76. data/spec/unit/mutant/matcher/method/instance_spec.rb +5 -5
  77. data/spec/unit/mutant/matcher/method/singleton_spec.rb +8 -8
  78. data/spec/unit/mutant/matcher/methods/instance_spec.rb +5 -8
  79. data/spec/unit/mutant/matcher/methods/singleton_spec.rb +5 -5
  80. data/spec/unit/mutant/matcher/namespace_spec.rb +18 -23
  81. data/spec/unit/mutant/mutation_spec.rb +1 -1
  82. data/spec/unit/mutant/runner/config_spec.rb +4 -5
  83. data/spec/unit/mutant/runner/mutation_spec.rb +21 -21
  84. data/spec/unit/mutant/runner/subject_spec.rb +6 -6
  85. data/spec/unit/mutant/subject/method/instance_spec.rb +0 -4
  86. data/spec/unit/mutant/subject/method/singleton_spec.rb +0 -1
  87. data/spec/unit/mutant/subject_spec.rb +3 -3
  88. metadata +20 -6
  89. data/lib/mutant/node_helpers.rb +0 -52
@@ -37,6 +37,8 @@ module Mutant
37
37
  #
38
38
  # @param [Class,Module] host
39
39
  #
40
+ # @return [undefined]
41
+ #
40
42
  # @api private
41
43
  #
42
44
  def self.included(host)
@@ -3,6 +3,10 @@ module Mutant
3
3
  class Diff
4
4
  include Adamantium::Flat, Concord.new(:old, :new)
5
5
 
6
+ ADDITION = '+'.freeze
7
+ DELETION = '-'.freeze
8
+ NEWLINE = "\n".freeze
9
+
6
10
  # Return source diff
7
11
  #
8
12
  # @return [String]
@@ -16,7 +20,7 @@ module Mutant
16
20
  def diff
17
21
  return unless diffs.length.equal?(1)
18
22
  ::Diff::LCS::Hunk.new(old, new, diffs.first, max_length, 0)
19
- .diff(:unified) << "\n"
23
+ .diff(:unified) << NEWLINE
20
24
  end
21
25
  memoize :diff
22
26
 
@@ -97,9 +101,9 @@ module Mutant
97
101
  #
98
102
  def self.colorize_line(line)
99
103
  case line[0]
100
- when '+'
104
+ when ADDITION
101
105
  Color::GREEN
102
- when '-'
106
+ when DELETION
103
107
  Color::RED
104
108
  else
105
109
  Color::NONE
@@ -0,0 +1,49 @@
1
+ module Mutant
2
+ # Abstract base class for mutant environments
3
+ class Env
4
+ include AbstractType, Adamantium
5
+
6
+ # Return config
7
+ #
8
+ # @return [Config]
9
+ #
10
+ # @api private
11
+ #
12
+ abstract_method :config
13
+
14
+ # Return cache
15
+ #
16
+ # @return [Cache]
17
+ #
18
+ # @api private
19
+ #
20
+ abstract_method :cache
21
+
22
+ # Return reporter
23
+ #
24
+ # @return [Reporter]
25
+ #
26
+ # @api private
27
+ #
28
+ abstract_method :reporter
29
+
30
+ # Print warning message
31
+ #
32
+ # @param [String]
33
+ #
34
+ # @return [self]
35
+ #
36
+ # @api private
37
+ #
38
+ def warn(message)
39
+ reporter.warn(message)
40
+ self
41
+ end
42
+
43
+ # Boot environment used for matching
44
+ class Boot < self
45
+ include Concord::Public.new(:reporter, :cache)
46
+ end # Boot
47
+
48
+ end # ENV
49
+ end # Mutant
@@ -10,7 +10,7 @@ module Mutant
10
10
 
11
11
  METHOD_NAME_PATTERN = Regexp.union(
12
12
  /[A-Za-z_][A-Za-z\d_]*[!?=]?/,
13
- *OPERATOR_METHODS.map(&:to_s)
13
+ *AST::Types::OPERATOR_METHODS.map(&:to_s)
14
14
  ).freeze
15
15
 
16
16
  SCOPE_PATTERN = /#{SCOPE_NAME_PATTERN}(?:#{SCOPE_OPERATOR}#{SCOPE_NAME_PATTERN})*/.freeze
@@ -38,20 +38,32 @@ module Mutant
38
38
 
39
39
  # Return match length for expression
40
40
  #
41
- # @param [Expression] neddle
41
+ # @param [Expression] other
42
42
  #
43
43
  # @return [Fixnum]
44
44
  #
45
45
  # @api private
46
46
  #
47
- def match_length(neddle)
48
- if eql?(neddle)
47
+ def match_length(other)
48
+ if eql?(other)
49
49
  syntax.length
50
50
  else
51
51
  0
52
52
  end
53
53
  end
54
54
 
55
+ # Test if expression is prefix
56
+ #
57
+ # @param [Expression] other
58
+ #
59
+ # @return [Boolean]
60
+ #
61
+ # @api private
62
+ #
63
+ def prefix?(other)
64
+ !match_length(other).zero?
65
+ end
66
+
55
67
  # Register expression
56
68
  #
57
69
  # @return [undefined]
@@ -63,9 +75,25 @@ module Mutant
63
75
  end
64
76
  private_class_method :register
65
77
 
78
+ # Parse input into expression or raise
79
+ #
80
+ # @param [String] syntax
81
+ #
82
+ # @return [Expression]
83
+ # if expression is valid
84
+ #
85
+ # @raise [RuntimeError]
86
+ # otherwise
87
+ #
88
+ # @api private
89
+ #
90
+ def self.parse(input)
91
+ try_parse(input) or raise "Expression: #{input.inspect} is not valid"
92
+ end
93
+
66
94
  # Parse input into expression
67
95
  #
68
- # @param [String] pattern
96
+ # @param [String] input
69
97
  #
70
98
  # @return [Expression]
71
99
  # if expression is valid
@@ -75,14 +103,14 @@ module Mutant
75
103
  #
76
104
  # @api private
77
105
  #
78
- def self.parse(pattern)
79
- expressions = expressions(pattern)
106
+ def self.try_parse(input)
107
+ expressions = expressions(input)
80
108
  case expressions.length
81
109
  when 0
82
110
  when 1
83
111
  expressions.first
84
112
  else
85
- fail "Ambigous expression: #{pattern.inspect}"
113
+ fail "Ambigous expression: #{input.inspect}"
86
114
  end
87
115
  end
88
116
 
@@ -0,0 +1,62 @@
1
+ module Mutant
2
+ class Expression
3
+
4
+ # Abstrat base class for methods expression
5
+ class Methods < self
6
+
7
+ MATCHERS = {
8
+ '.' => Matcher::Methods::Singleton,
9
+ '#' => Matcher::Methods::Instance
10
+ }.freeze
11
+
12
+ register(
13
+ /\A(?<scope_name>#{SCOPE_PATTERN})(?<scope_symbol>[.#])\z/
14
+ )
15
+
16
+ # Return method matcher
17
+ #
18
+ # @param [Cache] cache
19
+ #
20
+ # @return [Matcher::Method]
21
+ #
22
+ # @api private
23
+ #
24
+ def matcher(cache)
25
+ MATCHERS.fetch(scope_symbol).new(cache, scope)
26
+ end
27
+
28
+ private
29
+
30
+ # Return scope name
31
+ #
32
+ # @return [String]
33
+ #
34
+ # @api private
35
+ #
36
+ def scope_name
37
+ match[__method__]
38
+ end
39
+
40
+ # Return scope
41
+ #
42
+ # @return [Class, Method]
43
+ #
44
+ # @api private
45
+ #
46
+ def scope
47
+ Mutant.constant_lookup(scope_name)
48
+ end
49
+
50
+ # Return scope symbol
51
+ #
52
+ # @return [Symbol]
53
+ #
54
+ # @api private
55
+ #
56
+ def scope_symbol
57
+ match[__method__]
58
+ end
59
+
60
+ end # Method
61
+ end # Expression
62
+ end # Mutant
@@ -4,37 +4,11 @@ module Mutant
4
4
  class Namespace < self
5
5
  include AbstractType
6
6
 
7
- # Return matcher
8
- #
9
- # @param [Cache] cache
10
- #
11
- # @return [Matcher]
12
- #
13
- # @api private
14
- #
15
- def matcher(cache)
16
- self.class::MATCHER.new(cache, namespace)
17
- end
18
-
19
- private
20
-
21
- # Return namespace
22
- #
23
- # @return [Class, Module]
24
- #
25
- # @api private
26
- #
27
- def namespace
28
- Mutant.constant_lookup(match[__method__].to_s)
29
- end
30
-
31
7
  # Recursive namespace expression
32
8
  class Recursive < self
33
9
 
34
10
  register(/\A(?<namespace>#{SCOPE_PATTERN})?\*\z/)
35
11
 
36
- MATCHER = Matcher::Namespace
37
-
38
12
  # Initialize object
39
13
  #
40
14
  # @return [undefined]
@@ -43,7 +17,23 @@ module Mutant
43
17
  def initialize(*)
44
18
  super
45
19
  namespace_src = Regexp.escape(namespace)
46
- @recursion_pattern = Regexp.union(/\A#{namespace_src}\z/, /\A#{namespace_src}::/)
20
+ @recursion_pattern = Regexp.union(
21
+ /\A#{namespace_src}\z/,
22
+ /\A#{namespace_src}::/,
23
+ /\A#{namespace_src}[.#]/
24
+ )
25
+ end
26
+
27
+ # Return matcher
28
+ #
29
+ # @param [Cache] cache
30
+ #
31
+ # @return [Matcher]
32
+ #
33
+ # @api private
34
+ #
35
+ def matcher(cache)
36
+ Matcher::Namespace.new(cache, self)
47
37
  end
48
38
 
49
39
  # Return length of match
@@ -83,8 +73,31 @@ module Mutant
83
73
 
84
74
  MATCHER = Matcher::Scope
85
75
 
86
- end # Exact
76
+ # Return matcher
77
+ #
78
+ # @param [Cache] cache
79
+ #
80
+ # @return [Matcher]
81
+ #
82
+ # @api private
83
+ #
84
+ def matcher(cache)
85
+ Matcher::Scope.new(cache, Mutant.constant_lookup(namespace))
86
+ end
87
+
88
+ private
89
+
90
+ # Return namespace
91
+ #
92
+ # @return [String]
93
+ #
94
+ # @api private
95
+ #
96
+ def namespace
97
+ match[__method__].to_s
98
+ end
87
99
 
100
+ end # Exact
88
101
  end # Namespace
89
102
  end # Namespace
90
103
  end # Mutant
@@ -1,16 +1,16 @@
1
1
  module Mutant
2
2
 
3
- # Abstract base class for killing strategies
4
- class Strategy
3
+ # Abstract base class mutant test framework integrations
4
+ class Integration
5
5
  include AbstractType, Adamantium::Flat, Equalizer.new
6
6
 
7
7
  REGISTRY = {}
8
8
 
9
- # Lookup strategy for name
9
+ # Lookup integration for name
10
10
  #
11
11
  # @param [String] name
12
12
  #
13
- # @return [Strategy]
13
+ # @return [Integration]
14
14
  # if found
15
15
  #
16
16
  # @api private
@@ -19,7 +19,7 @@ module Mutant
19
19
  REGISTRY.fetch(name)
20
20
  end
21
21
 
22
- # Register strategy
22
+ # Register integration
23
23
  #
24
24
  # @param [String] name
25
25
  #
@@ -29,10 +29,12 @@ module Mutant
29
29
  #
30
30
  def self.register(name)
31
31
  REGISTRY[name] = self
32
+
33
+ define_method(:name) { name }
32
34
  end
33
35
  private_class_method :register
34
36
 
35
- # Perform strategy setup
37
+ # Perform integration setup
36
38
  #
37
39
  # @return [self]
38
40
  #
@@ -42,7 +44,7 @@ module Mutant
42
44
  self
43
45
  end
44
46
 
45
- # Perform strategy teardown
47
+ # Perform integration teardown
46
48
  #
47
49
  # @return [self]
48
50
  #
@@ -52,7 +54,7 @@ module Mutant
52
54
  self
53
55
  end
54
56
 
55
- # Return all available tests by strategy
57
+ # Return all available tests by integration
56
58
  #
57
59
  # @return [Enumerable<Test>]
58
60
  #
@@ -60,28 +62,7 @@ module Mutant
60
62
  #
61
63
  abstract_method :all_tests
62
64
 
63
- # Return tests for mutation
64
- #
65
- # TODO: This logic is now centralized but still fucked.
66
- #
67
- # @param [Mutation] mutation
68
- #
69
- # @return [Enumerable<Test>]
70
- #
71
- # @api private
72
- #
73
- def tests(subject)
74
- subject.match_prefixes.map do |match_expression|
75
- tests = all_tests.select do |test|
76
- test.subject_identification.start_with?(match_expression)
77
- end
78
- return tests if tests.any?
79
- end
80
-
81
- EMPTY_ARRAY
82
- end
83
-
84
- # Null strategy that never kills a mutation
65
+ # Null integration that never kills a mutation
85
66
  class Null < self
86
67
 
87
68
  register('null')
@@ -98,5 +79,5 @@ module Mutant
98
79
 
99
80
  end # Null
100
81
 
101
- end # Strategy
82
+ end # Integration
102
83
  end # Mutant
@@ -16,7 +16,7 @@ module Mutant
16
16
  # @api private
17
17
  #
18
18
  def self.call(&block)
19
- Parallel.map([block], in_processes: 1) do |block|
19
+ Parallel.map([block], in_processes: 1) do
20
20
  block.call
21
21
  end.first
22
22
  end
@@ -3,29 +3,9 @@ module Mutant
3
3
  class Matcher
4
4
  include Adamantium::Flat, Enumerable, AbstractType
5
5
 
6
- # Enumerate subjects
7
- #
8
- # @param [Object] input
9
- #
10
- # @return [self]
11
- # if block given
12
- #
13
- # @return [Enumerable<Subject>]
14
- # otherwise
15
- #
16
- # @api private
17
- #
18
- def self.each(cache, input, &block)
19
- return to_enum(__method__, cache, input) unless block_given?
20
-
21
- build(cache, input).each(&block)
22
-
23
- self
24
- end
25
-
26
6
  # Default matcher build implementation
27
7
  #
28
- # @param [Cache] cache
8
+ # @param [Env] env
29
9
  # @param [Object] input
30
10
  #
31
11
  # @return [undefined]