Class: PropCheck::Property

Inherits:
Object
  • Object
show all
Defined in:
lib/prop_check/property.rb,
lib/prop_check/property/configuration.rb

Overview

Create and run property-checks.

For simple usage, see ‘.forall`.

For advanced usage, call ‘PropCheck::Property.new(…)` and then configure it to your liking using e.g. `#with_config`, `#before`, `#after`, `#around` etc. Each of these methods will return a new `Property`, so earlier properties are not mutated. This allows you to re-use configuration and hooks between multiple tests.

Defined Under Namespace

Modules: OutputFormatter Classes: Configuration, Shrinker

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*bindings, **kwbindings) ⇒ Property

Returns a new instance of Property.



66
67
68
69
70
71
72
# File 'lib/prop_check/property.rb', line 66

def initialize(*bindings, **kwbindings)
  @config = self.class.configuration
  @hooks = PropCheck::Hooks.new

  @gen = gen_from_bindings(bindings, kwbindings) unless bindings.empty? && kwbindings.empty?
  freeze
end

Class Method Details

.configurationObject

Returns the default configuration of the library as it is configured right now for introspection.

For the configuration of a single property, check its ‘configuration` instance method. See PropCheck::Property::Configuration for more info on available settings.



55
56
57
# File 'lib/prop_check/property.rb', line 55

def self.configuration
  @configuration ||= Configuration.new
end

.configure {|configuration| ... } ⇒ Object

Yields the library’s configuration object for you to alter. See PropCheck::Property::Configuration for more info on available settings.

Yields:



62
63
64
# File 'lib/prop_check/property.rb', line 62

def self.configure
  yield(configuration)
end

.forall(*bindings, **kwbindings, &block) ⇒ Object

Main entry-point to create (and possibly immediately run) a property-test.

This method accepts a list of generators and a block. The block will then be executed many times, passing the values generated by the generators as respective arguments:

“‘ include PropCheck::Generators PropCheck.forall(integer(), float()) { |x, y| … } “`

It is also possible (and recommended when having more than a few generators) to use a keyword-list of generators instead:

“‘ include PropCheck::Generators PropCheck.forall(x: integer(), y: float()) { |x:, y:| … } “`

If you do not pass a block right away, a Property object is returned, which you can call the other instance methods of this class on before finally passing a block to it using ‘#check`. (so `forall(Generators.integer) do |val| … end` and forall(Generators.integer).check do |val| … end` are the same)



44
45
46
47
# File 'lib/prop_check/property.rb', line 44

def self.forall(*bindings, **kwbindings, &block)
  new(*bindings, **kwbindings)
    .check(&block)
end

Instance Method Details

#after(&hook) ⇒ Object

Calls ‘hook` after each time a check is run with new data.

This is useful to add teardown logic When called multiple times, earlier-added hooks will be called after ‘hook` is called.



216
217
218
219
220
221
# File 'lib/prop_check/property.rb', line 216

def after(&hook)
  duplicate = dup
  duplicate.instance_variable_set(:@hooks, @hooks.add_after(&hook))
  duplicate.freeze
  duplicate
end

#around(&hook) ⇒ Object

Calls ‘hook` around each time a check is run with new data.

‘hook` should `yield` to the passed block.

When called multiple times, earlier-added hooks will be wrapped around ‘hook`.

Around hooks will be called after all ‘#before` hooks and before all `#after` hooks.

Note that if the block passed to ‘hook` raises an exception, it is possible for the code after `yield` not to be called. So make sure that cleanup logic is wrapped with the `ensure` keyword.



236
237
238
239
240
241
# File 'lib/prop_check/property.rb', line 236

def around(&hook)
  duplicate = dup
  duplicate.instance_variable_set(:@hooks, @hooks.add_around(&hook))
  duplicate.freeze
  duplicate
end

#before(&hook) ⇒ Object

Calls ‘hook` before each time a check is run with new data.

This is useful to add setup logic When called multiple times, earlier-added hooks will be called before ‘hook` is called.



204
205
206
207
208
209
# File 'lib/prop_check/property.rb', line 204

def before(&hook)
  duplicate = dup
  duplicate.instance_variable_set(:@hooks, @hooks.add_before(&hook))
  duplicate.freeze
  duplicate
end

#check(&block) ⇒ Object

Checks the property (after settings have been altered using the other instance methods in this class.)



245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
# File 'lib/prop_check/property.rb', line 245

def check(&block)
  return self unless block_given?

  n_runs = 0
  n_successful = 0

  # Loop stops at first exception
  attempts_enum(@gen).each do |generator_result|
    n_runs += 1
    check_attempt(generator_result, n_successful, &block)
    n_successful += 1
  end

  ensure_not_exhausted!(n_runs)
end

#configurationObject

Returns the configuration of this property for introspection.

See PropCheck::Property::Configuration for more info on available settings.



90
91
92
# File 'lib/prop_check/property.rb', line 90

def configuration
  @config
end

#growing_exponentially(&block) ⇒ Object

Resizes all generators in this property. The new size is ‘2.pow(orig_size)`

c.f. #resize



124
125
126
127
128
# File 'lib/prop_check/property.rb', line 124

def growing_exponentially(&block)
  orig_fun = @config.resize_function
  fun = proc { |size| 2.pow(orig_fun.call(size)) }
  with_config(resize_function: fun, &block)
end

#growing_fast(&block) ⇒ Object

Resizes all generators in this property. The new size is ‘2 * orig_size`

c.f. #resize



144
145
146
147
148
# File 'lib/prop_check/property.rb', line 144

def growing_fast(&block)
  orig_fun = @config.resize_function
  fun = proc { |size| orig_fun.call(size) * 2 }
  with_config(resize_function: fun, &block)
end

#growing_logarithmically(&block) ⇒ Object

Resizes all generators in this property. The new size is ‘Math.log2(orig_size)`

c.f. #resize



164
165
166
167
168
# File 'lib/prop_check/property.rb', line 164

def growing_logarithmically(&block)
  orig_fun = @config.resize_function
  fun = proc { |size| Math.log2(orig_fun.call(size)) }
  with_config(resize_function: fun, &block)
end

#growing_quadratically(&block) ⇒ Object

Resizes all generators in this property. The new size is ‘orig_size * orig_size`

c.f. #resize



134
135
136
137
138
# File 'lib/prop_check/property.rb', line 134

def growing_quadratically(&block)
  orig_fun = @config.resize_function
  fun = proc { |size| orig_fun.call(size).pow(2) }
  with_config(resize_function: fun, &block)
end

#growing_slowly(&block) ⇒ Object

Resizes all generators in this property. The new size is ‘0.5 * orig_size`

c.f. #resize



154
155
156
157
158
# File 'lib/prop_check/property.rb', line 154

def growing_slowly(&block)
  orig_fun = @config.resize_function
  fun = proc { |size| orig_fun.call(size) * 0.5 }
  with_config(resize_function: fun, &block)
end

#resize(&block) ⇒ Object

Resizes all generators in this property with the given function.

Shorthand for manually wrapping ‘PropCheck::Property::Configuration.resize_function` with the new function.



113
114
115
116
117
118
# File 'lib/prop_check/property.rb', line 113

def resize(&block)
  raise '#resize called without a block' unless block_given?

  orig_fun = @config.resize_function
  with_config(resize_function: block)
end

#where(&condition) ⇒ Object

filters the generator using the given ‘condition`. The final property checking block will only be run if the condition is truthy.

If wanted, multiple ‘where`-conditions can be specified on a property. Be aware that if you filter away too much generated inputs, you might encounter a GeneratorExhaustedError. Only filter if you have few inputs to reject. Otherwise, improve your generators.



187
188
189
190
191
192
193
194
195
196
197
# File 'lib/prop_check/property.rb', line 187

def where(&condition)
  unless @gen
    raise ArgumentError,
          'No generator bindings specified! #where should be called after `#forall` or `#with_bindings`.'
  end

  duplicate = dup
  duplicate.instance_variable_set(:@gen, @gen.where(&condition))
  duplicate.freeze
  duplicate
end

#with_bindings(*bindings, **kwbindings) ⇒ Object

Raises:

  • (ArgumentError)


170
171
172
173
174
175
176
177
# File 'lib/prop_check/property.rb', line 170

def with_bindings(*bindings, **kwbindings)
  raise ArgumentError, 'No bindings specified!' if bindings.empty? && kwbindings.empty?

  duplicate = dup
  duplicate.instance_variable_set(:@gen, gen_from_bindings(bindings, kwbindings))
  duplicate.freeze
  duplicate
end

#with_config(**config, &block) ⇒ Object

Allows you to override the configuration of this property by giving a hash with new settings.

If no other changes need to occur before you want to check the property, you can immediately pass a block to this method. (so ‘forall(a: Generators.integer).with_config(verbose: true) do … end` is the same as `forall(a: Generators.integer).with_config(verbose: true).check do … end`)



101
102
103
104
105
106
107
# File 'lib/prop_check/property.rb', line 101

def with_config(**config, &block)
  duplicate = dup
  duplicate.instance_variable_set(:@config, @config.merge(config))
  duplicate.freeze

  duplicate.check(&block)
end