prop_check 0.11.1 → 0.14.1
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/.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
|