Class: PropCheck::Generator
- Inherits:
-
Object
- Object
- PropCheck::Generator
- Defined in:
- lib/prop_check/generator.rb
Overview
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.
Constant Summary collapse
- @@default_size =
10
- @@default_rng =
if RUBY_VERSION.to_i >= 3 Random else Random::DEFAULT end
- @@max_consecutive_attempts =
100
- @@default_kwargs =
{ size: @@default_size, rng: @@default_rng, max_consecutive_attempts: @@max_consecutive_attempts }
Class Method Summary collapse
-
.wrap(val) ⇒ Object
Creates a ‘constant’ generator that always returns the same value, regardless of ‘size` or `rng`.
Instance Method Summary collapse
-
#bind(&generator_proc) ⇒ Object
Create a generator whose implementation depends on the output of another generator.
-
#call(**kwargs) ⇒ Object
Generates a value, and only return this value (drop information for shrinking).
-
#generate(**kwargs) ⇒ Object
Given a ‘size` (integer) and a random number generator state `rng`, generate a LazyTree.
-
#initialize(&block) ⇒ Generator
constructor
Being a special kind of Proc, a Generator wraps a block.
-
#map(&proc) ⇒ Object
Creates a new Generator that returns a value by running ‘proc` on the output of the current Generator.
-
#resize(&proc) ⇒ Object
Resizes the generator to either grow faster or smaller than normal.
-
#sample(num_of_samples = 10, **kwargs) ⇒ Object
Returns ‘num_of_samples` values from calling this Generator.
-
#where(&condition) ⇒ Object
Creates a new Generator that only produces a value when the block ‘condition` returns a truthy value.
-
#with_config ⇒ Object
Turns a generator returning ‘x` into a generator returning `[x, config]` where `config` is the current `PropCheck::Property::Configuration`.
Constructor Details
#initialize(&block) ⇒ Generator
Being a special kind of Proc, a Generator wraps a block.
26 27 28 |
# File 'lib/prop_check/generator.rb', line 26 def initialize(&block) @block = block end |
Class Method Details
.wrap(val) ⇒ Object
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]
78 79 80 |
# File 'lib/prop_check/generator.rb', line 78 def self.wrap(val) Generator.new { LazyTree.wrap(val) } end |
Instance Method Details
#bind(&generator_proc) ⇒ Object
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]
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
# File 'lib/prop_check/generator.rb', line 90 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 = generate(**kwargs) outer_result.bind do |outer_val| inner_generator = generator_proc.call(outer_val) inner_generator.generate(**kwargs) end end end |
#call(**kwargs) ⇒ Object
Generates a value, and only return this value (drop information for shrinking)
>> Generators.integer.call(size: 1000, rng: Random.new(42))
=> 126
57 58 59 |
# File 'lib/prop_check/generator.rb', line 57 def call(**kwargs) generate(**@@default_kwargs.merge(kwargs)).root end |
#generate(**kwargs) ⇒ Object
Given a ‘size` (integer) and a random number generator state `rng`, generate a LazyTree.
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
# File 'lib/prop_check/generator.rb', line 33 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) ⇒ Object
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"
112 113 114 115 116 117 |
# File 'lib/prop_check/generator.rb', line 112 def map(&proc) Generator.new do |**kwargs| result = generate(**kwargs) result.map(&proc) end end |
#resize(&proc) ⇒ Object
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{}
156 157 158 159 160 161 |
# File 'lib/prop_check/generator.rb', line 156 def resize(&proc) Generator.new do |size:, **other_kwargs| new_size = proc.call(size) generate(**other_kwargs, size: new_size) end end |
#sample(num_of_samples = 10, **kwargs) ⇒ Object
Returns ‘num_of_samples` values from calling this Generator. This is mostly useful for debugging if a generator behaves as you intend it to.
64 65 66 67 68 |
# File 'lib/prop_check/generator.rb', line 64 def sample(num_of_samples = 10, **kwargs) num_of_samples.times.map do call(**@@default_kwargs.merge(kwargs)) end end |
#where(&condition) ⇒ Object
Creates a new Generator that only produces a value when the block ‘condition` returns a truthy value.
138 139 140 141 142 143 144 145 146 147 |
# File 'lib/prop_check/generator.rb', line 138 def where(&condition) map do |result| # if condition.call(*result) if PropCheck::Helper.call_splatted(result, &condition) result else :"_PropCheck.filter_me" end end end |
#with_config ⇒ Object
Turns a generator returning ‘x` into a generator returning `[x, config]` where `config` is the current `PropCheck::Property::Configuration`. This can be used to inspect the configuration inside a `#map` or `#where` and act on it.
>> example_config = PropCheck::Property::Configuration.new(default_epoch: Date.new(2022, 11, 22))
>> generator = Generators.choose(0..100).with_config.map { |int, conf| Date.jd(conf[:default_epoch].jd + int) }
>> generator.call(size: 10, rng: Random.new(42), config: example_config)
=> Date.new(2023, 01, 12)
129 130 131 132 133 134 |
# File 'lib/prop_check/generator.rb', line 129 def with_config Generator.new do |**kwargs| result = generate(**kwargs) result.map { |val| [val, kwargs[:config]] } end end |