mutant 0.5.23 → 0.5.24

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,19 @@
1
+ module Mutant
2
+ module AST
3
+ # Module for node predicates
4
+ module NodePredicates
5
+
6
+ Types::ALL.each do |type|
7
+ fail "method: #{type} is already defined" if instance_methods(true).include?(type)
8
+
9
+ name = "n_#{type.to_s.chomp('?')}?"
10
+
11
+ define_method(name) do |node|
12
+ node.type.equal?(type)
13
+ end
14
+ private name
15
+ end
16
+
17
+ end # NodePredicates
18
+ end # AST
19
+ end # Mutant
@@ -0,0 +1,21 @@
1
+ module Mutant
2
+ module AST
3
+ # Singleton nodes
4
+ module Nodes
5
+ extend Sexp
6
+
7
+ N_NAN = s(:send, s(:float, 0.0), :/, s(:float, 0.0))
8
+ N_INFINITY = s(:send, s(:float, 1.0), :/, s(:float, 0.0))
9
+ N_NEGATIVE_INFINITY = s(:send, s(:float, -1.0), :/, s(:float, 0.0))
10
+ N_RAISE = s(:send, nil, :raise)
11
+ N_TRUE = s(:true)
12
+ N_FALSE = s(:false)
13
+ N_NIL = s(:nil)
14
+ N_EMPTY = s(:empty)
15
+ N_SELF = s(:self)
16
+ N_ZSUPER = s(:zsuper)
17
+ N_EMPTY_SUPER = s(:super)
18
+
19
+ end # Node
20
+ end # AST
21
+ end # Mutant
@@ -0,0 +1,34 @@
1
+ module Mutant
2
+ module AST
3
+ # Mixin for node sexp syntax
4
+ module Sexp
5
+
6
+ private
7
+
8
+ # Build node
9
+ #
10
+ # @param [Symbol] type
11
+ #
12
+ # @return [Parser::AST::Node]
13
+ #
14
+ # @api private
15
+ #
16
+ def s(type, *children)
17
+ Parser::AST::Node.new(type, children)
18
+ end
19
+
20
+ # Build a negated boolean node
21
+ #
22
+ # @param [Parser::AST::Node] node
23
+ #
24
+ # @return [Parser::AST::Node]
25
+ #
26
+ # @api private
27
+ #
28
+ def n_not(node)
29
+ s(:send, node, :!)
30
+ end
31
+
32
+ end # Sexp
33
+ end # AST
34
+ end # Mutant
@@ -0,0 +1,48 @@
1
+ module Mutant
2
+ module AST
3
+ # Groups of node types
4
+ module Types
5
+ symbolset = ->(strings) { strings.map(&:to_sym).to_set.freeze }
6
+
7
+ ASSIGNABLE_VARIABLES = symbolset.(%w[ivasgn lvasgn cvasgn gvasgn])
8
+
9
+ INDEX_ASSIGN_OPERATOR = :[]=
10
+
11
+ # Set of nodes that cannot be on the LHS of an assignment
12
+ NOT_ASSIGNABLE = symbolset.(%w[int float str dstr class module self nil])
13
+
14
+ # Set of op-assign types
15
+ OP_ASSIGN = symbolset.(%w[or_asgn and_asgn op_asgn])
16
+ # Set of node types that are not valid when emitted standalone
17
+ NOT_STANDALONE = symbolset.(%w[splat restarg block_pass])
18
+ INDEX_OPERATORS = symbolset.(%w[[] []=])
19
+ UNARY_METHOD_OPERATORS = symbolset.(%w[~@ +@ -@ !])
20
+
21
+ # Operators ruby implementeds as methods
22
+ METHOD_OPERATORS = symbolset.(%w[
23
+ <=> === []= [] <= >= == !~ != =~ <<
24
+ >> ** * % / | ^ & < > + - ~@ +@ -@ !
25
+ ])
26
+
27
+ BINARY_METHOD_OPERATORS = (
28
+ METHOD_OPERATORS - (INDEX_OPERATORS + UNARY_METHOD_OPERATORS)
29
+ ).to_set.freeze
30
+
31
+ OPERATOR_METHODS = (
32
+ METHOD_OPERATORS + INDEX_OPERATORS + UNARY_METHOD_OPERATORS
33
+ ).to_set.freeze
34
+
35
+ # Nodes that are NOT handled by mutant.
36
+ #
37
+ # not - 1.8 only, mutant does not support 1.8
38
+ #
39
+ BLACKLIST = symbolset.(%w[not])
40
+
41
+ # Nodes that are NOT generated by parser but used by mutant / unparser.
42
+ EXTRA = symbolset.(%w[empty])
43
+
44
+ # All node types mutant handles
45
+ ALL = ((Parser::Meta::NODE_TYPES + EXTRA) - BLACKLIST).to_set.freeze
46
+ end # Types
47
+ end # AST
48
+ end # Mutant
@@ -1,8 +1,7 @@
1
1
  module Mutant
2
2
  # An AST cache
3
3
  class Cache
4
- # This is explicitly empty! Ask me if you are interested in reasons :D
5
- include Equalizer.new
4
+ include Equalizer.new, Adamantium::Mutable
6
5
 
7
6
  # Initialize object
8
7
  #
@@ -16,6 +15,8 @@ module Mutant
16
15
 
17
16
  # Return node for file
18
17
  #
18
+ # @param [#to_s] path
19
+ #
19
20
  # @return [AST::Node]
20
21
  #
21
22
  # @api private
@@ -4,7 +4,7 @@ module Mutant
4
4
 
5
5
  # Comandline parser
6
6
  class CLI
7
- include Adamantium::Flat, Equalizer.new(:config), NodeHelpers
7
+ include Adamantium::Flat, Equalizer.new(:config)
8
8
 
9
9
  # Error raised when CLI argv is invalid
10
10
  Error = Class.new(RuntimeError)
@@ -30,141 +30,6 @@ module Mutant
30
30
  EXIT_FAILURE
31
31
  end
32
32
 
33
- # Builder for configuration components
34
- class Builder
35
- include NodeHelpers
36
-
37
- # Initalize object
38
- #
39
- # @return [undefined]
40
- #
41
- # @api private
42
- #
43
- def initialize
44
- @matchers = []
45
- @subject_ignores = []
46
- @subject_selectors = []
47
- end
48
-
49
- # Add a subject ignore
50
- #
51
- # @param [Matcher]
52
- #
53
- # @return [self]
54
- #
55
- # @api private
56
- #
57
- def add_subject_ignore(matcher)
58
- @subject_ignores << matcher
59
- self
60
- end
61
-
62
- # Add a subject selector
63
- #
64
- # @param [#call] selector
65
- #
66
- # @return [self]
67
- #
68
- # @api private
69
- #
70
- def add_subject_selector(selector)
71
- @subject_selectors << selector
72
- self
73
- end
74
-
75
- # Add a subject matcher
76
- #
77
- # @param [#call] selector
78
- #
79
- # @return [self]
80
- #
81
- # @api private
82
- #
83
- def add_matcher(matcher)
84
- @matchers << matcher
85
- self
86
- end
87
-
88
- # Return generated matcher
89
- #
90
- # @return [Mutant::Matcher]
91
- #
92
- # @api private
93
- #
94
- def matcher
95
- if @matchers.empty?
96
- raise(Error, 'No patterns given')
97
- end
98
-
99
- matcher = Matcher::Chain.build(@matchers)
100
-
101
- if predicate
102
- Matcher::Filter.new(matcher, predicate)
103
- else
104
- matcher
105
- end
106
- end
107
-
108
- private
109
-
110
- # Return subject selector
111
- #
112
- # @return [#call]
113
- # if selector is present
114
- #
115
- # @return [nil]
116
- # otherwise
117
- #
118
- # @api private
119
- #
120
- def subject_selector
121
- Morpher::Evaluator::Predicate::Boolean::Or.new(@subject_selectors) if @subject_selectors.any?
122
- end
123
-
124
- # Return predicate
125
- #
126
- # @return [#call]
127
- # if filter is needed
128
- #
129
- # @return [nil]
130
- # othrwise
131
- #
132
- # @api private
133
- #
134
- def predicate
135
- if subject_selector && subject_rejector
136
- Morpher::Evaluator::Predicate::Boolean::And.new([
137
- subject_selector,
138
- Morpher::Evaluator::Predicate::Negation.new(subject_rejector)
139
- ])
140
- elsif subject_selector
141
- subject_selector
142
- elsif subject_rejector
143
- Morpher::Evaluator::Predicate::Negation.new(subject_rejector)
144
- else
145
- nil
146
- end
147
- end
148
-
149
- # Return subject rejector
150
- #
151
- # @return [#call]
152
- # if there is a subject rejector
153
- #
154
- # @return [nil]
155
- # otherwise
156
- #
157
- # @api private
158
- #
159
- def subject_rejector
160
- rejectors = @subject_ignores.flat_map(&:to_a).map do |subject|
161
- Morpher.compile(s(:eql, s(:attribute, :identification), s(:static, subject.identification)))
162
- end
163
-
164
- Morpher::Evaluator::Predicate::Boolean::Or.new(rejectors) if rejectors.any?
165
- end
166
- end
167
-
168
33
  # Initialize objecct
169
34
  #
170
35
  # @param [Array<String>]
@@ -174,18 +39,16 @@ module Mutant
174
39
  # @api private
175
40
  #
176
41
  def initialize(arguments = [])
177
- @builder = Builder.new
42
+ @builder = Matcher::Builder.new(Env::Boot.new(Reporter::CLI.new($stderr), Cache.new))
178
43
  @debug = @fail_fast = @zombie = false
179
44
  @expected_coverage = 100.0
180
- @strategy = Strategy::Null.new
181
- @cache = Mutant::Cache.new
45
+ @integration = Integration::Null.new
182
46
  parse(arguments)
183
47
  @config = Config.new(
184
- cache: @cache,
185
48
  zombie: @zombie,
186
49
  debug: @debug,
187
50
  matcher: @builder.matcher,
188
- strategy: @strategy,
51
+ integration: @integration,
189
52
  fail_fast: @fail_fast,
190
53
  reporter: Reporter::CLI.new($stdout),
191
54
  expected_coverage: @expected_coverage
@@ -236,19 +99,16 @@ module Mutant
236
99
 
237
100
  # Parse matchers
238
101
  #
239
- # @param [Enumerable<String>] patterns
102
+ # @param [Array<String>] patterns
240
103
  #
241
104
  # @return [undefined]
242
105
  #
243
106
  # @api private
244
107
  #
245
108
  def parse_matchers(patterns)
109
+ raise Error, 'No patterns given' if patterns.empty?
246
110
  patterns.each do |pattern|
247
- expression = Expression.parse(pattern)
248
- unless expression
249
- raise Error, "Invalid mutant expression: #{pattern.inspect}"
250
- end
251
- @builder.add_matcher(expression.matcher(@cache))
111
+ @builder.add_match_expression(Expression.parse(pattern))
252
112
  end
253
113
  end
254
114
 
@@ -283,8 +143,8 @@ module Mutant
283
143
  # @api private
284
144
  #
285
145
  def use(name)
286
- require "mutant/#{name}"
287
- @strategy = Strategy.lookup(name).new
146
+ require "mutant/integration/#{name}"
147
+ @integration = Integration.lookup(name).new
288
148
  rescue LoadError
289
149
  $stderr.puts("Cannot load plugin: #{name.inspect}")
290
150
  raise
@@ -319,10 +179,10 @@ module Mutant
319
179
  #
320
180
  def add_filter_options(opts)
321
181
  opts.on('--ignore-subject PATTERN', 'Ignore subjects that match PATTERN') do |pattern|
322
- @builder.add_subject_ignore(Expression.parse(pattern).matcher(@cache))
182
+ @builder.add_subject_ignore(Expression.parse(pattern))
323
183
  end
324
184
  opts.on('--code CODE', 'Scope execution to subjects with CODE') do |code|
325
- @builder.add_subject_selector(Morpher.compile(s(:eql, s(:attribute, :code), s(:static, code))))
185
+ @builder.add_subject_selector(:code, code)
326
186
  end
327
187
  end
328
188
 
@@ -2,9 +2,8 @@ module Mutant
2
2
  # The configuration of a mutator run
3
3
  class Config
4
4
  include Adamantium::Flat, Anima.new(
5
- :cache,
6
5
  :debug,
7
- :strategy,
6
+ :integration,
8
7
  :matcher,
9
8
  :reporter,
10
9
  :fail_fast,
@@ -30,5 +29,26 @@ module Mutant
30
29
  self
31
30
  end
32
31
 
32
+ # Return tests for mutation
33
+ #
34
+ # TODO: This logic is now centralized but still fucked.
35
+ #
36
+ # @param [Mutation] mutation
37
+ #
38
+ # @return [Enumerable<Test>]
39
+ #
40
+ # @api private
41
+ #
42
+ def tests(subject)
43
+ subject.match_expressions.each do |match_expression|
44
+ tests = integration.all_tests.select do |test|
45
+ match_expression.prefix?(test.expression)
46
+ end
47
+ return tests if tests.any?
48
+ end
49
+
50
+ EMPTY_ARRAY
51
+ end
52
+
33
53
  end # Config
34
54
  end # Mutant
@@ -2,8 +2,10 @@ module Mutant
2
2
  class Context
3
3
  # Scope context for mutation (Class or Module)
4
4
  class Scope < self
5
- include Adamantium::Flat, Equalizer.new(:scope, :source_path)
6
- extend NodeHelpers
5
+ include Adamantium::Flat, Concord::Public.new(:scope, :source_path)
6
+ extend AST::Sexp
7
+
8
+ NAMESPACE_DELIMITER = '::'.freeze
7
9
 
8
10
  # Return AST wrapping mutated node
9
11
  #
@@ -41,7 +43,7 @@ module Mutant
41
43
  # @api private
42
44
  #
43
45
  def self.wrap(scope, node)
44
- name = s(:const, nil, scope.name.split('::').last.to_sym)
46
+ name = s(:const, nil, scope.name.split(NAMESPACE_DELIMITER).last.to_sym)
45
47
  case scope
46
48
  when ::Class
47
49
  s(:class, name, nil, node)
@@ -87,18 +89,18 @@ module Mutant
87
89
  scope.name
88
90
  end
89
91
 
90
- # Return match prefixes
92
+ # Return match expressions
91
93
  #
92
- # @return [Enumerable<String>]
94
+ # @return [Enumerable<Expression>]
93
95
  #
94
96
  # @api private
95
97
  #
96
- def match_prefixes
98
+ def match_expressions
97
99
  name_nesting.each_index.reverse_each.map do |index|
98
- name_nesting.take(index.succ).join('::')
100
+ Expression.parse("#{name_nesting.take(index.succ).join(NAMESPACE_DELIMITER)}*")
99
101
  end
100
102
  end
101
- memoize :match_prefixes
103
+ memoize :match_expressions
102
104
 
103
105
  # Return scope wrapped by context
104
106
  #
@@ -110,18 +112,6 @@ module Mutant
110
112
 
111
113
  private
112
114
 
113
- # Initialize object
114
- #
115
- # @param [Object] scope
116
- # @param [String] source_path
117
- #
118
- # @api private
119
- #
120
- def initialize(scope, source_path)
121
- super(source_path)
122
- @scope = scope
123
- end
124
-
125
115
  # Return nesting of names of scope
126
116
  #
127
117
  # @return [Array<String>]
@@ -129,7 +119,7 @@ module Mutant
129
119
  # @api private
130
120
  #
131
121
  def name_nesting
132
- scope.name.split('::')
122
+ scope.name.split(NAMESPACE_DELIMITER)
133
123
  end
134
124
  memoize :name_nesting
135
125