zizek 0.1.0

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.
@@ -0,0 +1,879 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+
5
+ module Zizek
6
+ module Generators
7
+ # Transformations every generator carries. Neither records a choice of its
8
+ # own, so composing them leaves the reproduce blob byte-compatible with the
9
+ # other packages.
10
+ module Combinators
11
+ # Apply a block to each drawn value.
12
+ def map(&transform)
13
+ MappedGenerator.new(self, transform)
14
+ end
15
+
16
+ # Keep only values satisfying a block. Draws are retried a few times
17
+ # before the example is rejected, matching how the engine treats a
18
+ # filtered generator: a rejection discards the example rather than
19
+ # failing the property.
20
+ def filter(&predicate)
21
+ FilteredGenerator.new(self, predicate)
22
+ end
23
+
24
+ # Build the next generator from each drawn value — monadic bind.
25
+ #
26
+ # Generators.integers(0..3).flat_map do |length|
27
+ # Generators.lists(Generators.booleans, min: length, max: length)
28
+ # end
29
+ #
30
+ # Both draws share one `flat_map` span, so the shrinker can delete or
31
+ # simplify the whole unit together: shrinking the source value changes
32
+ # which inner generator runs, and the inner draws replay or pun by the
33
+ # ordinary prefix rules.
34
+ def flat_map(&transform)
35
+ FlatMappedGenerator.new(self, transform)
36
+ end
37
+
38
+ # Either generator, shrinking towards the left one.
39
+ #
40
+ # Generators.integers(max: 0) | Generators.text(max: 4)
41
+ #
42
+ # Chains flatten: `a | b | c` is the three-way `one_of(a, b, c)`, not a
43
+ # two-way choice nested inside another, so the shrinker sees one index
44
+ # to lower rather than two.
45
+ def |(other)
46
+ Generators.one_of(*Generators.__send__(:one_of_parts, self), *Generators.__send__(:one_of_parts, other))
47
+ end
48
+
49
+ # One value, for a REPL or a docstring.
50
+ #
51
+ # This is not a generator you can shrink or replay — it runs no property
52
+ # and records nothing. It exists so `Generators.text(max: 5).example`
53
+ # answers "what does this actually produce?" without writing a test.
54
+ def example(seed: nil)
55
+ rng = Random.new(seed.nil? ? Random.new_seed : seed)
56
+ attempts = 0
57
+ begin
58
+ attempts += 1
59
+ TestCase.new(rng: rng).draw(self)
60
+ rescue AssumptionRejected, ChoiceOverrun
61
+ retry if attempts < 32
62
+ raise Unsatisfiable, "no example after #{attempts} attempts; the filter may be too strict"
63
+ end
64
+ end
65
+ end
66
+
67
+ class MappedGenerator
68
+ include Combinators
69
+
70
+ def initialize(source, transform)
71
+ @source = source
72
+ @transform = transform
73
+ end
74
+
75
+ def do_draw(test_case)
76
+ @transform.call(@source.do_draw(test_case))
77
+ end
78
+ end
79
+
80
+ class FlatMappedGenerator
81
+ include Combinators
82
+
83
+ def initialize(source, transform)
84
+ @source = source
85
+ @transform = transform
86
+ end
87
+
88
+ def do_draw(test_case)
89
+ test_case.start_span("flat_map")
90
+ value = @source.do_draw(test_case)
91
+ result = Generators.__send__(:coerce, @transform.call(value)).do_draw(test_case)
92
+ test_case.stop_span
93
+ result
94
+ end
95
+ end
96
+
97
+ class FilteredGenerator
98
+ include Combinators
99
+
100
+ def initialize(source, predicate)
101
+ @source = source
102
+ @predicate = predicate
103
+ end
104
+
105
+ def do_draw(test_case)
106
+ # The attempt budget is the same one a unique collection spends per
107
+ # element, so it comes from ir/many.zir rather than a constant here.
108
+ attempts = 0
109
+ while Core.filter_retry_allowed(attempts)
110
+ value = @source.do_draw(test_case)
111
+ attempts += 1
112
+ return value if @predicate.call(value)
113
+ end
114
+ test_case.assume(false)
115
+ end
116
+ end
117
+
118
+ # Always the same value, recording no choice at all.
119
+ class JustGenerator
120
+ include Combinators
121
+
122
+ def initialize(value)
123
+ @value = value
124
+ end
125
+
126
+ def do_draw(_test_case)
127
+ @value
128
+ end
129
+ end
130
+
131
+ class BooleanGenerator
132
+ include Combinators
133
+
134
+ def do_draw(test_case)
135
+ test_case.boolean
136
+ end
137
+ end
138
+
139
+ class WeightedBooleanGenerator
140
+ include Combinators
141
+
142
+ attr_reader :probability
143
+ private :probability
144
+
145
+ def initialize(probability)
146
+ @probability = probability
147
+ end
148
+
149
+ def do_draw(test_case)
150
+ test_case.weighted(@probability)
151
+ end
152
+ end
153
+
154
+ class BytesGenerator
155
+ include Combinators
156
+
157
+ attr_reader :minimum, :maximum
158
+ private :minimum, :maximum
159
+
160
+ def initialize(minimum = 0, maximum = 16)
161
+ @minimum = minimum
162
+ @maximum = maximum
163
+ end
164
+
165
+ def min_size(value)
166
+ self.class.new(value, maximum)
167
+ end
168
+
169
+ def max_size(value)
170
+ self.class.new(minimum, value)
171
+ end
172
+
173
+ def do_draw(test_case)
174
+ test_case.bytes(minimum, maximum)
175
+ end
176
+ end
177
+
178
+ # One of a fixed sequence, shrinking towards the first element. The index
179
+ # is a real integer choice, so it shrinks and replays like any other.
180
+ class SampledGenerator
181
+ include Combinators
182
+
183
+ def initialize(values)
184
+ @values = values
185
+ end
186
+
187
+ def do_draw(test_case)
188
+ @values[test_case.integer(0, @values.length - 1)]
189
+ end
190
+ end
191
+
192
+ # One of several generators, shrinking towards the first.
193
+ class OneOfGenerator
194
+ include Combinators
195
+
196
+ attr_reader :choices
197
+ private :choices
198
+
199
+ def initialize(choices)
200
+ @choices = choices
201
+ end
202
+
203
+ def do_draw(test_case)
204
+ @choices[test_case.integer(0, @choices.length - 1)].do_draw(test_case)
205
+ end
206
+ end
207
+
208
+ # Each generator in order, collected into an array.
209
+ class TupleGenerator
210
+ include Combinators
211
+
212
+ def initialize(parts)
213
+ @parts = parts
214
+ end
215
+
216
+ def do_draw(test_case)
217
+ @parts.map { |part| part.do_draw(test_case) }
218
+ end
219
+ end
220
+
221
+ # A generator assembled by a block that draws as it goes.
222
+ class CompositeGenerator
223
+ include Combinators
224
+
225
+ def initialize(build)
226
+ @build = build
227
+ end
228
+
229
+ # The block is handed the test case itself rather than a bare draw
230
+ # lambda, so a composite can `assume`, `note`, and name its draws the
231
+ # same way a property can.
232
+ def do_draw(test_case)
233
+ @build.call(test_case)
234
+ end
235
+ end
236
+
237
+ # A generator that builds an object out of drawn parts.
238
+ class BuiltGenerator
239
+ include Combinators
240
+
241
+ def initialize(builder, parts, keyword_parts)
242
+ @builder = builder
243
+ @parts = parts
244
+ @keyword_parts = keyword_parts
245
+ end
246
+
247
+ def do_draw(test_case)
248
+ args = @parts.map { |part| part.do_draw(test_case) }
249
+ if @keyword_parts.empty?
250
+ @builder.call(*args)
251
+ else
252
+ kwargs = @keyword_parts.transform_values { |part| part.do_draw(test_case) }
253
+ @builder.call(*args, **kwargs)
254
+ end
255
+ end
256
+ end
257
+ end
258
+ end
259
+
260
+ module Zizek
261
+ module Generators
262
+ class IntegerGenerator
263
+ include Combinators
264
+
265
+ attr_reader :minimum, :maximum, :target
266
+ private :minimum, :maximum, :target
267
+
268
+ def initialize(minimum = -(1 << 63), maximum = (1 << 63) - 1, target = nil)
269
+ @minimum = minimum
270
+ @maximum = maximum
271
+ @target = target
272
+ end
273
+
274
+ def min_value(value)
275
+ self.class.new(value, maximum, target)
276
+ end
277
+
278
+ def max_value(value)
279
+ self.class.new(minimum, value, target)
280
+ end
281
+
282
+ def shrink_towards(value)
283
+ self.class.new(minimum, maximum, value)
284
+ end
285
+
286
+ def do_draw(test_case)
287
+ # An explicit target is passed through unclamped: integer_simplest
288
+ # already clamps it to the nearer bound. The default is compiled so the
289
+ # shells cannot drift on it.
290
+ destination = target
291
+ destination = Core.integer_default_shrink_target(minimum, maximum) if destination.nil?
292
+ test_case.integer(minimum, maximum, shrink_towards: destination)
293
+ end
294
+ end
295
+
296
+ # Text over an alphabet, by default every Unicode scalar value. Values
297
+ # shrink shortlex over the alphabet's shrink order — shorter first, then
298
+ # towards "0", the digits, and the uppercase letters — so a minimal
299
+ # failing string is one a reader can retype.
300
+ class TextGenerator
301
+ include Combinators
302
+
303
+ attr_reader :minimum, :maximum, :characters
304
+ private :minimum, :maximum, :characters
305
+
306
+ def initialize(minimum = 0, maximum = nil, characters = nil)
307
+ @minimum = minimum
308
+ @maximum = maximum
309
+ @characters = characters
310
+ end
311
+
312
+ def min_size(value)
313
+ self.class.new(value, maximum, characters)
314
+ end
315
+
316
+ def max_size(value)
317
+ self.class.new(minimum, value, characters)
318
+ end
319
+
320
+ # Restrict the characters drawn. Takes either the characters themselves
321
+ # or an interval set from Zizek.alphabet_range.
322
+ def alphabet(value)
323
+ value = Zizek.alphabet(value) if value.is_a?(String) && value.encoding != Encoding::BINARY
324
+ self.class.new(minimum, maximum, value)
325
+ end
326
+
327
+ def do_draw(test_case)
328
+ limit = maximum.nil? ? Core.text_widened_max(minimum) : maximum
329
+ test_case.string(minimum, limit, alphabet: characters)
330
+ end
331
+ end
332
+
333
+ class FloatGenerator
334
+ include Combinators
335
+
336
+ attr_reader :minimum, :maximum, :allow_nan_values, :allow_infinity_values,
337
+ :smallest_nonzero
338
+ private :minimum, :maximum, :allow_nan_values, :allow_infinity_values, :smallest_nonzero
339
+
340
+ def initialize(
341
+ minimum = -Float::INFINITY,
342
+ maximum = Float::INFINITY,
343
+ allow_nan_values = false,
344
+ allow_infinity_values = false,
345
+ smallest_nonzero = 0.0
346
+ )
347
+ @minimum = minimum
348
+ @maximum = maximum
349
+ @allow_nan_values = allow_nan_values
350
+ @allow_infinity_values = allow_infinity_values
351
+ @smallest_nonzero = smallest_nonzero
352
+ end
353
+
354
+ def copy(**changes)
355
+ self.class.new(
356
+ changes.fetch(:minimum, minimum),
357
+ changes.fetch(:maximum, maximum),
358
+ changes.fetch(:allow_nan_values, allow_nan_values),
359
+ changes.fetch(:allow_infinity_values, allow_infinity_values),
360
+ changes.fetch(:smallest_nonzero, smallest_nonzero)
361
+ )
362
+ end
363
+
364
+ private :copy
365
+
366
+ def min_value(value)
367
+ copy(minimum: value)
368
+ end
369
+
370
+ def max_value(value)
371
+ copy(maximum: value)
372
+ end
373
+
374
+ def allow_nan(value = true)
375
+ copy(allow_nan_values: value)
376
+ end
377
+
378
+ def allow_infinity(value = true)
379
+ copy(allow_infinity_values: value)
380
+ end
381
+
382
+ def smallest_nonzero_magnitude(value)
383
+ copy(smallest_nonzero: value)
384
+ end
385
+
386
+ def do_draw(test_case)
387
+ test_case.float(
388
+ minimum,
389
+ maximum,
390
+ allow_nan: allow_nan_values,
391
+ allow_infinity: allow_infinity_values,
392
+ smallest_nonzero_magnitude: smallest_nonzero
393
+ )
394
+ end
395
+ end
396
+
397
+ # A variable-length list of values drawn from one element generator.
398
+ class ListGenerator
399
+ include Combinators
400
+
401
+ attr_reader :element, :minimum, :maximum
402
+ private :element, :minimum, :maximum
403
+
404
+ def initialize(element, minimum = 0, maximum = nil)
405
+ @element = element
406
+ @minimum = minimum
407
+ @maximum = maximum
408
+ end
409
+
410
+ def min_size(value)
411
+ self.class.new(element, value, maximum)
412
+ end
413
+
414
+ def max_size(value)
415
+ self.class.new(element, minimum, value)
416
+ end
417
+
418
+ def do_draw(test_case)
419
+ test_case.__send__(:collect, element, minimum, maximum, "list.element")
420
+ end
421
+ end
422
+
423
+ # A variable-length collection whose elements are distinct under a key.
424
+ class UniqueListGenerator
425
+ include Combinators
426
+
427
+ attr_reader :element, :key, :minimum, :maximum, :label
428
+ private :element, :key, :minimum, :maximum, :label
429
+
430
+ def initialize(element, key = nil, minimum = 0, maximum = nil, label = "list.element")
431
+ @element = element
432
+ @key = key
433
+ @minimum = minimum
434
+ @maximum = maximum
435
+ @label = label
436
+ end
437
+
438
+ def min_size(value)
439
+ self.class.new(element, key, value, maximum, label)
440
+ end
441
+
442
+ def max_size(value)
443
+ self.class.new(element, key, minimum, value, label)
444
+ end
445
+
446
+ def unique_by(&block)
447
+ self.class.new(element, block, minimum, maximum, label)
448
+ end
449
+
450
+ def do_draw(test_case)
451
+ test_case.__send__(:collect_unique, element, key, minimum, maximum, label)
452
+ end
453
+ end
454
+
455
+ # A set of distinct values, materialized as a Ruby Set.
456
+ class SetGenerator < UniqueListGenerator
457
+ def initialize(element, minimum = 0, maximum = nil)
458
+ super(element, nil, minimum, maximum, "set.element")
459
+ end
460
+
461
+ def min_size(value)
462
+ self.class.new(element, value, maximum)
463
+ end
464
+
465
+ def max_size(value)
466
+ self.class.new(element, minimum, value)
467
+ end
468
+
469
+ def do_draw(test_case)
470
+ Set.new(super)
471
+ end
472
+ end
473
+
474
+ # One key/value pair, drawn as a unit so that deleting its span deletes both
475
+ # halves together.
476
+ class PairGenerator
477
+ include Combinators
478
+
479
+ attr_reader :keys, :values
480
+ private :keys, :values
481
+
482
+ def initialize(keys, values)
483
+ @keys = keys
484
+ @values = values
485
+ end
486
+
487
+ def do_draw(test_case)
488
+ [test_case.draw(keys), test_case.draw(values)]
489
+ end
490
+ end
491
+
492
+ # A Hash whose keys are distinct.
493
+ class HashGenerator
494
+ include Combinators
495
+
496
+ attr_reader :keys, :values, :minimum, :maximum
497
+ private :keys, :values, :minimum, :maximum
498
+
499
+ def initialize(keys, values, minimum = 0, maximum = nil)
500
+ @keys = keys
501
+ @values = values
502
+ @minimum = minimum
503
+ @maximum = maximum
504
+ end
505
+
506
+ def min_size(value)
507
+ self.class.new(keys, values, value, maximum)
508
+ end
509
+
510
+ def max_size(value)
511
+ self.class.new(keys, values, minimum, value)
512
+ end
513
+
514
+ def do_draw(test_case)
515
+ entries = test_case.__send__(:collect_unique,
516
+ PairGenerator.new(keys, values), ->(entry) { entry[0] }, minimum, maximum, "hash.entry"
517
+ )
518
+ entries.to_h
519
+ end
520
+ end
521
+
522
+ class RecursiveLeafBudget < StandardError; end
523
+
524
+ class RecursiveState
525
+ attr_accessor :attempt, :leaves, :start_price, :closed_children,
526
+ :closed_branches, :open_branches, :reprices
527
+ attr_reader :max_depth, :max_leaves, :target
528
+
529
+ def initialize(max_depth, max_leaves, target)
530
+ @max_depth = max_depth
531
+ @max_leaves = max_leaves
532
+ @target = target
533
+ @attempt = @leaves = @closed_children = @closed_branches = @reprices = 0
534
+ @start_price = 0.0
535
+ @open_branches = []
536
+ end
537
+
538
+ def observe_decision(depth)
539
+ while !open_branches.empty? && open_branches.last[0] >= depth
540
+ _, children = open_branches.pop
541
+ self.closed_children += children
542
+ self.closed_branches += 1
543
+ end
544
+ open_branches.last[1] += 1 if depth.positive? && !open_branches.empty?
545
+ end
546
+
547
+ def close_branches
548
+ until open_branches.empty?
549
+ _, children = open_branches.pop
550
+ self.closed_children += children
551
+ self.closed_branches += 1
552
+ end
553
+ end
554
+ end
555
+
556
+ class RecursiveSubtreeGenerator
557
+ include Combinators
558
+
559
+ def initialize(owner, state, depth)
560
+ @owner = owner
561
+ @state = state
562
+ @depth = depth
563
+ end
564
+
565
+ def do_draw(test_case)
566
+ test_case.start_span("recursive")
567
+ @state.observe_decision(@depth)
568
+ probability = Core.recursive_decision_probability(
569
+ @state.max_depth, @depth, @state.target, @state.attempt,
570
+ @state.closed_children, @state.closed_branches
571
+ )
572
+ branch = if @depth >= @state.max_depth
573
+ test_case.__send__(:forced_boolean, false)
574
+ else
575
+ test_case.weighted(probability)
576
+ end
577
+ value = if branch
578
+ @state.open_branches << [@depth, 0]
579
+ @owner.__send__(:branch).call(
580
+ self.class.new(@owner, @state, @depth + 1)
581
+ ).do_draw(test_case)
582
+ else
583
+ raise RecursiveLeafBudget if @state.leaves >= @state.max_leaves
584
+
585
+ @state.leaves += 1
586
+ @owner.__send__(:leaf).do_draw(test_case)
587
+ end
588
+ test_case.stop_span unless @depth.zero?
589
+ value
590
+ end
591
+ end
592
+
593
+ class RecursiveGenerator
594
+ include Combinators
595
+
596
+ attr_reader :leaf, :branch, :max_depth_limit, :max_leaves_limit
597
+ private :leaf, :branch, :max_depth_limit, :max_leaves_limit
598
+
599
+ def initialize(leaf, branch, max_depth = 32, max_leaves = 100)
600
+ @leaf = leaf
601
+ @branch = branch
602
+ @max_depth_limit = max_depth
603
+ @max_leaves_limit = max_leaves
604
+ end
605
+
606
+ def max_depth(value)
607
+ raise ArgumentError, "max_depth must be nonnegative" if value.negative?
608
+
609
+ self.class.new(leaf, branch, value, max_leaves_limit)
610
+ end
611
+
612
+ def max_leaves(value)
613
+ if value.negative? || value > (1 << 63) - 1
614
+ raise ArgumentError, "max_leaves must be in [0, 2^63 - 1]"
615
+ end
616
+
617
+ self.class.new(leaf, branch, max_depth_limit, value)
618
+ end
619
+
620
+ def do_draw(test_case)
621
+ base_depth = test_case.__send__(:open_span_depth)
622
+ target = 1
623
+ if Core.recursive_target_drawn(max_leaves_limit) &&
624
+ test_case.weighted(Core.recursive_large_target_probability)
625
+ target = test_case.integer(2, max_leaves_limit, _uniform: true)
626
+ end
627
+ state = RecursiveState.new(max_depth_limit, max_leaves_limit, target)
628
+ state.start_price = Core.recursive_priced_probability(0, 0, 0, target)
629
+ loop do
630
+ begin
631
+ value = RecursiveSubtreeGenerator.new(self, state, 0).do_draw(test_case)
632
+ rescue RecursiveLeafBudget
633
+ test_case.__send__(:reset_open_spans_to, base_depth)
634
+ state.open_branches.clear
635
+ state.leaves = 0
636
+ state.attempt += 1
637
+ test_case.assume(false) unless Core.recursive_retry_allowed(state.attempt)
638
+ state.start_price = Core.recursive_priced_probability(
639
+ state.closed_children, state.closed_branches, state.attempt, state.target
640
+ )
641
+ next
642
+ end
643
+ state.close_branches
644
+ unless Core.recursive_finish_mispriced(
645
+ state.max_depth, state.target, state.attempt, state.leaves,
646
+ state.closed_children, state.closed_branches, state.start_price, state.reprices
647
+ )
648
+ test_case.stop_span
649
+ return value
650
+ end
651
+ test_case.__send__(:reset_open_spans_to, base_depth)
652
+ state.leaves = 0
653
+ state.reprices += 1
654
+ state.start_price = Core.recursive_priced_probability(
655
+ state.closed_children, state.closed_branches, state.attempt, state.target
656
+ )
657
+ end
658
+ end
659
+ end
660
+
661
+ module_function
662
+
663
+ # Whole numbers, biased towards interesting boundaries and shrinking
664
+ # towards `shrink_towards` (zero, clamped into range, by default).
665
+ #
666
+ # Generators.integers(min: 0, max: 100)
667
+ # Generators.integers(min: -100, max: 100, shrink_towards: 10)
668
+ # Generators.integers(0..100) # the same, as a range
669
+ #
670
+ # The fluent form stays available for building a generator in stages, and
671
+ # means the same thing: `integers(min: 0, max: 100)` is
672
+ # `integers.min_value(0).max_value(100)`.
673
+ def integers(range = nil, min: nil, max: nil, shrink_towards: nil)
674
+ min, max = range_bounds(range, min, max, :integers, exclusive_end: true)
675
+ generator = IntegerGenerator.new
676
+ generator = generator.min_value(min) unless min.nil?
677
+ generator = generator.max_value(max) unless max.nil?
678
+ generator = generator.shrink_towards(shrink_towards) unless shrink_towards.nil?
679
+ generator
680
+ end
681
+
682
+ # Interpret the optional range argument the bounded factories accept.
683
+ #
684
+ # A range and explicit bounds are mutually exclusive rather than merged:
685
+ # `integers(0..10, min: 5)` reads as if one of them wins, and guessing
686
+ # which would be worse than refusing.
687
+ def range_bounds(range, min, max, name, exclusive_end:)
688
+ return [min, max] if range.nil?
689
+ unless min.nil? && max.nil?
690
+ raise ArgumentError, "#{name} takes either a range or min:/max:, not both"
691
+ end
692
+ raise ArgumentError, "#{name} takes a Range, got #{range.class}" unless range.is_a?(Range)
693
+
694
+ high = range.end
695
+ if range.exclude_end? && !high.nil?
696
+ unless exclusive_end
697
+ raise ArgumentError, "#{name} needs an inclusive range; #{range.inspect} excludes its end"
698
+ end
699
+
700
+ high -= 1
701
+ end
702
+ [range.begin, high]
703
+ end
704
+ private_class_method :range_bounds
705
+
706
+ # Generate a list whose elements each occupy a deletable span.
707
+ #
708
+ # Unlike the scalar generators this one has structure the shrinker can
709
+ # reduce: it deletes whole elements before it simplifies the values inside
710
+ # them, so a failing list minimizes towards the shortest list that still
711
+ # fails rather than towards small elements in a long list.
712
+ def lists(element, min: 0, max: nil, unique: false, unique_by: nil)
713
+ return ListGenerator.new(element, min, max) unless unique || unique_by
714
+
715
+ UniqueListGenerator.new(element, unique_by, min, max)
716
+ end
717
+
718
+ # Generate a Set of distinct values.
719
+ #
720
+ # A set is a unique list the shrinker can still delete elements from: each
721
+ # element keeps its own span, and a candidate that duplicates an earlier one
722
+ # is discarded rather than failing the example.
723
+ def sets(element, min: 0, max: nil)
724
+ SetGenerator.new(element, min, max)
725
+ end
726
+
727
+ # Generate a Hash with distinct keys.
728
+ #
729
+ # Uniqueness is judged on the key alone, so the value generator is free to
730
+ # repeat.
731
+ def hashes(keys, values, min: 0, max: nil)
732
+ HashGenerator.new(keys, values, min, max)
733
+ end
734
+
735
+ # Generate text over an alphabet, by default every Unicode scalar value.
736
+ def text(min: 0, max: nil, alphabet: nil)
737
+ alphabet = Zizek.alphabet(alphabet) if alphabet.is_a?(String) && alphabet.encoding != Encoding::BINARY
738
+ TextGenerator.new(min, max, alphabet)
739
+ end
740
+
741
+ # IEEE-754 doubles, biased towards interesting bit patterns.
742
+ #
743
+ # Generators.floats(min: 0.0, max: 1.0)
744
+ # Generators.floats(allow_nan: true, allow_infinity: true)
745
+ # Generators.floats(0.0..1.0)
746
+ #
747
+ # NaN and the infinities are excluded by default: a property that has an
748
+ # opinion about them should say so.
749
+ def floats(range = nil, min: nil, max: nil, allow_nan: nil, allow_infinity: nil,
750
+ smallest_nonzero_magnitude: nil)
751
+ min, max = range_bounds(range, min, max, :floats, exclusive_end: false)
752
+ generator = FloatGenerator.new
753
+ generator = generator.min_value(min) unless min.nil?
754
+ generator = generator.max_value(max) unless max.nil?
755
+ generator = generator.allow_nan(allow_nan) unless allow_nan.nil?
756
+ generator = generator.allow_infinity(allow_infinity) unless allow_infinity.nil?
757
+ unless smallest_nonzero_magnitude.nil?
758
+ generator = generator.smallest_nonzero_magnitude(smallest_nonzero_magnitude)
759
+ end
760
+ generator
761
+ end
762
+
763
+ # Generate booleans, shrinking towards false.
764
+ def booleans
765
+ BooleanGenerator.new
766
+ end
767
+
768
+ # Generate booleans that are true with the given probability. Recorded as
769
+ # an ordinary boolean choice: the weight biases generation only, so a
770
+ # blob replays the same whatever probability produced it.
771
+ def weighted_booleans(probability)
772
+ unless probability.is_a?(Numeric) && probability.finite? && probability >= 0.0 && probability <= 1.0
773
+ raise ArgumentError, "weighted_booleans requires a probability in [0.0, 1.0]"
774
+ end
775
+
776
+ WeightedBooleanGenerator.new(probability)
777
+ end
778
+
779
+ # Generate byte strings, shrinking towards shorter and lower values.
780
+ def binary(min: 0, max: 16)
781
+ BytesGenerator.new(min, max)
782
+ end
783
+
784
+ # Always produce one value, recording no choice.
785
+ def just(value)
786
+ JustGenerator.new(value)
787
+ end
788
+
789
+ # Always produce nil.
790
+ def none
791
+ JustGenerator.new(nil)
792
+ end
793
+
794
+ # Pick one of a fixed sequence, shrinking towards the first element.
795
+ def sampled_from(values)
796
+ values = values.to_a
797
+ raise ArgumentError, "sampled_from requires at least one value" if values.empty?
798
+
799
+ SampledGenerator.new(values)
800
+ end
801
+
802
+ # Draw from one of several generators, shrinking towards the first.
803
+ def one_of(*choices)
804
+ choices = choices.flatten(1)
805
+ raise ArgumentError, "one_of requires at least one generator" if choices.empty?
806
+ return choices.first if choices.length == 1
807
+
808
+ OneOfGenerator.new(choices)
809
+ end
810
+
811
+ # The parts `|` should flatten a generator into: its own alternatives if it
812
+ # is already a one_of, otherwise just itself.
813
+ def one_of_parts(generator)
814
+ generator.is_a?(OneOfGenerator) ? generator.__send__(:choices) : [generator]
815
+ end
816
+
817
+ # Build an object out of drawn parts.
818
+ #
819
+ # Generators.builds(Point, x: Generators.integers, y: Generators.integers)
820
+ # Generators.builds(->(a, b) { a..b }, Generators.integers, Generators.integers)
821
+ #
822
+ # The first argument is anything that responds to `call` or `new`.
823
+ # Arguments that are not generators are passed through unchanged, so a
824
+ # constant does not need wrapping in `just`.
825
+ def builds(target, *parts, **keyword_parts)
826
+ builder =
827
+ if target.respond_to?(:call) then target
828
+ elsif target.respond_to?(:new) then target.method(:new)
829
+ else raise ArgumentError, "builds needs something callable or a class, got #{target.inspect}"
830
+ end
831
+
832
+ BuiltGenerator.new(
833
+ builder,
834
+ parts.map { |part| coerce(part) },
835
+ keyword_parts.transform_values { |part| coerce(part) }
836
+ )
837
+ end
838
+
839
+ # Accept a plain value anywhere a generator is expected.
840
+ def coerce(value)
841
+ value.respond_to?(:do_draw) ? value : just(value)
842
+ end
843
+
844
+ # Draw each generator in order and collect the values into an array.
845
+ def tuples(*parts)
846
+ TupleGenerator.new(parts)
847
+ end
848
+
849
+ # Build a generator from a block that draws as it goes.
850
+ #
851
+ # The block is handed the test case, so it can do anything a property can
852
+ # — draw, assume, note:
853
+ #
854
+ # ordered = Generators.composite do |t|
855
+ # low = t.draw(Generators.integers(max: 10))
856
+ # [low, t.draw(Generators.integers(min: low))]
857
+ # end
858
+ def composite(&build)
859
+ CompositeGenerator.new(build)
860
+ end
861
+
862
+ # Generate recursively-defined values such as trees or JSON documents.
863
+ def recursive(leaf, &branch)
864
+ raise ArgumentError, "recursive requires a branch block" unless branch
865
+
866
+ RecursiveGenerator.new(coerce(leaf), branch)
867
+ end
868
+
869
+ private_class_method :coerce, :one_of_parts
870
+ private_constant :MappedGenerator, :FlatMappedGenerator, :FilteredGenerator,
871
+ :JustGenerator, :BooleanGenerator, :WeightedBooleanGenerator,
872
+ :BytesGenerator, :SampledGenerator, :OneOfGenerator,
873
+ :TupleGenerator, :CompositeGenerator, :BuiltGenerator,
874
+ :IntegerGenerator, :TextGenerator, :FloatGenerator,
875
+ :ListGenerator, :UniqueListGenerator, :SetGenerator,
876
+ :PairGenerator, :HashGenerator, :RecursiveLeafBudget,
877
+ :RecursiveState, :RecursiveSubtreeGenerator, :RecursiveGenerator
878
+ end
879
+ end