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.



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

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.



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

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:



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

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)



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

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.



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

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.



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

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.



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

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.)



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

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.



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

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



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

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



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

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



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

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



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

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



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

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.



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

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.



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

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)


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

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`)



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

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

  duplicate.check(&block)
end