class PropCheck::Generator

A `Generator` is a special kind of 'proc' that, given a size an random number generator state, will generate a (finite) LazyTree of output values:

The root of this tree is the value to be used during testing, and the children are 'smaller' values related to the root, to be used during the shrinking phase.

Public Class Methods

new(&block) click to toggle source

Being a special kind of Proc, a Generator wraps a block.

# File lib/prop_check/generator.rb, line 18
def initialize(&block)
  @block = block
end
wrap(val) click to toggle source

Creates a 'constant' generator that always returns the same value, regardless of `size` or `rng`.

Keen readers may notice this as the Monadic 'pure'/'return' implementation for Generators.

>> Generators.integer.bind { |a| Generators.integer.bind { |b| Generator.wrap([a , b]) } }.call(size: 100, rng: Random.new(42))
=> [2, 79]
# File lib/prop_check/generator.rb, line 70
def self.wrap(val)
  Generator.new { LazyTree.wrap(val) }
end

Public Instance Methods

bind(&generator_proc) click to toggle source

Create a generator whose implementation depends on the output of another generator. this allows us to compose multiple generators.

Keen readers may notice this as the Monadic 'bind' (sometimes known as '>>=') implementation for Generators.

>> Generators.integer.bind { |a| Generators.integer.bind { |b| Generator.wrap([a , b]) } }.call(size: 100, rng: Random.new(42))
=> [2, 79]
# File lib/prop_check/generator.rb, line 82
def bind(&generator_proc)
  # Generator.new do |size, rng|
  #   outer_result = generate(size, rng)
  #   outer_result.map do |outer_val|
  #     inner_generator = generator_proc.call(outer_val)
  #     inner_generator.generate(size, rng)
  #   end.flatten
  # end
  Generator.new do |**kwargs|
    outer_result = self.generate(**kwargs)
    outer_result.bind do |outer_val|
      inner_generator = generator_proc.call(outer_val)
      inner_generator.generate(**kwargs)
    end
  end
end
call(**kwargs) click to toggle source

Generates a value, and only return this value (drop information for shrinking)

>> Generators.integer.call(size: 1000, rng: Random.new(42))
=> 126
# File lib/prop_check/generator.rb, line 49
def call(**kwargs)
  generate(**@@default_kwargs.merge(kwargs)).root
end
generate(**kwargs) click to toggle source

Given a `size` (integer) and a random number generator state `rng`, generate a LazyTree.

# File lib/prop_check/generator.rb, line 25
def generate(**kwargs)
  kwargs = @@default_kwargs.merge(kwargs)
  max_consecutive_attempts = kwargs[:max_consecutive_attempts]

  (0..max_consecutive_attempts).each do
    res = @block.call(**kwargs)
    next if res.root == :"_PropCheck.filter_me"

    return res
  end

  raise Errors::GeneratorExhaustedError, """
  Exhausted #{max_consecutive_attempts} consecutive generation attempts.

  Probably too few generator results were adhering to a `where` condition.
  """
end
map(&proc) click to toggle source

Creates a new Generator that returns a value by running `proc` on the output of the current Generator.

>> Generators.choose(32..128).map(&:chr).call(size: 10, rng: Random.new(42))
=> "S"
# File lib/prop_check/generator.rb, line 104
def map(&proc)
  Generator.new do |**kwargs|
    result = self.generate(**kwargs)
    result.map(&proc)
  end
end
resize(&proc) click to toggle source

Resizes the generator to either grow faster or smaller than normal.

`proc` takes the current size as input and is expected to return the new size. a size should always be a nonnegative integer.

>> Generators.integer.resize{}
# File lib/prop_check/generator.rb, line 131
def resize(&proc)
  Generator.new do |size:, **other_kwargs|
    new_size = proc.call(size)
    self.generate(**other_kwargs, size: new_size)
  end
end
sample(num_of_samples = 10, **kwargs) click to toggle source

Returns `num_of_samples` values from calling this Generator. This is mostly useful for debugging if a generator behaves as you intend it to.

# File lib/prop_check/generator.rb, line 56
def sample(num_of_samples = 10, **kwargs)
  num_of_samples.times.map do
    call(**@@default_kwargs.merge(kwargs))
  end
end
where(&condition) click to toggle source

Creates a new Generator that only produces a value when the block `condition` returns a truthy value.

# File lib/prop_check/generator.rb, line 113
def where(&condition)
  self.map do |result|
    # if condition.call(*result)
    if PropCheck::Helper.call_splatted(result, &condition)
      result
    else
      :"_PropCheck.filter_me"
    end
  end
end