Module: PropCheck::Generators

Defined in:
lib/prop_check/generators.rb

Overview

Contains common generators. Use this module by including it in the class (e.g. in your test suite) where you want to use them.

Constant Summary collapse

@@special_floats =
[Float::NAN, Float::INFINITY, -Float::INFINITY, Float::MAX, -Float::MAX, Float::MIN, -Float::MIN, Float::EPSILON, -Float::EPSILON, 0.0.next_float, 0.0.prev_float]
@@alphanumeric_chars =
[('a'..'z'), ('A'..'Z'), ('0'..'9')].flat_map(&:to_a).freeze
@@printable_ascii_chars =
(' '..'~').to_a.freeze
@@ascii_chars =
[ @@printable_ascii_chars, [ "\n", "\r", "\t", "\v", "\b", "\f", "\e", "\d", "\a" ] ].flat_map(&:to_a).freeze
@@printable_chars =
[ @@ascii_chars, "\u{A0}".."\u{D7FF}", "\u{E000}".."\u{FFFD}", "\u{10000}".."\u{10FFFF}" ].flat_map(&:to_a).freeze

Class Method Summary collapse

Class Method Details

.alphanumeric_charObject

Generates a single-character string containing one of a..z, A..Z, 0..9

Shrinks towards lowercase ‘a’.

>> Generators.alphanumeric_char.sample(5, size: 10, rng: Random.new(42)) => ["M", "Z", "C", "o", "Q"] 
 497 498 499
# File 'lib/prop_check/generators.rb', line 497 def alphanumeric_char one_of(*@@alphanumeric_chars.map(&method(:constant))) end

.alphanumeric_string(**kwargs) ⇒ Object

Generates a string containing only the characters a..z, A..Z, 0..9

Shrinks towards fewer characters, and towards lowercase ‘a’.

>> Generators.alphanumeric_string.sample(5, size: 10, rng: Random.new(42)) => ["ZCoQ", "8uM", "wkkx0JNx", "v0bxRDLb", "Gl5v8RyWA6"] 

Accepts the same options as ‘array`

 511 512 513
# File 'lib/prop_check/generators.rb', line 511 def alphanumeric_string(**kwargs) array(alphanumeric_char, **kwargs).map(&:join) end

.array(element_generator, min: 0, max: nil, empty: true, uniq: false) ⇒ Object

Generates an array of elements, where each of the elements is generated by ‘element_generator`.

Shrinks to shorter arrays (with shrunken elements). Accepted keyword arguments:

‘empty:` When false, behaves the same as `min: 1` `min:` Ensures at least this many elements are generated. (default: 0) `max:` Ensures at most this many elements are generated. When nil, an arbitrary count is used instead. (default: nil) `uniq:` When `true`, ensures that all elements in the array are unique.

 When given a proc, uses the result of this proc to check for uniqueness. (matching the behaviour of `Array#uniq`) If it is not possible to generate another unique value after the configured `max_consecutive_attempts` an `PropCheck::Errors::GeneratorExhaustedError` will be raised. (default: `false`) >> Generators.array(Generators.positive_integer).sample(5, size: 1, rng: Random.new(42)) => [[2], [2], [2], [1], [2]] >> Generators.array(Generators.positive_integer).sample(5, size: 10, rng: Random.new(42)) => [[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]] >> Generators.array(Generators.positive_integer, empty: true).sample(5, size: 1, rng: Random.new(1)) => [[], [2], [], [], [2]] >> Generators.array(Generators.positive_integer, empty: false).sample(5, size: 1, rng: Random.new(1)) => [[2], [1], [2], [1], [1]] >> Generators.array(Generators.boolean, uniq: true).sample(5, rng: Random.new(1)) => [[true, false], [false, true], [true, false], [false, true], [false, true]] 
 376 377 378 379 380 381 382 383 384 385
# File 'lib/prop_check/generators.rb', line 376 def array(element_generator, min: 0, max: nil, empty: true, uniq: false) min = 1 if min.zero? && !empty uniq = proc { |x| x } if uniq == true if max.nil? nonnegative_integer.bind { |count| make_array(element_generator, min, count, uniq) } else choose(min..max).bind { |count| make_array(element_generator, min, count, uniq) } end end

.ascii_charObject

Generates a single-character string from the printable ASCII character set.

Shrinks towards ‘n’.

>> Generators.ascii_char.sample(size: 10, rng: Random.new(42)) => ["d", "S", "|", ".", "g", "\\", "4", "d", "r", "v"] 
 566 567 568
# File 'lib/prop_check/generators.rb', line 566 def ascii_char one_of(*@@ascii_chars.map(&method(:constant))) end

.ascii_string(**kwargs) ⇒ Object

Generates strings from the printable ASCII character set.

Shrinks towards fewer characters, and towards ‘n’.

>> Generators.ascii_string.sample(5, size: 10, rng: Random.new(42)) => ["S|.g", "drvjjw\b\a7\"", "!w=E!_[4@k", "x", "zZI{[o"] 

Accepts the same options as ‘array`

 580 581 582
# File 'lib/prop_check/generators.rb', line 580 def ascii_string(**kwargs) array(ascii_char, **kwargs).map(&:join) end

.booleanObject

Generates either ‘true` or `false`

Shrinks towards ‘false`

>> Generators.boolean.sample(5, size: 10, rng: Random.new(42)) => [false, true, false, false, false] 
 652 653 654
# File 'lib/prop_check/generators.rb', line 652 def boolean one_of(constant(false), constant(true)) end

.charObject

Generates a single unicode character (both printable and non-printable).

Shrinks towards characters with lower codepoints, e.g. ASCII

>> Generators.printable_char.sample(size: 10, rng: Random.new(42)) => ["吏", "", "", "", "", "", "", "", "", "Ȍ"] 
 625 626 627 628 629
# File 'lib/prop_check/generators.rb', line 625 def char choose(0..0x10FFFF).map do |num| [num].pack('U') end end

.choose(range) ⇒ Object

Returns a random integer in the given range (if a range is given) or between 0..num (if a single integer is given).

Does not scale when ‘size` changes. This means `choose` is useful for e.g. picking an element out of multiple possibilities, but for other purposes you probably want to use `integer` et co.

Shrinks to integers closer to zero.

>> r = Random.new(42); Generators.choose(0..5).sample(size: 10, rng: r) => [3, 4, 2, 4, 4, 1, 2, 2, 2, 4] >> r = Random.new(42); Generators.choose(0..5).sample(size: 20000, rng: r) => [3, 4, 2, 4, 4, 1, 2, 2, 2, 4] 
 62 63 64 65 66 67
# File 'lib/prop_check/generators.rb', line 62 def choose(range) Generator.new do |rng:, **| val = rng.rand(range) LazyTree.new(val, integer_shrink(val)) end end

.constant(val) ⇒ Object

Always returns the same value, regardless of ‘size` or `rng` (random number generator state)

No shrinking (only considers the current single value ‘val`).

>> Generators.constant("pie").sample(5, size: 10, rng: Random.new(42)) => ["pie", "pie", "pie", "pie", "pie"] 
 21 22 23
# File 'lib/prop_check/generators.rb', line 21 def constant(val) Generator.wrap(val) end

.date(epoch: nil) ⇒ Object

Generates ‘Date` objects. DateTimes start around the given `epoch:` and deviate more when `size` increases. when no epoch is set, `PropCheck::Property::Configuration.default_epoch` is used, which defaults to `DateTime.now.to_date`.

>> Generators.date(epoch: Date.new(2022, 01, 01)).sample(2, rng: Random.new(42)) => [Date.new(2021, 12, 28), Date.new(2022, 01, 10)] 
 733 734 735
# File 'lib/prop_check/generators.rb', line 733 def date(epoch: nil) date_from_offset(integer, epoch: epoch) end

.date_time(epoch: nil) ⇒ Object

alias for ‘#datetime`, for backwards compatibility. Prefer using `datetime`!

 780 781 782
# File 'lib/prop_check/generators.rb', line 780 def date_time(epoch: nil) datetime(epoch: epoch) end

.datetime(epoch: nil) ⇒ Object

Generates ‘DateTime` objects. DateTimes start around the given `epoch:` and deviate more when `size` increases. when no epoch is set, `PropCheck::Property::Configuration.default_epoch` is used, which defaults to `DateTime.now`.

>> PropCheck::Generators.datetime(epoch: Date.new(2022, 11, 20)).sample(2, rng: Random.new(42), config: PropCheck::Property::Configuration.new) => [DateTime.parse("2022-11-17 07:11:59.999983907 +0000"), DateTime.parse("2022-11-19 05:27:16.363618076 +0000")] 
 773 774 775
# File 'lib/prop_check/generators.rb', line 773 def datetime(epoch: nil) datetime_from_offset(real_float, epoch: epoch) end

.falseyObject

Generates ‘nil` or `false`.

Shrinks towards ‘nil`.

>> Generators.falsey.sample(5, size: 10, rng: Random.new(42)) => [nil, false, nil, nil, nil] 
 674 675 676
# File 'lib/prop_check/generators.rb', line 674 def falsey one_of(constant(nil), constant(false)) end

.fixed_hash(hash) ⇒ Object

Given a ‘hash` where the values are generators, creates a generator that returns hashes with the same keys, and their corresponding values from their corresponding generators.

Shrinks element generators.

>> Generators.fixed_hash(a: Generators.integer(), b: Generators.real_float(), c: Generators.integer()).call(size: 10, rng: Random.new(42)) => {:a=>-4, :b=>13.0, :c=>-3} 
 335 336 337 338 339 340 341 342 343
# File 'lib/prop_check/generators.rb', line 335 def fixed_hash(hash) keypair_generators = hash.map do |key, generator| generator.map { |val| [key, val] } end tuple(*keypair_generators) .map(&:to_h) end

.floatObject

Generates floating-point numbers Will generate NaN, Infinity, -Infinity, as well as Float::EPSILON, Float::MAX, Float::MIN, 0.0.next_float, 0.0.prev_float, to test the handling of floating-point edge cases. Approx. 1/50 generated numbers is a special one.

Shrinks to smaller, real floats.

>> Generators.float().sample(10, size: 10, rng: Random.new(42)) >> Generators.float().sample(10, size: 10, rng: Random.new(4)) => [-8.0, 2.0, 2.7142857142857144, -4.0, -10.2, -6.666666666666667, -Float::INFINITY, -10.2, 2.1818181818181817, -6.2] 
 232 233 234
# File 'lib/prop_check/generators.rb', line 232 def float frequency(49 => real_float, 1 => one_of(*@@special_floats.map(&method(:constant)))) end

.frequency(frequencies) ⇒ Object

Picks one of the choices given in ‘frequencies` at random every time. `frequencies` expects keys to be numbers (representing the relative frequency of this generator) and values to be generators.

Side note: If you want to use the same frequency number for multiple generators, Ruby syntax requires you to send an array of two-element arrays instead of a hash.

Shrinks to arbitrary elements (since hashes are not ordered).

>> Generators.frequency(5 => Generators.integer, 1 => Generators.printable_ascii_char).sample(size: 10, rng: Random.new(42)) => [4, -3, 10, 8, 0, -7, 10, 1, "E", 10] 
 302 303 304 305 306 307 308
# File 'lib/prop_check/generators.rb', line 302 def frequency(frequencies) choices = frequencies.reduce([]) do |acc, elem| freq, val = elem acc + ([val] * freq) end one_of(*choices) end

.future_date(epoch: Date.today) ⇒ Object

variant of #date that only generates dates in the future (relative to ‘:epoch`).

>> Generators.future_date(epoch: Date.new(2022, 01, 01)).sample(2, rng: Random.new(42)) => [Date.new(2022, 01, 06), Date.new(2022, 01, 11)] 
 742 743 744
# File 'lib/prop_check/generators.rb', line 742 def future_date(epoch: Date.today) date_from_offset(positive_integer, epoch: epoch) end

.future_datetime(epoch: nil) ⇒ Object

Variant of ‘#datetime` that only generates datetimes in the future (relative to `:epoch`).

>> PropCheck::Generators.future_datetime(epoch: Date.new(2022, 11, 20)).sample(2, rng: Random.new(42), config: PropCheck::Property::Configuration.new).map(&:inspect) => ["#<DateTime: 2022-11-21T16:48:00+00:00 ((2459905j,60480s,16093n),+0s,2299161j)>", "#<DateTime: 2022-11-19T18:32:43+00:00 ((2459903j,66763s,636381924n),+0s,2299161j)>"] 
 789 790 791
# File 'lib/prop_check/generators.rb', line 789 def future_datetime(epoch: nil) datetime_from_offset(real_positive_float, epoch: epoch) end

.future_time(epoch: nil) ⇒ Object

Variant of ‘#time` that only generates datetimes in the future (relative to `:epoch`).

 815 816 817
# File 'lib/prop_check/generators.rb', line 815 def future_time(epoch: nil) future_datetime(epoch: epoch).map(&:to_time) end

.hash(*args, **kwargs) ⇒ Object

Generates a hash of key->values, where each of the keys is made using the ‘key_generator` and each of the values using the `value_generator`.

Shrinks to hashes with less key/value pairs.

>> Generators.hash(Generators.printable_ascii_string, Generators.positive_integer).sample(5, size: 3, rng: Random.new(42)) => [{""=>2, "g\\4"=>4, "rv"=>2}, {"7"=>2}, {"!"=>1, "E!"=>1}, {"kY5"=>2}, {}] 
 471 472 473 474 475 476 477
# File 'lib/prop_check/generators.rb', line 471 def hash(*args, **kwargs) if args.length == 2 hash_of(*args, **kwargs) else super end end

.hash_of(key_generator, value_generator, **kwargs) ⇒ Object

Alias for ‘#hash` that does not conflict with a possibly overridden `Object#hash`.

 483 484 485 486
# File 'lib/prop_check/generators.rb', line 483 def hash_of(key_generator, value_generator, **kwargs) array(tuple(key_generator, value_generator), **kwargs) .map(&:to_h) end

.instance(klass, *args, **kwargs) ⇒ Object

Generates an instance of ‘klass` using `args` and/or `kwargs` as generators for the arguments that are passed to `klass.new`

## Example:

Given a class like this:

 class User attr_accessor :name, :age def initialize(name: , age: ) @name = name @age = age end def inspect "<User name: #{@name.inspect}, age: #{@age.inspect}>" end end >> user_gen = Generators.instance(User, name: Generators.printable_ascii_string, age: Generators.nonnegative_integer) >> user_gen.sample(3, rng: Random.new(42)).inspect => "[<User name: \"S|.g\", age: 10>, <User name: \"rvjj\", age: 10>, <User name: \"7\\\"5T!w=\", age: 5>]" 
 861 862 863 864 865 866 867 868 869 870 871 872 873
# File 'lib/prop_check/generators.rb', line 861 def instance(klass, *args, **kwargs) tuple(*args).bind do |vals| fixed_hash(**kwargs).map do |kwvals| if kwvals == {} klass.new(*vals) elsif vals == [] klass.new(**kwvals) else klass.new(*vals, **kwvals) end end end end

.integerObject

A random integer which scales with ‘size`. Integers start small (around 0) and become more extreme (both higher and lower, negative) when `size` increases.

Shrinks to integers closer to zero.

>> Generators.integer.call(size: 2, rng: Random.new(42)) => 1 >> Generators.integer.call(size: 10000, rng: Random.new(42)) => 5795 >> r = Random.new(42); Generators.integer.sample(size: 20000, rng: r) => [-4205, -19140, 18158, -8716, -13735, -3150, 17194, 1962, -3977, -18315] 
 83 84 85 86 87 88 89 90
# File 'lib/prop_check/generators.rb', line 83 def integer Generator.new do |size:, rng:, **| ensure_proper_size!(size) val = rng.rand(-size..size) LazyTree.new(val, integer_shrink(val)) end end

.negative_floatObject

Generates negative floating point numbers Will generate special floats (except NaN) from time to time. c.f. #float

 272 273 274
# File 'lib/prop_check/generators.rb', line 272 def negative_float positive_float.map(&:-@).where { |val| val != 0.0 } end

.negative_integerObject

Only returns integers that are smaller than zero. See ‘integer` for more information.

 122 123 124
# File 'lib/prop_check/generators.rb', line 122 def negative_integer positive_integer.map(&:-@) end

.nilObject

Generates always ‘nil`.

Does not shrink.

>> Generators.nil.sample(5, size: 10, rng: Random.new(42)) => [nil, nil, nil, nil, nil] 
 663 664 665
# File 'lib/prop_check/generators.rb', line 663 def nil constant(nil) end

.nillable(other_generator) ⇒ Object

Generates whatever ‘other_generator` generates but sometimes instead `nil`.`

>> Generators.nillable(Generators.integer).sample(20, size: 10, rng: Random.new(42)) => [9, 10, 8, 0, 10, -3, -8, 10, 1, -9, -10, nil, 1, 6, nil, 1, 9, -8, 8, 10] 
 722 723 724
# File 'lib/prop_check/generators.rb', line 722 def nillable(other_generator) frequency(9 => other_generator, 1 => constant(nil)) end

.nonnegative_floatObject

Generates nonnegative floating point numbers Will generate special floats (except NaN) from time to time. c.f. #float

 248 249 250
# File 'lib/prop_check/generators.rb', line 248 def nonnegative_float float.map(&:abs).where { |val| val != Float::NAN } end

.nonnegative_integerObject

Only returns integers that are zero or larger. See ‘integer` for more information.

 101 102 103
# File 'lib/prop_check/generators.rb', line 101 def nonnegative_integer integer.map(&:abs) end

.nonpositive_floatObject

Generates nonpositive floating point numbers Will generate special floats (except NaN) from time to time. c.f. #float

 256 257 258
# File 'lib/prop_check/generators.rb', line 256 def nonpositive_float nonnegative_float.map(&:-@) end

.nonpositive_integerObject

Only returns integers that are zero or smaller. See ‘integer` for more information.

 115 116 117
# File 'lib/prop_check/generators.rb', line 115 def nonpositive_integer nonnegative_integer.map(&:-@) end

.nonzero_floatObject

Generates any nonzero floating-point number. Will generate special floats (except NaN) from time to time. c.f. #float

 240 241 242
# File 'lib/prop_check/generators.rb', line 240 def nonzero_float float.where { |val| val != 0.0 && val } end

.one_of(*choices) ⇒ Object

Picks one of the given generators in ‘choices` at random uniformly every time.

Shrinks to values earlier in the list of ‘choices`.

>> Generators.one_of(Generators.constant(true), Generators.constant(false)).sample(5, size: 10, rng: Random.new(42)) => [true, false, true, true, true] 
 283 284 285 286 287
# File 'lib/prop_check/generators.rb', line 283 def one_of(*choices) choose(choices.length).bind do |index| choices[index] end end

.past_date(epoch: Date.today) ⇒ Object

variant of #date that only generates dates in the past (relative to ‘:epoch`).

>> Generators.past_date(epoch: Date.new(2022, 01, 01)).sample(2, rng: Random.new(42)) => [Date.new(2021, 12, 27), Date.new(2021, 12, 22)] 
 751 752 753
# File 'lib/prop_check/generators.rb', line 751 def past_date(epoch: Date.today) date_from_offset(negative_integer, epoch: epoch) end

.past_datetime(epoch: nil) ⇒ Object

Variant of ‘#datetime` that only generates datetimes in the past (relative to `:epoch`).

>> PropCheck::Generators.past_datetime(epoch: Date.new(2022, 11, 20)).sample(2, rng: Random.new(42), config: PropCheck::Property::Configuration.new) => [DateTime.parse("2022-11-17 07:11:59.999983907 +0000"), DateTime.parse("2022-11-19 05:27:16.363618076 +0000")] 
 798 799 800
# File 'lib/prop_check/generators.rb', line 798 def past_datetime(epoch: nil) datetime_from_offset(real_negative_float, epoch: epoch) end

.past_time(epoch: nil) ⇒ Object

Variant of ‘#time` that only generates datetimes in the past (relative to `:epoch`).

 821 822 823
# File 'lib/prop_check/generators.rb', line 821 def past_time(epoch: nil) past_datetime(epoch: epoch).map(&:to_time) end

.positive_floatObject

Generates positive floating point numbers Will generate special floats (except NaN) from time to time. c.f. #float

 264 265 266
# File 'lib/prop_check/generators.rb', line 264 def positive_float nonnegative_float.where { |val| val != 0.0 && val } end

.positive_integerObject

Only returns integers that are larger than zero. See ‘integer` for more information.

 108 109 110
# File 'lib/prop_check/generators.rb', line 108 def positive_integer nonnegative_integer.map { |x| x + 1 } end

.printable_ascii_charObject

Generates a single-character string from the printable ASCII character set.

Shrinks towards ‘ ’.

>> Generators.printable_ascii_char.sample(size: 10, rng: Random.new(42)) => ["S", "|", ".", "g", "\\", "4", "r", "v", "j", "j"] 
 525 526 527
# File 'lib/prop_check/generators.rb', line 525 def printable_ascii_char one_of(*@@printable_ascii_chars.map(&method(:constant))) end

.printable_ascii_string(**kwargs) ⇒ Object

Generates strings from the printable ASCII character set.

Shrinks towards fewer characters, and towards ‘ ’.

>> Generators.printable_ascii_string.sample(5, size: 10, rng: Random.new(42)) => ["S|.g", "rvjjw7\"5T!", "=", "!_[4@", "Y"] 

Accepts the same options as ‘array`

 539 540 541
# File 'lib/prop_check/generators.rb', line 539 def printable_ascii_string(**kwargs) array(printable_ascii_char, **kwargs).map(&:join) end

.printable_charObject

Generates a single-character printable string both ASCII characters and Unicode.

Shrinks towards characters with lower codepoints, e.g. ASCII

>> Generators.printable_char.sample(size: 10, rng: Random.new(42)) => ["吏", "", "", "", "", "", "", "", "", "Ȍ"] 
 599 600 601
# File 'lib/prop_check/generators.rb', line 599 def printable_char one_of(*@@printable_chars.map(&method(:constant))) end

.printable_string(**kwargs) ⇒ Object

Generates a printable string both ASCII characters and Unicode.

Shrinks towards shorter strings, and towards characters with lower codepoints, e.g. ASCII

>> Generators.printable_string.sample(5, size: 10, rng: Random.new(42)) => ["", "Ȍ", "𐁂", "Ȕ", ""] 

Accepts the same options as ‘array`

 613 614 615
# File 'lib/prop_check/generators.rb', line 613 def printable_string(**kwargs) array(printable_char, **kwargs).map(&:join) end

.real_floatObject

Generates floating-point numbers These start small (around 0) and become more extreme (large positive and large negative numbers)

Will only generate ‘reals’, that is: no infinity, no NaN, no numbers testing the limits of floating-point arithmetic.

Shrinks towards zero. The shrinking strategy also moves towards ‘simpler’ floats (like ‘1.0`) from ’complicated’ floats (like ‘3.76543`).

>> Generators.real_float().sample(10, size: 10, rng: Random.new(42)) => [-2.2, -0.2727272727272727, 4.0, 1.25, -3.7272727272727275, -8.833333333333334, -8.090909090909092, 1.1428571428571428, 0.0, 8.0] 
 144 145 146 147 148
# File 'lib/prop_check/generators.rb', line 144 def real_float tuple(integer, integer, integer).map do |a, b, c| fraction(a, b, c) end end

.real_negative_floatObject

Generates real floating-point numbers which are always negative Shrinks towards -Float::MIN

Does not consider denormals. c.f. #real_float

>> Generators.real_negative_float().sample(10, size: 10, rng: Random.new(42)) => [-2.2, -0.2727272727272727, -4.0, -1.25, -3.7272727272727275, -8.833333333333334, -8.090909090909092, -1.1428571428571428, -2.2250738585072014e-308, -8.0] 
 205 206 207
# File 'lib/prop_check/generators.rb', line 205 def real_negative_float real_positive_float.map(&:-@) end

.real_nonnegative_floatObject

Generates real floating-point numbers which are never negative. Shrinks towards 0 c.f. #real_float

>> Generators.real_nonnegative_float().sample(10, size: 10, rng: Random.new(43)) => [7.25, 7.125, 7.636363636363637, 3.0, 8.444444444444445, 0.0, 6.857142857142857, 2.4545454545454546, 3.0, 7.454545454545455] 
 168 169 170
# File 'lib/prop_check/generators.rb', line 168 def real_nonnegative_float real_float.map(&:abs) end

.real_nonpositive_floatObject

Generates real floating-point numbers which are never positive. Shrinks towards 0 c.f. #real_float

>> Generators.real_nonpositive_float().sample(10, size: 10, rng: Random.new(44)) => [-9.125, -2.3636363636363638, -8.833333333333334, -1.75, -8.4, -2.4, -3.5714285714285716, -1.0, -6.111111111111111, -4.0] 
 179 180 181
# File 'lib/prop_check/generators.rb', line 179 def real_nonpositive_float real_nonnegative_float.map(&:-@) end

.real_nonzero_floatObject

Generates any real floating-point numbers, but will never generate zero. c.f. #real_float

>> Generators.real_nonzero_float().sample(10, size: 10, rng: Random.new(43)) => [-7.25, 7.125, -7.636363636363637, -3.0, -8.444444444444445, -6.857142857142857, 2.4545454545454546, 3.0, -7.454545454545455, -6.25] 
 157 158 159
# File 'lib/prop_check/generators.rb', line 157 def real_nonzero_float real_float.where { |val| val != 0.0 } end

.real_positive_floatObject

Generates real floating-point numbers which are always positive Shrinks towards Float::MIN

Does not consider denormals. c.f. #real_float

>> Generators.real_positive_float().sample(10, size: 10, rng: Random.new(42)) => [2.2, 0.2727272727272727, 4.0, 1.25, 3.7272727272727275, 8.833333333333334, 8.090909090909092, 1.1428571428571428, 2.2250738585072014e-308, 8.0] 
 192 193 194
# File 'lib/prop_check/generators.rb', line 192 def real_positive_float real_nonnegative_float.map { |val| val + Float::MIN } end

.set(element_generator, min: 0, max: nil, empty: true) ⇒ Object

Generates a set of elements, where each of the elements is generated by ‘element_generator`.

Shrinks to smaller sets (with shrunken elements). Accepted keyword arguments:

‘empty:` When false, behaves the same as `min: 1` `min:` Ensures at least this many elements are generated. (default: 0) `max:` Ensures at most this many elements are generated. When nil, an arbitrary count is used instead. (default: nil)

In the set, elements are always unique. If it is not possible to generate another unique value after the configured ‘max_consecutive_attempts` a `PropCheck::Errors::GeneratorExhaustedError` will be raised.

>> Generators.set(Generators.positive_integer).sample(5, size: 4, rng: Random.new(42)) => [Set[2, 4], Set[], Set[3, 4], Set[], Set[4]] 
 458 459 460
# File 'lib/prop_check/generators.rb', line 458 def set(element_generator, min: 0, max: nil, empty: true) array(element_generator, min: min, max: max, empty: empty, uniq: true).map(&:to_set) end

.simple_symbolObject

Generates symbols consisting of lowercase letters and potentially underscores.

Shrinks towards shorter symbols and the letter ‘a’.

>> Generators.simple_symbol.sample(5, size: 10, rng: Random.new(42)) => [:tokh, :gzswkkxudh, :vubxlfbu, :lzvlyq__jp, :oslw] 
 685 686 687 688 689 690 691
# File 'lib/prop_check/generators.rb', line 685 def simple_symbol alphabet = ('a'..'z').to_a alphabet << '_' array(one_of(*alphabet.map(&method(:constant)))) .map(&:join) .map(&:to_sym) end

.string(**kwargs) ⇒ Object

Generates a string of unicode characters (which might contain both printable and non-printable characters).

Shrinks towards characters with lower codepoints, e.g. ASCII

>> Generators.string.sample(5, size: 10, rng: Random.new(42)) => ["\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}"] 

Accepts the same options as ‘array`

 641 642 643
# File 'lib/prop_check/generators.rb', line 641 def string(**kwargs) array(char, **kwargs).map(&:join) end

.time(epoch: nil) ⇒ Object

Generates ‘Time` objects. Times start around the given `epoch:` and deviate more when `size` increases. when no epoch is set, `PropCheck::Property::Configuration.default_epoch` is used, which defaults to `DateTime.now`.

>> PropCheck::Generators.time(epoch: Date.new(2022, 11, 20)).sample(2, rng: Random.new(42), config: PropCheck::Property::Configuration.new) => [DateTime.parse("2022-11-17 07:11:59.999983907 +0000").to_time, DateTime.parse("2022-11-19 05:27:16.363618076 +0000").to_time] 
 809 810 811
# File 'lib/prop_check/generators.rb', line 809 def time(epoch: nil) datetime(epoch: epoch).map(&:to_time) end

.tree(leaf_generator, &block) ⇒ Object

Helper to build recursive generators

Given a ‘leaf_generator` and a block which:

- is given a generator that generates subtrees. - it should return the generator for intermediate tree nodes. 

This is best explained with an example. Say we want to generate a binary tree of integers.

If we have a struct representing internal nodes:

```ruby Branch = Struct.new(:left, :right, keyword_init: true) ``` we can generate trees like so: ```ruby Generators.tree(Generators.integer) do |subtree_gen| G.instance(Branch, left: subtree_gen, right: subtree_gen) end ``` 

As another example, consider generating lists of integers:

>> G = PropCheck::Generators >> G.tree(G.integer) {|child_gen| G.array(child_gen) }.sample(5, size: 37, rng: Random.new(42)) => [[7, [2, 3], -10], [[-2], [-2, [3]], [[2, 3]]], [], [0, [-2, -3]], [[1], -19, [], [1, -1], [1], [-1, -1], [1]]] 

And finally, here is how one could create a simple generator for parsed JSON data:

“‘ruby`

G = PropCheck::Generators def json G.tree(G.one_of(G.boolean, G.real_float, G.ascii_string)) do |json_gen| G.one_of(G.array(json_gen), G.hash_of(G.ascii_string, json_gen)) end end 

“‘

 914 915 916 917 918 919 920 921 922 923 924 925 926
# File 'lib/prop_check/generators.rb', line 914 def tree(leaf_generator, &block) # Implementation is based on  # https://hexdocs.pm/stream_data/StreamData.html#tree/2  Generator.new do |size:, rng:, **other_kwargs| nodes_on_each_level = random_pseudofactors(size.pow(1.1).to_i, rng) result = nodes_on_each_level.reduce(leaf_generator) do |subtree_generator, nodes_on_this_level| frequency(1 => subtree_generator, 2 => block.call(subtree_generator).resize { |_size| nodes_on_this_level }) end result.generate(size: size, rng: rng, **other_kwargs) end end

.truthyObject

Generates common terms that are not ‘nil` or `false`.

Shrinks towards simpler terms, like ‘true`, an empty array, a single character or an integer.

>> Generators.truthy.sample(5, size: 2, rng: Random.new(42)) => [[2], {:gz=>0, :""=>0}, [1.0, 0.5], 0.6666666666666667, {"𦐺\u{9FDDB}"=>1, ""=>1}] 
 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714
# File 'lib/prop_check/generators.rb', line 700 def truthy one_of(constant(true), constant([]), char, integer, float, string, array(integer), array(float), array(char), array(string), hash(simple_symbol, integer), hash(string, integer), hash(string, string)) end

.tuple(*generators) ⇒ Object

Generates an array containing always exactly one value from each of the passed generators, in the same order as specified:

Shrinks element generators, one at a time (trying last one first).

>> Generators.tuple(Generators.integer, Generators.real_float).call(size: 10, rng: Random.new(42)) => [-4, 13.0] 
 318 319 320 321 322 323 324
# File 'lib/prop_check/generators.rb', line 318 def tuple(*generators) Generator.new do |**kwargs| LazyTree.zip(generators.map do |generator| generator.generate(**kwargs) end) end end