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
-
.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
trueorfalse. -
.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
sizeorrng(random number generator state). -
.date(epoch: nil) ⇒ Object
Generates
Dateobjects. -
.date_time(epoch: nil) ⇒ Object
alias for
#datetime, for backwards compatibility. -
.datetime(epoch: nil) ⇒ Object
Generates
DateTimeobjects. -
.falsey ⇒ Object
Generates
nilorfalse. -
.fixed_hash(hash) ⇒ Object
Given a
hashwhere 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
frequenciesat random every time. -
.future_date(epoch: Date.today) ⇒ Object
variant of #date that only generates dates in the future (relative to
:epoch). -
.future_datetime(epoch: nil) ⇒ Object
Variant of
#datetimethat only generates datetimes in the future (relative to:epoch). -
.future_time(epoch: nil) ⇒ Object
Variant of
#timethat only generates datetimes in the future (relative to:epoch). -
.hash(*args, **kwargs) ⇒ Object
Generates a hash of key->values, where each of the keys is made using the
key_generatorand each of the values using thevalue_generator. -
.hash_of(key_generator, value_generator, **kwargs) ⇒ Object
Alias for
#hashthat does not conflict with a possibly overridden ‘Object#hash`. -
.instance(klass, *args, **kwargs) ⇒ Object
Generates an instance of
klassusingargsand/orkwargsas generators for the arguments that are passed toklass.new. -
.integer ⇒ Object
A random integer which scales with
size. -
.negative_float ⇒ Object
Generates negative floating point numbers Will generate special floats (except NaN) from time to time.
-
.negative_integer ⇒ Object
Only returns integers that are smaller than zero.
-
.nil ⇒ Object
Generates always
nil. -
.nillable(other_generator) ⇒ Object
Generates whatever
other_generatorgenerates but sometimes insteadnil.‘. -
.nonnegative_float ⇒ Object
Generates nonnegative floating point numbers Will generate special floats (except NaN) from time to time.
-
.nonnegative_integer ⇒ Object
Only returns integers that are zero or larger.
-
.nonpositive_float ⇒ Object
Generates nonpositive floating point numbers Will generate special floats (except NaN) from time to time.
-
.nonpositive_integer ⇒ Object
Only returns integers that are zero or smaller.
-
.nonzero_float ⇒ Object
Generates any nonzero floating-point number.
-
.one_of(*choices) ⇒ Object
Picks one of the given generators in
choicesat random uniformly every time. -
.past_date(epoch: Date.today) ⇒ Object
variant of #date that only generates dates in the past (relative to
:epoch). -
.past_datetime(epoch: nil) ⇒ Object
Variant of
#datetimethat only generates datetimes in the past (relative to:epoch). -
.past_time(epoch: nil) ⇒ Object
Variant of
#timethat only generates datetimes in the past (relative to:epoch). -
.positive_float ⇒ Object
Generates positive floating point numbers Will generate special floats (except NaN) from time to 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).
-
.real_negative_float ⇒ Object
Generates real floating-point numbers which are always negative Shrinks towards -Float::MIN.
-
.real_nonnegative_float ⇒ Object
Generates real floating-point numbers which are never negative.
-
.real_nonpositive_float ⇒ Object
Generates real floating-point numbers which are never positive.
-
.real_nonzero_float ⇒ Object
Generates any real floating-point numbers, but will never generate zero.
-
.real_positive_float ⇒ Object
Generates real floating-point numbers which are always positive Shrinks towards Float::MIN.
-
.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. -
.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).
-
.time(epoch: nil) ⇒ Object
Generates
Timeobjects. -
.tree(leaf_generator, &block) ⇒ Object
Helper to build recursive generators.
-
.truthy ⇒ Object
Generates common terms that are not
nilorfalse. -
.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"]
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_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"]
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 |
.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]
652 653 654 |
# File 'lib/prop_check/generators.rb', line 652 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))
=> ["吏", "", "", "", "", "", "", "", "", "Ȍ"]
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 |
.falsey ⇒ Object
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 |
.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/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 |
.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_float ⇒ Object
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_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(&:-@) end |
.nil ⇒ Object
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_float ⇒ Object
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_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_float ⇒ Object
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_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(&:-@) end |
.nonzero_float ⇒ Object
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_float ⇒ Object
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_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"]
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_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))
=> ["吏", "", "", "", "", "", "", "", "", "Ȍ"]
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_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 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_float ⇒ Object
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_float ⇒ Object
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_float ⇒ Object
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_float ⇒ Object
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_float ⇒ Object
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_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]
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 |
.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: 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 |