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.


69
70
71
72
73
74
75
# File 'lib/prop_check/property.rb', line 69

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.


58
59
60
# File 'lib/prop_check/property.rb', line 58

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:


65
66
67
# File 'lib/prop_check/property.rb', line 65

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
48
49
50
# File 'lib/prop_check/property.rb', line 44

def self.forall(*bindings, **kwbindings, &block)
  property = new(*bindings, **kwbindings)

  return property.check(&block) if block_given?

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


158
159
160
161
162
163
# File 'lib/prop_check/property.rb', line 158

def after(&hook)
  duplicate = self.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.


178
179
180
181
182
183
# File 'lib/prop_check/property.rb', line 178

def around(&hook)
  duplicate = self.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.


146
147
148
149
150
151
# File 'lib/prop_check/property.rb', line 146

def before(&hook)
  duplicate = self.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.)


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

def check(&block)
  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.


93
94
95
# File 'lib/prop_check/property.rb', line 93

def configuration
  @config
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.

Raises:

  • (ArgumentError)

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

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

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

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

Raises:

  • (ArgumentError)

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

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

  duplicate = self.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`)


104
105
106
107
108
109
110
111
112
# File 'lib/prop_check/property.rb', line 104

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

  return duplicate.check(&block) if block_given?

  duplicate
end