mutant 0.10.21 → 0.10.26

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 (78) hide show
  1. checksums.yaml +4 -4
  2. data/lib/mutant.rb +32 -13
  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/expression/namespace.rb +1 -1
  28. data/lib/mutant/integration.rb +8 -2
  29. data/lib/mutant/isolation/fork.rb +4 -11
  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 +10 -9
  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 -1
  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/argument.rb +2 -2
  43. data/lib/mutant/mutator/node/block.rb +5 -1
  44. data/lib/mutant/mutator/node/dynamic_literal.rb +1 -1
  45. data/lib/mutant/mutator/node/kwargs.rb +44 -0
  46. data/lib/mutant/mutator/node/literal/regex.rb +12 -0
  47. data/lib/mutant/mutator/node/named_value/variable_assignment.rb +3 -3
  48. data/lib/mutant/mutator/node/regexp.rb +20 -0
  49. data/lib/mutant/mutator/node/regexp/alternation_meta.rb +20 -0
  50. data/lib/mutant/mutator/node/regexp/beginning_of_line_anchor.rb +20 -0
  51. data/lib/mutant/mutator/node/regexp/capture_group.rb +25 -0
  52. data/lib/mutant/mutator/node/regexp/character_type.rb +31 -0
  53. data/lib/mutant/mutator/node/regexp/end_of_line_anchor.rb +20 -0
  54. data/lib/mutant/mutator/node/regexp/end_of_string_or_before_end_of_line_anchor.rb +20 -0
  55. data/lib/mutant/mutator/node/regexp/named_group.rb +39 -0
  56. data/lib/mutant/mutator/node/regexp/zero_or_more.rb +34 -0
  57. data/lib/mutant/mutator/node/regopt.rb +1 -1
  58. data/lib/mutant/mutator/node/sclass.rb +1 -1
  59. data/lib/mutant/mutator/node/send.rb +55 -6
  60. data/lib/mutant/parallel.rb +2 -2
  61. data/lib/mutant/parallel/driver.rb +1 -1
  62. data/lib/mutant/parallel/worker.rb +1 -1
  63. data/lib/mutant/parser.rb +1 -1
  64. data/lib/mutant/pipe.rb +1 -1
  65. data/lib/mutant/procto.rb +23 -0
  66. data/lib/mutant/reporter/cli/printer.rb +10 -4
  67. data/lib/mutant/reporter/cli/printer/env.rb +3 -3
  68. data/lib/mutant/reporter/cli/printer/env_progress.rb +2 -2
  69. data/lib/mutant/selector.rb +1 -1
  70. data/lib/mutant/subject.rb +2 -4
  71. data/lib/mutant/subject/method/instance.rb +6 -45
  72. data/lib/mutant/timer.rb +2 -2
  73. data/lib/mutant/transform.rb +25 -0
  74. data/lib/mutant/variable.rb +322 -0
  75. data/lib/mutant/version.rb +1 -1
  76. data/lib/mutant/world.rb +2 -3
  77. metadata +39 -151
  78. data/lib/mutant/warnings.rb +0 -106
@@ -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(:hash,
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
@@ -14,8 +14,8 @@ module Mutant
14
14
  class Deadline
15
15
  include Anima.new(:timer, :allowed_time)
16
16
 
17
- def initialize(**arguments)
18
- super(**arguments)
17
+ def initialize(*arguments)
18
+ super(*arguments)
19
19
  @start_at = timer.now
20
20
  end
21
21
 
@@ -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