lib/prop_check/property.rb in prop_check-0.13.0 vs lib/prop_check/property.rb in prop_check-0.14.0
- old
+ new
@@ -5,13 +5,19 @@
require 'prop_check/property/output_formatter'
require 'prop_check/property/shrinker'
require 'prop_check/hooks'
module PropCheck
##
- # Run properties
+ # 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.
class Property
-
##
# 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
@@ -34,11 +40,10 @@
# 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)
def self.forall(*bindings, **kwbindings, &block)
-
property = new(*bindings, **kwbindings)
return property.check(&block) if block_given?
property
@@ -59,23 +64,29 @@
# See PropCheck::Property::Configuration for more info on available settings.
def self.configure
yield(configuration)
end
- attr_reader :bindings, :condition
-
def initialize(*bindings, **kwbindings)
- raise ArgumentError, 'No bindings specified!' if bindings.empty? && kwbindings.empty?
-
- # @bindings = bindings
- # @kwbindings = kwbindings
- @gen = gen_from_bindings(bindings, kwbindings)
- @condition = proc { true }
@config = self.class.configuration
@hooks = PropCheck::Hooks.new
+
+ @gen = gen_from_bindings(bindings, kwbindings) unless bindings.empty? && kwbindings.empty?
+ freeze
end
+ # [:condition, :config, :hooks, :gen].each do |symbol|
+ # define_method(symbol) do
+ # self.instance_variable_get("@#{symbol}")
+ # end
+
+ # protected define_method("#{symbol}=") do |value|
+ # duplicate = self.dup
+ # duplicate.instance_variable_set("@#{symbol}", value)
+ # duplicate
+ # end
+
##
# Returns the configuration of this property
# for introspection.
#
# See PropCheck::Property::Configuration for more info on available settings.
@@ -89,55 +100,68 @@
#
# 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`)
def with_config(**config, &block)
- @config = @config.merge(config)
+ duplicate = self.dup
+ duplicate.instance_variable_set(:@config, @config.merge(config))
+ duplicate.freeze
- return self.check(&block) if block_given?
+ return duplicate.check(&block) if block_given?
- self
+ duplicate
end
+ 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
+
##
# 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.
def where(&condition)
- # original_condition = @condition.dup
- # @condition = proc do |val|
- # call_splatted(val, &original_condition) && call_splatted(val, &condition)
- # # original_condition.call(val) && condition.call(val)
- # end
- @gen = @gen.where(&condition)
+ raise ArgumentError, 'No generator bindings specified! #where should be called after `#forall` or `#with_bindings`.' unless @gen
- self
+ duplicate = self.dup
+ duplicate.instance_variable_set(:@gen, @gen.where(&condition))
+ duplicate.freeze
+ duplicate
end
##
# 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.
def before(&hook)
- @hooks.add_before(&hook)
- self
+ duplicate = self.dup
+ duplicate.instance_variable_set(:@hooks, @hooks.add_before(&hook))
+ duplicate.freeze
+ duplicate
end
##
# 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.
def after(&hook)
- @hooks.add_after(&hook)
- self
+ duplicate = self.dup
+ duplicate.instance_variable_set(:@hooks, @hooks.add_after(&hook))
+ duplicate.freeze
+ duplicate
end
##
# Calls `hook` around each time a check is run with new data.
#
@@ -150,12 +174,14 @@
#
# 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.
def around(&hook)
- @hooks.add_around(&hook)
- self
+ duplicate = self.dup
+ duplicate.instance_variable_set(:@hooks, @hooks.add_around(&hook))
+ duplicate.freeze
+ duplicate
end
##
# Checks the property (after settings have been altered using the other instance methods in this class.)
def check(&block)
@@ -236,9 +262,11 @@
raise e, output_string, e.backtrace
end
private def attempts_enum(binding_generator)
+ ap @hooks
+
@hooks
.wrap_enum(raw_attempts_enum(binding_generator))
.lazy
.take(@config.n_runs)
end