lib/prop_check/property.rb in prop_check-0.7.1 vs lib/prop_check/property.rb in prop_check-0.8.0
- old
+ new
@@ -1,27 +1,42 @@
require 'stringio'
require "awesome_print"
require 'prop_check/property/configuration'
-require 'prop_check/property/check_evaluator'
module PropCheck
##
# Run properties
class Property
##
- # Call this with a keyword argument list of (symbol => generators) and a block.
- # The block will then be executed many times, with the respective symbol names
- # being defined as having a single generated value.
+ # 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(a: Generators.integer) do ... end` and forall(a: Generators.integer).check do ... end` are the same)
- def self.forall(**bindings, &block)
+ # (so `forall(Generators.integer) do |val| ... end` and forall(Generators.integer).check do |val| ... end` are the same)
+ def self.forall(*bindings, &block)
- property = new(bindings)
+ property = new(*bindings)
return property.check(&block) if block_given?
property
end
@@ -43,15 +58,16 @@
yield(configuration)
end
attr_reader :bindings, :condition
- def initialize(**bindings)
- raise ArgumentError, 'No bindings specified!' if bindings.empty?
+ def initialize(*bindings, **kwbindings)
+ raise ArgumentError, 'No bindings specified!' if bindings.empty? && kwbindings.empty?
@bindings = bindings
- @condition = -> { true }
+ @kwbindings = kwbindings
+ @condition = proc { true }
@config = self.class.configuration
end
##
# Returns the configuration of this property
@@ -85,19 +101,29 @@
# 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 = -> { instance_exec(&original_condition) && instance_exec(&condition) }
+ @condition = proc do |**kwargs|
+ original_condition.call(**kwargs) && condition.call(**kwargs)
+ end
self
end
##
# Checks the property (after settings have been altered using the other instance methods in this class.)
def check(&block)
- binding_generator = PropCheck::Generators.fixed_hash(bindings)
+ gens =
+ if @kwbindings != {}
+ kwbinding_generator = PropCheck::Generators.fixed_hash(**@kwbindings)
+ @bindings + [kwbinding_generator]
+ else
+ @bindings
+ end
+ binding_generator = PropCheck::Generators.tuple(*gens)
+ # binding_generator = PropCheck::Generators.fixed_hash(**@kwbindings)
n_runs = 0
n_successful = 0
# Loop stops at first exception
@@ -111,10 +137,14 @@
end
private def ensure_not_exhausted!(n_runs)
return if n_runs >= @config.n_runs
+ raise_generator_exhausted!
+ end
+
+ private def raise_generator_exhausted!()
raise Errors::GeneratorExhaustedError, """
Could not perform `n_runs = #{@config.n_runs}` runs,
(exhausted #{@config.max_generate_attempts} tries)
because too few generator results were adhering to
the `where` condition.
@@ -122,11 +152,11 @@
Try refining your generators instead.
"""
end
private def check_attempt(generator_result, n_successful, &block)
- CheckEvaluator.new(generator_result.root, &block).call
+ block.call(*generator_result.root)
# immediately stop (without shrinnking) for when the app is asked
# to close by outside intervention
rescue SignalException, SystemExit
raise
@@ -158,12 +188,12 @@
n_runs = 0
size = 1
(0...@config.max_generate_attempts)
.lazy
.map { binding_generator.generate(size, rng) }
- .reject { |val| val.root == :"_PropCheck.filter_me" }
- .select { |val| CheckEvaluator.new(val.root, &@condition).call }
+ .reject { |val| val.root.any? { |elem| elem == :"_PropCheck.filter_me" }}
+ .select { |val| @condition.call(*val.root) }
.map do |result|
n_runs += 1
size += 1
result
@@ -192,29 +222,33 @@
output
end
private def post_output(output, n_shrink_steps, shrunken_result, shrunken_exception)
- output.puts ''
- output.puts "Shrunken input (after #{n_shrink_steps} shrink steps):"
- output.puts "`#{print_roots(shrunken_result)}`"
- output.puts ""
- output.puts "Shrunken exception:\n---\n#{shrunken_exception}"
- output.puts "---"
- output.puts ""
-
+ if n_shrink_steps == 0
+ output.puts '(shrinking impossible)'
+ else
+ output.puts ''
+ output.puts "Shrunken input (after #{n_shrink_steps} shrink steps):"
+ output.puts "`#{print_roots(shrunken_result)}`"
+ output.puts ""
+ output.puts "Shrunken exception:\n---\n#{shrunken_exception}"
+ output.puts "---"
+ output.puts ""
+ end
output
end
- private def print_roots(lazy_tree_hash)
- # lazy_tree_hash.map do |name, val|
- # "#{name} = #{val.inspect}"
- # end.join(", ")
- lazy_tree_hash.ai
+ private def print_roots(lazy_tree_val)
+ if lazy_tree_val.is_a?(Array) && lazy_tree_val.length == 1 && lazy_tree_val[0].is_a?(Hash)
+ lazy_tree_val[0].ai
+ else
+ lazy_tree_val.ai
+ end
end
- private def shrink(bindings_tree, io, &fun)
+ private def shrink(bindings_tree, io, &block)
io.puts 'Shrinking...' if @config.verbose
problem_child = bindings_tree
siblings = problem_child.children.lazy
parent_siblings = nil
problem_exception = nil
@@ -232,10 +266,10 @@
shrink_steps += 1
io.print '.' if @config.verbose
begin
- CheckEvaluator.new(sibling.root, &fun).call
+ block.call(*sibling.root)
rescue Exception => e
problem_child = sibling
parent_siblings = siblings
siblings = problem_child.children.lazy
problem_exception = e