prop_check 0.11.0 → 0.14.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +2 -0
- data/.tool-versions +1 -1
- data/.travis.yml +1 -1
- data/CHANGELOG.md +7 -1
- data/README.md +3 -1
- data/bin/rspec +29 -0
- data/lib/prop_check/generator.rb +37 -25
- data/lib/prop_check/generators.rb +154 -37
- data/lib/prop_check/helper.rb +14 -0
- data/lib/prop_check/hooks.rb +20 -13
- data/lib/prop_check/lazy_tree.rb +15 -18
- data/lib/prop_check/property.rb +80 -41
- 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 +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 013ca5101f31799feb0c86533c4100a781c451f937d6d8497a30476073e27e4b
|
4
|
+
data.tar.gz: f592ec086ca3018cfca1016baa1690619a5783274f02c910eb2ad03e5861a53d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f49b2514fe4e179013bd8c4a4683c1210b65d6852901156c6c5373f26305343fa4cae40860f13386a8f1fda33406927bf20a357f88cd5b99936318410837ba00
|
7
|
+
data.tar.gz: 261b980fb68c2d1af8baccb09919a838f91c1f6ae33f8c8a79923b2cb50490e4406e848ffbf6a75361d13d33719907a3342b4a7eed88ebdf37d1bdd8cd981ee4
|
data/.rubocop.yml
CHANGED
data/.tool-versions
CHANGED
@@ -1 +1 @@
|
|
1
|
-
ruby 2.
|
1
|
+
ruby 2.7.1
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1 +1,7 @@
|
|
1
|
-
- 0.
|
1
|
+
- 0.14.0 - Adds `uniq: true` option to `Generators.array`. Makes `PropCheck::Property` an immutable object that returns copies that have changes whenever reconfiguring, allowing re-usable configuration.
|
2
|
+
- 0.13.0 - Adds Generator#resize
|
3
|
+
- 0.12.1 - Fixes shrinking when filtering bug.
|
4
|
+
- 0.12.0 - `PropCheck::Generators#instance`
|
5
|
+
- 0.11.0 - Improved syntax to support Ruby 2.7 and up without deprecation warnings, full support for `#where`.
|
6
|
+
- 0.10.0 - Some bugfixes, support for `#where`
|
7
|
+
- 0.8.0 - New syntax that is more explicit, passng generated values to blocks as parameters.
|
data/README.md
CHANGED
@@ -24,10 +24,12 @@ Before releasing this gem on Rubygems, the following things need to be finished:
|
|
24
24
|
- [x] Filtering generators.
|
25
25
|
- [x] Customize the max. of samples to run.
|
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
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.
|
31
|
+
- [x] `#instance` generator to allow the easy creation of generators for custom datatypes.
|
32
|
+
- [ ] A usage guide.
|
31
33
|
|
32
34
|
# Nice-to-haves
|
33
35
|
|
data/bin/rspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'rspec' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load Gem.bin_path("rspec-core", "rspec")
|
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,10 +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
|
+
def generate(**kwargs)
|
26
|
+
kwargs = @@default_kwargs.merge(kwargs)
|
27
|
+
max_consecutive_attempts = kwargs[:max_consecutive_attempts]
|
28
|
+
|
25
29
|
(0..max_consecutive_attempts).each do
|
26
|
-
res = @block.call(
|
27
|
-
next if res == :"
|
30
|
+
res = @block.call(**kwargs)
|
31
|
+
next if res.root == :"_PropCheck.filter_me"
|
28
32
|
|
29
33
|
return res
|
30
34
|
end
|
@@ -40,18 +44,18 @@ module PropCheck
|
|
40
44
|
# Generates a value, and only return this value
|
41
45
|
# (drop information for shrinking)
|
42
46
|
#
|
43
|
-
# >> Generators.integer.call(1000, Random.new(42))
|
47
|
+
# >> Generators.integer.call(size: 1000, rng: Random.new(42))
|
44
48
|
# => 126
|
45
|
-
def call(
|
46
|
-
generate(
|
49
|
+
def call(**kwargs)
|
50
|
+
generate(**@@default_kwargs.merge(kwargs)).root
|
47
51
|
end
|
48
52
|
|
49
53
|
##
|
50
54
|
# Returns `num_of_samples` values from calling this Generator.
|
51
55
|
# This is mostly useful for debugging if a generator behaves as you intend it to.
|
52
|
-
def sample(num_of_samples = 10,
|
56
|
+
def sample(num_of_samples = 10, **kwargs)
|
53
57
|
num_of_samples.times.map do
|
54
|
-
call(
|
58
|
+
call(**@@default_kwargs.merge(kwargs))
|
55
59
|
end
|
56
60
|
end
|
57
61
|
|
@@ -61,10 +65,10 @@ module PropCheck
|
|
61
65
|
#
|
62
66
|
# Keen readers may notice this as the Monadic 'pure'/'return' implementation for Generators.
|
63
67
|
#
|
64
|
-
# >> Generators.integer.bind { |a| Generators.integer.bind { |b| Generator.wrap([a , b]) } }.call(100, Random.new(42))
|
68
|
+
# >> Generators.integer.bind { |a| Generators.integer.bind { |b| Generator.wrap([a , b]) } }.call(size: 100, rng: Random.new(42))
|
65
69
|
# => [2, 79]
|
66
70
|
def self.wrap(val)
|
67
|
-
Generator.new {
|
71
|
+
Generator.new { LazyTree.wrap(val) }
|
68
72
|
end
|
69
73
|
|
70
74
|
##
|
@@ -73,7 +77,7 @@ module PropCheck
|
|
73
77
|
#
|
74
78
|
# Keen readers may notice this as the Monadic 'bind' (sometimes known as '>>=') implementation for Generators.
|
75
79
|
#
|
76
|
-
# >> Generators.integer.bind { |a| Generators.integer.bind { |b| Generator.wrap([a , b]) } }.call(100, Random.new(42))
|
80
|
+
# >> Generators.integer.bind { |a| Generators.integer.bind { |b| Generator.wrap([a , b]) } }.call(size: 100, rng: Random.new(42))
|
77
81
|
# => [2, 79]
|
78
82
|
def bind(&generator_proc)
|
79
83
|
# Generator.new do |size, rng|
|
@@ -83,11 +87,11 @@ module PropCheck
|
|
83
87
|
# inner_generator.generate(size, rng)
|
84
88
|
# end.flatten
|
85
89
|
# end
|
86
|
-
Generator.new do |
|
87
|
-
outer_result = self.generate(
|
90
|
+
Generator.new do |**kwargs|
|
91
|
+
outer_result = self.generate(**kwargs)
|
88
92
|
outer_result.bind do |outer_val|
|
89
93
|
inner_generator = generator_proc.call(outer_val)
|
90
|
-
inner_generator.generate(
|
94
|
+
inner_generator.generate(**kwargs)
|
91
95
|
end
|
92
96
|
end
|
93
97
|
end
|
@@ -95,11 +99,11 @@ module PropCheck
|
|
95
99
|
##
|
96
100
|
# Creates a new Generator that returns a value by running `proc` on the output of the current Generator.
|
97
101
|
#
|
98
|
-
# >> Generators.choose(32..128).map(&:chr).call(10, Random.new(42))
|
102
|
+
# >> Generators.choose(32..128).map(&:chr).call(size: 10, rng: Random.new(42))
|
99
103
|
# => "S"
|
100
104
|
def map(&proc)
|
101
|
-
Generator.new do |
|
102
|
-
result = self.generate(
|
105
|
+
Generator.new do |**kwargs|
|
106
|
+
result = self.generate(**kwargs)
|
103
107
|
result.map(&proc)
|
104
108
|
end
|
105
109
|
end
|
@@ -108,19 +112,27 @@ module PropCheck
|
|
108
112
|
# Creates a new Generator that only produces a value when the block `condition` returns a truthy value.
|
109
113
|
def where(&condition)
|
110
114
|
self.map do |result|
|
111
|
-
if condition.call(result)
|
115
|
+
# if condition.call(*result)
|
116
|
+
if PropCheck::Helper.call_splatted(result, &condition)
|
112
117
|
result
|
113
118
|
else
|
114
119
|
:"_PropCheck.filter_me"
|
115
120
|
end
|
116
121
|
end
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
122
|
+
end
|
123
|
+
|
124
|
+
##
|
125
|
+
# Resizes the generator to either grow faster or smaller than normal.
|
126
|
+
#
|
127
|
+
# `proc` takes the current size as input and is expected to return the new size.
|
128
|
+
# a size should always be a nonnegative integer.
|
129
|
+
#
|
130
|
+
# >> Generators.integer.resize{}
|
131
|
+
def resize(&proc)
|
132
|
+
Generator.new do |size:, **other_kwargs|
|
133
|
+
new_size = proc.call(size)
|
134
|
+
self.generate(**other_kwargs, size: new_size)
|
135
|
+
end
|
124
136
|
end
|
125
137
|
end
|
126
138
|
end
|
@@ -9,7 +9,8 @@ module PropCheck
|
|
9
9
|
# Use this module by including it in the class (e.g. in your test suite)
|
10
10
|
# where you want to use them.
|
11
11
|
module Generators
|
12
|
-
|
12
|
+
module_function
|
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,19 +74,27 @@ 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:, **|
|
85
|
+
ensure_proper_size!(size)
|
86
|
+
|
84
87
|
val = rng.rand(-size..size)
|
85
88
|
LazyTree.new(val, integer_shrink(val))
|
86
89
|
end
|
87
90
|
end
|
88
91
|
|
92
|
+
private def ensure_proper_size!(size)
|
93
|
+
return if size.is_a?(Integer) && size >= 0
|
94
|
+
|
95
|
+
raise ArgumentError, "`size:` should be a nonnegative integer but got `#{size.inspect}`"
|
96
|
+
end
|
97
|
+
|
89
98
|
##
|
90
99
|
# Only returns integers that are zero or larger.
|
91
100
|
# See `integer` for more information.
|
@@ -137,7 +146,7 @@ module PropCheck
|
|
137
146
|
end
|
138
147
|
end
|
139
148
|
|
140
|
-
|
149
|
+
@@special_floats = [Float::NAN, Float::INFINITY, -Float::INFINITY, Float::MAX, Float::MIN, 0.0.next_float, 0.0.prev_float]
|
141
150
|
##
|
142
151
|
# Generates floating-point numbers
|
143
152
|
# Will generate NaN, Infinity, -Infinity,
|
@@ -150,7 +159,7 @@ module PropCheck
|
|
150
159
|
# >> Generators.float().sample(10, size: 10, rng: Random.new(42))
|
151
160
|
# => [4.0, 9.555555555555555, 0.0, -Float::INFINITY, 5.5, -5.818181818181818, 1.1428571428571428, 0.0, 8.0, 7.857142857142858]
|
152
161
|
def float
|
153
|
-
frequency(99 => real_float, 1 => one_of(
|
162
|
+
frequency(99 => real_float, 1 => one_of(*@@special_floats.map(&method(:constant))))
|
154
163
|
end
|
155
164
|
|
156
165
|
##
|
@@ -193,12 +202,12 @@ module PropCheck
|
|
193
202
|
#
|
194
203
|
# Shrinks element generators, one at a time (trying last one first).
|
195
204
|
#
|
196
|
-
# >> Generators.tuple(Generators.integer, Generators.real_float).call(10, Random.new(42))
|
205
|
+
# >> Generators.tuple(Generators.integer, Generators.real_float).call(size: 10, rng: Random.new(42))
|
197
206
|
# => [-4, 13.0]
|
198
207
|
def tuple(*generators)
|
199
|
-
Generator.new do |
|
208
|
+
Generator.new do |**kwargs|
|
200
209
|
LazyTree.zip(generators.map do |generator|
|
201
|
-
generator.generate(
|
210
|
+
generator.generate(**kwargs)
|
202
211
|
end)
|
203
212
|
end
|
204
213
|
end
|
@@ -210,7 +219,7 @@ module PropCheck
|
|
210
219
|
#
|
211
220
|
# Shrinks element generators.
|
212
221
|
#
|
213
|
-
# >> Generators.fixed_hash(a: Generators.integer(), b: Generators.real_float(), c: Generators.integer()).call(10, Random.new(42))
|
222
|
+
# >> Generators.fixed_hash(a: Generators.integer(), b: Generators.real_float(), c: Generators.integer()).call(size: 10, rng: Random.new(42))
|
214
223
|
# => {:a=>-4, :b=>13.0, :c=>-3}
|
215
224
|
def fixed_hash(hash)
|
216
225
|
keypair_generators =
|
@@ -232,6 +241,12 @@ module PropCheck
|
|
232
241
|
# `empty:` When false, behaves the same as `min: 1`
|
233
242
|
# `min:` Ensures at least this many elements are generated. (default: 0)
|
234
243
|
# `max:` Ensures at most this many elements are generated. When nil, an arbitrary count is used instead. (default: nil)
|
244
|
+
# `uniq:` When `true`, ensures that all elements in the array are unique.
|
245
|
+
# When given a proc, uses the result of this proc to check for uniqueness.
|
246
|
+
# (matching the behaviour of `Array#uniq`)
|
247
|
+
# If it is not possible to generate another unique value after the configured `max_consecutive_attempts`
|
248
|
+
# an `PropCheck::Errors::GeneratorExhaustedError` will be raised.
|
249
|
+
# (default: `false`)
|
235
250
|
#
|
236
251
|
#
|
237
252
|
# >> Generators.array(Generators.positive_integer).sample(5, size: 1, rng: Random.new(42))
|
@@ -243,28 +258,70 @@ module PropCheck
|
|
243
258
|
# => [[], [2], [], [], [2]]
|
244
259
|
# >> Generators.array(Generators.positive_integer, empty: false).sample(5, size: 1, rng: Random.new(1))
|
245
260
|
# => [[2], [1], [2], [1], [1]]
|
261
|
+
#
|
262
|
+
# >> Generators.array(Generators.boolean, uniq: true).sample(5, rng: Random.new(1))
|
263
|
+
# => [[true, false], [false, true], [true, false], [false, true], [false, true]]
|
246
264
|
|
247
|
-
|
248
|
-
def array(element_generator, min: 0, max: nil, empty: true)
|
265
|
+
def array(element_generator, min: 0, max: nil, empty: true, uniq: false)
|
249
266
|
min = 1 if min.zero? && !empty
|
250
|
-
|
251
|
-
res = proc do |count|
|
252
|
-
count = min + 1 if count < min
|
253
|
-
count += 1 if count == min && min != 0
|
254
|
-
generators = (min...count).map do
|
255
|
-
element_generator.clone
|
256
|
-
end
|
257
|
-
|
258
|
-
tuple(*generators)
|
259
|
-
end
|
267
|
+
uniq = proc { |x| x } if uniq == true
|
260
268
|
|
261
269
|
if max.nil?
|
262
|
-
nonnegative_integer.bind(
|
270
|
+
nonnegative_integer.bind { |count| make_array(element_generator, min, count, uniq) }
|
263
271
|
else
|
264
|
-
|
272
|
+
make_array(element_generator, min, max, uniq)
|
265
273
|
end
|
266
274
|
end
|
267
275
|
|
276
|
+
private def make_array(element_generator, min, count, uniq)
|
277
|
+
amount = min if count < min
|
278
|
+
amount = min if count == min && min != 0
|
279
|
+
amount ||= (count - min)
|
280
|
+
|
281
|
+
# Simple, optimized implementation:
|
282
|
+
return make_array_simple(element_generator, amount) unless uniq
|
283
|
+
|
284
|
+
# More complex implementation that filters duplicates
|
285
|
+
make_array_uniq(element_generator, min, amount, uniq)
|
286
|
+
end
|
287
|
+
|
288
|
+
private def make_array_simple(element_generator, amount)
|
289
|
+
generators = amount.times.map do
|
290
|
+
element_generator.clone
|
291
|
+
end
|
292
|
+
|
293
|
+
tuple(*generators)
|
294
|
+
end
|
295
|
+
|
296
|
+
private def make_array_uniq(element_generator, min, amount, uniq_fun)
|
297
|
+
Generator.new do |**kwargs|
|
298
|
+
arr = []
|
299
|
+
uniques = Set.new
|
300
|
+
count = 0
|
301
|
+
(0..).lazy.map do
|
302
|
+
elem = element_generator.clone.generate(**kwargs)
|
303
|
+
if uniques.add?(uniq_fun.call(elem.root))
|
304
|
+
arr.push(elem)
|
305
|
+
count = 0
|
306
|
+
else
|
307
|
+
count += 1
|
308
|
+
end
|
309
|
+
|
310
|
+
if count > kwargs[:max_consecutive_attempts]
|
311
|
+
if arr.size >= min
|
312
|
+
# Give up and return shorter array in this case
|
313
|
+
amount = min
|
314
|
+
else
|
315
|
+
raise Errors::GeneratorExhaustedError, "Too many consecutive elements filtered by 'uniq:'."
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
319
|
+
.take_while { arr.size < amount }
|
320
|
+
.force
|
321
|
+
|
322
|
+
LazyTree.zip(arr).map { |array| array.uniq(&uniq_fun) }
|
323
|
+
end
|
324
|
+
end
|
268
325
|
|
269
326
|
##
|
270
327
|
# Generates a hash of key->values,
|
@@ -275,13 +332,24 @@ module PropCheck
|
|
275
332
|
#
|
276
333
|
# >> Generators.hash(Generators.printable_ascii_string, Generators.positive_integer).sample(5, size: 3, rng: Random.new(42))
|
277
334
|
# => [{""=>2, "g\\4"=>4, "rv"=>2}, {"7"=>2}, {"!"=>1, "E!"=>1}, {"kY5"=>2}, {}]
|
278
|
-
def hash(
|
335
|
+
def hash(*args, **kwargs)
|
336
|
+
if args.length == 2
|
337
|
+
hash_of(*args, **kwargs)
|
338
|
+
else
|
339
|
+
super
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
##
|
344
|
+
#
|
345
|
+
# Alias for `#hash` that does not conflict with a possibly overriden `Object#hash`.
|
346
|
+
#
|
347
|
+
def hash_of(key_generator, value_generator, **kwargs)
|
279
348
|
array(tuple(key_generator, value_generator), **kwargs)
|
280
349
|
.map(&:to_h)
|
281
350
|
end
|
282
351
|
|
283
|
-
|
284
|
-
@alphanumeric_chars = [('a'..'z'), ('A'..'Z'), ('0'..'9')].flat_map(&:to_a).freeze
|
352
|
+
@@alphanumeric_chars = [('a'..'z'), ('A'..'Z'), ('0'..'9')].flat_map(&:to_a).freeze
|
285
353
|
##
|
286
354
|
# Generates a single-character string
|
287
355
|
# containing one of a..z, A..Z, 0..9
|
@@ -291,7 +359,7 @@ module PropCheck
|
|
291
359
|
# >> Generators.alphanumeric_char.sample(5, size: 10, rng: Random.new(42))
|
292
360
|
# => ["M", "Z", "C", "o", "Q"]
|
293
361
|
def alphanumeric_char
|
294
|
-
one_of(
|
362
|
+
one_of(*@@alphanumeric_chars.map(&method(:constant)))
|
295
363
|
end
|
296
364
|
|
297
365
|
##
|
@@ -302,11 +370,13 @@ module PropCheck
|
|
302
370
|
#
|
303
371
|
# >> Generators.alphanumeric_string.sample(5, size: 10, rng: Random.new(42))
|
304
372
|
# => ["ZCoQ", "8uM", "wkkx0JNx", "v0bxRDLb", "Gl5v8RyWA6"]
|
373
|
+
#
|
374
|
+
# Accepts the same options as `array`
|
305
375
|
def alphanumeric_string(**kwargs)
|
306
376
|
array(alphanumeric_char, **kwargs).map(&:join)
|
307
377
|
end
|
308
378
|
|
309
|
-
|
379
|
+
@@printable_ascii_chars = (' '..'~').to_a.freeze
|
310
380
|
|
311
381
|
##
|
312
382
|
# Generates a single-character string
|
@@ -317,7 +387,7 @@ module PropCheck
|
|
317
387
|
# >> Generators.printable_ascii_char.sample(size: 10, rng: Random.new(42))
|
318
388
|
# => ["S", "|", ".", "g", "\\", "4", "r", "v", "j", "j"]
|
319
389
|
def printable_ascii_char
|
320
|
-
one_of(
|
390
|
+
one_of(*@@printable_ascii_chars.map(&method(:constant)))
|
321
391
|
end
|
322
392
|
|
323
393
|
##
|
@@ -328,12 +398,14 @@ module PropCheck
|
|
328
398
|
#
|
329
399
|
# >> Generators.printable_ascii_string.sample(5, size: 10, rng: Random.new(42))
|
330
400
|
# => ["S|.g", "rvjjw7\"5T!", "=", "!_[4@", "Y"]
|
401
|
+
#
|
402
|
+
# Accepts the same options as `array`
|
331
403
|
def printable_ascii_string(**kwargs)
|
332
404
|
array(printable_ascii_char, **kwargs).map(&:join)
|
333
405
|
end
|
334
406
|
|
335
|
-
|
336
|
-
|
407
|
+
@@ascii_chars = [
|
408
|
+
@@printable_ascii_chars,
|
337
409
|
[
|
338
410
|
"\n",
|
339
411
|
"\r",
|
@@ -356,7 +428,7 @@ module PropCheck
|
|
356
428
|
# >> Generators.ascii_char.sample(size: 10, rng: Random.new(42))
|
357
429
|
# => ["d", "S", "|", ".", "g", "\\", "4", "d", "r", "v"]
|
358
430
|
def ascii_char
|
359
|
-
one_of(
|
431
|
+
one_of(*@@ascii_chars.map(&method(:constant)))
|
360
432
|
end
|
361
433
|
|
362
434
|
##
|
@@ -367,12 +439,14 @@ module PropCheck
|
|
367
439
|
#
|
368
440
|
# >> Generators.ascii_string.sample(5, size: 10, rng: Random.new(42))
|
369
441
|
# => ["S|.g", "drvjjw\b\a7\"", "!w=E!_[4@k", "x", "zZI{[o"]
|
442
|
+
#
|
443
|
+
# Accepts the same options as `array`
|
370
444
|
def ascii_string(**kwargs)
|
371
445
|
array(ascii_char, **kwargs).map(&:join)
|
372
446
|
end
|
373
447
|
|
374
|
-
|
375
|
-
|
448
|
+
@@printable_chars = [
|
449
|
+
@@ascii_chars,
|
376
450
|
"\u{A0}".."\u{D7FF}",
|
377
451
|
"\u{E000}".."\u{FFFD}",
|
378
452
|
"\u{10000}".."\u{10FFFF}"
|
@@ -387,7 +461,7 @@ module PropCheck
|
|
387
461
|
# >> Generators.printable_char.sample(size: 10, rng: Random.new(42))
|
388
462
|
# => ["吏", "", "", "", "", "", "", "", "", "Ȍ"]
|
389
463
|
def printable_char
|
390
|
-
one_of(
|
464
|
+
one_of(*@@printable_chars.map(&method(:constant)))
|
391
465
|
end
|
392
466
|
|
393
467
|
##
|
@@ -398,6 +472,8 @@ module PropCheck
|
|
398
472
|
#
|
399
473
|
# >> Generators.printable_string.sample(5, size: 10, rng: Random.new(42))
|
400
474
|
# => ["", "Ȍ", "𐁂", "Ȕ", ""]
|
475
|
+
#
|
476
|
+
# Accepts the same options as `array`
|
401
477
|
def printable_string(**kwargs)
|
402
478
|
array(printable_char, **kwargs).map(&:join)
|
403
479
|
end
|
@@ -424,6 +500,8 @@ module PropCheck
|
|
424
500
|
#
|
425
501
|
# >> Generators.string.sample(5, size: 10, rng: Random.new(42))
|
426
502
|
# => ["\u{A3DB3}𠍜\u{3F46A}\u{1AEBC}", "𡡹\u{DED74}𪱣\u{43E97}ꂂ\u{50695}\u{C0301}", "\u{4FD9D}", "\u{C14BF}\u{193BB}𭇋\u{76B58}", "𦐺\u{9FDDB}\u{80ABB}\u{9E3CF}𐂽\u{14AAE}"]
|
503
|
+
#
|
504
|
+
# Accepts the same options as `array`
|
427
505
|
def string(**kwargs)
|
428
506
|
array(char, **kwargs).map(&:join)
|
429
507
|
end
|
@@ -509,5 +587,44 @@ module PropCheck
|
|
509
587
|
def nillable(other_generator)
|
510
588
|
frequency(9 => other_generator, 1 => constant(nil))
|
511
589
|
end
|
590
|
+
|
591
|
+
##
|
592
|
+
# Generates an instance of `klass`
|
593
|
+
# using `args` and/or `kwargs`
|
594
|
+
# as generators for the arguments that are passed to `klass.new`
|
595
|
+
#
|
596
|
+
# ## Example:
|
597
|
+
#
|
598
|
+
# Given a class like this:
|
599
|
+
#
|
600
|
+
#
|
601
|
+
# class User
|
602
|
+
# attr_accessor :name, :age
|
603
|
+
# def initialize(name: , age: )
|
604
|
+
# @name = name
|
605
|
+
# @age = age
|
606
|
+
# end
|
607
|
+
#
|
608
|
+
# def inspect
|
609
|
+
# "<User name: #{@name.inspect}, age: #{@age.inspect}>"
|
610
|
+
# end
|
611
|
+
# end
|
612
|
+
#
|
613
|
+
# >> user_gen = Generators.instance(User, name: Generators.printable_ascii_string, age: Generators.nonnegative_integer)
|
614
|
+
# >> user_gen.sample(3, rng: Random.new(42)).inspect
|
615
|
+
# => "[<User name: \"S|.g\", age: 10>, <User name: \"rvjj\", age: 10>, <User name: \"7\\\"5T!w=\", age: 5>]"
|
616
|
+
def instance(klass, *args, **kwargs)
|
617
|
+
tuple(*args).bind do |vals|
|
618
|
+
fixed_hash(**kwargs).map do |kwvals|
|
619
|
+
if kwvals == {}
|
620
|
+
klass.new(*vals)
|
621
|
+
elsif vals == []
|
622
|
+
klass.new(**kwvals)
|
623
|
+
else
|
624
|
+
klass.new(*vals, **kwvals)
|
625
|
+
end
|
626
|
+
end
|
627
|
+
end
|
628
|
+
end
|
512
629
|
end
|
513
630
|
end
|
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/hooks.rb
CHANGED
@@ -19,10 +19,11 @@
|
|
19
19
|
# wrapping the elements of an enumerable with hooks.
|
20
20
|
class PropCheck::Hooks
|
21
21
|
# attr_reader :before, :after, :around
|
22
|
-
def initialize()
|
23
|
-
@before =
|
24
|
-
@after =
|
25
|
-
@around =
|
22
|
+
def initialize(before: proc {}, after: proc {}, around: proc { |*args, &block| block.call(*args) })
|
23
|
+
@before = before
|
24
|
+
@after = after
|
25
|
+
@around = around
|
26
|
+
freeze
|
26
27
|
end
|
27
28
|
|
28
29
|
def wrap_enum(enumerable)
|
@@ -59,34 +60,40 @@ class PropCheck::Hooks
|
|
59
60
|
# Adds `hook` to the `before` proc.
|
60
61
|
# It is called after earlier-added `before` procs.
|
61
62
|
def add_before(&hook)
|
62
|
-
old_before = @before
|
63
|
-
|
64
|
-
|
63
|
+
# old_before = @before
|
64
|
+
new_before = proc {
|
65
|
+
@before.call
|
65
66
|
hook.call
|
66
67
|
}
|
68
|
+
# self
|
69
|
+
self.class.new(before: new_before, after: @after, around: @around)
|
67
70
|
end
|
68
71
|
|
69
72
|
##
|
70
73
|
# Adds `hook` to the `after` proc.
|
71
74
|
# It is called before earlier-added `after` procs.
|
72
75
|
def add_after(&hook)
|
73
|
-
old_after = @after
|
74
|
-
|
76
|
+
# old_after = @after
|
77
|
+
new_after = proc {
|
75
78
|
hook.call
|
76
|
-
|
79
|
+
@after.call
|
77
80
|
}
|
81
|
+
# self
|
82
|
+
self.class.new(before: @before, after: new_after, around: @around)
|
78
83
|
end
|
79
84
|
|
80
85
|
##
|
81
86
|
# Adds `hook` to the `around` proc.
|
82
87
|
# It is called _inside_ earlier-added `around` procs.
|
83
88
|
def add_around(&hook)
|
84
|
-
old_around = @around
|
85
|
-
|
86
|
-
|
89
|
+
# old_around = @around
|
90
|
+
new_around = proc do |&block|
|
91
|
+
@around.call do |*args|
|
87
92
|
hook.call(*args, &block)
|
88
93
|
end
|
89
94
|
end
|
95
|
+
# self
|
96
|
+
self.class.new(before: @before, after: @after, around: new_around)
|
90
97
|
end
|
91
98
|
|
92
99
|
##
|
data/lib/prop_check/lazy_tree.rb
CHANGED
@@ -7,12 +7,18 @@ module PropCheck
|
|
7
7
|
class LazyTree
|
8
8
|
require 'prop_check/helper'
|
9
9
|
|
10
|
-
|
10
|
+
include Enumerable
|
11
|
+
|
12
|
+
attr_accessor :root
|
11
13
|
def initialize(root, children = [].lazy)
|
12
14
|
@root = root
|
13
15
|
@children = children
|
14
16
|
end
|
15
17
|
|
18
|
+
def children
|
19
|
+
@children.reject { |child| child.root == :"_PropCheck.filter_me" }
|
20
|
+
end
|
21
|
+
|
16
22
|
##
|
17
23
|
# Maps `block` eagerly over `root` and lazily over `children`, returning a new LazyTree as result.
|
18
24
|
#
|
@@ -66,25 +72,15 @@ module PropCheck
|
|
66
72
|
# >> LazyTree.new(1, [LazyTree.new(2, [LazyTree.new(3)]), LazyTree.new(4)]).each.force
|
67
73
|
# => [1, 4, 2, 3]
|
68
74
|
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, [])
|
75
|
+
self.to_enum(:each) unless block_given?
|
76
76
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
# return res.each(&block) if block_given?
|
82
|
-
|
83
|
-
# res
|
77
|
+
squish([])
|
78
|
+
.each(&block)
|
79
|
+
end
|
84
80
|
|
85
|
-
|
86
|
-
|
87
|
-
|
81
|
+
protected def squish(arr)
|
82
|
+
new_children = self.children.reduce(arr) { |acc, elem| elem.squish(acc) }
|
83
|
+
PropCheck::Helper.lazy_append([self.root], new_children)
|
88
84
|
end
|
89
85
|
|
90
86
|
##
|
@@ -129,5 +125,6 @@ module PropCheck
|
|
129
125
|
end
|
130
126
|
end
|
131
127
|
end
|
128
|
+
|
132
129
|
end
|
133
130
|
end
|
data/lib/prop_check/property.rb
CHANGED
@@ -7,9 +7,15 @@ require 'prop_check/property/shrinker'
|
|
7
7
|
require 'prop_check/hooks'
|
8
8
|
module PropCheck
|
9
9
|
##
|
10
|
-
#
|
10
|
+
# Create and run property-checks.
|
11
|
+
#
|
12
|
+
# For simple usage, see `.forall`.
|
13
|
+
#
|
14
|
+
# For advanced usage, call `PropCheck::Property.new(...)` and then configure it to your liking
|
15
|
+
# using e.g. `#with_config`, `#before`, `#after`, `#around` etc.
|
16
|
+
# Each of these methods will return a new `Property`, so earlier properties are not mutated.
|
17
|
+
# This allows you to re-use configuration and hooks between multiple tests.
|
11
18
|
class Property
|
12
|
-
|
13
19
|
##
|
14
20
|
# Main entry-point to create (and possibly immediately run) a property-test.
|
15
21
|
#
|
@@ -35,9 +41,8 @@ module PropCheck
|
|
35
41
|
# a Property object is returned, which you can call the other instance methods
|
36
42
|
# of this class on before finally passing a block to it using `#check`.
|
37
43
|
# (so `forall(Generators.integer) do |val| ... end` and forall(Generators.integer).check do |val| ... end` are the same)
|
38
|
-
def self.forall(*bindings, &block)
|
39
|
-
|
40
|
-
property = new(*bindings)
|
44
|
+
def self.forall(*bindings, **kwbindings, &block)
|
45
|
+
property = new(*bindings, **kwbindings)
|
41
46
|
|
42
47
|
return property.check(&block) if block_given?
|
43
48
|
|
@@ -61,18 +66,25 @@ module PropCheck
|
|
61
66
|
yield(configuration)
|
62
67
|
end
|
63
68
|
|
64
|
-
attr_reader :bindings, :condition
|
65
|
-
|
66
69
|
def initialize(*bindings, **kwbindings)
|
67
|
-
raise ArgumentError, 'No bindings specified!' if bindings.empty? && kwbindings.empty?
|
68
|
-
|
69
|
-
@bindings = bindings
|
70
|
-
@kwbindings = kwbindings
|
71
|
-
@condition = proc { true }
|
72
70
|
@config = self.class.configuration
|
73
71
|
@hooks = PropCheck::Hooks.new
|
72
|
+
|
73
|
+
@gen = gen_from_bindings(bindings, kwbindings) unless bindings.empty? && kwbindings.empty?
|
74
|
+
freeze
|
74
75
|
end
|
75
76
|
|
77
|
+
# [:condition, :config, :hooks, :gen].each do |symbol|
|
78
|
+
# define_method(symbol) do
|
79
|
+
# self.instance_variable_get("@#{symbol}")
|
80
|
+
# end
|
81
|
+
|
82
|
+
# protected define_method("#{symbol}=") do |value|
|
83
|
+
# duplicate = self.dup
|
84
|
+
# duplicate.instance_variable_set("@#{symbol}", value)
|
85
|
+
# duplicate
|
86
|
+
# end
|
87
|
+
|
76
88
|
##
|
77
89
|
# Returns the configuration of this property
|
78
90
|
# for introspection.
|
@@ -90,11 +102,22 @@ module PropCheck
|
|
90
102
|
# you can immediately pass a block to this method.
|
91
103
|
# (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`)
|
92
104
|
def with_config(**config, &block)
|
93
|
-
|
105
|
+
duplicate = self.dup
|
106
|
+
duplicate.instance_variable_set(:@config, @config.merge(config))
|
107
|
+
duplicate.freeze
|
108
|
+
|
109
|
+
return duplicate.check(&block) if block_given?
|
94
110
|
|
95
|
-
|
111
|
+
duplicate
|
112
|
+
end
|
113
|
+
|
114
|
+
def with_bindings(*bindings, **kwbindings)
|
115
|
+
raise ArgumentError, 'No bindings specified!' if bindings.empty? && kwbindings.empty?
|
96
116
|
|
97
|
-
self
|
117
|
+
duplicate = self.dup
|
118
|
+
duplicate.instance_variable_set(:@gen, gen_from_bindings(bindings, kwbindings))
|
119
|
+
duplicate.freeze
|
120
|
+
duplicate
|
98
121
|
end
|
99
122
|
|
100
123
|
##
|
@@ -106,22 +129,25 @@ module PropCheck
|
|
106
129
|
# you might encounter a GeneratorExhaustedError.
|
107
130
|
# Only filter if you have few inputs to reject. Otherwise, improve your generators.
|
108
131
|
def where(&condition)
|
109
|
-
|
110
|
-
@condition = proc do |*args|
|
111
|
-
original_condition.call(*args) && condition.call(*args)
|
112
|
-
end
|
132
|
+
raise ArgumentError, 'No generator bindings specified! #where should be called after `#forall` or `#with_bindings`.' unless @gen
|
113
133
|
|
114
|
-
self
|
134
|
+
duplicate = self.dup
|
135
|
+
duplicate.instance_variable_set(:@gen, @gen.where(&condition))
|
136
|
+
duplicate.freeze
|
137
|
+
duplicate
|
115
138
|
end
|
116
139
|
|
140
|
+
|
117
141
|
##
|
118
142
|
# Calls `hook` before each time a check is run with new data.
|
119
143
|
#
|
120
144
|
# This is useful to add setup logic
|
121
145
|
# When called multiple times, earlier-added hooks will be called _before_ `hook` is called.
|
122
146
|
def before(&hook)
|
123
|
-
|
124
|
-
|
147
|
+
duplicate = self.dup
|
148
|
+
duplicate.instance_variable_set(:@hooks, @hooks.add_before(&hook))
|
149
|
+
duplicate.freeze
|
150
|
+
duplicate
|
125
151
|
end
|
126
152
|
|
127
153
|
##
|
@@ -130,8 +156,10 @@ module PropCheck
|
|
130
156
|
# This is useful to add teardown logic
|
131
157
|
# When called multiple times, earlier-added hooks will be called _after_ `hook` is called.
|
132
158
|
def after(&hook)
|
133
|
-
|
134
|
-
|
159
|
+
duplicate = self.dup
|
160
|
+
duplicate.instance_variable_set(:@hooks, @hooks.add_after(&hook))
|
161
|
+
duplicate.freeze
|
162
|
+
duplicate
|
135
163
|
end
|
136
164
|
|
137
165
|
##
|
@@ -148,28 +176,20 @@ module PropCheck
|
|
148
176
|
# it is possible for the code after `yield` not to be called.
|
149
177
|
# So make sure that cleanup logic is wrapped with the `ensure` keyword.
|
150
178
|
def around(&hook)
|
151
|
-
|
152
|
-
|
179
|
+
duplicate = self.dup
|
180
|
+
duplicate.instance_variable_set(:@hooks, @hooks.add_around(&hook))
|
181
|
+
duplicate.freeze
|
182
|
+
duplicate
|
153
183
|
end
|
154
184
|
|
155
185
|
##
|
156
186
|
# Checks the property (after settings have been altered using the other instance methods in this class.)
|
157
187
|
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
188
|
n_runs = 0
|
169
189
|
n_successful = 0
|
170
190
|
|
171
191
|
# Loop stops at first exception
|
172
|
-
attempts_enum(
|
192
|
+
attempts_enum(@gen).each do |generator_result|
|
173
193
|
n_runs += 1
|
174
194
|
check_attempt(generator_result, n_successful, &block)
|
175
195
|
n_successful += 1
|
@@ -178,6 +198,25 @@ module PropCheck
|
|
178
198
|
ensure_not_exhausted!(n_runs)
|
179
199
|
end
|
180
200
|
|
201
|
+
private def gen_from_bindings(bindings, kwbindings)
|
202
|
+
if bindings == [] && kwbindings != {}
|
203
|
+
PropCheck::Generators.fixed_hash(**kwbindings)
|
204
|
+
elsif bindings != [] && kwbindings == {}
|
205
|
+
if bindings.size == 1
|
206
|
+
bindings.first
|
207
|
+
else
|
208
|
+
PropCheck::Generators.tuple(*bindings)
|
209
|
+
end
|
210
|
+
else
|
211
|
+
raise ArgumentError,
|
212
|
+
'Attempted to use both normal and keyword bindings at the same time.
|
213
|
+
This is not supported because of the separation of positional and keyword arguments
|
214
|
+
(the old behaviour is deprecated in Ruby 2.7 and will be removed in 3.0)
|
215
|
+
c.f. https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/
|
216
|
+
'
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
181
220
|
private def ensure_not_exhausted!(n_runs)
|
182
221
|
return if n_runs >= @config.n_runs
|
183
222
|
|
@@ -196,7 +235,7 @@ module PropCheck
|
|
196
235
|
end
|
197
236
|
|
198
237
|
private def check_attempt(generator_result, n_successful, &block)
|
199
|
-
|
238
|
+
PropCheck::Helper.call_splatted(generator_result.root, &block)
|
200
239
|
|
201
240
|
# immediately stop (without shrinnking) for when the app is asked
|
202
241
|
# to close by outside intervention
|
@@ -225,6 +264,8 @@ module PropCheck
|
|
225
264
|
end
|
226
265
|
|
227
266
|
private def attempts_enum(binding_generator)
|
267
|
+
ap @hooks
|
268
|
+
|
228
269
|
@hooks
|
229
270
|
.wrap_enum(raw_attempts_enum(binding_generator))
|
230
271
|
.lazy
|
@@ -236,9 +277,7 @@ module PropCheck
|
|
236
277
|
size = 1
|
237
278
|
(0...@config.max_generate_attempts)
|
238
279
|
.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) }
|
280
|
+
.map { binding_generator.generate(size: size, rng: rng, max_consecutive_attempts: @config.max_consecutive_attempts) }
|
242
281
|
.map do |result|
|
243
282
|
size += 1
|
244
283
|
|
@@ -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: 100,
|
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,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: prop_check
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.14.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Qqwy/Wiebe-Marten Wijnja
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-08-
|
11
|
+
date: 2020-08-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: awesome_print
|
@@ -47,6 +47,7 @@ files:
|
|
47
47
|
- README.md
|
48
48
|
- Rakefile
|
49
49
|
- bin/console
|
50
|
+
- bin/rspec
|
50
51
|
- bin/setup
|
51
52
|
- lib/prop_check.rb
|
52
53
|
- lib/prop_check/generator.rb
|
@@ -83,7 +84,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
83
84
|
- !ruby/object:Gem::Version
|
84
85
|
version: '0'
|
85
86
|
requirements: []
|
86
|
-
rubygems_version: 3.
|
87
|
+
rubygems_version: 3.1.2
|
87
88
|
signing_key:
|
88
89
|
specification_version: 4
|
89
90
|
summary: PropCheck allows you to do property-based testing, including shrinking.
|