prop_check 0.11.0 → 0.14.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/.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.
|