prop_check 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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