mutant 0.10.23 → 0.10.28

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/lib/mutant.rb +34 -14
  3. data/lib/mutant/ast/find_metaclass_containing.rb +1 -1
  4. data/lib/mutant/ast/regexp.rb +54 -0
  5. data/lib/mutant/ast/regexp/transformer.rb +150 -0
  6. data/lib/mutant/ast/regexp/transformer/direct.rb +121 -0
  7. data/lib/mutant/ast/regexp/transformer/named_group.rb +50 -0
  8. data/lib/mutant/ast/regexp/transformer/options_group.rb +68 -0
  9. data/lib/mutant/ast/regexp/transformer/quantifier.rb +92 -0
  10. data/lib/mutant/ast/regexp/transformer/recursive.rb +56 -0
  11. data/lib/mutant/ast/regexp/transformer/root.rb +28 -0
  12. data/lib/mutant/ast/regexp/transformer/text.rb +58 -0
  13. data/lib/mutant/ast/types.rb +115 -2
  14. data/lib/mutant/bootstrap.rb +1 -1
  15. data/lib/mutant/cli/command.rb +4 -0
  16. data/lib/mutant/cli/command/environment.rb +9 -3
  17. data/lib/mutant/cli/command/environment/subject.rb +0 -4
  18. data/lib/mutant/cli/command/environment/test.rb +36 -0
  19. data/lib/mutant/cli/command/root.rb +1 -1
  20. data/lib/mutant/config.rb +9 -55
  21. data/lib/mutant/config/coverage_criteria.rb +61 -0
  22. data/lib/mutant/context.rb +1 -1
  23. data/lib/mutant/env.rb +2 -2
  24. data/lib/mutant/expression.rb +0 -12
  25. data/lib/mutant/expression/method.rb +4 -4
  26. data/lib/mutant/expression/methods.rb +5 -4
  27. data/lib/mutant/integration.rb +8 -2
  28. data/lib/mutant/isolation/exception.rb +22 -0
  29. data/lib/mutant/isolation/fork.rb +9 -12
  30. data/lib/mutant/loader.rb +1 -1
  31. data/lib/mutant/matcher.rb +3 -3
  32. data/lib/mutant/matcher/config.rb +30 -8
  33. data/lib/mutant/matcher/method.rb +14 -13
  34. data/lib/mutant/matcher/method/instance.rb +6 -2
  35. data/lib/mutant/matcher/method/metaclass.rb +1 -1
  36. data/lib/mutant/matcher/methods.rb +2 -4
  37. data/lib/mutant/meta/example.rb +1 -1
  38. data/lib/mutant/meta/example/dsl.rb +6 -2
  39. data/lib/mutant/meta/example/verification.rb +1 -1
  40. data/lib/mutant/mutation.rb +1 -1
  41. data/lib/mutant/mutator.rb +8 -1
  42. data/lib/mutant/mutator/node.rb +0 -5
  43. data/lib/mutant/mutator/node/argument.rb +2 -2
  44. data/lib/mutant/mutator/node/dynamic_literal.rb +1 -1
  45. data/lib/mutant/mutator/node/index.rb +1 -0
  46. data/lib/mutant/mutator/node/kwargs.rb +2 -2
  47. data/lib/mutant/mutator/node/literal/float.rb +1 -3
  48. data/lib/mutant/mutator/node/literal/integer.rb +3 -6
  49. data/lib/mutant/mutator/node/literal/regex.rb +12 -0
  50. data/lib/mutant/mutator/node/module.rb +19 -0
  51. data/lib/mutant/mutator/node/named_value/variable_assignment.rb +3 -3
  52. data/lib/mutant/mutator/node/regexp.rb +20 -0
  53. data/lib/mutant/mutator/node/regexp/alternation_meta.rb +20 -0
  54. data/lib/mutant/mutator/node/regexp/beginning_of_line_anchor.rb +20 -0
  55. data/lib/mutant/mutator/node/regexp/capture_group.rb +25 -0
  56. data/lib/mutant/mutator/node/regexp/character_type.rb +31 -0
  57. data/lib/mutant/mutator/node/regexp/end_of_line_anchor.rb +20 -0
  58. data/lib/mutant/mutator/node/regexp/end_of_string_or_before_end_of_line_anchor.rb +20 -0
  59. data/lib/mutant/mutator/node/regexp/named_group.rb +39 -0
  60. data/lib/mutant/mutator/node/regexp/zero_or_more.rb +34 -0
  61. data/lib/mutant/mutator/node/regopt.rb +1 -1
  62. data/lib/mutant/mutator/node/sclass.rb +1 -1
  63. data/lib/mutant/mutator/node/send.rb +73 -6
  64. data/lib/mutant/parallel.rb +2 -2
  65. data/lib/mutant/parallel/driver.rb +1 -1
  66. data/lib/mutant/parallel/worker.rb +1 -1
  67. data/lib/mutant/parser.rb +1 -1
  68. data/lib/mutant/pipe.rb +1 -1
  69. data/lib/mutant/procto.rb +23 -0
  70. data/lib/mutant/reporter/cli/printer.rb +10 -4
  71. data/lib/mutant/reporter/cli/printer/env.rb +3 -3
  72. data/lib/mutant/reporter/cli/printer/env_progress.rb +2 -2
  73. data/lib/mutant/reporter/cli/printer/isolation_result.rb +3 -1
  74. data/lib/mutant/selector.rb +1 -1
  75. data/lib/mutant/subject.rb +2 -4
  76. data/lib/mutant/subject/method/instance.rb +6 -45
  77. data/lib/mutant/transform.rb +25 -0
  78. data/lib/mutant/variable.rb +322 -0
  79. data/lib/mutant/version.rb +1 -1
  80. data/lib/mutant/world.rb +2 -3
  81. metadata +38 -149
  82. data/lib/mutant/warnings.rb +0 -106
data/lib/mutant/pipe.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  module Mutant
4
4
  # Pipe abstraction
5
5
  class Pipe
6
- include Adamantium::Flat, Anima.new(:reader, :writer)
6
+ include Adamantium, Anima.new(:reader, :writer)
7
7
 
8
8
  # Run block with pipe in binmode
9
9
  #
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ module Procto
5
+ # Define the .call method on +host+
6
+ #
7
+ # @param [Object] host
8
+ # the hosting object
9
+ #
10
+ # @return [undefined]
11
+ #
12
+ # @api private
13
+ def self.included(host)
14
+ host.extend(ClassMethods)
15
+ end
16
+
17
+ module ClassMethods
18
+ def call(*arguments)
19
+ new(*arguments).call
20
+ end
21
+ end
22
+ end # Procto
23
+ end # Unparser
@@ -5,13 +5,19 @@ module Mutant
5
5
  class CLI
6
6
  # CLI runner status printer base class
7
7
  class Printer
8
- include AbstractType,
9
- Adamantium::Flat,
10
- Concord.new(:output, :object),
11
- Procto.call(:run)
8
+ include(
9
+ AbstractType,
10
+ Adamantium,
11
+ Concord.new(:output, :object),
12
+ Procto
13
+ )
12
14
 
13
15
  private_class_method :new
14
16
 
17
+ def call
18
+ run
19
+ end
20
+
15
21
  # Create delegators to object
16
22
  #
17
23
  # @return [undefined]
@@ -15,13 +15,13 @@ module Mutant
15
15
  :test_subject_ratio
16
16
  )
17
17
 
18
- FORMATS = IceNine.deep_freeze([
18
+ FORMATS = [
19
19
  [:info, 'Subjects: %s', :amount_subjects ],
20
20
  [:info, 'Total-Tests: %s', :amount_total_tests ],
21
21
  [:info, 'Selected-Tests: %s', :amount_selected_tests],
22
22
  [:info, 'Tests/Subject: %0.2f avg', :test_subject_ratio ],
23
23
  [:info, 'Mutations: %s', :amount_mutations ]
24
- ])
24
+ ].each(&:freeze)
25
25
 
26
26
  # Run printer
27
27
  #
@@ -33,7 +33,7 @@ module Mutant
33
33
  __send__(report, format, __send__(value))
34
34
  end
35
35
  end
36
- end # EnvProgress
36
+ end # Env
37
37
  end # Printer
38
38
  end # CLI
39
39
  end # Reporter
@@ -18,7 +18,7 @@ module Mutant
18
18
  :runtime
19
19
  )
20
20
 
21
- FORMATS = IceNine.deep_freeze([
21
+ FORMATS = [
22
22
  [:info, 'Results: %s', :amount_mutation_results],
23
23
  [:info, 'Kills: %s', :amount_mutations_killed],
24
24
  [:info, 'Alive: %s', :amount_mutations_alive ],
@@ -28,7 +28,7 @@ module Mutant
28
28
  [:info, 'Overhead: %0.2f%%', :overhead_percent ],
29
29
  [:info, 'Mutations/s: %0.2f', :mutations_per_second ],
30
30
  [:status, 'Coverage: %0.2f%%', :coverage_percent ]
31
- ])
31
+ ].each(&:freeze)
32
32
 
33
33
  # Run printer
34
34
  #
@@ -28,6 +28,7 @@ module Mutant
28
28
  ```
29
29
  %s
30
30
  %s
31
+ %s
31
32
  ```
32
33
  MESSAGE
33
34
 
@@ -81,7 +82,8 @@ module Mutant
81
82
 
82
83
  puts(
83
84
  EXCEPTION_ERROR_MESSAGE % [
84
- exception.inspect,
85
+ exception.original_class,
86
+ exception.message,
85
87
  exception.backtrace.join("\n")
86
88
  ]
87
89
  )
@@ -3,7 +3,7 @@
3
3
  module Mutant
4
4
  # Abstract base class for test selectors
5
5
  class Selector
6
- include AbstractType, Adamantium::Flat
6
+ include AbstractType, Adamantium
7
7
 
8
8
  # Tests for subject
9
9
  #
@@ -3,10 +3,8 @@
3
3
  module Mutant
4
4
  # Subject of a mutation
5
5
  class Subject
6
- include AbstractType, Adamantium::Flat, Enumerable
7
- include Anima.new(:context, :node, :warnings)
8
-
9
- private :warnings
6
+ include AbstractType, Adamantium, Enumerable
7
+ include Anima.new(:context, :node)
10
8
 
11
9
  # Mutations for this subject
12
10
  #
@@ -13,9 +13,7 @@ module Mutant
13
13
  #
14
14
  # @return [self]
15
15
  def prepare
16
- warnings.call do
17
- scope.public_send(:undef_method, name)
18
- end
16
+ scope.undef_method(name)
19
17
  self
20
18
  end
21
19
 
@@ -23,59 +21,22 @@ module Mutant
23
21
  class Memoized < self
24
22
  include AST::Sexp
25
23
 
26
- FREEZER_OPTION_VALUES = {
27
- Adamantium::Freezer::Deep => :deep,
28
- Adamantium::Freezer::Flat => :flat,
29
- Adamantium::Freezer::Noop => :noop
30
- }.freeze
31
-
32
- private_constant(*constants(false))
33
-
34
24
  # Prepare subject for mutation insertion
35
25
  #
36
26
  # @return [self]
37
27
  def prepare
38
- memory.delete(name)
28
+ scope
29
+ .instance_variable_get(:@memoized_methods)
30
+ .delete(name)
31
+
39
32
  super()
40
33
  end
41
34
 
42
35
  private
43
36
 
44
37
  def wrap_node(mutant)
45
- s(:begin, mutant, s(:send, nil, :memoize, s(:sym, name), *options))
46
- end
47
-
48
- # The optional AST node for adamantium memoization options
49
- #
50
- # @return [Array(Parser::AST::Node), nil]
51
- def options
52
- # rubocop:disable Style/GuardClause
53
- if FREEZER_OPTION_VALUES.key?(freezer)
54
- [
55
- s(:kwargs,
56
- s(:pair,
57
- s(:sym, :freezer),
58
- s(:sym, FREEZER_OPTION_VALUES.fetch(freezer))))
59
- ]
60
- end
61
- # rubocop:enable Style/GuardClause
38
+ s(:begin, mutant, s(:send, nil, :memoize, s(:sym, name)))
62
39
  end
63
-
64
- # The freezer used for memoization
65
- #
66
- # @return [Object]
67
- def freezer
68
- memory.fetch(name).instance_variable_get(:@freezer)
69
- end
70
- memoize :freezer, freezer: :noop
71
-
72
- # The memory used for memoization
73
- #
74
- # @return [ThreadSafe::Cache]
75
- def memory
76
- scope.__send__(:memoized_methods).instance_variable_get(:@memory)
77
- end
78
-
79
40
  end # Memoized
80
41
  end # Instance
81
42
  end # Method
@@ -73,6 +73,31 @@ module Mutant
73
73
  end
74
74
  end # Named
75
75
 
76
+ class Block < self
77
+ include Anima.new(:block, :name)
78
+
79
+ def self.capture(name, &block)
80
+ new(block: block, name: name)
81
+ end
82
+
83
+ def call(input)
84
+ block
85
+ .call(input)
86
+ .lmap do |message|
87
+ Error.new(
88
+ cause: nil,
89
+ input: input,
90
+ message: message,
91
+ transform: self
92
+ )
93
+ end
94
+ end
95
+
96
+ def slug
97
+ name
98
+ end
99
+ end
100
+
76
101
  private
77
102
 
78
103
  def error(cause: nil, input:, message: nil)
@@ -0,0 +1,322 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ # Lightweight concurrency variables
5
+ #
6
+ # These are inspired by Haskells MVar and IVar types.
7
+ class Variable
8
+ EMPTY = Class.new do
9
+ const_set(:INSPECT, 'Variable::EMPTY')
10
+ end.new.freeze
11
+
12
+ TIMEOUT = Class.new do
13
+ const_set(:INSPECT, 'Variable::TIMEOUT')
14
+ end.new.freeze
15
+
16
+ # Result of operation that may time out
17
+ class Result
18
+ include Equalizer.new(:value)
19
+ attr_reader :value
20
+
21
+ # Initialize result
22
+ #
23
+ # @return [undefined]
24
+ def initialize(value)
25
+ @value = value
26
+ freeze
27
+ end
28
+
29
+ # Test if take resulted in a timeout
30
+ #
31
+ # @return [Boolean]
32
+ #
33
+ # @api private
34
+ def timeout?
35
+ instance_of?(Timeout)
36
+ end
37
+
38
+ # Instance returned on timeouts
39
+ class Timeout < self
40
+ INSTANCE = new(nil)
41
+
42
+ # Construct new object
43
+ #
44
+ # @return [Timeout]
45
+ def self.new
46
+ INSTANCE
47
+ end
48
+ end # Timeout
49
+
50
+ # Instance returned without timeouts
51
+ class Value < self
52
+ end # Value
53
+ end # Result
54
+
55
+ private_constant(*constants(false))
56
+
57
+ module Timer
58
+ # Monotonic elapsed time of block execution
59
+ #
60
+ # @return [Float]
61
+ def self.elapsed
62
+ start = now
63
+ yield
64
+ now - start
65
+ end
66
+
67
+ # The now monotonic time
68
+ #
69
+ # @return [Float]
70
+ def self.now
71
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
72
+ end
73
+ private_class_method :now
74
+ end # Timer
75
+
76
+ # Initialize object
77
+ #
78
+ # @param [Object] value
79
+ # the initial value
80
+ #
81
+ # @return [undefined]
82
+ def initialize(condition_variable:, mutex:, value: EMPTY)
83
+ @full = condition_variable.new
84
+ @mutex = mutex.new
85
+ @value = value
86
+ end
87
+
88
+ # Take value, block on empty
89
+ #
90
+ # @return [Object]
91
+ def take
92
+ synchronize do
93
+ wait_full
94
+ perform_take
95
+ end
96
+ end
97
+
98
+ # Take value, with timeout
99
+ #
100
+ # @param [Float] Timeout
101
+ #
102
+ # @return [Result::Timeout]
103
+ # in case take resulted in a timeout
104
+ #
105
+ # @return [Result::Value]
106
+ # in case take resulted in a value
107
+ def take_timeout(timeout)
108
+ synchronize do
109
+ if wait_timeout(@full, timeout, &method(:full?))
110
+ Result::Timeout.new
111
+ else
112
+ Result::Value.new(perform_take)
113
+ end
114
+ end
115
+ end
116
+
117
+ # Read value, block on empty
118
+ #
119
+ # @return [Object]
120
+ # the variable value
121
+ def read
122
+ synchronize do
123
+ wait_full
124
+ @value
125
+ end
126
+ end
127
+
128
+ # Try put value into the variable, non blocking
129
+ #
130
+ # @param [Object] value
131
+ #
132
+ # @return [self]
133
+ def try_put(value)
134
+ synchronize do
135
+ perform_put(value) if empty?
136
+ end
137
+
138
+ self
139
+ end
140
+
141
+ # Execute block with value, blocking
142
+ #
143
+ # @yield [Object]
144
+ #
145
+ # @return [Object]
146
+ # the blocks return value
147
+ def with
148
+ synchronize do
149
+ wait_full
150
+ yield @value
151
+ end
152
+ end
153
+
154
+ private
155
+
156
+ # Perform the put
157
+ #
158
+ # @param [Object] value
159
+ def perform_put(value)
160
+ (@value = value).tap { @full.signal }
161
+ end
162
+
163
+ # Execute block under mutex
164
+ #
165
+ # @return [self]
166
+ def synchronize(&block)
167
+ @mutex.synchronize(&block)
168
+ end
169
+
170
+ # Wait for block predicate
171
+ #
172
+ # @param [ConditionVariable] event
173
+ #
174
+ # @return [undefined]
175
+ def wait(event)
176
+ event.wait(@mutex) until yield
177
+ end
178
+
179
+ # Wait with timeout for block predicate
180
+ #
181
+ # @param [ConditionVariable] event
182
+ #
183
+ # @return [Boolean]
184
+ # if wait was terminated due a timeout
185
+ #
186
+ # @return [undefined]
187
+ # otherwise
188
+ def wait_timeout(event, timeout)
189
+ loop do
190
+ break true if timeout <= 0
191
+ break if yield
192
+ timeout -= Timer.elapsed { event.wait(@mutex, timeout) }
193
+ end
194
+ end
195
+
196
+ # Wait till mvar is full
197
+ #
198
+ # @return [undefined]
199
+ def wait_full
200
+ wait(@full, &method(:full?))
201
+ end
202
+
203
+ # Test if state is full
204
+ #
205
+ # @return [Boolean]
206
+ def full?
207
+ !empty?
208
+ end
209
+
210
+ # Test if state is empty
211
+ #
212
+ # @return [Boolean]
213
+ def empty?
214
+ @value.equal?(EMPTY)
215
+ end
216
+
217
+ # Shared variable that can be written at most once
218
+ #
219
+ # ignore :reek:InstanceVariableAssumption
220
+ class IVar < self
221
+
222
+ # Exception raised on ivar errors
223
+ class Error < RuntimeError; end
224
+
225
+ # Put value, raises if already full
226
+ #
227
+ # @param [Object] value
228
+ #
229
+ # @return [self]
230
+ #
231
+ # @raise Error
232
+ # if already full
233
+ def put(value)
234
+ synchronize do
235
+ fail Error, 'is immutable' if full?
236
+ perform_put(value)
237
+ end
238
+
239
+ self
240
+ end
241
+
242
+ # Populate and return value, use block to compute value if empty
243
+ #
244
+ # The block is guaranteed to be executed at max once.
245
+ #
246
+ # Subsequent reads are guaranteed to return the block value.
247
+ #
248
+ # @return [Object]
249
+ def populate_with
250
+ return @value if full?
251
+
252
+ synchronize do
253
+ perform_put(yield) if empty?
254
+ end
255
+
256
+ @value
257
+ end
258
+
259
+ private
260
+
261
+ # Perform take operation
262
+ #
263
+ # @return [Object]
264
+ def perform_take
265
+ @value
266
+ end
267
+ end # IVar
268
+
269
+ # Shared variable that can be written multiple times
270
+ #
271
+ # ignore :reek:InstanceVariableAssumption
272
+ class MVar < self
273
+
274
+ # Initialize object
275
+ #
276
+ # @param [Object] value
277
+ # the initial value
278
+ #
279
+ # @return [undefined]
280
+ def initialize(condition_variable:, mutex:, value: EMPTY)
281
+ super
282
+ @empty = condition_variable.new
283
+ end
284
+
285
+ # Put value, block on full
286
+ #
287
+ # @param [Object] value
288
+ #
289
+ # @return [self]
290
+ def put(value)
291
+ synchronize do
292
+ wait(@empty, &method(:empty?))
293
+ perform_put(value)
294
+ end
295
+
296
+ self
297
+ end
298
+
299
+ # Modify value, blocks if empty
300
+ #
301
+ # @return [Object]
302
+ def modify
303
+ synchronize do
304
+ wait_full
305
+ perform_put(yield(@value))
306
+ end
307
+ end
308
+
309
+ private
310
+
311
+ # Empty the variable
312
+ #
313
+ # @return [Object]
314
+ def perform_take
315
+ @value.tap do
316
+ @value = EMPTY
317
+ @empty.signal
318
+ end
319
+ end
320
+ end # MVar
321
+ end # Variable
322
+ end # Mutant