prop_check 0.9.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,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "prop_check"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,36 @@
1
+ require "prop_check/version"
2
+ require 'prop_check/property'
3
+ require 'prop_check/generator'
4
+ require 'prop_check/generators'
5
+ require 'prop_check/helper'
6
+ ##
7
+ # Main module of the PropCheck library.
8
+ #
9
+ # You probably want to look at the documentation of
10
+ # PropCheck::Generator and PropCheck::Generators
11
+ # to find out more about how to use generators.
12
+ #
13
+ # Common usage is to call `extend PropCheck` in your (testing) modules.
14
+ #
15
+ # This will:
16
+ # 1. Add the local method `forall` which will call `PropCheck.forall`
17
+ # 2. `include PropCheck::Generators`.
18
+ #
19
+ module PropCheck
20
+ module Errors
21
+ class Error < StandardError; end
22
+ class UserError < Error; end
23
+ class GeneratorExhaustedError < UserError; end
24
+ class MaxShrinkStepsExceededError < UserError; end
25
+ end
26
+
27
+ extend self
28
+
29
+ ##
30
+ # Runs a property.
31
+ #
32
+ # See the README for more details.
33
+ def forall(*args, **kwargs, &block)
34
+ PropCheck::Property.forall(*args, **kwargs, &block)
35
+ end
36
+ end
@@ -0,0 +1,114 @@
1
+ module PropCheck
2
+ ##
3
+ # A `Generator` is a special kind of 'proc' that,
4
+ # given a size an random number generator state,
5
+ # will generate a (finite) LazyTree of output values:
6
+ #
7
+ # The root of this tree is the value to be used during testing,
8
+ # and the children are 'smaller' values related to the root,
9
+ # to be used during the shrinking phase.
10
+ class Generator
11
+ @@default_size = 10
12
+ @@default_rng = Random.new
13
+
14
+ ##
15
+ # Being a special kind of Proc, a Generator wraps a block.
16
+ def initialize(&block)
17
+ @block = block
18
+ end
19
+
20
+ ##
21
+ # Given a `size` (integer) and a random number generator state `rng`,
22
+ # generate a LazyTree.
23
+ def generate(size = @@default_size, rng = @@default_rng)
24
+ @block.call(size, rng)
25
+ end
26
+
27
+ ##
28
+ # Generates a value, and only return this value
29
+ # (drop information for shrinking)
30
+ #
31
+ # >> Generators.integer.call(1000, Random.new(42))
32
+ # => 126
33
+ def call(size = @@default_size, rng = @@default_rng)
34
+ generate(size, rng).root
35
+ end
36
+
37
+ ##
38
+ # Returns `num_of_samples` values from calling this Generator.
39
+ # This is mostly useful for debugging if a generator behaves as you intend it to.
40
+ def sample(num_of_samples = 10, size: @@default_size, rng: @@default_rng)
41
+ num_of_samples.times.map do
42
+ call(size, rng)
43
+ end
44
+ end
45
+
46
+ ##
47
+ # Creates a 'constant' generator that always returns the same value,
48
+ # regardless of `size` or `rng`.
49
+ #
50
+ # Keen readers may notice this as the Monadic 'pure'/'return' implementation for Generators.
51
+ #
52
+ # >> Generators.integer.bind { |a| Generators.integer.bind { |b| Generator.wrap([a , b]) } }.call(100, Random.new(42))
53
+ # => [2, 79]
54
+ def self.wrap(val)
55
+ Generator.new { |_size, _rng| LazyTree.wrap(val) }
56
+ end
57
+
58
+ ##
59
+ # Create a generator whose implementation depends on the output of another generator.
60
+ # this allows us to compose multiple generators.
61
+ #
62
+ # Keen readers may notice this as the Monadic 'bind' (sometimes known as '>>=') implementation for Generators.
63
+ #
64
+ # >> Generators.integer.bind { |a| Generators.integer.bind { |b| Generator.wrap([a , b]) } }.call(100, Random.new(42))
65
+ # => [2, 79]
66
+ def bind(&generator_proc)
67
+ # Generator.new do |size, rng|
68
+ # outer_result = generate(size, rng)
69
+ # outer_result.map do |outer_val|
70
+ # inner_generator = generator_proc.call(outer_val)
71
+ # inner_generator.generate(size, rng)
72
+ # end.flatten
73
+ # end
74
+ Generator.new do |size, rng|
75
+ outer_result = self.generate(size, rng)
76
+ outer_result.bind do |outer_val|
77
+ inner_generator = generator_proc.call(outer_val)
78
+ inner_generator.generate(size, rng)
79
+ end
80
+ end
81
+ end
82
+
83
+ ##
84
+ # Creates a new Generator that returns a value by running `proc` on the output of the current Generator.
85
+ #
86
+ # >> Generators.choose(32..128).map(&:chr).call(10, Random.new(42))
87
+ # => "S"
88
+ def map(&proc)
89
+ Generator.new do |size, rng|
90
+ result = self.generate(size, rng)
91
+ result.map(&proc)
92
+ end
93
+ end
94
+
95
+ ##
96
+ # Creates a new Generator that only produces a value when the block `condition` returns a truthy value.
97
+ def where(&condition)
98
+ self.map do |result|
99
+ if condition.call(*result)
100
+ result
101
+ else
102
+ :"_PropCheck.filter_me"
103
+ end
104
+ end
105
+ # self.map do |*result|
106
+ # if condition.call(*result)
107
+ # result
108
+ # else
109
+ # :'_PropCheck.filter_me'
110
+ # end
111
+ # end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,487 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ require 'prop_check/generator'
5
+ require 'prop_check/lazy_tree'
6
+ module PropCheck
7
+ ##
8
+ # Contains common generators.
9
+ # Use this module by including it in the class (e.g. in your test suite)
10
+ # where you want to use them.
11
+ module Generators
12
+ extend self
13
+ ##
14
+ # Always returns the same value, regardless of `size` or `rng` (random number generator state)
15
+ #
16
+ # No shrinking (only considers the current single value `val`).
17
+ #
18
+ # >> Generators.constant("pie").sample(5, size: 10, rng: Random.new(42))
19
+ # => ["pie", "pie", "pie", "pie", "pie"]
20
+ def constant(val)
21
+ Generator.wrap(val)
22
+ end
23
+
24
+ private def integer_shrink(val)
25
+ # 0 cannot shrink further; base case
26
+ return [] if val.zero?
27
+
28
+ # Numbers are shrunken by
29
+ # subtracting themselves, their half, quarter, eight, ... (rounded towards zero!)
30
+ # from themselves, until the number itself is reached.
31
+ # So: for 20 we have [0, 10, 15, 18, 19, 20]
32
+ halvings =
33
+ Helper
34
+ .scanl(val) { |x| (x / 2.0).truncate }
35
+ .take_while { |x| !x.zero? }
36
+ .map { |x| val - x }
37
+ .map { |x| LazyTree.new(x, integer_shrink(x)) }
38
+
39
+ # For negative numbers, we also attempt if the positive number has the same result.
40
+ if val.abs > val
41
+ [LazyTree.new(val.abs, halvings)].lazy
42
+ else
43
+ halvings
44
+ end
45
+ end
46
+
47
+ ##
48
+ # Returns a random integer in the given range (if a range is given)
49
+ # or between 0..num (if a single integer is given).
50
+ #
51
+ # Does not scale when `size` changes.
52
+ # This means `choose` is useful for e.g. picking an element out of multiple possibilities,
53
+ # but for other purposes you probably want to use `integer` et co.
54
+ #
55
+ # Shrinks to integers closer to zero.
56
+ #
57
+ # >> r = Random.new(42); Generators.choose(0..5).sample(size: 10, rng: r)
58
+ # => [3, 4, 2, 4, 4, 1, 2, 2, 2, 4]
59
+ # >> r = Random.new(42); Generators.choose(0..5).sample(size: 20000, rng: r)
60
+ # => [3, 4, 2, 4, 4, 1, 2, 2, 2, 4]
61
+ def choose(range)
62
+ Generator.new do |_size, rng|
63
+ val = rng.rand(range)
64
+ LazyTree.new(val, integer_shrink(val))
65
+ end
66
+ end
67
+
68
+ ##
69
+ # A random integer which scales with `size`.
70
+ # Integers start small (around 0)
71
+ # and become more extreme (both higher and lower, negative) when `size` increases.
72
+ #
73
+ #
74
+ # Shrinks to integers closer to zero.
75
+ #
76
+ # >> Generators.integer.call(2, Random.new(42))
77
+ # => 1
78
+ # >> Generators.integer.call(10000, Random.new(42))
79
+ # => 5795
80
+ # >> r = Random.new(42); Generators.integer.sample(size: 20000, rng: r)
81
+ # => [-4205, -19140, 18158, -8716, -13735, -3150, 17194, 1962, -3977, -18315]
82
+ def integer
83
+ Generator.new do |size, rng|
84
+ val = rng.rand(-size..size)
85
+ LazyTree.new(val, integer_shrink(val))
86
+ end
87
+ end
88
+
89
+ ##
90
+ # Only returns integers that are zero or larger.
91
+ # See `integer` for more information.
92
+ def nonnegative_integer
93
+ integer.map(&:abs)
94
+ end
95
+
96
+ ##
97
+ # Only returns integers that are larger than zero.
98
+ # See `integer` for more information.
99
+ def positive_integer
100
+ nonnegative_integer.map { |x| x + 1 }
101
+ end
102
+
103
+ ##
104
+ # Only returns integers that are zero or smaller.
105
+ # See `integer` for more information.
106
+ def nonpositive_integer
107
+ nonnegative_integer.map(&:-@)
108
+ end
109
+
110
+ ##
111
+ # Only returns integers that are smaller than zero.
112
+ # See `integer` for more information.
113
+ def negative_integer
114
+ positive_integer.map(&:-@)
115
+ end
116
+
117
+ private def fraction(num_a, num_b, num_c)
118
+ num_a.to_f + num_b.to_f / (num_c.to_f.abs + 1.0)
119
+ end
120
+
121
+ ##
122
+ # Generates floating-point numbers
123
+ # These start small (around 0)
124
+ # and become more extreme (large positive and large negative numbers)
125
+ #
126
+ # Will only generate 'reals',
127
+ # that is: no infinity, no NaN,
128
+ # no numbers testing the limits of floating-point arithmetic.
129
+ #
130
+ # Shrinks to numbers closer to zero.
131
+ #
132
+ # >> Generators.real_float().sample(10, size: 10, rng: Random.new(42))
133
+ # => [-2.2, -0.2727272727272727, 4.0, 1.25, -3.7272727272727275, -8.833333333333334, -8.090909090909092, 1.1428571428571428, 0.0, 8.0]
134
+ def real_float
135
+ tuple(integer, integer, integer).map do |a, b, c|
136
+ fraction(a, b, c)
137
+ end
138
+ end
139
+
140
+ @special_floats = [Float::NAN, Float::INFINITY, -Float::INFINITY, Float::MAX, Float::MIN, 0.0.next_float, 0.0.prev_float]
141
+ ##
142
+ # Generates floating-point numbers
143
+ # Will generate NaN, Infinity, -Infinity,
144
+ # as well as Float::EPSILON, Float::MAX, Float::MIN,
145
+ # 0.0.next_float, 0.0.prev_float,
146
+ # to test the handling of floating-point edge cases.
147
+ # Approx. 1/100 generated numbers is a special one.
148
+ #
149
+ # Shrinks to smaller, real floats.
150
+ # >> Generators.float().sample(10, size: 10, rng: Random.new(42))
151
+ # => [4.0, 9.555555555555555, 0.0, -Float::INFINITY, 5.5, -5.818181818181818, 1.1428571428571428, 0.0, 8.0, 7.857142857142858]
152
+ def float
153
+ frequency(99 => real_float, 1 => one_of(*@special_floats.map(&method(:constant))))
154
+ end
155
+
156
+ ##
157
+ # Picks one of the given generators in `choices` at random uniformly every time.
158
+ #
159
+ # Shrinks to values earlier in the list of `choices`.
160
+ #
161
+ # >> Generators.one_of(Generators.constant(true), Generators.constant(false)).sample(5, size: 10, rng: Random.new(42))
162
+ # => [true, false, true, true, true]
163
+ def one_of(*choices)
164
+ choose(choices.length).bind do |index|
165
+ choices[index]
166
+ end
167
+ end
168
+
169
+ ##
170
+ # Picks one of the choices given in `frequencies` at random every time.
171
+ # `frequencies` expects keys to be numbers
172
+ # (representing the relative frequency of this generator)
173
+ # and values to be generators.
174
+ #
175
+ # Side note: If you want to use the same frequency number for multiple generators,
176
+ # Ruby syntax requires you to send an array of two-element arrays instead of a hash.
177
+ #
178
+ # Shrinks to arbitrary elements (since hashes are not ordered).
179
+ #
180
+ # >> Generators.frequency(5 => Generators.integer, 1 => Generators.printable_ascii_char).sample(size: 10, rng: Random.new(42))
181
+ # => [4, -3, 10, 8, 0, -7, 10, 1, "E", 10]
182
+ def frequency(frequencies)
183
+ choices = frequencies.reduce([]) do |acc, elem|
184
+ freq, val = elem
185
+ acc + ([val] * freq)
186
+ end
187
+ one_of(*choices)
188
+ end
189
+
190
+ ##
191
+ # Generates an array containing always exactly one value from each of the passed generators,
192
+ # in the same order as specified:
193
+ #
194
+ # Shrinks element generators, one at a time (trying last one first).
195
+ #
196
+ # >> Generators.tuple(Generators.integer, Generators.real_float).call(10, Random.new(42))
197
+ # => [-4, 13.0]
198
+ def tuple(*generators)
199
+ Generator.new do |size, rng|
200
+ LazyTree.zip(generators.map do |generator|
201
+ generator.generate(size, rng)
202
+ end)
203
+ end
204
+ end
205
+
206
+ ##
207
+ # Given a `hash` where the values are generators,
208
+ # creates a generator that returns hashes
209
+ # with the same keys, and their corresponding values from their corresponding generators.
210
+ #
211
+ # Shrinks element generators.
212
+ #
213
+ # >> Generators.fixed_hash(a: Generators.integer(), b: Generators.real_float(), c: Generators.integer()).call(10, Random.new(42))
214
+ # => {:a=>-4, :b=>13.0, :c=>-3}
215
+ def fixed_hash(hash)
216
+ keypair_generators =
217
+ hash.map do |key, generator|
218
+ generator.map { |val| [key, val] }
219
+ end
220
+
221
+ tuple(*keypair_generators)
222
+ .map(&:to_h)
223
+ end
224
+
225
+ ##
226
+ # Generates an array of elements, where each of the elements
227
+ # is generated by `element_generator`.
228
+ #
229
+ # Shrinks to shorter arrays (with shrunken elements).
230
+ #
231
+ # >> Generators.array(Generators.positive_integer).sample(5, size: 10, rng: Random.new(42))
232
+ # => [[10, 5, 1, 4], [5, 9, 1, 1, 11, 8, 4, 9, 11, 10], [6], [11, 11, 2, 2, 7, 2, 6, 5, 5], [2, 10, 9, 7, 9, 5, 11, 3]]
233
+ def array(element_generator)
234
+ nonnegative_integer.bind do |generator|
235
+ generators = (0...generator).map do
236
+ element_generator.clone
237
+ end
238
+
239
+ tuple(*generators)
240
+ end
241
+ end
242
+
243
+ ##
244
+ # Generates a hash of key->values,
245
+ # where each of the keys is made using the `key_generator`
246
+ # and each of the values using the `value_generator`.
247
+ #
248
+ # Shrinks to hashes with less key/value pairs.
249
+ #
250
+ # >> Generators.hash(Generators.printable_ascii_string, Generators.positive_integer).sample(5, size: 3, rng: Random.new(42))
251
+ # => [{""=>2, "g\\4"=>4, "rv"=>2}, {"7"=>2}, {"!"=>1, "E!"=>1}, {"kY5"=>2}, {}]
252
+ def hash(key_generator, value_generator)
253
+ array(tuple(key_generator, value_generator))
254
+ .map(&:to_h)
255
+ end
256
+
257
+
258
+ @alphanumeric_chars = [('a'..'z'), ('A'..'Z'), ('0'..'9')].flat_map(&:to_a).freeze
259
+ ##
260
+ # Generates a single-character string
261
+ # containing one of a..z, A..Z, 0..9
262
+ #
263
+ # Shrinks towards lowercase 'a'.
264
+ #
265
+ # >> Generators.alphanumeric_char.sample(5, size: 10, rng: Random.new(42))
266
+ # => ["M", "Z", "C", "o", "Q"]
267
+ def alphanumeric_char
268
+ one_of(*@alphanumeric_chars.map(&method(:constant)))
269
+ end
270
+
271
+ ##
272
+ # Generates a string
273
+ # containing only the characters a..z, A..Z, 0..9
274
+ #
275
+ # Shrinks towards fewer characters, and towards lowercase 'a'.
276
+ #
277
+ # >> Generators.alphanumeric_string.sample(5, size: 10, rng: Random.new(42))
278
+ # => ["ZCoQ", "8uM", "wkkx0JNx", "v0bxRDLb", "Gl5v8RyWA6"]
279
+ def alphanumeric_string
280
+ array(alphanumeric_char).map(&:join)
281
+ end
282
+
283
+ @printable_ascii_chars = (' '..'~').to_a.freeze
284
+
285
+ ##
286
+ # Generates a single-character string
287
+ # from the printable ASCII character set.
288
+ #
289
+ # Shrinks towards ' '.
290
+ #
291
+ # >> Generators.printable_ascii_char.sample(size: 10, rng: Random.new(42))
292
+ # => ["S", "|", ".", "g", "\\", "4", "r", "v", "j", "j"]
293
+ def printable_ascii_char
294
+ one_of(*@printable_ascii_chars.map(&method(:constant)))
295
+ end
296
+
297
+ ##
298
+ # Generates strings
299
+ # from the printable ASCII character set.
300
+ #
301
+ # Shrinks towards fewer characters, and towards ' '.
302
+ #
303
+ # >> Generators.printable_ascii_string.sample(5, size: 10, rng: Random.new(42))
304
+ # => ["S|.g", "rvjjw7\"5T!", "=", "!_[4@", "Y"]
305
+ def printable_ascii_string
306
+ array(printable_ascii_char).map(&:join)
307
+ end
308
+
309
+ @ascii_chars = [
310
+ @printable_ascii_chars,
311
+ [
312
+ "\n",
313
+ "\r",
314
+ "\t",
315
+ "\v",
316
+ "\b",
317
+ "\f",
318
+ "\e",
319
+ "\d",
320
+ "\a"
321
+ ]
322
+ ].flat_map(&:to_a).freeze
323
+
324
+ ##
325
+ # Generates a single-character string
326
+ # from the printable ASCII character set.
327
+ #
328
+ # Shrinks towards '\n'.
329
+ #
330
+ # >> Generators.ascii_char.sample(size: 10, rng: Random.new(42))
331
+ # => ["d", "S", "|", ".", "g", "\\", "4", "d", "r", "v"]
332
+ def ascii_char
333
+ one_of(*@ascii_chars.map(&method(:constant)))
334
+ end
335
+
336
+ ##
337
+ # Generates strings
338
+ # from the printable ASCII character set.
339
+ #
340
+ # Shrinks towards fewer characters, and towards '\n'.
341
+ #
342
+ # >> Generators.ascii_string.sample(5, size: 10, rng: Random.new(42))
343
+ # => ["S|.g", "drvjjw\b\a7\"", "!w=E!_[4@k", "x", "zZI{[o"]
344
+ def ascii_string
345
+ array(ascii_char).map(&:join)
346
+ end
347
+
348
+ @printable_chars = [
349
+ @ascii_chars,
350
+ "\u{A0}".."\u{D7FF}",
351
+ "\u{E000}".."\u{FFFD}",
352
+ "\u{10000}".."\u{10FFFF}"
353
+ ].flat_map(&:to_a).freeze
354
+
355
+ ##
356
+ # Generates a single-character printable string
357
+ # both ASCII characters and Unicode.
358
+ #
359
+ # Shrinks towards characters with lower codepoints, e.g. ASCII
360
+ #
361
+ # >> Generators.printable_char.sample(size: 10, rng: Random.new(42))
362
+ # => ["吏", "", "", "", "", "", "", "", "", "Ȍ"]
363
+ def printable_char
364
+ one_of(*@printable_chars.map(&method(:constant)))
365
+ end
366
+
367
+ ##
368
+ # Generates a printable string
369
+ # both ASCII characters and Unicode.
370
+ #
371
+ # Shrinks towards shorter strings, and towards characters with lower codepoints, e.g. ASCII
372
+ #
373
+ # >> Generators.printable_string.sample(5, size: 10, rng: Random.new(42))
374
+ # => ["", "Ȍ", "𐁂", "Ȕ", ""]
375
+ def printable_string
376
+ array(printable_char).map(&:join)
377
+ end
378
+
379
+ ##
380
+ # Generates a single unicode character
381
+ # (both printable and non-printable).
382
+ #
383
+ # Shrinks towards characters with lower codepoints, e.g. ASCII
384
+ #
385
+ # >> Generators.printable_char.sample(size: 10, rng: Random.new(42))
386
+ # => ["吏", "", "", "", "", "", "", "", "", "Ȍ"]
387
+ def char
388
+ choose(0..0x10FFFF).map do |num|
389
+ [num].pack('U')
390
+ end
391
+ end
392
+
393
+ ##
394
+ # Generates a string of unicode characters
395
+ # (which might contain both printable and non-printable characters).
396
+ #
397
+ # Shrinks towards characters with lower codepoints, e.g. ASCII
398
+ #
399
+ # >> Generators.string.sample(5, size: 10, rng: Random.new(42))
400
+ # => ["\u{A3DB3}𠍜\u{3F46A}\u{1AEBC}", "􍙦𡡹󴇒\u{DED74}𪱣\u{43E97}ꂂ\u{50695}􏴴\u{C0301}", "\u{4FD9D}", "\u{C14BF}\u{193BB}𭇋󱣼\u{76B58}", "𦐺\u{9FDDB}\u{80ABB}\u{9E3CF}𐂽\u{14AAE}"]
401
+ def string
402
+ array(char).map(&:join)
403
+ end
404
+
405
+ ##
406
+ # Generates either `true` or `false`
407
+ #
408
+ # Shrinks towards `false`
409
+ #
410
+ # >> Generators.boolean.sample(5, size: 10, rng: Random.new(42))
411
+ # => [false, true, false, false, false]
412
+ def boolean
413
+ one_of(constant(false), constant(true))
414
+ end
415
+
416
+ ##
417
+ # Generates always `nil`.
418
+ #
419
+ # Does not shrink.
420
+ #
421
+ # >> Generators.nil.sample(5, size: 10, rng: Random.new(42))
422
+ # => [nil, nil, nil, nil, nil]
423
+ def nil
424
+ constant(nil)
425
+ end
426
+
427
+ ##
428
+ # Generates `nil` or `false`.
429
+ #
430
+ # Shrinks towards `nil`.
431
+ #
432
+ # >> Generators.falsey.sample(5, size: 10, rng: Random.new(42))
433
+ # => [nil, false, nil, nil, nil]
434
+ def falsey
435
+ one_of(constant(nil), constant(false))
436
+ end
437
+
438
+ ##
439
+ # Generates symbols consisting of lowercase letters and potentially underscores.
440
+ #
441
+ # Shrinks towards shorter symbols and the letter 'a'.
442
+ #
443
+ # >> Generators.simple_symbol.sample(5, size: 10, rng: Random.new(42))
444
+ # => [:tokh, :gzswkkxudh, :vubxlfbu, :lzvlyq__jp, :oslw]
445
+ def simple_symbol
446
+ alphabet = ('a'..'z').to_a
447
+ alphabet << '_'
448
+ array(one_of(*alphabet.map(&method(:constant))))
449
+ .map(&:join)
450
+ .map(&:to_sym)
451
+ end
452
+
453
+ ##
454
+ # Generates common terms that are not `nil` or `false`.
455
+ #
456
+ # Shrinks towards simpler terms, like `true`, an empty array, a single character or an integer.
457
+ #
458
+ # >> Generators.truthy.sample(5, size: 10, rng: Random.new(42))
459
+ # => [[4, 0, -3, 10, -4, 8, 0, 0, 10], -3, [5.5, -5.818181818181818, 1.1428571428571428, 0.0, 8.0, 7.857142857142858, -0.6666666666666665, 5.25], [], ["\u{9E553}\u{DD56E}\u{A5BBB}\u{8BDAB}\u{3E9FC}\u{C4307}\u{DAFAE}\u{1A022}\u{938CD}\u{70631}", "\u{C4C01}\u{32D85}\u{425DC}"]]
460
+ def truthy
461
+ one_of(constant(true),
462
+ constant([]),
463
+ char,
464
+ integer,
465
+ float,
466
+ string,
467
+ array(integer),
468
+ array(float),
469
+ array(char),
470
+ array(string),
471
+ hash(simple_symbol, integer),
472
+ hash(string, integer),
473
+ hash(string, string)
474
+ )
475
+ end
476
+
477
+ ##
478
+ # Generates whatever `other_generator` generates
479
+ # but sometimes instead `nil`.`
480
+ #
481
+ # >> Generators.nillable(Generators.integer).sample(20, size: 10, rng: Random.new(42))
482
+ # => [9, 10, 8, 0, 10, -3, -8, 10, 1, -9, -10, nil, 1, 6, nil, 1, 9, -8, 8, 10]
483
+ def nillable(other_generator)
484
+ frequency(9 => other_generator, 1 => constant(nil))
485
+ end
486
+ end
487
+ end