prop_check 0.10.4 → 0.13.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f8f707bae1dadf800e667376a749c7d5de3f3d741d13e63118de79dfb7233fb5
4
- data.tar.gz: e86ac3b096bff66cfa88c0a66b1fc8ea91dc8452ded41c417fb1452c33c2b1d7
3
+ metadata.gz: fb51503257a33dbe349da1b2b582be1c0fe38465bbe10fb14f610885591c9f7b
4
+ data.tar.gz: d357a2221e6b078c58d78826758cb22f4df3b8c34dd746001d854c505dfe2dea
5
5
  SHA512:
6
- metadata.gz: e0985469e34e0b353aab0fac657a073fadfe135c03029187d3f7824ebded0bfd244122aa2ae4361b5ccad3fb05ca52162e3967e9f46a6e43674e7d7626a700f7
7
- data.tar.gz: 0b0505768ea68855beaf9c8954cb973215dd392c40975e7f581e7faa9d7570c9e4fb76402227c5b0f223f96e87f72d58decfb7dffd2744897c22db3ba0959ac6
6
+ metadata.gz: 5d757c5c95b19cb3805ff1cc14af811fbb5e4f0cdb0cadffa581f46d33d20f2c37b8093fda66828b6ce3862b0743723ebb5afe01b183acc4bdbe6fc3f96ddb9b
7
+ data.tar.gz: e9288cedccdaafe609c2557a74176b9eea306fbff4aa2e9b9a4219702755efa33e53fb9f22d13f5ad0d7aa1af0dca732a1fe924d2119c43df39b71d1107be6d4
@@ -1 +1 @@
1
- ruby 2.6.5
1
+ ruby 2.7.1
@@ -3,7 +3,7 @@ sudo: false
3
3
  language: ruby
4
4
  cache: bundler
5
5
  rvm:
6
- - 2.5.1
6
+ - 2.6.5
7
7
  before_install: gem install bundler -v 2.0.2
8
8
  env:
9
9
  global:
@@ -1 +1,6 @@
1
- - 0.8.0 New syntax that is more explicit, passng generated values to blocks as parameters.
1
+ - 0.13.0 - Adds Generator#resize
2
+ - 0.12.1 - Fixes shrinking when filtering bug.
3
+ - 0.12.0 - `PropCheck::Generators#instance`
4
+ - 0.11.0 - Improved syntax to support Ruby 2.7 and up without deprecation warnings, full support for `#where`.
5
+ - 0.10.0 - Some bugfixes, support for `#where`
6
+ - 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
- - [x] Look into customization of settings from e.g. command line arguments.
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
 
@@ -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")
@@ -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(size = @@default_size, rng = @@default_rng, max_consecutive_attempts = @@max_consecutive_attempts)
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(size, rng)
27
- next if res == :"PropCheck.filter_me"
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(size = @@default_size, rng = @@default_rng)
46
- generate(size, rng).root
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, size: @@default_size, rng: @@default_rng)
56
+ def sample(num_of_samples = 10, **kwargs)
53
57
  num_of_samples.times.map do
54
- call(size, rng)
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 { |_size, _rng| LazyTree.wrap(val) }
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 |size, rng|
87
- outer_result = self.generate(size, rng)
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(size, rng)
94
+ inner_generator.generate(**kwargs)
91
95
  end
92
96
  end
93
97
  end
@@ -95,34 +99,40 @@ 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 |size, rng|
102
- result = self.generate(size, rng)
103
- result.map do |*val|
104
- proc.call(*val)
105
- end
105
+ Generator.new do |**kwargs|
106
+ result = self.generate(**kwargs)
107
+ result.map(&proc)
106
108
  end
107
109
  end
108
110
 
109
111
  ##
110
112
  # Creates a new Generator that only produces a value when the block `condition` returns a truthy value.
111
113
  def where(&condition)
112
- self.map do |*result|
113
- if condition.call(*result)
114
+ self.map do |result|
115
+ # if condition.call(*result)
116
+ if PropCheck::Helper.call_splatted(result, &condition)
114
117
  result
115
118
  else
116
119
  :"_PropCheck.filter_me"
117
120
  end
118
121
  end
119
- # self.map do |*result|
120
- # if condition.call(*result)
121
- # result
122
- # else
123
- # :'_PropCheck.filter_me'
124
- # end
125
- # end
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
126
136
  end
127
137
  end
128
138
  end
@@ -10,6 +10,7 @@ module PropCheck
10
10
  # where you want to use them.
11
11
  module Generators
12
12
  extend self
13
+
13
14
  ##
14
15
  # Always returns the same value, regardless of `size` or `rng` (random number generator state)
15
16
  #
@@ -59,7 +60,7 @@ module PropCheck
59
60
  # >> r = Random.new(42); Generators.choose(0..5).sample(size: 20000, rng: r)
60
61
  # => [3, 4, 2, 4, 4, 1, 2, 2, 2, 4]
61
62
  def choose(range)
62
- Generator.new do |_size, rng|
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, rng|
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.
@@ -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 |size, rng|
208
+ Generator.new do |**kwargs|
200
209
  LazyTree.zip(generators.map do |generator|
201
- generator.generate(size, rng)
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 =
@@ -227,17 +236,42 @@ module PropCheck
227
236
  # is generated by `element_generator`.
228
237
  #
229
238
  # Shrinks to shorter arrays (with shrunken elements).
239
+ # Accepted keyword arguments:
240
+ #
241
+ # `empty:` When false, behaves the same as `min: 1`
242
+ # `min:` Ensures at least this many elements are generated. (default: 0)
243
+ # `max:` Ensures at most this many elements are generated. When nil, an arbitrary count is used instead. (default: nil)
244
+ #
230
245
  #
246
+ # >> Generators.array(Generators.positive_integer).sample(5, size: 1, rng: Random.new(42))
247
+ # => [[2], [2], [2], [1], [2]]
231
248
  # >> Generators.array(Generators.positive_integer).sample(5, size: 10, rng: Random.new(42))
232
249
  # => [[10, 5, 1, 4], [5, 9, 1, 1, 11, 8, 4, 9, 11, 10], [6], [11, 11, 2, 2, 7, 2, 6, 5, 5], [2, 10, 9, 7, 9, 5, 11, 3]]
233
- def array(element_generator)
234
- nonnegative_integer.bind do |generator|
235
- generators = (0...generator).map do
250
+ #
251
+ # >> Generators.array(Generators.positive_integer, empty: true).sample(5, size: 1, rng: Random.new(1))
252
+ # => [[], [2], [], [], [2]]
253
+ # >> Generators.array(Generators.positive_integer, empty: false).sample(5, size: 1, rng: Random.new(1))
254
+ # => [[2], [1], [2], [1], [1]]
255
+
256
+
257
+ def array(element_generator, min: 0, max: nil, empty: true)
258
+ min = 1 if min.zero? && !empty
259
+
260
+ res = proc do |count|
261
+ count = min + 1 if count < min
262
+ count += 1 if count == min && min != 0
263
+ generators = (min...count).map do
236
264
  element_generator.clone
237
265
  end
238
266
 
239
267
  tuple(*generators)
240
268
  end
269
+
270
+ if max.nil?
271
+ nonnegative_integer.bind(&res)
272
+ else
273
+ res.call(max)
274
+ end
241
275
  end
242
276
 
243
277
  ##
@@ -249,11 +283,22 @@ module PropCheck
249
283
  #
250
284
  # >> Generators.hash(Generators.printable_ascii_string, Generators.positive_integer).sample(5, size: 3, rng: Random.new(42))
251
285
  # => [{""=>2, "g\\4"=>4, "rv"=>2}, {"7"=>2}, {"!"=>1, "E!"=>1}, {"kY5"=>2}, {}]
252
- def hash(key_generator, value_generator)
253
- array(tuple(key_generator, value_generator))
254
- .map(&:to_h)
286
+ def hash(*args, **kwargs)
287
+ if args.length == 2
288
+ hash_of(*args, **kwargs)
289
+ else
290
+ super
291
+ end
255
292
  end
256
293
 
294
+ ##
295
+ #
296
+ # Alias for `#hash` that does not conflict with a possibly overriden `Object#hash`.
297
+ #
298
+ def hash_of(key_generator, value_generator, **kwargs)
299
+ array(tuple(key_generator, value_generator), **kwargs)
300
+ .map(&:to_h)
301
+ end
257
302
 
258
303
  @alphanumeric_chars = [('a'..'z'), ('A'..'Z'), ('0'..'9')].flat_map(&:to_a).freeze
259
304
  ##
@@ -276,8 +321,8 @@ module PropCheck
276
321
  #
277
322
  # >> Generators.alphanumeric_string.sample(5, size: 10, rng: Random.new(42))
278
323
  # => ["ZCoQ", "8uM", "wkkx0JNx", "v0bxRDLb", "Gl5v8RyWA6"]
279
- def alphanumeric_string
280
- array(alphanumeric_char).map(&:join)
324
+ def alphanumeric_string(**kwargs)
325
+ array(alphanumeric_char, **kwargs).map(&:join)
281
326
  end
282
327
 
283
328
  @printable_ascii_chars = (' '..'~').to_a.freeze
@@ -302,8 +347,8 @@ module PropCheck
302
347
  #
303
348
  # >> Generators.printable_ascii_string.sample(5, size: 10, rng: Random.new(42))
304
349
  # => ["S|.g", "rvjjw7\"5T!", "=", "!_[4@", "Y"]
305
- def printable_ascii_string
306
- array(printable_ascii_char).map(&:join)
350
+ def printable_ascii_string(**kwargs)
351
+ array(printable_ascii_char, **kwargs).map(&:join)
307
352
  end
308
353
 
309
354
  @ascii_chars = [
@@ -341,8 +386,8 @@ module PropCheck
341
386
  #
342
387
  # >> Generators.ascii_string.sample(5, size: 10, rng: Random.new(42))
343
388
  # => ["S|.g", "drvjjw\b\a7\"", "!w=E!_[4@k", "x", "zZI{[o"]
344
- def ascii_string
345
- array(ascii_char).map(&:join)
389
+ def ascii_string(**kwargs)
390
+ array(ascii_char, **kwargs).map(&:join)
346
391
  end
347
392
 
348
393
  @printable_chars = [
@@ -372,8 +417,8 @@ module PropCheck
372
417
  #
373
418
  # >> Generators.printable_string.sample(5, size: 10, rng: Random.new(42))
374
419
  # => ["", "Ȍ", "𐁂", "Ȕ", ""]
375
- def printable_string
376
- array(printable_char).map(&:join)
420
+ def printable_string(**kwargs)
421
+ array(printable_char, **kwargs).map(&:join)
377
422
  end
378
423
 
379
424
  ##
@@ -398,8 +443,8 @@ module PropCheck
398
443
  #
399
444
  # >> Generators.string.sample(5, size: 10, rng: Random.new(42))
400
445
  # => ["\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}"]
401
- def string
402
- array(char).map(&:join)
446
+ def string(**kwargs)
447
+ array(char, **kwargs).map(&:join)
403
448
  end
404
449
 
405
450
  ##
@@ -483,5 +528,44 @@ module PropCheck
483
528
  def nillable(other_generator)
484
529
  frequency(9 => other_generator, 1 => constant(nil))
485
530
  end
531
+
532
+ ##
533
+ # Generates an instance of `klass`
534
+ # using `args` and/or `kwargs`
535
+ # as generators for the arguments that are passed to `klass.new`
536
+ #
537
+ # ## Example:
538
+ #
539
+ # Given a class like this:
540
+ #
541
+ #
542
+ # class User
543
+ # attr_accessor :name, :age
544
+ # def initialize(name: , age: )
545
+ # @name = name
546
+ # @age = age
547
+ # end
548
+ #
549
+ # def inspect
550
+ # "<User name: #{@name.inspect}, age: #{@age.inspect}>"
551
+ # end
552
+ # end
553
+ #
554
+ # >> user_gen = Generators.instance(User, name: Generators.printable_ascii_string, age: Generators.nonnegative_integer)
555
+ # >> user_gen.sample(3, rng: Random.new(42)).inspect
556
+ # => "[<User name: \"S|.g\", age: 10>, <User name: \"rvjj\", age: 10>, <User name: \"7\\\"5T!w=\", age: 5>]"
557
+ def instance(klass, *args, **kwargs)
558
+ tuple(*args).bind do |vals|
559
+ fixed_hash(**kwargs).map do |kwvals|
560
+ if kwvals == {}
561
+ klass.new(*vals)
562
+ elsif vals == []
563
+ klass.new(**kwvals)
564
+ else
565
+ klass.new(*vals, **kwvals)
566
+ end
567
+ end
568
+ end
569
+ end
486
570
  end
487
571
  end
@@ -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
@@ -7,12 +7,18 @@ module PropCheck
7
7
  class LazyTree
8
8
  require 'prop_check/helper'
9
9
 
10
- attr_accessor :root, :children
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
- squish = lambda do |tree, list|
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
- # base = [root]
78
- # recursive = children.map(&:each)
79
- # res = PropCheck::Helper.lazy_append(base, recursive)
80
-
81
- # return res.each(&block) if block_given?
82
-
83
- # res
77
+ squish([])
78
+ .each(&block)
79
+ end
84
80
 
85
- # res = [[root], children.flat_map(&:each)].lazy.flat_map(&:lazy)
86
- # res = res.map(&block) if block_given?
87
- # res
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
@@ -35,9 +35,9 @@ module PropCheck
35
35
  # a Property object is returned, which you can call the other instance methods
36
36
  # of this class on before finally passing a block to it using `#check`.
37
37
  # (so `forall(Generators.integer) do |val| ... end` and forall(Generators.integer).check do |val| ... end` are the same)
38
- def self.forall(*bindings, &block)
38
+ def self.forall(*bindings, **kwbindings, &block)
39
39
 
40
- property = new(*bindings)
40
+ property = new(*bindings, **kwbindings)
41
41
 
42
42
  return property.check(&block) if block_given?
43
43
 
@@ -66,8 +66,9 @@ module PropCheck
66
66
  def initialize(*bindings, **kwbindings)
67
67
  raise ArgumentError, 'No bindings specified!' if bindings.empty? && kwbindings.empty?
68
68
 
69
- @bindings = bindings
70
- @kwbindings = kwbindings
69
+ # @bindings = bindings
70
+ # @kwbindings = kwbindings
71
+ @gen = gen_from_bindings(bindings, kwbindings)
71
72
  @condition = proc { true }
72
73
  @config = self.class.configuration
73
74
  @hooks = PropCheck::Hooks.new
@@ -106,14 +107,17 @@ module PropCheck
106
107
  # you might encounter a GeneratorExhaustedError.
107
108
  # Only filter if you have few inputs to reject. Otherwise, improve your generators.
108
109
  def where(&condition)
109
- original_condition = @condition.dup
110
- @condition = proc do |*args|
111
- original_condition.call(*args) && condition.call(*args)
112
- end
110
+ # original_condition = @condition.dup
111
+ # @condition = proc do |val|
112
+ # call_splatted(val, &original_condition) && call_splatted(val, &condition)
113
+ # # original_condition.call(val) && condition.call(val)
114
+ # end
115
+ @gen = @gen.where(&condition)
113
116
 
114
117
  self
115
118
  end
116
119
 
120
+
117
121
  ##
118
122
  # Calls `hook` before each time a check is run with new data.
119
123
  #
@@ -155,21 +159,11 @@ module PropCheck
155
159
  ##
156
160
  # Checks the property (after settings have been altered using the other instance methods in this class.)
157
161
  def check(&block)
158
- gens =
159
- if @kwbindings != {}
160
- kwbinding_generator = PropCheck::Generators.fixed_hash(**@kwbindings)
161
- @bindings + [kwbinding_generator]
162
- else
163
- @bindings
164
- end
165
- binding_generator = PropCheck::Generators.tuple(*gens)
166
- # binding_generator = PropCheck::Generators.fixed_hash(**@kwbindings)
167
-
168
162
  n_runs = 0
169
163
  n_successful = 0
170
164
 
171
165
  # Loop stops at first exception
172
- attempts_enum(binding_generator).each do |generator_result|
166
+ attempts_enum(@gen).each do |generator_result|
173
167
  n_runs += 1
174
168
  check_attempt(generator_result, n_successful, &block)
175
169
  n_successful += 1
@@ -178,6 +172,25 @@ module PropCheck
178
172
  ensure_not_exhausted!(n_runs)
179
173
  end
180
174
 
175
+ private def gen_from_bindings(bindings, kwbindings)
176
+ if bindings == [] && kwbindings != {}
177
+ PropCheck::Generators.fixed_hash(**kwbindings)
178
+ elsif bindings != [] && kwbindings == {}
179
+ if bindings.size == 1
180
+ bindings.first
181
+ else
182
+ PropCheck::Generators.tuple(*bindings)
183
+ end
184
+ else
185
+ raise ArgumentError,
186
+ 'Attempted to use both normal and keyword bindings at the same time.
187
+ This is not supported because of the separation of positional and keyword arguments
188
+ (the old behaviour is deprecated in Ruby 2.7 and will be removed in 3.0)
189
+ c.f. https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/
190
+ '
191
+ end
192
+ end
193
+
181
194
  private def ensure_not_exhausted!(n_runs)
182
195
  return if n_runs >= @config.n_runs
183
196
 
@@ -196,7 +209,7 @@ module PropCheck
196
209
  end
197
210
 
198
211
  private def check_attempt(generator_result, n_successful, &block)
199
- block.call(*generator_result.root)
212
+ PropCheck::Helper.call_splatted(generator_result.root, &block)
200
213
 
201
214
  # immediately stop (without shrinnking) for when the app is asked
202
215
  # to close by outside intervention
@@ -236,9 +249,7 @@ module PropCheck
236
249
  size = 1
237
250
  (0...@config.max_generate_attempts)
238
251
  .lazy
239
- .map { binding_generator.generate(size, rng) }
240
- .reject { |val| val.root.any? { |elem| elem == :"_PropCheck.filter_me" }}
241
- .select { |val| @condition.call(*val.root) }
252
+ .map { binding_generator.generate(size: size, rng: rng, max_consecutive_attempts: @config.max_consecutive_attempts) }
242
253
  .map do |result|
243
254
  size += 1
244
255
 
@@ -1,8 +1,20 @@
1
1
  module PropCheck
2
2
  class Property
3
- Configuration = Struct.new(:verbose, :n_runs, :max_generate_attempts, :max_shrink_steps, keyword_init: true) do
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(verbose: false, n_runs: 1_000, max_generate_attempts: 10_000, max_shrink_steps: 10_000)
11
+ def initialize(
12
+ verbose: false,
13
+ n_runs: 1_000,
14
+ max_generate_attempts: 10_000,
15
+ max_shrink_steps: 10_000,
16
+ max_consecutive_attempts: 30
17
+ )
6
18
  super
7
19
  end
8
20
 
@@ -1,3 +1,4 @@
1
+ require 'prop_check/helper'
1
2
  class PropCheck::Property::Shrinker
2
3
  def initialize(bindings_tree, io, hooks, config)
3
4
  @problem_child = bindings_tree
@@ -62,7 +63,7 @@ class PropCheck::Property::Shrinker
62
63
 
63
64
  private def safe_call_block(sibling, &block)
64
65
  begin
65
- block.call(*sibling.root)
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
@@ -1,3 +1,3 @@
1
1
  module PropCheck
2
- VERSION = '0.10.4'
2
+ VERSION = '0.13.0'
3
3
  end
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.10.4
4
+ version: 0.13.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-02 00:00:00.000000000 Z
11
+ date: 2020-08-03 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.0.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.