prop_check 0.11.0 → 0.11.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.tool-versions +1 -1
- data/lib/prop_check/generator.rb +23 -27
- data/lib/prop_check/generators.rb +22 -11
- data/lib/prop_check/helper.rb +14 -0
- data/lib/prop_check/lazy_tree.rb +9 -17
- data/lib/prop_check/property.rb +34 -23
- data/lib/prop_check/property/configuration.rb +14 -2
- data/lib/prop_check/property/shrinker.rb +2 -1
- data/lib/prop_check/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '099a3bafe1fae426f476e3add7fb93ab333506670c72c35bb78e5e505b9f0764'
|
4
|
+
data.tar.gz: d1c433ef97043f38c84a5bf8768fb46aa5fcc97f00518da831fbebc36142d2e5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 782411f48a77c4bc2213d6ecaecd70d7d7c3609c13bab2125ce853dedd7d3029fd8801ac882154d8c924cea212698740d8e330f8e2b90f460a5a87484413e560
|
7
|
+
data.tar.gz: dd18135801b66e2f073e82066506f8bb26cf031a4bd8d2fc208864acd7ef3a7ec564f8364628d34e4ab40429a5444a1dbc41f329bb4c7d3a608011407bb0a923
|
data/.tool-versions
CHANGED
@@ -1 +1 @@
|
|
1
|
-
ruby 2.
|
1
|
+
ruby 2.7.1
|
data/lib/prop_check/generator.rb
CHANGED
@@ -11,6 +11,7 @@ module PropCheck
|
|
11
11
|
@@default_size = 10
|
12
12
|
@@default_rng = Random.new
|
13
13
|
@@max_consecutive_attempts = 100
|
14
|
+
@@default_kwargs = {size: @@default_size, rng: @@default_rng, max_consecutive_attempts: @@max_consecutive_attempts}
|
14
15
|
|
15
16
|
##
|
16
17
|
# Being a special kind of Proc, a Generator wraps a block.
|
@@ -21,12 +22,13 @@ module PropCheck
|
|
21
22
|
##
|
22
23
|
# Given a `size` (integer) and a random number generator state `rng`,
|
23
24
|
# generate a LazyTree.
|
24
|
-
def generate(
|
25
|
-
(
|
26
|
-
|
27
|
-
next if res == :"PropCheck.filter_me"
|
25
|
+
def generate(**kwargs)
|
26
|
+
kwargs = @@default_kwargs.merge(kwargs)
|
27
|
+
max_consecutive_attempts = kwargs[:max_consecutive_attempts]
|
28
28
|
|
29
|
-
|
29
|
+
(0..max_consecutive_attempts).each do
|
30
|
+
res = @block.call(**kwargs)
|
31
|
+
return res unless res.root == :"_PropCheck.filter_me"
|
30
32
|
end
|
31
33
|
|
32
34
|
raise Errors::GeneratorExhaustedError, """
|
@@ -40,18 +42,18 @@ module PropCheck
|
|
40
42
|
# Generates a value, and only return this value
|
41
43
|
# (drop information for shrinking)
|
42
44
|
#
|
43
|
-
# >> Generators.integer.call(1000, Random.new(42))
|
45
|
+
# >> Generators.integer.call(size: 1000, rng: Random.new(42))
|
44
46
|
# => 126
|
45
|
-
def call(
|
46
|
-
generate(
|
47
|
+
def call(**kwargs)
|
48
|
+
generate(**@@default_kwargs.merge(kwargs)).root
|
47
49
|
end
|
48
50
|
|
49
51
|
##
|
50
52
|
# Returns `num_of_samples` values from calling this Generator.
|
51
53
|
# This is mostly useful for debugging if a generator behaves as you intend it to.
|
52
|
-
def sample(num_of_samples = 10,
|
54
|
+
def sample(num_of_samples = 10, **kwargs)
|
53
55
|
num_of_samples.times.map do
|
54
|
-
call(
|
56
|
+
call(**@@default_kwargs.merge(kwargs))
|
55
57
|
end
|
56
58
|
end
|
57
59
|
|
@@ -61,10 +63,10 @@ module PropCheck
|
|
61
63
|
#
|
62
64
|
# Keen readers may notice this as the Monadic 'pure'/'return' implementation for Generators.
|
63
65
|
#
|
64
|
-
# >> Generators.integer.bind { |a| Generators.integer.bind { |b| Generator.wrap([a , b]) } }.call(100, Random.new(42))
|
66
|
+
# >> Generators.integer.bind { |a| Generators.integer.bind { |b| Generator.wrap([a , b]) } }.call(size: 100, rng: Random.new(42))
|
65
67
|
# => [2, 79]
|
66
68
|
def self.wrap(val)
|
67
|
-
Generator.new {
|
69
|
+
Generator.new { LazyTree.wrap(val) }
|
68
70
|
end
|
69
71
|
|
70
72
|
##
|
@@ -73,7 +75,7 @@ module PropCheck
|
|
73
75
|
#
|
74
76
|
# Keen readers may notice this as the Monadic 'bind' (sometimes known as '>>=') implementation for Generators.
|
75
77
|
#
|
76
|
-
# >> Generators.integer.bind { |a| Generators.integer.bind { |b| Generator.wrap([a , b]) } }.call(100, Random.new(42))
|
78
|
+
# >> Generators.integer.bind { |a| Generators.integer.bind { |b| Generator.wrap([a , b]) } }.call(size: 100, rng: Random.new(42))
|
77
79
|
# => [2, 79]
|
78
80
|
def bind(&generator_proc)
|
79
81
|
# Generator.new do |size, rng|
|
@@ -83,11 +85,11 @@ module PropCheck
|
|
83
85
|
# inner_generator.generate(size, rng)
|
84
86
|
# end.flatten
|
85
87
|
# end
|
86
|
-
Generator.new do |
|
87
|
-
outer_result = self.generate(
|
88
|
+
Generator.new do |**kwargs|
|
89
|
+
outer_result = self.generate(**kwargs)
|
88
90
|
outer_result.bind do |outer_val|
|
89
91
|
inner_generator = generator_proc.call(outer_val)
|
90
|
-
inner_generator.generate(
|
92
|
+
inner_generator.generate(**kwargs)
|
91
93
|
end
|
92
94
|
end
|
93
95
|
end
|
@@ -95,11 +97,11 @@ module PropCheck
|
|
95
97
|
##
|
96
98
|
# Creates a new Generator that returns a value by running `proc` on the output of the current Generator.
|
97
99
|
#
|
98
|
-
# >> Generators.choose(32..128).map(&:chr).call(10, Random.new(42))
|
100
|
+
# >> Generators.choose(32..128).map(&:chr).call(size: 10, rng: Random.new(42))
|
99
101
|
# => "S"
|
100
102
|
def map(&proc)
|
101
|
-
Generator.new do |
|
102
|
-
result = self.generate(
|
103
|
+
Generator.new do |**kwargs|
|
104
|
+
result = self.generate(**kwargs)
|
103
105
|
result.map(&proc)
|
104
106
|
end
|
105
107
|
end
|
@@ -108,19 +110,13 @@ module PropCheck
|
|
108
110
|
# Creates a new Generator that only produces a value when the block `condition` returns a truthy value.
|
109
111
|
def where(&condition)
|
110
112
|
self.map do |result|
|
111
|
-
if condition.call(result)
|
113
|
+
# if condition.call(*result)
|
114
|
+
if PropCheck::Helper.call_splatted(result, &condition)
|
112
115
|
result
|
113
116
|
else
|
114
117
|
:"_PropCheck.filter_me"
|
115
118
|
end
|
116
119
|
end
|
117
|
-
# self.map do |*result|
|
118
|
-
# if condition.call(*result)
|
119
|
-
# result
|
120
|
-
# else
|
121
|
-
# :'_PropCheck.filter_me'
|
122
|
-
# end
|
123
|
-
# end
|
124
120
|
end
|
125
121
|
end
|
126
122
|
end
|
@@ -10,6 +10,7 @@ module PropCheck
|
|
10
10
|
# where you want to use them.
|
11
11
|
module Generators
|
12
12
|
extend self
|
13
|
+
|
13
14
|
##
|
14
15
|
# Always returns the same value, regardless of `size` or `rng` (random number generator state)
|
15
16
|
#
|
@@ -59,7 +60,7 @@ module PropCheck
|
|
59
60
|
# >> r = Random.new(42); Generators.choose(0..5).sample(size: 20000, rng: r)
|
60
61
|
# => [3, 4, 2, 4, 4, 1, 2, 2, 2, 4]
|
61
62
|
def choose(range)
|
62
|
-
Generator.new do |
|
63
|
+
Generator.new do |rng:, **|
|
63
64
|
val = rng.rand(range)
|
64
65
|
LazyTree.new(val, integer_shrink(val))
|
65
66
|
end
|
@@ -73,14 +74,14 @@ module PropCheck
|
|
73
74
|
#
|
74
75
|
# Shrinks to integers closer to zero.
|
75
76
|
#
|
76
|
-
# >> Generators.integer.call(2, Random.new(42))
|
77
|
+
# >> Generators.integer.call(size: 2, rng: Random.new(42))
|
77
78
|
# => 1
|
78
|
-
# >> Generators.integer.call(10000, Random.new(42))
|
79
|
+
# >> Generators.integer.call(size: 10000, rng: Random.new(42))
|
79
80
|
# => 5795
|
80
81
|
# >> r = Random.new(42); Generators.integer.sample(size: 20000, rng: r)
|
81
82
|
# => [-4205, -19140, 18158, -8716, -13735, -3150, 17194, 1962, -3977, -18315]
|
82
83
|
def integer
|
83
|
-
Generator.new do |size
|
84
|
+
Generator.new do |size:, rng:, **|
|
84
85
|
val = rng.rand(-size..size)
|
85
86
|
LazyTree.new(val, integer_shrink(val))
|
86
87
|
end
|
@@ -193,12 +194,12 @@ module PropCheck
|
|
193
194
|
#
|
194
195
|
# Shrinks element generators, one at a time (trying last one first).
|
195
196
|
#
|
196
|
-
# >> Generators.tuple(Generators.integer, Generators.real_float).call(10, Random.new(42))
|
197
|
+
# >> Generators.tuple(Generators.integer, Generators.real_float).call(size: 10, rng: Random.new(42))
|
197
198
|
# => [-4, 13.0]
|
198
199
|
def tuple(*generators)
|
199
|
-
Generator.new do |
|
200
|
+
Generator.new do |**kwargs|
|
200
201
|
LazyTree.zip(generators.map do |generator|
|
201
|
-
generator.generate(
|
202
|
+
generator.generate(**kwargs)
|
202
203
|
end)
|
203
204
|
end
|
204
205
|
end
|
@@ -210,7 +211,7 @@ module PropCheck
|
|
210
211
|
#
|
211
212
|
# Shrinks element generators.
|
212
213
|
#
|
213
|
-
# >> Generators.fixed_hash(a: Generators.integer(), b: Generators.real_float(), c: Generators.integer()).call(10, Random.new(42))
|
214
|
+
# >> Generators.fixed_hash(a: Generators.integer(), b: Generators.real_float(), c: Generators.integer()).call(size: 10, rng: Random.new(42))
|
214
215
|
# => {:a=>-4, :b=>13.0, :c=>-3}
|
215
216
|
def fixed_hash(hash)
|
216
217
|
keypair_generators =
|
@@ -265,7 +266,6 @@ module PropCheck
|
|
265
266
|
end
|
266
267
|
end
|
267
268
|
|
268
|
-
|
269
269
|
##
|
270
270
|
# Generates a hash of key->values,
|
271
271
|
# where each of the keys is made using the `key_generator`
|
@@ -275,12 +275,23 @@ module PropCheck
|
|
275
275
|
#
|
276
276
|
# >> Generators.hash(Generators.printable_ascii_string, Generators.positive_integer).sample(5, size: 3, rng: Random.new(42))
|
277
277
|
# => [{""=>2, "g\\4"=>4, "rv"=>2}, {"7"=>2}, {"!"=>1, "E!"=>1}, {"kY5"=>2}, {}]
|
278
|
-
def hash(
|
278
|
+
def hash(*args, **kwargs)
|
279
|
+
if args.length == 2
|
280
|
+
hash_of(*args, **kwargs)
|
281
|
+
else
|
282
|
+
super
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
##
|
287
|
+
#
|
288
|
+
# Alias for `#hash` that does not conflict with a possibly overriden `Object#hash`.
|
289
|
+
#
|
290
|
+
def hash_of(key_generator, value_generator, **kwargs)
|
279
291
|
array(tuple(key_generator, value_generator), **kwargs)
|
280
292
|
.map(&:to_h)
|
281
293
|
end
|
282
294
|
|
283
|
-
|
284
295
|
@alphanumeric_chars = [('a'..'z'), ('A'..'Z'), ('0'..'9')].flat_map(&:to_a).freeze
|
285
296
|
##
|
286
297
|
# Generates a single-character string
|
data/lib/prop_check/helper.rb
CHANGED
@@ -31,5 +31,19 @@ module PropCheck
|
|
31
31
|
def lazy_append(this_enumerator, other_enumerator)
|
32
32
|
[this_enumerator, other_enumerator].lazy.flat_map(&:lazy)
|
33
33
|
end
|
34
|
+
|
35
|
+
def call_splatted(val, &block)
|
36
|
+
case val
|
37
|
+
when Hash
|
38
|
+
block.call(**val)
|
39
|
+
else
|
40
|
+
block.call(val)
|
41
|
+
end
|
42
|
+
# if kwval != {}
|
43
|
+
# block.call(**kwval)
|
44
|
+
# else
|
45
|
+
# block.call(*val)
|
46
|
+
# end
|
47
|
+
end
|
34
48
|
end
|
35
49
|
end
|
data/lib/prop_check/lazy_tree.rb
CHANGED
@@ -7,6 +7,8 @@ module PropCheck
|
|
7
7
|
class LazyTree
|
8
8
|
require 'prop_check/helper'
|
9
9
|
|
10
|
+
include Enumerable
|
11
|
+
|
10
12
|
attr_accessor :root, :children
|
11
13
|
def initialize(root, children = [].lazy)
|
12
14
|
@root = root
|
@@ -66,25 +68,15 @@ module PropCheck
|
|
66
68
|
# >> LazyTree.new(1, [LazyTree.new(2, [LazyTree.new(3)]), LazyTree.new(4)]).each.force
|
67
69
|
# => [1, 4, 2, 3]
|
68
70
|
def each(&block)
|
69
|
-
|
70
|
-
new_children = tree.children.reduce(list) { |acc, elem| squish.call(elem, acc) }
|
71
|
-
PropCheck::Helper.lazy_append([tree.root], new_children)
|
72
|
-
end
|
73
|
-
|
74
|
-
squish
|
75
|
-
.call(self, [])
|
71
|
+
self.to_enum(:each) unless block_given?
|
76
72
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
# return res.each(&block) if block_given?
|
82
|
-
|
83
|
-
# res
|
73
|
+
squish([])
|
74
|
+
.each(&block)
|
75
|
+
end
|
84
76
|
|
85
|
-
|
86
|
-
|
87
|
-
|
77
|
+
protected def squish(arr)
|
78
|
+
new_children = self.children.reduce(arr) { |acc, elem| elem.squish(acc) }
|
79
|
+
PropCheck::Helper.lazy_append([self.root], new_children)
|
88
80
|
end
|
89
81
|
|
90
82
|
##
|
data/lib/prop_check/property.rb
CHANGED
@@ -35,9 +35,9 @@ module PropCheck
|
|
35
35
|
# a Property object is returned, which you can call the other instance methods
|
36
36
|
# of this class on before finally passing a block to it using `#check`.
|
37
37
|
# (so `forall(Generators.integer) do |val| ... end` and forall(Generators.integer).check do |val| ... end` are the same)
|
38
|
-
def self.forall(*bindings, &block)
|
38
|
+
def self.forall(*bindings, **kwbindings, &block)
|
39
39
|
|
40
|
-
property = new(*bindings)
|
40
|
+
property = new(*bindings, **kwbindings)
|
41
41
|
|
42
42
|
return property.check(&block) if block_given?
|
43
43
|
|
@@ -66,8 +66,9 @@ module PropCheck
|
|
66
66
|
def initialize(*bindings, **kwbindings)
|
67
67
|
raise ArgumentError, 'No bindings specified!' if bindings.empty? && kwbindings.empty?
|
68
68
|
|
69
|
-
@bindings = bindings
|
70
|
-
@kwbindings = kwbindings
|
69
|
+
# @bindings = bindings
|
70
|
+
# @kwbindings = kwbindings
|
71
|
+
@gen = gen_from_bindings(bindings, kwbindings)
|
71
72
|
@condition = proc { true }
|
72
73
|
@config = self.class.configuration
|
73
74
|
@hooks = PropCheck::Hooks.new
|
@@ -106,14 +107,17 @@ module PropCheck
|
|
106
107
|
# you might encounter a GeneratorExhaustedError.
|
107
108
|
# Only filter if you have few inputs to reject. Otherwise, improve your generators.
|
108
109
|
def where(&condition)
|
109
|
-
original_condition = @condition.dup
|
110
|
-
@condition = proc do
|
111
|
-
|
112
|
-
|
110
|
+
# original_condition = @condition.dup
|
111
|
+
# @condition = proc do |val|
|
112
|
+
# call_splatted(val, &original_condition) && call_splatted(val, &condition)
|
113
|
+
# # original_condition.call(val) && condition.call(val)
|
114
|
+
# end
|
115
|
+
@gen = @gen.where(&condition)
|
113
116
|
|
114
117
|
self
|
115
118
|
end
|
116
119
|
|
120
|
+
|
117
121
|
##
|
118
122
|
# Calls `hook` before each time a check is run with new data.
|
119
123
|
#
|
@@ -155,21 +159,11 @@ module PropCheck
|
|
155
159
|
##
|
156
160
|
# Checks the property (after settings have been altered using the other instance methods in this class.)
|
157
161
|
def check(&block)
|
158
|
-
gens =
|
159
|
-
if @kwbindings != {}
|
160
|
-
kwbinding_generator = PropCheck::Generators.fixed_hash(**@kwbindings)
|
161
|
-
@bindings + [kwbinding_generator]
|
162
|
-
else
|
163
|
-
@bindings
|
164
|
-
end
|
165
|
-
binding_generator = PropCheck::Generators.tuple(*gens)
|
166
|
-
# binding_generator = PropCheck::Generators.fixed_hash(**@kwbindings)
|
167
|
-
|
168
162
|
n_runs = 0
|
169
163
|
n_successful = 0
|
170
164
|
|
171
165
|
# Loop stops at first exception
|
172
|
-
attempts_enum(
|
166
|
+
attempts_enum(@gen).each do |generator_result|
|
173
167
|
n_runs += 1
|
174
168
|
check_attempt(generator_result, n_successful, &block)
|
175
169
|
n_successful += 1
|
@@ -178,6 +172,25 @@ module PropCheck
|
|
178
172
|
ensure_not_exhausted!(n_runs)
|
179
173
|
end
|
180
174
|
|
175
|
+
private def gen_from_bindings(bindings, kwbindings)
|
176
|
+
if bindings == [] && kwbindings != {}
|
177
|
+
PropCheck::Generators.fixed_hash(**kwbindings)
|
178
|
+
elsif bindings != [] && kwbindings == {}
|
179
|
+
if bindings.size == 1
|
180
|
+
bindings.first
|
181
|
+
else
|
182
|
+
PropCheck::Generators.tuple(*bindings)
|
183
|
+
end
|
184
|
+
else
|
185
|
+
raise ArgumentError,
|
186
|
+
'Attempted to use both normal and keyword bindings at the same time.
|
187
|
+
This is not supported because of the separation of positional and keyword arguments
|
188
|
+
(the old behaviour is deprecated in Ruby 2.7 and will be removed in 3.0)
|
189
|
+
c.f. https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/
|
190
|
+
'
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
181
194
|
private def ensure_not_exhausted!(n_runs)
|
182
195
|
return if n_runs >= @config.n_runs
|
183
196
|
|
@@ -196,7 +209,7 @@ module PropCheck
|
|
196
209
|
end
|
197
210
|
|
198
211
|
private def check_attempt(generator_result, n_successful, &block)
|
199
|
-
|
212
|
+
PropCheck::Helper.call_splatted(generator_result.root, &block)
|
200
213
|
|
201
214
|
# immediately stop (without shrinnking) for when the app is asked
|
202
215
|
# to close by outside intervention
|
@@ -236,9 +249,7 @@ module PropCheck
|
|
236
249
|
size = 1
|
237
250
|
(0...@config.max_generate_attempts)
|
238
251
|
.lazy
|
239
|
-
.map { binding_generator.generate(size, rng) }
|
240
|
-
.reject { |val| val.root.any? { |elem| elem == :"_PropCheck.filter_me" }}
|
241
|
-
.select { |val| @condition.call(*val.root) }
|
252
|
+
.map { binding_generator.generate(size: size, rng: rng, max_consecutive_attempts: @config.max_consecutive_attempts) }
|
242
253
|
.map do |result|
|
243
254
|
size += 1
|
244
255
|
|
@@ -1,8 +1,20 @@
|
|
1
1
|
module PropCheck
|
2
2
|
class Property
|
3
|
-
Configuration = Struct.new(
|
3
|
+
Configuration = Struct.new(
|
4
|
+
:verbose,
|
5
|
+
:n_runs,
|
6
|
+
:max_generate_attempts,
|
7
|
+
:max_shrink_steps,
|
8
|
+
:max_consecutive_attempts,
|
9
|
+
keyword_init: true) do
|
4
10
|
|
5
|
-
def initialize(
|
11
|
+
def initialize(
|
12
|
+
verbose: false,
|
13
|
+
n_runs: 1_000,
|
14
|
+
max_generate_attempts: 10_000,
|
15
|
+
max_shrink_steps: 10_000,
|
16
|
+
max_consecutive_attempts: 30
|
17
|
+
)
|
6
18
|
super
|
7
19
|
end
|
8
20
|
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'prop_check/helper'
|
1
2
|
class PropCheck::Property::Shrinker
|
2
3
|
def initialize(bindings_tree, io, hooks, config)
|
3
4
|
@problem_child = bindings_tree
|
@@ -62,7 +63,7 @@ class PropCheck::Property::Shrinker
|
|
62
63
|
|
63
64
|
private def safe_call_block(sibling, &block)
|
64
65
|
begin
|
65
|
-
|
66
|
+
PropCheck::Helper.call_splatted(sibling.root, &block)
|
66
67
|
# It is correct that we want to rescue _all_ Exceptions
|
67
68
|
# not only 'StandardError's
|
68
69
|
rescue Exception => e
|
data/lib/prop_check/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: prop_check
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.11.
|
4
|
+
version: 0.11.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Qqwy/Wiebe-Marten Wijnja
|
@@ -83,7 +83,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
83
83
|
- !ruby/object:Gem::Version
|
84
84
|
version: '0'
|
85
85
|
requirements: []
|
86
|
-
rubygems_version: 3.
|
86
|
+
rubygems_version: 3.1.2
|
87
87
|
signing_key:
|
88
88
|
specification_version: 4
|
89
89
|
summary: PropCheck allows you to do property-based testing, including shrinking.
|