prop_check 0.10.2 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 88b54994bb83cb44c77b887dfc6839e19b467bddd8a32dd7d1fe91e624261799
4
- data.tar.gz: e515d1522c939cb72eed02ed9fd98b458b1944a16b6c1f50f6c6bbc7076caaa5
3
+ metadata.gz: 0b569736a2799a9455febcbb900042168be2f872a46657080cf1f31771e8f569
4
+ data.tar.gz: a97cf13cfdaaab60b16221493eab5a2de0fe7160e8d785f1ac9f5daad838e66a
5
5
  SHA512:
6
- metadata.gz: 3ba9d57ffa4e1d1f7d15b9c202cf232b5a931a57ac6d2d2bba4537030400e892d3154d2ab2db9ce97f6896bf97bb3a64a3cbdba02ac9cc15a561c815e7963aa6
7
- data.tar.gz: 90a2fd3a15cf2ed27fc1eb1aea4273e75c39143df861bcf0a25b456c6373a088452a7d4b5e1b52aa52a0f61b1c94b0ceceef442bf3507793417d38ad91bcb2b3
6
+ metadata.gz: 980bda5f214ae656a859d14041846b540341b7279cfe5fd997639d624d282811fc18968ade23822687c7b97de9363b4a3346d9dac58a0a3708585f1c02b5c45d
7
+ data.tar.gz: f62979c7577fae30c86b357bb50894b79c15d62603325c2496f4e341fdb93d40079873a83138f357c36c66a32089908e3e2368ac9ee579396e57f9c1714d1439
@@ -1 +1 @@
1
- ruby 2.6.5
1
+ ruby 2.7.1
@@ -1 +1,4 @@
1
- - 0.8.0 New syntax that is more explicit, passng generated values to blocks as parameters.
1
+ - 0.12.0 - `PropCheck::Generators#instance`
2
+ - 0.11.0 - Improved syntax to support Ruby 2.7 and up without deprecation warnings, full support for `#where`.
3
+ - 0.10.0 - Some bugfixes, support for `#where`
4
+ - 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
 
@@ -10,6 +10,8 @@ module PropCheck
10
10
  class Generator
11
11
  @@default_size = 10
12
12
  @@default_rng = Random.new
13
+ @@max_consecutive_attempts = 100
14
+ @@default_kwargs = {size: @@default_size, rng: @@default_rng, max_consecutive_attempts: @@max_consecutive_attempts}
13
15
 
14
16
  ##
15
17
  # Being a special kind of Proc, a Generator wraps a block.
@@ -20,26 +22,38 @@ module PropCheck
20
22
  ##
21
23
  # Given a `size` (integer) and a random number generator state `rng`,
22
24
  # generate a LazyTree.
23
- def generate(size = @@default_size, rng = @@default_rng)
24
- @block.call(size, rng)
25
+ def generate(**kwargs)
26
+ kwargs = @@default_kwargs.merge(kwargs)
27
+ max_consecutive_attempts = kwargs[:max_consecutive_attempts]
28
+
29
+ (0..max_consecutive_attempts).each do
30
+ res = @block.call(**kwargs)
31
+ return res unless res.root == :"_PropCheck.filter_me"
32
+ end
33
+
34
+ raise Errors::GeneratorExhaustedError, """
35
+ Exhausted #{max_consecutive_attempts} consecutive generation attempts.
36
+
37
+ Probably too few generator results were adhering to a `where` condition.
38
+ """
25
39
  end
26
40
 
27
41
  ##
28
42
  # Generates a value, and only return this value
29
43
  # (drop information for shrinking)
30
44
  #
31
- # >> Generators.integer.call(1000, Random.new(42))
45
+ # >> Generators.integer.call(size: 1000, rng: Random.new(42))
32
46
  # => 126
33
- def call(size = @@default_size, rng = @@default_rng)
34
- generate(size, rng).root
47
+ def call(**kwargs)
48
+ generate(**@@default_kwargs.merge(kwargs)).root
35
49
  end
36
50
 
37
51
  ##
38
52
  # Returns `num_of_samples` values from calling this Generator.
39
53
  # This is mostly useful for debugging if a generator behaves as you intend it to.
40
- def sample(num_of_samples = 10, size: @@default_size, rng: @@default_rng)
54
+ def sample(num_of_samples = 10, **kwargs)
41
55
  num_of_samples.times.map do
42
- call(size, rng)
56
+ call(**@@default_kwargs.merge(kwargs))
43
57
  end
44
58
  end
45
59
 
@@ -49,10 +63,10 @@ module PropCheck
49
63
  #
50
64
  # Keen readers may notice this as the Monadic 'pure'/'return' implementation for Generators.
51
65
  #
52
- # >> Generators.integer.bind { |a| Generators.integer.bind { |b| Generator.wrap([a , b]) } }.call(100, Random.new(42))
66
+ # >> Generators.integer.bind { |a| Generators.integer.bind { |b| Generator.wrap([a , b]) } }.call(size: 100, rng: Random.new(42))
53
67
  # => [2, 79]
54
68
  def self.wrap(val)
55
- Generator.new { |_size, _rng| LazyTree.wrap(val) }
69
+ Generator.new { LazyTree.wrap(val) }
56
70
  end
57
71
 
58
72
  ##
@@ -61,7 +75,7 @@ module PropCheck
61
75
  #
62
76
  # Keen readers may notice this as the Monadic 'bind' (sometimes known as '>>=') implementation for Generators.
63
77
  #
64
- # >> Generators.integer.bind { |a| Generators.integer.bind { |b| Generator.wrap([a , b]) } }.call(100, Random.new(42))
78
+ # >> Generators.integer.bind { |a| Generators.integer.bind { |b| Generator.wrap([a , b]) } }.call(size: 100, rng: Random.new(42))
65
79
  # => [2, 79]
66
80
  def bind(&generator_proc)
67
81
  # Generator.new do |size, rng|
@@ -71,11 +85,11 @@ module PropCheck
71
85
  # inner_generator.generate(size, rng)
72
86
  # end.flatten
73
87
  # end
74
- Generator.new do |size, rng|
75
- outer_result = self.generate(size, rng)
88
+ Generator.new do |**kwargs|
89
+ outer_result = self.generate(**kwargs)
76
90
  outer_result.bind do |outer_val|
77
91
  inner_generator = generator_proc.call(outer_val)
78
- inner_generator.generate(size, rng)
92
+ inner_generator.generate(**kwargs)
79
93
  end
80
94
  end
81
95
  end
@@ -83,11 +97,11 @@ module PropCheck
83
97
  ##
84
98
  # Creates a new Generator that returns a value by running `proc` on the output of the current Generator.
85
99
  #
86
- # >> Generators.choose(32..128).map(&:chr).call(10, Random.new(42))
100
+ # >> Generators.choose(32..128).map(&:chr).call(size: 10, rng: Random.new(42))
87
101
  # => "S"
88
102
  def map(&proc)
89
- Generator.new do |size, rng|
90
- result = self.generate(size, rng)
103
+ Generator.new do |**kwargs|
104
+ result = self.generate(**kwargs)
91
105
  result.map(&proc)
92
106
  end
93
107
  end
@@ -95,20 +109,14 @@ module PropCheck
95
109
  ##
96
110
  # Creates a new Generator that only produces a value when the block `condition` returns a truthy value.
97
111
  def where(&condition)
98
- self.map do |*result|
99
- if condition.call(*result)
112
+ self.map do |result|
113
+ # if condition.call(*result)
114
+ if PropCheck::Helper.call_splatted(result, &condition)
100
115
  result
101
116
  else
102
117
  :"_PropCheck.filter_me"
103
118
  end
104
119
  end
105
- # self.map do |*result|
106
- # if condition.call(*result)
107
- # result
108
- # else
109
- # :'_PropCheck.filter_me'
110
- # end
111
- # end
112
120
  end
113
121
  end
114
122
  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,14 +74,14 @@ 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:, **|
84
85
  val = rng.rand(-size..size)
85
86
  LazyTree.new(val, integer_shrink(val))
86
87
  end
@@ -193,12 +194,12 @@ module PropCheck
193
194
  #
194
195
  # Shrinks element generators, one at a time (trying last one first).
195
196
  #
196
- # >> Generators.tuple(Generators.integer, Generators.real_float).call(10, Random.new(42))
197
+ # >> Generators.tuple(Generators.integer, Generators.real_float).call(size: 10, rng: Random.new(42))
197
198
  # => [-4, 13.0]
198
199
  def tuple(*generators)
199
- Generator.new do |size, rng|
200
+ Generator.new do |**kwargs|
200
201
  LazyTree.zip(generators.map do |generator|
201
- generator.generate(size, rng)
202
+ generator.generate(**kwargs)
202
203
  end)
203
204
  end
204
205
  end
@@ -210,7 +211,7 @@ module PropCheck
210
211
  #
211
212
  # Shrinks element generators.
212
213
  #
213
- # >> Generators.fixed_hash(a: Generators.integer(), b: Generators.real_float(), c: Generators.integer()).call(10, Random.new(42))
214
+ # >> Generators.fixed_hash(a: Generators.integer(), b: Generators.real_float(), c: Generators.integer()).call(size: 10, rng: Random.new(42))
214
215
  # => {:a=>-4, :b=>13.0, :c=>-3}
215
216
  def fixed_hash(hash)
216
217
  keypair_generators =
@@ -227,17 +228,42 @@ module PropCheck
227
228
  # is generated by `element_generator`.
228
229
  #
229
230
  # Shrinks to shorter arrays (with shrunken elements).
231
+ # Accepted keyword arguments:
232
+ #
233
+ # `empty:` When false, behaves the same as `min: 1`
234
+ # `min:` Ensures at least this many elements are generated. (default: 0)
235
+ # `max:` Ensures at most this many elements are generated. When nil, an arbitrary count is used instead. (default: nil)
236
+ #
230
237
  #
238
+ # >> Generators.array(Generators.positive_integer).sample(5, size: 1, rng: Random.new(42))
239
+ # => [[2], [2], [2], [1], [2]]
231
240
  # >> Generators.array(Generators.positive_integer).sample(5, size: 10, rng: Random.new(42))
232
241
  # => [[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
242
+ #
243
+ # >> Generators.array(Generators.positive_integer, empty: true).sample(5, size: 1, rng: Random.new(1))
244
+ # => [[], [2], [], [], [2]]
245
+ # >> Generators.array(Generators.positive_integer, empty: false).sample(5, size: 1, rng: Random.new(1))
246
+ # => [[2], [1], [2], [1], [1]]
247
+
248
+
249
+ def array(element_generator, min: 0, max: nil, empty: true)
250
+ min = 1 if min.zero? && !empty
251
+
252
+ res = proc do |count|
253
+ count = min + 1 if count < min
254
+ count += 1 if count == min && min != 0
255
+ generators = (min...count).map do
236
256
  element_generator.clone
237
257
  end
238
258
 
239
259
  tuple(*generators)
240
260
  end
261
+
262
+ if max.nil?
263
+ nonnegative_integer.bind(&res)
264
+ else
265
+ proc.call(max)
266
+ end
241
267
  end
242
268
 
243
269
  ##
@@ -249,11 +275,22 @@ module PropCheck
249
275
  #
250
276
  # >> Generators.hash(Generators.printable_ascii_string, Generators.positive_integer).sample(5, size: 3, rng: Random.new(42))
251
277
  # => [{""=>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)
278
+ def hash(*args, **kwargs)
279
+ if args.length == 2
280
+ hash_of(*args, **kwargs)
281
+ else
282
+ super
283
+ end
255
284
  end
256
285
 
286
+ ##
287
+ #
288
+ # Alias for `#hash` that does not conflict with a possibly overriden `Object#hash`.
289
+ #
290
+ def hash_of(key_generator, value_generator, **kwargs)
291
+ array(tuple(key_generator, value_generator), **kwargs)
292
+ .map(&:to_h)
293
+ end
257
294
 
258
295
  @alphanumeric_chars = [('a'..'z'), ('A'..'Z'), ('0'..'9')].flat_map(&:to_a).freeze
259
296
  ##
@@ -276,8 +313,8 @@ module PropCheck
276
313
  #
277
314
  # >> Generators.alphanumeric_string.sample(5, size: 10, rng: Random.new(42))
278
315
  # => ["ZCoQ", "8uM", "wkkx0JNx", "v0bxRDLb", "Gl5v8RyWA6"]
279
- def alphanumeric_string
280
- array(alphanumeric_char).map(&:join)
316
+ def alphanumeric_string(**kwargs)
317
+ array(alphanumeric_char, **kwargs).map(&:join)
281
318
  end
282
319
 
283
320
  @printable_ascii_chars = (' '..'~').to_a.freeze
@@ -302,8 +339,8 @@ module PropCheck
302
339
  #
303
340
  # >> Generators.printable_ascii_string.sample(5, size: 10, rng: Random.new(42))
304
341
  # => ["S|.g", "rvjjw7\"5T!", "=", "!_[4@", "Y"]
305
- def printable_ascii_string
306
- array(printable_ascii_char).map(&:join)
342
+ def printable_ascii_string(**kwargs)
343
+ array(printable_ascii_char, **kwargs).map(&:join)
307
344
  end
308
345
 
309
346
  @ascii_chars = [
@@ -341,8 +378,8 @@ module PropCheck
341
378
  #
342
379
  # >> Generators.ascii_string.sample(5, size: 10, rng: Random.new(42))
343
380
  # => ["S|.g", "drvjjw\b\a7\"", "!w=E!_[4@k", "x", "zZI{[o"]
344
- def ascii_string
345
- array(ascii_char).map(&:join)
381
+ def ascii_string(**kwargs)
382
+ array(ascii_char, **kwargs).map(&:join)
346
383
  end
347
384
 
348
385
  @printable_chars = [
@@ -372,8 +409,8 @@ module PropCheck
372
409
  #
373
410
  # >> Generators.printable_string.sample(5, size: 10, rng: Random.new(42))
374
411
  # => ["", "Ȍ", "𐁂", "Ȕ", ""]
375
- def printable_string
376
- array(printable_char).map(&:join)
412
+ def printable_string(**kwargs)
413
+ array(printable_char, **kwargs).map(&:join)
377
414
  end
378
415
 
379
416
  ##
@@ -398,8 +435,8 @@ module PropCheck
398
435
  #
399
436
  # >> Generators.string.sample(5, size: 10, rng: Random.new(42))
400
437
  # => ["\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)
438
+ def string(**kwargs)
439
+ array(char, **kwargs).map(&:join)
403
440
  end
404
441
 
405
442
  ##
@@ -483,5 +520,43 @@ module PropCheck
483
520
  def nillable(other_generator)
484
521
  frequency(9 => other_generator, 1 => constant(nil))
485
522
  end
523
+
524
+ ##
525
+ # Generates an instance of `klass`
526
+ # using `args` and/or `kwargs`
527
+ # as generators for the arguments that are passed to `klass.new`
528
+ #
529
+ # ## Example:
530
+ #
531
+ # Given a class like this:
532
+ #
533
+ #
534
+ # class User
535
+ # def initialize(name: , age: )
536
+ # @name = name
537
+ # @age = age
538
+ # end
539
+ #
540
+ # def inspect
541
+ # "<User name: #{@name.inspect}, age: #{@age.inspect}>"
542
+ # end
543
+ # end
544
+ #
545
+ # >> user_gen = Generators.instance(User, name: Generators.printable_ascii_string, age: Generators.nonnegative_integer)
546
+ # >> user_gen.sample(3, rng: Random.new(42)).inspect
547
+ # => "[<User name: \"S|.g\", age: 10>, <User name: \"rvjj\", age: 10>, <User name: \"7\\\"5T!w=\", age: 5>]"
548
+ def instance(klass, *args, **kwargs)
549
+ tuple(*args).bind do |vals|
550
+ fixed_hash(**kwargs).map do |kwvals|
551
+ if kwvals == {}
552
+ klass.new(*vals)
553
+ elsif vals == []
554
+ klass.new(**kwvals)
555
+ else
556
+ klass.new(*vals, **kwvals)
557
+ end
558
+ end
559
+ end
560
+ end
486
561
  end
487
562
  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,6 +7,8 @@ module PropCheck
7
7
  class LazyTree
8
8
  require 'prop_check/helper'
9
9
 
10
+ include Enumerable
11
+
10
12
  attr_accessor :root, :children
11
13
  def initialize(root, children = [].lazy)
12
14
  @root = root
@@ -66,25 +68,15 @@ module PropCheck
66
68
  # >> LazyTree.new(1, [LazyTree.new(2, [LazyTree.new(3)]), LazyTree.new(4)]).each.force
67
69
  # => [1, 4, 2, 3]
68
70
  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, [])
71
+ self.to_enum(:each) unless block_given?
76
72
 
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
73
+ squish([])
74
+ .each(&block)
75
+ end
84
76
 
85
- # res = [[root], children.flat_map(&:each)].lazy.flat_map(&:lazy)
86
- # res = res.map(&block) if block_given?
87
- # res
77
+ protected def squish(arr)
78
+ new_children = self.children.reduce(arr) { |acc, elem| elem.squish(acc) }
79
+ PropCheck::Helper.lazy_append([self.root], new_children)
88
80
  end
89
81
 
90
82
  ##
@@ -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.2'
2
+ VERSION = '0.12.0'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prop_check
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.2
4
+ version: 0.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Qqwy/Wiebe-Marten Wijnja
@@ -83,7 +83,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
83
83
  - !ruby/object:Gem::Version
84
84
  version: '0'
85
85
  requirements: []
86
- rubygems_version: 3.0.3
86
+ rubygems_version: 3.1.2
87
87
  signing_key:
88
88
  specification_version: 4
89
89
  summary: PropCheck allows you to do property-based testing, including shrinking.