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::MIN, 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
-
.alphanumeric_char ⇒ Object
Generates a single-character string containing one of a..z, A..Z, 0..9.
-
.alphanumeric_string(**kwargs) ⇒ Object
Generates a string containing only the characters a..z, A..Z, 0..9.
-
.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`.
-
.ascii_char ⇒ Object
Generates a single-character string from the printable ASCII character set.
-
.ascii_string(**kwargs) ⇒ Object
Generates strings from the printable ASCII character set.
-
.boolean ⇒ Object
Generates either `true` or `false`.
-
.char ⇒ Object
Generates a single unicode character (both printable and non-printable).
-
.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).
-
.constant(val) ⇒ Object
Always returns the same value, regardless of `size` or `rng` (random number generator state).
-
.falsey ⇒ Object
Generates `nil` or `false`.
-
.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.
-
.float ⇒ Object
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.
-
.frequency(frequencies) ⇒ Object
Picks one of the choices given in `frequencies` at random every time.
-
.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`.
-
.hash_of(key_generator, value_generator, **kwargs) ⇒ Object
Alias for `#hash` that does not conflict with a possibly overriden `Object#hash`.
-
.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`.
-
.integer ⇒ Object
A random integer which scales with `size`.
-
.negative_integer ⇒ Object
Only returns integers that are smaller than zero.
-
.nil ⇒ Object
Generates always `nil`.
-
.nillable(other_generator) ⇒ Object
Generates whatever `other_generator` generates but sometimes instead `nil`.`.
-
.nonnegative_integer ⇒ Object
Only returns integers that are zero or larger.
-
.nonpositive_integer ⇒ Object
Only returns integers that are zero or smaller.
-
.one_of(*choices) ⇒ Object
Picks one of the given generators in `choices` at random uniformly every time.
-
.positive_integer ⇒ Object
Only returns integers that are larger than zero.
-
.printable_ascii_char ⇒ Object
Generates a single-character string from the printable ASCII character set.
-
.printable_ascii_string(**kwargs) ⇒ Object
Generates strings from the printable ASCII character set.
-
.printable_char ⇒ Object
Generates a single-character printable string both ASCII characters and Unicode.
-
.printable_string(**kwargs) ⇒ Object
Generates a printable string both ASCII characters and Unicode.
-
.real_float ⇒ Object
Generates floating-point numbers These start small (around 0) and become more extreme (large positive and large negative numbers).
-
.simple_symbol ⇒ Object
Generates symbols consisting of lowercase letters and potentially underscores.
-
.string(**kwargs) ⇒ Object
Generates a string of unicode characters (which might contain both printable and non-printable characters).
-
.truthy ⇒ Object
Generates common terms that are not `nil` or `false`.
-
.tuple(*generators) ⇒ Object
Generates an array containing always exactly one value from each of the passed generators, in the same order as specified:.
Class Method Details
.alphanumeric_char ⇒ Object
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"]
361 362 363 |
# File 'lib/prop_check/generators.rb', line 361 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`
375 376 377 |
# File 'lib/prop_check/generators.rb', line 375 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]]
265 266 267 268 269 270 271 272 273 274 |
# File 'lib/prop_check/generators.rb', line 265 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 make_array(element_generator, min, max, uniq) end end |
.ascii_char ⇒ Object
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"]
430 431 432 |
# File 'lib/prop_check/generators.rb', line 430 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!_[[email protected]", "x", "zZI{[o"]
Accepts the same options as `array`
444 445 446 |
# File 'lib/prop_check/generators.rb', line 444 def ascii_string(**kwargs) array(ascii_char, **kwargs).map(&:join) end |
.boolean ⇒ Object
Generates either `true` or `false`
Shrinks towards `false`
>> Generators.boolean.sample(5, size: 10, rng: Random.new(42))
=> [false, true, false, false, false]
516 517 518 |
# File 'lib/prop_check/generators.rb', line 516 def boolean one_of(constant(false), constant(true)) end |
.char ⇒ Object
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))
=> ["吏", "", "", "", "", "", "", "", "", "Ȍ"]
489 490 491 492 493 |
# File 'lib/prop_check/generators.rb', line 489 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 |
.falsey ⇒ Object
Generates `nil` or `false`.
Shrinks towards `nil`.
>> Generators.falsey.sample(5, size: 10, rng: Random.new(42))
=> [nil, false, nil, nil, nil]
538 539 540 |
# File 'lib/prop_check/generators.rb', line 538 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}
224 225 226 227 228 229 230 231 232 |
# File 'lib/prop_check/generators.rb', line 224 def fixed_hash(hash) keypair_generators = hash.map do |key, generator| generator.map { |val| [key, val] } end tuple(*keypair_generators) .map(&:to_h) end |
.float ⇒ Object
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/100 generated numbers is a special one.
Shrinks to smaller, real floats.
>> Generators.float().sample(10, size: 10, rng: Random.new(42))
=> [4.0, 9.555555555555555, 0.0, -Float::INFINITY, 5.5, -5.818181818181818, 1.1428571428571428, 0.0, 8.0, 7.857142857142858]
161 162 163 |
# File 'lib/prop_check/generators.rb', line 161 def float frequency(99 => 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]
191 192 193 194 195 196 197 |
# File 'lib/prop_check/generators.rb', line 191 def frequency(frequencies) choices = frequencies.reduce([]) do |acc, elem| freq, val = elem acc + ([val] * freq) end one_of(*choices) 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}, {}]
335 336 337 338 339 340 341 |
# File 'lib/prop_check/generators.rb', line 335 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 overriden `Object#hash`.
347 348 349 350 |
# File 'lib/prop_check/generators.rb', line 347 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>]"
616 617 618 619 620 621 622 623 624 625 626 627 628 |
# File 'lib/prop_check/generators.rb', line 616 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 |
.integer ⇒ Object
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_integer ⇒ Object
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(&:[email protected]) end |
.nil ⇒ Object
Generates always `nil`.
Does not shrink.
>> Generators.nil.sample(5, size: 10, rng: Random.new(42))
=> [nil, nil, nil, nil, nil]
527 528 529 |
# File 'lib/prop_check/generators.rb', line 527 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]
587 588 589 |
# File 'lib/prop_check/generators.rb', line 587 def nillable(other_generator) frequency(9 => other_generator, 1 => constant(nil)) end |
.nonnegative_integer ⇒ Object
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_integer ⇒ Object
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(&:[email protected]) 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]
172 173 174 175 176 |
# File 'lib/prop_check/generators.rb', line 172 def one_of(*choices) choose(choices.length).bind do |index| choices[index] end end |
.positive_integer ⇒ Object
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_char ⇒ Object
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"]
389 390 391 |
# File 'lib/prop_check/generators.rb', line 389 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!", "=", "!_[[email protected]", "Y"]
Accepts the same options as `array`
403 404 405 |
# File 'lib/prop_check/generators.rb', line 403 def printable_ascii_string(**kwargs) array(printable_ascii_char, **kwargs).map(&:join) end |
.printable_char ⇒ Object
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))
=> ["吏", "", "", "", "", "", "", "", "", "Ȍ"]
463 464 465 |
# File 'lib/prop_check/generators.rb', line 463 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`
477 478 479 |
# File 'lib/prop_check/generators.rb', line 477 def printable_string(**kwargs) array(printable_char, **kwargs).map(&:join) end |
.real_float ⇒ Object
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 to numbers closer to zero.
>> 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]
143 144 145 146 147 |
# File 'lib/prop_check/generators.rb', line 143 def real_float tuple(integer, integer, integer).map do |a, b, c| fraction(a, b, c) end end |
.simple_symbol ⇒ Object
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]
549 550 551 552 553 554 555 |
# File 'lib/prop_check/generators.rb', line 549 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`
505 506 507 |
# File 'lib/prop_check/generators.rb', line 505 def string(**kwargs) array(char, **kwargs).map(&:join) end |
.truthy ⇒ Object
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: 10, rng: Random.new(42))
=> [[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}"]]
564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 |
# File 'lib/prop_check/generators.rb', line 564 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]
207 208 209 210 211 212 213 |
# File 'lib/prop_check/generators.rb', line 207 def tuple(*generators) Generator.new do |**kwargs| LazyTree.zip(generators.map do |generator| generator.generate(**kwargs) end) end end |