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