prop_check 0.6.2 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +2 -1
- data/.tool-versions +1 -1
- data/CHANGELOG.md +1 -0
- data/Gemfile +6 -2
- data/README.md +82 -15
- data/lib/prop_check.rb +7 -0
- data/lib/prop_check/generator.rb +20 -1
- data/lib/prop_check/hooks.rb +122 -0
- data/lib/prop_check/lazy_tree.rb +4 -2
- data/lib/prop_check/property.rb +91 -35
- data/lib/prop_check/version.rb +1 -1
- data/prop_check.gemspec +1 -3
- metadata +11 -42
- data/Gemfile.lock +0 -48
- data/lib/prop_check/property/check_evaluator.rb +0 -45
- data/lib/prop_check/rspec.rb +0 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: aaa23e40547bec5672e806d62c567947cd596e1c11ed2bc80fbe6912cd75a777
|
4
|
+
data.tar.gz: a0263cddf3efb8ee55f29395004915c38b6e78c7fc7bbf2c1b456f344013b8da
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b7976fb344d4c7135cd858ca86606beb97cd9e9f57c40e2bb322a3c76287de46491d4feb1dd3a65158ffef8ea98894490edc7eb9bc506d80ddb110b2f026de8d
|
7
|
+
data.tar.gz: f80bffd3b08d1a1cf120a70645b548c2f70b540ae5be317b7635f7416cc4e7be94d3a1bfca10acd117166e08fd0a0c6cd8ab08bf68097182fc064519600cfb55
|
data/.gitignore
CHANGED
data/.tool-versions
CHANGED
@@ -1 +1 @@
|
|
1
|
-
ruby 2.5
|
1
|
+
ruby 2.6.5
|
data/CHANGELOG.md
CHANGED
@@ -0,0 +1 @@
|
|
1
|
+
- 0.8.0 New syntax that is more explicit, passng generated values to blocks as parameters.
|
data/Gemfile
CHANGED
@@ -3,5 +3,9 @@ source "https://rubygems.org"
|
|
3
3
|
# Specify your gem's dependencies in prop_check.gemspec
|
4
4
|
gemspec
|
5
5
|
|
6
|
-
gem
|
7
|
-
gem
|
6
|
+
gem "bundler", "~> 2.0"
|
7
|
+
gem "rake", "~> 12.3", require: false, group: :test
|
8
|
+
gem "rspec", "~> 3.0", require: false, group: :test
|
9
|
+
gem "doctest-rspec", require: false, group: :test
|
10
|
+
gem "simplecov", require: false, group: :test
|
11
|
+
|
data/README.md
CHANGED
@@ -14,7 +14,7 @@ It features:
|
|
14
14
|
- Shrinking to a minimal counter-example on failure.
|
15
15
|
|
16
16
|
|
17
|
-
## TODOs before release
|
17
|
+
## TODOs before stable release
|
18
18
|
|
19
19
|
Before releasing this gem on Rubygems, the following things need to be finished:
|
20
20
|
|
@@ -26,11 +26,12 @@ Before releasing this gem on Rubygems, the following things need to be finished:
|
|
26
26
|
- [x] Stop after a ludicrous amount of generator runs, to prevent malfunctioning (infinitely looping) generators from blowing up someone's computer.
|
27
27
|
- [x] Look into customization of settings from e.g. command line arguments.
|
28
28
|
- [x] Good, unicode-compliant, string generators.
|
29
|
-
- [
|
29
|
+
- [x] Filtering generator outputs.
|
30
|
+
- [x] Before/after/around hooks to add setup/teardown logic to be called before/after/around each time a check is run with new data.
|
30
31
|
|
31
32
|
# Nice-to-haves
|
32
33
|
|
33
|
-
- [
|
34
|
+
- [x] Basic integration with RSpec. See also https://groups.google.com/forum/#!msg/rspec/U-LmL0OnO-Y/iW_Jcd6JBAAJ for progress on this.
|
34
35
|
- [ ] `aggregate` , `resize` and similar generator-modifying calls (c.f. PropEr's variants of these) which will help with introspection/metrics.
|
35
36
|
- [ ] Integration with other Ruby test frameworks.
|
36
37
|
- Stateful property testing. If implemented at some point, will probably happen in a separate add-on library.
|
@@ -66,8 +67,9 @@ _(to be precise: a method on the execution context is defined which returns the
|
|
66
67
|
Raise an exception from the block if there is a problem. If there is no problem, just return normally.
|
67
68
|
|
68
69
|
```ruby
|
70
|
+
include PropCheck::Generators
|
69
71
|
# testing that Enumerable#sort sorts in ascending order
|
70
|
-
PropCheck.forall(
|
72
|
+
PropCheck.forall(array(integer)) do |numbers|
|
71
73
|
sorted_numbers = numbers.sort
|
72
74
|
|
73
75
|
# Check that no number is smaller than the previous number
|
@@ -77,6 +79,64 @@ PropCheck.forall(numbers: array(integer())) do
|
|
77
79
|
end
|
78
80
|
```
|
79
81
|
|
82
|
+
|
83
|
+
Here is another example, using it inside a test case.
|
84
|
+
Here we check if `naive_average` indeed always returns an integer for all arrays of numbers we can pass it:
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
# Somewhere you have this function definition:
|
88
|
+
def naive_average(array)
|
89
|
+
array.sum / array.length
|
90
|
+
end
|
91
|
+
```
|
92
|
+
```ruby
|
93
|
+
# And then in a test case:
|
94
|
+
include PropCheck::Generators
|
95
|
+
PropCheck.forall(numbers: array(integer)) do |numbers:|
|
96
|
+
result = naive_average(numbers)
|
97
|
+
unless result.is_a?(Integer) do
|
98
|
+
raise "Expected the average to be an integer!"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Or if you e.g. are using RSpec:
|
103
|
+
describe "#naive_average" do
|
104
|
+
include PropCheck
|
105
|
+
include PropCheck::Generators
|
106
|
+
|
107
|
+
it "returns an integer for any input" do
|
108
|
+
forall(numbers: array(integer)) do |numbers:|
|
109
|
+
result = naive_average(numbers)
|
110
|
+
expect(result).to be_a(Integer)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
```
|
115
|
+
|
116
|
+
When running this particular example PropCheck very quickly finds out that we have made a programming mistake:
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
ZeroDivisionError:
|
120
|
+
(after 6 successful property test runs)
|
121
|
+
Failed on:
|
122
|
+
`{
|
123
|
+
:numbers => []
|
124
|
+
}`
|
125
|
+
|
126
|
+
Exception message:
|
127
|
+
---
|
128
|
+
divided by 0
|
129
|
+
---
|
130
|
+
|
131
|
+
(shrinking impossible)
|
132
|
+
---
|
133
|
+
```
|
134
|
+
|
135
|
+
Clearly we forgot to handle the case of an empty array being passed to the function.
|
136
|
+
This is a good example of the kind of conceptual bugs that PropCheck (and property-based testing in general)
|
137
|
+
are able to check for.
|
138
|
+
|
139
|
+
|
80
140
|
#### Shrinking
|
81
141
|
|
82
142
|
When a failure is found, PropCheck will re-run the block given to `forall` to test
|
@@ -88,8 +148,8 @@ PropCheck will see if the failure still happens with `x = 50`.
|
|
88
148
|
If it does , it will try `x = 25`. If not, it will try `x = 75`, and so on.
|
89
149
|
|
90
150
|
This means if something only goes wrong for `x = 2`, the program will try:
|
91
|
-
- `x = 100`(fails)
|
92
|
-
- x = 50`(fails),
|
151
|
+
- `x = 100`(fails),
|
152
|
+
- `x = 50`(fails),
|
93
153
|
- `x = 25`(fails),
|
94
154
|
- `x = 12`(fails),
|
95
155
|
- `x = 6`(fails),
|
@@ -106,21 +166,28 @@ A short summary:
|
|
106
166
|
- Arrays and hashes shrink to fewer elements, as well as shrinking their elements.
|
107
167
|
- Strings shrink to shorter strings, as well as characters earlier in their alphabet.
|
108
168
|
|
169
|
+
### Builtin Generators
|
170
|
+
|
171
|
+
PropCheck comes with [many builtin generators in the PropCheck::Generators](https://www.rubydoc.info/github/Qqwy/ruby-prop_check/master/PropCheck/Generators) module.
|
172
|
+
|
173
|
+
It contains generators for:
|
174
|
+
- (any, positive, negative, etc.) integers,
|
175
|
+
- (any, only real-valued) floats,
|
176
|
+
- (any, printable only, alphanumeric only, etc) strings and symbols
|
177
|
+
- fixed-size arrays and hashes
|
178
|
+
- as well as varying-size arrays and hashes.
|
179
|
+
- and many more!
|
180
|
+
|
181
|
+
It is common to call `include PropCheck::Generators` in e.g. your testing-suite files to be able to use these.
|
182
|
+
If you want to be more explicit (but somewhat more verbose) when calling these functions. feel free to e.g. create a module-alias (like `PG = PropCheck::Generators`) instead.
|
109
183
|
|
110
184
|
### Writing Custom Generators
|
111
185
|
|
112
|
-
PropCheck comes bundled with a bunch of common generators
|
113
|
-
- integers
|
114
|
-
- floats
|
115
|
-
- strings
|
116
|
-
- symbols
|
117
|
-
- arrays
|
118
|
-
- hashes
|
119
|
-
etc.
|
186
|
+
As described in the previous section, PropCheck already comes bundled with a bunch of common generators.
|
120
187
|
|
121
188
|
However, you can easily adapt them to generate your own datatypes:
|
122
189
|
|
123
|
-
#### Generator#wrap
|
190
|
+
#### Generators#constant / Generator#wrap
|
124
191
|
|
125
192
|
Always returns the given value. No shrinking.
|
126
193
|
|
data/lib/prop_check.rb
CHANGED
@@ -9,6 +9,13 @@ require 'prop_check/helper'
|
|
9
9
|
# You probably want to look at the documentation of
|
10
10
|
# PropCheck::Generator and PropCheck::Generators
|
11
11
|
# to find out more about how to use generators.
|
12
|
+
#
|
13
|
+
# Common usage is to call `extend PropCheck` in your (testing) modules.
|
14
|
+
#
|
15
|
+
# This will:
|
16
|
+
# 1. Add the local method `forall` which will call `PropCheck.forall`
|
17
|
+
# 2. `include PropCheck::Generators`.
|
18
|
+
#
|
12
19
|
module PropCheck
|
13
20
|
module Errors
|
14
21
|
class Error < StandardError; end
|
data/lib/prop_check/generator.rb
CHANGED
@@ -72,7 +72,7 @@ module PropCheck
|
|
72
72
|
# end.flatten
|
73
73
|
# end
|
74
74
|
Generator.new do |size, rng|
|
75
|
-
outer_result = generate(size, rng)
|
75
|
+
outer_result = self.generate(size, rng)
|
76
76
|
outer_result.bind do |outer_val|
|
77
77
|
inner_generator = generator_proc.call(outer_val)
|
78
78
|
inner_generator.generate(size, rng)
|
@@ -91,5 +91,24 @@ module PropCheck
|
|
91
91
|
result.map(&proc)
|
92
92
|
end
|
93
93
|
end
|
94
|
+
|
95
|
+
##
|
96
|
+
# Creates a new Generator that only produces a value when the block `condition` returns a truthy value.
|
97
|
+
def where(&condition)
|
98
|
+
self.map do |result|
|
99
|
+
if condition.call(*result)
|
100
|
+
result
|
101
|
+
else
|
102
|
+
:"_PropCheck.filter_me"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
# self.map do |*result|
|
106
|
+
# if condition.call(*result)
|
107
|
+
# result
|
108
|
+
# else
|
109
|
+
# :'_PropCheck.filter_me'
|
110
|
+
# end
|
111
|
+
# end
|
112
|
+
end
|
94
113
|
end
|
95
114
|
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
##
|
4
|
+
# Contains the logic to combine potentially many before/after/around hooks
|
5
|
+
# into a single pair of procedures called `before` and `after`.
|
6
|
+
#
|
7
|
+
# _Note: This module is an implementation detail of PropCheck._
|
8
|
+
#
|
9
|
+
# These can be invoked by manually calling `#before` and `#after`.
|
10
|
+
# Important:
|
11
|
+
# - Always call first `#before` and then `#after`.
|
12
|
+
# This is required to make sure that `around` callbacks will work properly.
|
13
|
+
# - Make sure that if you call `#before`, to also call `#after`.
|
14
|
+
# It is thus highly recommended to call `#after` inside an `ensure`.
|
15
|
+
# This is to make sure that `around` callbacks indeed perform their proper cleanup.
|
16
|
+
#
|
17
|
+
# Alternatively, check out `PropCheck::Hooks::Enumerable` which allows
|
18
|
+
# wrapping the elements of an enumerable with hooks.
|
19
|
+
class PropCheck::Hooks
|
20
|
+
# attr_reader :before, :after, :around
|
21
|
+
def initialize()
|
22
|
+
@before = proc {}
|
23
|
+
@after = proc {}
|
24
|
+
@around = proc { |*args, &block| block.call(*args) }
|
25
|
+
end
|
26
|
+
|
27
|
+
def wrap_enum(enumerable)
|
28
|
+
PropCheck::Hooks::Enumerable.new(enumerable, self)
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
##
|
33
|
+
# Wraps a block with all hooks that were configured this far.
|
34
|
+
#
|
35
|
+
# This means that whenever the block is called,
|
36
|
+
# the before/around/after hooks are called before/around/after it.
|
37
|
+
def wrap_block(&block)
|
38
|
+
proc { |*args| call(*args, &block) }
|
39
|
+
end
|
40
|
+
|
41
|
+
##
|
42
|
+
# Wraps a block with all hooks that were configured this far,
|
43
|
+
# and immediately calls it using the given `*args`.
|
44
|
+
#
|
45
|
+
# See also #wrap_block
|
46
|
+
def call(*args, &block)
|
47
|
+
begin
|
48
|
+
@before.call()
|
49
|
+
@around.call do
|
50
|
+
block.call(*args)
|
51
|
+
end
|
52
|
+
ensure
|
53
|
+
@after.call()
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
##
|
58
|
+
# Adds `hook` to the `before` proc.
|
59
|
+
# It is called after earlier-added `before` procs.
|
60
|
+
def add_before(&hook)
|
61
|
+
old_before = @before
|
62
|
+
@before = proc {
|
63
|
+
old_before.call
|
64
|
+
hook.call
|
65
|
+
}
|
66
|
+
end
|
67
|
+
|
68
|
+
##
|
69
|
+
# Adds `hook` to the `after` proc.
|
70
|
+
# It is called before earlier-added `after` procs.
|
71
|
+
def add_after(&hook)
|
72
|
+
old_after = @after
|
73
|
+
@after = proc {
|
74
|
+
hook.call
|
75
|
+
old_after.call
|
76
|
+
}
|
77
|
+
end
|
78
|
+
|
79
|
+
##
|
80
|
+
# Adds `hook` to the `around` proc.
|
81
|
+
# It is called _inside_ earlier-added `around` procs.
|
82
|
+
def add_around(&hook)
|
83
|
+
old_around = @around
|
84
|
+
@around = proc do |&block|
|
85
|
+
old_around.call do |*args|
|
86
|
+
hook.call(*args, &block)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
##
|
92
|
+
# Wraps enumerable `inner` with a `PropCheck::Hooks` object
|
93
|
+
# such that the before/after/around hooks are called
|
94
|
+
# before/after/around each element that is fetched from `inner`.
|
95
|
+
#
|
96
|
+
# This is very helpful if you need to perform cleanup logic
|
97
|
+
# before/after/around e.g. data is generated or fetched.
|
98
|
+
#
|
99
|
+
# Note that whatever is after a `yield` in an `around` hook
|
100
|
+
# is not guaranteed to be called (for instance when a StopIteration is raised).
|
101
|
+
# Thus: make sure you use `ensure` to clean up resources.
|
102
|
+
class Enumerable
|
103
|
+
include ::Enumerable
|
104
|
+
|
105
|
+
def initialize(inner, hooks)
|
106
|
+
@inner = inner
|
107
|
+
@hooks = hooks
|
108
|
+
end
|
109
|
+
|
110
|
+
def each(&task)
|
111
|
+
return to_enum(:each) unless block_given?
|
112
|
+
|
113
|
+
enum = @inner.to_enum
|
114
|
+
|
115
|
+
wrapped_yielder = @hooks.wrap_block do
|
116
|
+
yield enum.next(&task)
|
117
|
+
end
|
118
|
+
|
119
|
+
loop(&wrapped_yielder)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
data/lib/prop_check/lazy_tree.rb
CHANGED
@@ -73,7 +73,8 @@ module PropCheck
|
|
73
73
|
[tree.root].lazy_append(new_children)
|
74
74
|
end
|
75
75
|
|
76
|
-
squish
|
76
|
+
squish
|
77
|
+
.call(self, [])
|
77
78
|
|
78
79
|
# base = [root]
|
79
80
|
# recursive = children.map(&:each)
|
@@ -99,7 +100,8 @@ module PropCheck
|
|
99
100
|
# >> LazyTree.new(1, [LazyTree.new(2, [LazyTree.new(3)]), LazyTree.new(4)]).to_a
|
100
101
|
# => [1, 4, 2, 3]
|
101
102
|
def to_a
|
102
|
-
each
|
103
|
+
each
|
104
|
+
.force
|
103
105
|
end
|
104
106
|
|
105
107
|
# TODO: fix implementation
|
data/lib/prop_check/property.rb
CHANGED
@@ -1,24 +1,41 @@
|
|
1
1
|
require 'stringio'
|
2
|
+
require "awesome_print"
|
2
3
|
|
3
4
|
require 'prop_check/property/configuration'
|
4
|
-
require 'prop_check/
|
5
|
+
require 'prop_check/hooks'
|
5
6
|
module PropCheck
|
6
7
|
##
|
7
8
|
# Run properties
|
8
9
|
class Property
|
9
10
|
|
10
11
|
##
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
12
|
+
# Main entry-point to create (and possibly immediately run) a property-test.
|
13
|
+
#
|
14
|
+
# This method accepts a list of generators and a block.
|
15
|
+
# The block will then be executed many times, passing the values generated by the generators
|
16
|
+
# as respective arguments:
|
17
|
+
#
|
18
|
+
# ```
|
19
|
+
# include PropCheck::Generators
|
20
|
+
# PropCheck.forall(integer(), float()) { |x, y| ... }
|
21
|
+
# ```
|
22
|
+
#
|
23
|
+
# It is also possible (and recommended when having more than a few generators) to use a keyword-list
|
24
|
+
# of generators instead:
|
25
|
+
#
|
26
|
+
# ```
|
27
|
+
# include PropCheck::Generators
|
28
|
+
# PropCheck.forall(x: integer(), y: float()) { |x:, y:| ... }
|
29
|
+
# ```
|
30
|
+
#
|
14
31
|
#
|
15
32
|
# If you do not pass a block right away,
|
16
33
|
# a Property object is returned, which you can call the other instance methods
|
17
34
|
# of this class on before finally passing a block to it using `#check`.
|
18
|
-
# (so `forall(
|
19
|
-
def self.forall(
|
35
|
+
# (so `forall(Generators.integer) do |val| ... end` and forall(Generators.integer).check do |val| ... end` are the same)
|
36
|
+
def self.forall(*bindings, &block)
|
20
37
|
|
21
|
-
property = new(bindings)
|
38
|
+
property = new(*bindings)
|
22
39
|
|
23
40
|
return property.check(&block) if block_given?
|
24
41
|
|
@@ -44,12 +61,14 @@ module PropCheck
|
|
44
61
|
|
45
62
|
attr_reader :bindings, :condition
|
46
63
|
|
47
|
-
def initialize(**
|
48
|
-
raise ArgumentError, 'No bindings specified!' if bindings.empty?
|
64
|
+
def initialize(*bindings, **kwbindings)
|
65
|
+
raise ArgumentError, 'No bindings specified!' if bindings.empty? && kwbindings.empty?
|
49
66
|
|
50
67
|
@bindings = bindings
|
51
|
-
@
|
68
|
+
@kwbindings = kwbindings
|
69
|
+
@condition = proc { true }
|
52
70
|
@config = self.class.configuration
|
71
|
+
@hooks = PropCheck::Hooks.new
|
53
72
|
end
|
54
73
|
|
55
74
|
##
|
@@ -86,21 +105,46 @@ module PropCheck
|
|
86
105
|
# Only filter if you have few inputs to reject. Otherwise, improve your generators.
|
87
106
|
def where(&condition)
|
88
107
|
original_condition = @condition.dup
|
89
|
-
@condition =
|
108
|
+
@condition = proc do |*args|
|
109
|
+
original_condition.call(*args) && condition.call(*args)
|
110
|
+
end
|
111
|
+
|
112
|
+
self
|
113
|
+
end
|
90
114
|
|
115
|
+
def before(&hook)
|
116
|
+
@hooks.add_before(&hook)
|
117
|
+
self
|
118
|
+
end
|
119
|
+
|
120
|
+
def after(&hook)
|
121
|
+
@hooks.add_after(&hook)
|
122
|
+
self
|
123
|
+
end
|
124
|
+
|
125
|
+
def around(&hook)
|
126
|
+
@hooks.add_around(&hook)
|
91
127
|
self
|
92
128
|
end
|
93
129
|
|
94
130
|
##
|
95
131
|
# Checks the property (after settings have been altered using the other instance methods in this class.)
|
96
132
|
def check(&block)
|
97
|
-
|
133
|
+
gens =
|
134
|
+
if @kwbindings != {}
|
135
|
+
kwbinding_generator = PropCheck::Generators.fixed_hash(**@kwbindings)
|
136
|
+
@bindings + [kwbinding_generator]
|
137
|
+
else
|
138
|
+
@bindings
|
139
|
+
end
|
140
|
+
binding_generator = PropCheck::Generators.tuple(*gens)
|
141
|
+
# binding_generator = PropCheck::Generators.fixed_hash(**@kwbindings)
|
98
142
|
|
99
143
|
n_runs = 0
|
100
144
|
n_successful = 0
|
101
145
|
|
102
146
|
# Loop stops at first exception
|
103
|
-
|
147
|
+
attempts_enum(binding_generator).each do |generator_result|
|
104
148
|
n_runs += 1
|
105
149
|
check_attempt(generator_result, n_successful, &block)
|
106
150
|
n_successful += 1
|
@@ -112,6 +156,10 @@ module PropCheck
|
|
112
156
|
private def ensure_not_exhausted!(n_runs)
|
113
157
|
return if n_runs >= @config.n_runs
|
114
158
|
|
159
|
+
raise_generator_exhausted!
|
160
|
+
end
|
161
|
+
|
162
|
+
private def raise_generator_exhausted!()
|
115
163
|
raise Errors::GeneratorExhaustedError, """
|
116
164
|
Could not perform `n_runs = #{@config.n_runs}` runs,
|
117
165
|
(exhausted #{@config.max_generate_attempts} tries)
|
@@ -123,7 +171,7 @@ module PropCheck
|
|
123
171
|
end
|
124
172
|
|
125
173
|
private def check_attempt(generator_result, n_successful, &block)
|
126
|
-
|
174
|
+
block.call(*generator_result.root)
|
127
175
|
|
128
176
|
# immediately stop (without shrinnking) for when the app is asked
|
129
177
|
# to close by outside intervention
|
@@ -151,23 +199,26 @@ module PropCheck
|
|
151
199
|
raise e, output_string, e.backtrace
|
152
200
|
end
|
153
201
|
|
154
|
-
private def
|
202
|
+
private def attempts_enum(binding_generator)
|
203
|
+
@hooks
|
204
|
+
.wrap_enum(raw_attempts_enum(binding_generator))
|
205
|
+
.lazy
|
206
|
+
.take(@config.n_runs)
|
207
|
+
end
|
155
208
|
|
209
|
+
private def raw_attempts_enum(binding_generator)
|
156
210
|
rng = Random::DEFAULT
|
157
|
-
n_runs = 0
|
158
211
|
size = 1
|
159
212
|
(0...@config.max_generate_attempts)
|
160
213
|
.lazy
|
161
214
|
.map { binding_generator.generate(size, rng) }
|
162
|
-
.reject { |val| val.root == :"_PropCheck.filter_me" }
|
163
|
-
.select { |val|
|
215
|
+
.reject { |val| val.root.any? { |elem| elem == :"_PropCheck.filter_me" }}
|
216
|
+
.select { |val| @condition.call(*val.root) }
|
164
217
|
.map do |result|
|
165
|
-
n_runs += 1
|
166
218
|
size += 1
|
167
219
|
|
168
220
|
result
|
169
221
|
end
|
170
|
-
.take_while { n_runs <= @config.n_runs }
|
171
222
|
end
|
172
223
|
|
173
224
|
private def show_problem_output(problem, generator_results, n_successful, &block)
|
@@ -193,31 +244,36 @@ module PropCheck
|
|
193
244
|
end
|
194
245
|
|
195
246
|
private def post_output(output, n_shrink_steps, shrunken_result, shrunken_exception)
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
247
|
+
if n_shrink_steps == 0
|
248
|
+
output.puts '(shrinking impossible)'
|
249
|
+
else
|
250
|
+
output.puts ''
|
251
|
+
output.puts "Shrunken input (after #{n_shrink_steps} shrink steps):"
|
252
|
+
output.puts "`#{print_roots(shrunken_result)}`"
|
253
|
+
output.puts ""
|
254
|
+
output.puts "Shrunken exception:\n---\n#{shrunken_exception}"
|
255
|
+
output.puts "---"
|
256
|
+
output.puts ""
|
257
|
+
end
|
204
258
|
output
|
205
259
|
end
|
206
260
|
|
207
|
-
private def print_roots(
|
208
|
-
|
209
|
-
|
210
|
-
|
261
|
+
private def print_roots(lazy_tree_val)
|
262
|
+
if lazy_tree_val.is_a?(Array) && lazy_tree_val.length == 1 && lazy_tree_val[0].is_a?(Hash)
|
263
|
+
lazy_tree_val[0].ai
|
264
|
+
else
|
265
|
+
lazy_tree_val.ai
|
266
|
+
end
|
211
267
|
end
|
212
268
|
|
213
|
-
private def shrink(bindings_tree, io, &
|
269
|
+
private def shrink(bindings_tree, io, &block)
|
214
270
|
io.puts 'Shrinking...' if @config.verbose
|
215
271
|
problem_child = bindings_tree
|
216
272
|
siblings = problem_child.children.lazy
|
217
273
|
parent_siblings = nil
|
218
274
|
problem_exception = nil
|
219
275
|
shrink_steps = 0
|
220
|
-
(0..@config.max_shrink_steps).each do
|
276
|
+
@hooks.wrap_enum(0..@config.max_shrink_steps).lazy.each do
|
221
277
|
begin
|
222
278
|
sibling = siblings.next
|
223
279
|
rescue StopIteration
|
@@ -232,7 +288,7 @@ module PropCheck
|
|
232
288
|
io.print '.' if @config.verbose
|
233
289
|
|
234
290
|
begin
|
235
|
-
|
291
|
+
block.call(*sibling.root)
|
236
292
|
rescue Exception => e
|
237
293
|
problem_child = sibling
|
238
294
|
parent_siblings = siblings
|
data/lib/prop_check/version.rb
CHANGED
data/prop_check.gemspec
CHANGED
@@ -36,7 +36,5 @@ Gem::Specification.new do |spec|
|
|
36
36
|
|
37
37
|
spec.required_ruby_version = '>= 2.5.1'
|
38
38
|
|
39
|
-
spec.
|
40
|
-
spec.add_development_dependency "rake", "~> 10.0"
|
41
|
-
spec.add_development_dependency "rspec", "~> 3.0"
|
39
|
+
spec.add_dependency 'awesome_print', '~> 1.8'
|
42
40
|
end
|
metadata
CHANGED
@@ -1,57 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: prop_check
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Qqwy/Wiebe-Marten Wijnja
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-07-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: awesome_print
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
20
|
-
type: :
|
19
|
+
version: '1.8'
|
20
|
+
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: rake
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - "~>"
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '10.0'
|
34
|
-
type: :development
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - "~>"
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '10.0'
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: rspec
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - "~>"
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: '3.0'
|
48
|
-
type: :development
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - "~>"
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: '3.0'
|
26
|
+
version: '1.8'
|
55
27
|
description: PropCheck allows you to do property-based testing, including shrinking.
|
56
28
|
(akin to Haskell's QuickCheck, Erlang's PropEr, Elixir's StreamData). This means
|
57
29
|
that your test are run many times with different, autogenerated inputs, and as soon
|
@@ -71,7 +43,6 @@ files:
|
|
71
43
|
- CHANGELOG.md
|
72
44
|
- CODE_OF_CONDUCT.md
|
73
45
|
- Gemfile
|
74
|
-
- Gemfile.lock
|
75
46
|
- LICENSE.txt
|
76
47
|
- README.md
|
77
48
|
- Rakefile
|
@@ -82,11 +53,10 @@ files:
|
|
82
53
|
- lib/prop_check/generators.rb
|
83
54
|
- lib/prop_check/helper.rb
|
84
55
|
- lib/prop_check/helper/lazy_append.rb
|
56
|
+
- lib/prop_check/hooks.rb
|
85
57
|
- lib/prop_check/lazy_tree.rb
|
86
58
|
- lib/prop_check/property.rb
|
87
|
-
- lib/prop_check/property/check_evaluator.rb
|
88
59
|
- lib/prop_check/property/configuration.rb
|
89
|
-
- lib/prop_check/rspec.rb
|
90
60
|
- lib/prop_check/version.rb
|
91
61
|
- prop_check.gemspec
|
92
62
|
homepage: https://github.com/Qqwy/ruby-prop_check/
|
@@ -96,7 +66,7 @@ metadata:
|
|
96
66
|
homepage_uri: https://github.com/Qqwy/ruby-prop_check/
|
97
67
|
source_code_uri: https://github.com/Qqwy/ruby-prop_check/
|
98
68
|
changelog_uri: https://github.com/Qqwy/ruby-prop_check/CHANGELOG.md
|
99
|
-
post_install_message:
|
69
|
+
post_install_message:
|
100
70
|
rdoc_options: []
|
101
71
|
require_paths:
|
102
72
|
- lib
|
@@ -111,9 +81,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
111
81
|
- !ruby/object:Gem::Version
|
112
82
|
version: '0'
|
113
83
|
requirements: []
|
114
|
-
|
115
|
-
|
116
|
-
signing_key:
|
84
|
+
rubygems_version: 3.0.3
|
85
|
+
signing_key:
|
117
86
|
specification_version: 4
|
118
87
|
summary: PropCheck allows you to do property-based testing, including shrinking.
|
119
88
|
test_files: []
|
data/Gemfile.lock
DELETED
@@ -1,48 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
prop_check (0.6.1)
|
5
|
-
|
6
|
-
GEM
|
7
|
-
remote: https://rubygems.org/
|
8
|
-
specs:
|
9
|
-
diff-lcs (1.3)
|
10
|
-
docile (1.3.2)
|
11
|
-
doctest-core (0.0.2)
|
12
|
-
doctest-rspec (0.0.3)
|
13
|
-
doctest-core (~> 0.0.2)
|
14
|
-
rspec
|
15
|
-
json (2.2.0)
|
16
|
-
rake (10.5.0)
|
17
|
-
rspec (3.8.0)
|
18
|
-
rspec-core (~> 3.8.0)
|
19
|
-
rspec-expectations (~> 3.8.0)
|
20
|
-
rspec-mocks (~> 3.8.0)
|
21
|
-
rspec-core (3.8.1)
|
22
|
-
rspec-support (~> 3.8.0)
|
23
|
-
rspec-expectations (3.8.4)
|
24
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
25
|
-
rspec-support (~> 3.8.0)
|
26
|
-
rspec-mocks (3.8.1)
|
27
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
28
|
-
rspec-support (~> 3.8.0)
|
29
|
-
rspec-support (3.8.2)
|
30
|
-
simplecov (0.16.1)
|
31
|
-
docile (~> 1.1)
|
32
|
-
json (>= 1.8, < 3)
|
33
|
-
simplecov-html (~> 0.10.0)
|
34
|
-
simplecov-html (0.10.2)
|
35
|
-
|
36
|
-
PLATFORMS
|
37
|
-
ruby
|
38
|
-
|
39
|
-
DEPENDENCIES
|
40
|
-
bundler (~> 2.0)
|
41
|
-
doctest-rspec
|
42
|
-
prop_check!
|
43
|
-
rake (~> 10.0)
|
44
|
-
rspec (~> 3.0)
|
45
|
-
simplecov
|
46
|
-
|
47
|
-
BUNDLED WITH
|
48
|
-
2.0.2
|
@@ -1,45 +0,0 @@
|
|
1
|
-
module PropCheck
|
2
|
-
class Property
|
3
|
-
##
|
4
|
-
# A wrapper class that implements the 'Cloaker' concept
|
5
|
-
# which allows us to refer to variables set in 'bindings',
|
6
|
-
# while still being able to access things that are only in scope
|
7
|
-
# in the creator of '&block'.
|
8
|
-
#
|
9
|
-
# This allows us to bind the variables specified in `bindings`
|
10
|
-
# one way during checking and another way during shrinking.
|
11
|
-
class CheckEvaluator
|
12
|
-
include RSpec::Matchers if Object.const_defined?('RSpec')
|
13
|
-
|
14
|
-
def initialize(bindings, &block)
|
15
|
-
@caller = block.binding.receiver
|
16
|
-
@block = block
|
17
|
-
define_named_instance_methods(bindings)
|
18
|
-
end
|
19
|
-
|
20
|
-
def call
|
21
|
-
self.instance_exec(&@block)
|
22
|
-
end
|
23
|
-
|
24
|
-
private def define_named_instance_methods(results)
|
25
|
-
results.each do |name, result|
|
26
|
-
define_singleton_method(name) { result }
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
##
|
31
|
-
# Dispatches to caller whenever something is not part of `bindings`.
|
32
|
-
# (No need to invoke this method manually)
|
33
|
-
def method_missing(method, *args, &block)
|
34
|
-
@caller.__send__(method, *args, &block) || super
|
35
|
-
end
|
36
|
-
|
37
|
-
##
|
38
|
-
# Checks respond_to of caller whenever something is not part of `bindings`.
|
39
|
-
# (No need to invoke this method manually)
|
40
|
-
def respond_to_missing?(*args)
|
41
|
-
@caller.respond_to?(*args) || super
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
data/lib/prop_check/rspec.rb
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
module PropCheck
|
2
|
-
##
|
3
|
-
# Integration with RSpec
|
4
|
-
module RSpec
|
5
|
-
# To make it available within examples
|
6
|
-
def self.extend_object(obj)
|
7
|
-
obj.define_method(:forall) do |*args, **kwargs, &block|
|
8
|
-
PropCheck::Property.forall(*args, **kwargs) do
|
9
|
-
instance_exec(self, &block)
|
10
|
-
end
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|