prop_check 0.11.1 → 0.14.1
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/.travis.yml +1 -1
- data/CHANGELOG.md +8 -1
- data/README.md +3 -1
- data/bin/rspec +29 -0
- data/lib/prop_check/generator.rb +17 -1
- data/lib/prop_check/generators.rb +132 -26
- data/lib/prop_check/hooks.rb +20 -13
- data/lib/prop_check/lazy_tree.rb +6 -1
- data/lib/prop_check/property.rb +55 -29
- data/lib/prop_check/property/configuration.rb +1 -1
- data/lib/prop_check/version.rb +1 -1
- data/prop_check.gemspec +1 -1
- metadata +6 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bd78cbcc636fbb2089c175708a6be52ebb8124b1f21758ee513f8a84fce33f97
|
4
|
+
data.tar.gz: d3bc244740e31fda8d3dbcdcafc3b7f2bd43676c13f945413bae852fbd82de6f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: eda47d7e95d30de4a4b12fe18063b4eb234d7e2bafa2c071613c12c882e71a777da288504330990edde061a8247db33bdb8a7a829419b2111912b7499cef9749
|
7
|
+
data.tar.gz: d44b56680e431af41f2df178795a576d25ccf42e0fb2af086398a18a8b39ccb39eb071acfd88323705346ce5fd528361d3e049509d924be44300a9951d1260e3
|
data/.rubocop.yml
CHANGED
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1 +1,8 @@
|
|
1
|
-
- 0.
|
1
|
+
- 0.14.1 - Swap `awesome_print` for `amazing_print` which is a fork of the former that is actively maintained.
|
2
|
+
- 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.
|
3
|
+
- 0.13.0 - Adds Generator#resize
|
4
|
+
- 0.12.1 - Fixes shrinking when filtering bug.
|
5
|
+
- 0.12.0 - `PropCheck::Generators#instance`
|
6
|
+
- 0.11.0 - Improved syntax to support Ruby 2.7 and up without deprecation warnings, full support for `#where`.
|
7
|
+
- 0.10.0 - Some bugfixes, support for `#where`
|
8
|
+
- 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
@@ -28,7 +28,9 @@ module PropCheck
|
|
28
28
|
|
29
29
|
(0..max_consecutive_attempts).each do
|
30
30
|
res = @block.call(**kwargs)
|
31
|
-
|
31
|
+
next if res.root == :"_PropCheck.filter_me"
|
32
|
+
|
33
|
+
return res
|
32
34
|
end
|
33
35
|
|
34
36
|
raise Errors::GeneratorExhaustedError, """
|
@@ -118,5 +120,19 @@ module PropCheck
|
|
118
120
|
end
|
119
121
|
end
|
120
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
|
136
|
+
end
|
121
137
|
end
|
122
138
|
end
|
@@ -9,7 +9,7 @@ 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
15
|
# Always returns the same value, regardless of `size` or `rng` (random number generator state)
|
@@ -82,11 +82,19 @@ module PropCheck
|
|
82
82
|
# => [-4205, -19140, 18158, -8716, -13735, -3150, 17194, 1962, -3977, -18315]
|
83
83
|
def integer
|
84
84
|
Generator.new do |size:, rng:, **|
|
85
|
+
ensure_proper_size!(size)
|
86
|
+
|
85
87
|
val = rng.rand(-size..size)
|
86
88
|
LazyTree.new(val, integer_shrink(val))
|
87
89
|
end
|
88
90
|
end
|
89
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
|
+
|
90
98
|
##
|
91
99
|
# Only returns integers that are zero or larger.
|
92
100
|
# See `integer` for more information.
|
@@ -138,7 +146,7 @@ module PropCheck
|
|
138
146
|
end
|
139
147
|
end
|
140
148
|
|
141
|
-
|
149
|
+
@@special_floats = [Float::NAN, Float::INFINITY, -Float::INFINITY, Float::MAX, Float::MIN, 0.0.next_float, 0.0.prev_float]
|
142
150
|
##
|
143
151
|
# Generates floating-point numbers
|
144
152
|
# Will generate NaN, Infinity, -Infinity,
|
@@ -151,7 +159,7 @@ module PropCheck
|
|
151
159
|
# >> Generators.float().sample(10, size: 10, rng: Random.new(42))
|
152
160
|
# => [4.0, 9.555555555555555, 0.0, -Float::INFINITY, 5.5, -5.818181818181818, 1.1428571428571428, 0.0, 8.0, 7.857142857142858]
|
153
161
|
def float
|
154
|
-
frequency(99 => real_float, 1 => one_of(
|
162
|
+
frequency(99 => real_float, 1 => one_of(*@@special_floats.map(&method(:constant))))
|
155
163
|
end
|
156
164
|
|
157
165
|
##
|
@@ -233,6 +241,12 @@ module PropCheck
|
|
233
241
|
# `empty:` When false, behaves the same as `min: 1`
|
234
242
|
# `min:` Ensures at least this many elements are generated. (default: 0)
|
235
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`)
|
236
250
|
#
|
237
251
|
#
|
238
252
|
# >> Generators.array(Generators.positive_integer).sample(5, size: 1, rng: Random.new(42))
|
@@ -244,25 +258,68 @@ module PropCheck
|
|
244
258
|
# => [[], [2], [], [], [2]]
|
245
259
|
# >> Generators.array(Generators.positive_integer, empty: false).sample(5, size: 1, rng: Random.new(1))
|
246
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]]
|
247
264
|
|
248
|
-
|
249
|
-
def array(element_generator, min: 0, max: nil, empty: true)
|
265
|
+
def array(element_generator, min: 0, max: nil, empty: true, uniq: false)
|
250
266
|
min = 1 if min.zero? && !empty
|
267
|
+
uniq = proc { |x| x } if uniq == true
|
251
268
|
|
252
|
-
|
253
|
-
count
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
269
|
+
if max.nil?
|
270
|
+
nonnegative_integer.bind { |count| make_array(element_generator, min, count, uniq) }
|
271
|
+
else
|
272
|
+
make_array(element_generator, min, max, uniq)
|
273
|
+
end
|
274
|
+
end
|
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
|
258
287
|
|
259
|
-
|
288
|
+
private def make_array_simple(element_generator, amount)
|
289
|
+
generators = amount.times.map do
|
290
|
+
element_generator.clone
|
260
291
|
end
|
261
292
|
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
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) }
|
266
323
|
end
|
267
324
|
end
|
268
325
|
|
@@ -292,7 +349,7 @@ module PropCheck
|
|
292
349
|
.map(&:to_h)
|
293
350
|
end
|
294
351
|
|
295
|
-
|
352
|
+
@@alphanumeric_chars = [('a'..'z'), ('A'..'Z'), ('0'..'9')].flat_map(&:to_a).freeze
|
296
353
|
##
|
297
354
|
# Generates a single-character string
|
298
355
|
# containing one of a..z, A..Z, 0..9
|
@@ -302,7 +359,7 @@ module PropCheck
|
|
302
359
|
# >> Generators.alphanumeric_char.sample(5, size: 10, rng: Random.new(42))
|
303
360
|
# => ["M", "Z", "C", "o", "Q"]
|
304
361
|
def alphanumeric_char
|
305
|
-
one_of(
|
362
|
+
one_of(*@@alphanumeric_chars.map(&method(:constant)))
|
306
363
|
end
|
307
364
|
|
308
365
|
##
|
@@ -313,11 +370,13 @@ module PropCheck
|
|
313
370
|
#
|
314
371
|
# >> Generators.alphanumeric_string.sample(5, size: 10, rng: Random.new(42))
|
315
372
|
# => ["ZCoQ", "8uM", "wkkx0JNx", "v0bxRDLb", "Gl5v8RyWA6"]
|
373
|
+
#
|
374
|
+
# Accepts the same options as `array`
|
316
375
|
def alphanumeric_string(**kwargs)
|
317
376
|
array(alphanumeric_char, **kwargs).map(&:join)
|
318
377
|
end
|
319
378
|
|
320
|
-
|
379
|
+
@@printable_ascii_chars = (' '..'~').to_a.freeze
|
321
380
|
|
322
381
|
##
|
323
382
|
# Generates a single-character string
|
@@ -328,7 +387,7 @@ module PropCheck
|
|
328
387
|
# >> Generators.printable_ascii_char.sample(size: 10, rng: Random.new(42))
|
329
388
|
# => ["S", "|", ".", "g", "\\", "4", "r", "v", "j", "j"]
|
330
389
|
def printable_ascii_char
|
331
|
-
one_of(
|
390
|
+
one_of(*@@printable_ascii_chars.map(&method(:constant)))
|
332
391
|
end
|
333
392
|
|
334
393
|
##
|
@@ -339,12 +398,14 @@ module PropCheck
|
|
339
398
|
#
|
340
399
|
# >> Generators.printable_ascii_string.sample(5, size: 10, rng: Random.new(42))
|
341
400
|
# => ["S|.g", "rvjjw7\"5T!", "=", "!_[4@", "Y"]
|
401
|
+
#
|
402
|
+
# Accepts the same options as `array`
|
342
403
|
def printable_ascii_string(**kwargs)
|
343
404
|
array(printable_ascii_char, **kwargs).map(&:join)
|
344
405
|
end
|
345
406
|
|
346
|
-
|
347
|
-
|
407
|
+
@@ascii_chars = [
|
408
|
+
@@printable_ascii_chars,
|
348
409
|
[
|
349
410
|
"\n",
|
350
411
|
"\r",
|
@@ -367,7 +428,7 @@ module PropCheck
|
|
367
428
|
# >> Generators.ascii_char.sample(size: 10, rng: Random.new(42))
|
368
429
|
# => ["d", "S", "|", ".", "g", "\\", "4", "d", "r", "v"]
|
369
430
|
def ascii_char
|
370
|
-
one_of(
|
431
|
+
one_of(*@@ascii_chars.map(&method(:constant)))
|
371
432
|
end
|
372
433
|
|
373
434
|
##
|
@@ -378,12 +439,14 @@ module PropCheck
|
|
378
439
|
#
|
379
440
|
# >> Generators.ascii_string.sample(5, size: 10, rng: Random.new(42))
|
380
441
|
# => ["S|.g", "drvjjw\b\a7\"", "!w=E!_[4@k", "x", "zZI{[o"]
|
442
|
+
#
|
443
|
+
# Accepts the same options as `array`
|
381
444
|
def ascii_string(**kwargs)
|
382
445
|
array(ascii_char, **kwargs).map(&:join)
|
383
446
|
end
|
384
447
|
|
385
|
-
|
386
|
-
|
448
|
+
@@printable_chars = [
|
449
|
+
@@ascii_chars,
|
387
450
|
"\u{A0}".."\u{D7FF}",
|
388
451
|
"\u{E000}".."\u{FFFD}",
|
389
452
|
"\u{10000}".."\u{10FFFF}"
|
@@ -398,7 +461,7 @@ module PropCheck
|
|
398
461
|
# >> Generators.printable_char.sample(size: 10, rng: Random.new(42))
|
399
462
|
# => ["吏", "", "", "", "", "", "", "", "", "Ȍ"]
|
400
463
|
def printable_char
|
401
|
-
one_of(
|
464
|
+
one_of(*@@printable_chars.map(&method(:constant)))
|
402
465
|
end
|
403
466
|
|
404
467
|
##
|
@@ -409,6 +472,8 @@ module PropCheck
|
|
409
472
|
#
|
410
473
|
# >> Generators.printable_string.sample(5, size: 10, rng: Random.new(42))
|
411
474
|
# => ["", "Ȍ", "𐁂", "Ȕ", ""]
|
475
|
+
#
|
476
|
+
# Accepts the same options as `array`
|
412
477
|
def printable_string(**kwargs)
|
413
478
|
array(printable_char, **kwargs).map(&:join)
|
414
479
|
end
|
@@ -435,6 +500,8 @@ module PropCheck
|
|
435
500
|
#
|
436
501
|
# >> Generators.string.sample(5, size: 10, rng: Random.new(42))
|
437
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`
|
438
505
|
def string(**kwargs)
|
439
506
|
array(char, **kwargs).map(&:join)
|
440
507
|
end
|
@@ -520,5 +587,44 @@ module PropCheck
|
|
520
587
|
def nillable(other_generator)
|
521
588
|
frequency(9 => other_generator, 1 => constant(nil))
|
522
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
|
523
629
|
end
|
524
630
|
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
@@ -9,12 +9,16 @@ module PropCheck
|
|
9
9
|
|
10
10
|
include Enumerable
|
11
11
|
|
12
|
-
attr_accessor :root
|
12
|
+
attr_accessor :root
|
13
13
|
def initialize(root, children = [].lazy)
|
14
14
|
@root = root
|
15
15
|
@children = children
|
16
16
|
end
|
17
17
|
|
18
|
+
def children
|
19
|
+
@children.reject { |child| child.root == :"_PropCheck.filter_me" }
|
20
|
+
end
|
21
|
+
|
18
22
|
##
|
19
23
|
# Maps `block` eagerly over `root` and lazily over `children`, returning a new LazyTree as result.
|
20
24
|
#
|
@@ -121,5 +125,6 @@ module PropCheck
|
|
121
125
|
end
|
122
126
|
end
|
123
127
|
end
|
128
|
+
|
124
129
|
end
|
125
130
|
end
|
data/lib/prop_check/property.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'stringio'
|
2
|
-
require
|
2
|
+
require 'amazing_print'
|
3
3
|
|
4
4
|
require 'prop_check/property/configuration'
|
5
5
|
require 'prop_check/property/output_formatter'
|
@@ -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
|
#
|
@@ -36,7 +42,6 @@ module PropCheck
|
|
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
44
|
def self.forall(*bindings, **kwbindings, &block)
|
39
|
-
|
40
45
|
property = new(*bindings, **kwbindings)
|
41
46
|
|
42
47
|
return property.check(&block) if block_given?
|
@@ -61,19 +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
|
-
@gen = gen_from_bindings(bindings, kwbindings)
|
72
|
-
@condition = proc { true }
|
73
70
|
@config = self.class.configuration
|
74
71
|
@hooks = PropCheck::Hooks.new
|
72
|
+
|
73
|
+
@gen = gen_from_bindings(bindings, kwbindings) unless bindings.empty? && kwbindings.empty?
|
74
|
+
freeze
|
75
75
|
end
|
76
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
|
+
|
77
88
|
##
|
78
89
|
# Returns the configuration of this property
|
79
90
|
# for introspection.
|
@@ -91,11 +102,22 @@ module PropCheck
|
|
91
102
|
# you can immediately pass a block to this method.
|
92
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`)
|
93
104
|
def with_config(**config, &block)
|
94
|
-
|
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?
|
110
|
+
|
111
|
+
duplicate
|
112
|
+
end
|
95
113
|
|
96
|
-
|
114
|
+
def with_bindings(*bindings, **kwbindings)
|
115
|
+
raise ArgumentError, 'No bindings specified!' if bindings.empty? && kwbindings.empty?
|
97
116
|
|
98
|
-
self
|
117
|
+
duplicate = self.dup
|
118
|
+
duplicate.instance_variable_set(:@gen, gen_from_bindings(bindings, kwbindings))
|
119
|
+
duplicate.freeze
|
120
|
+
duplicate
|
99
121
|
end
|
100
122
|
|
101
123
|
##
|
@@ -107,14 +129,12 @@ module PropCheck
|
|
107
129
|
# you might encounter a GeneratorExhaustedError.
|
108
130
|
# Only filter if you have few inputs to reject. Otherwise, improve your generators.
|
109
131
|
def where(&condition)
|
110
|
-
#
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
self
|
132
|
+
raise ArgumentError, 'No generator bindings specified! #where should be called after `#forall` or `#with_bindings`.' unless @gen
|
133
|
+
|
134
|
+
duplicate = self.dup
|
135
|
+
duplicate.instance_variable_set(:@gen, @gen.where(&condition))
|
136
|
+
duplicate.freeze
|
137
|
+
duplicate
|
118
138
|
end
|
119
139
|
|
120
140
|
|
@@ -124,8 +144,10 @@ module PropCheck
|
|
124
144
|
# This is useful to add setup logic
|
125
145
|
# When called multiple times, earlier-added hooks will be called _before_ `hook` is called.
|
126
146
|
def before(&hook)
|
127
|
-
|
128
|
-
|
147
|
+
duplicate = self.dup
|
148
|
+
duplicate.instance_variable_set(:@hooks, @hooks.add_before(&hook))
|
149
|
+
duplicate.freeze
|
150
|
+
duplicate
|
129
151
|
end
|
130
152
|
|
131
153
|
##
|
@@ -134,8 +156,10 @@ module PropCheck
|
|
134
156
|
# This is useful to add teardown logic
|
135
157
|
# When called multiple times, earlier-added hooks will be called _after_ `hook` is called.
|
136
158
|
def after(&hook)
|
137
|
-
|
138
|
-
|
159
|
+
duplicate = self.dup
|
160
|
+
duplicate.instance_variable_set(:@hooks, @hooks.add_after(&hook))
|
161
|
+
duplicate.freeze
|
162
|
+
duplicate
|
139
163
|
end
|
140
164
|
|
141
165
|
##
|
@@ -152,8 +176,10 @@ module PropCheck
|
|
152
176
|
# it is possible for the code after `yield` not to be called.
|
153
177
|
# So make sure that cleanup logic is wrapped with the `ensure` keyword.
|
154
178
|
def around(&hook)
|
155
|
-
|
156
|
-
|
179
|
+
duplicate = self.dup
|
180
|
+
duplicate.instance_variable_set(:@hooks, @hooks.add_around(&hook))
|
181
|
+
duplicate.freeze
|
182
|
+
duplicate
|
157
183
|
end
|
158
184
|
|
159
185
|
##
|
data/lib/prop_check/version.rb
CHANGED
data/prop_check.gemspec
CHANGED
metadata
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: prop_check
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.14.1
|
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
|
-
name:
|
14
|
+
name: amazing_print
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '1.
|
19
|
+
version: '1.2'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '1.
|
26
|
+
version: '1.2'
|
27
27
|
description: PropCheck allows you to do property-based testing, including shrinking.
|
28
28
|
(akin to Haskell's QuickCheck, Erlang's PropEr, Elixir's StreamData). This means
|
29
29
|
that your test are run many times with different, autogenerated inputs, and as soon
|
@@ -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
|