prop_check 0.10.3 → 0.12.1

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: 4011263e2c1f768286c567c0e5ff57388a2a788d951ff8681d9eebd0afcab75a
4
- data.tar.gz: c3494f2bb245d923facbdde4c0517ccfc8e4238c2ca35f7459d710b840039c36
3
+ metadata.gz: f3e24e4381dfa87447517c281cf863fa0aaea551a46ec6af6f268188c3ad2e40
4
+ data.tar.gz: 2230a4cbe52d63b1604b6fb3631dea6cdf8cae0e70d88eceffe3aae326c06ce7
5
5
  SHA512:
6
- metadata.gz: ef736e1441b8a1b34c60904ce981f35184de019939b4e1a07453cd78135405506194856c7a6956e14a8b07192358d1473aaa1a4c58675b88268455533cae5a4a
7
- data.tar.gz: 808fef14337c957754b470d4abdd628030fcfb09649bc44118dce34c91e9dfbf7b4c8f138b8ed388ce6ebe29555374c0d6d319157ab7dde8d7190827d8c9ea02
6
+ metadata.gz: e41a1b190f7589b807db57104de7df8ec9c3cc1f046e93de2f05c712861d2e8b5a4b2a997e4315e98e39dee8474156de5534ed0720336485eb7e1bda8da7d184
7
+ data.tar.gz: e73715ea79be1b1db8f6df792d0af99f235030a6a5719a5b23825364fcb22980776fe95eea14d579145e68984a79795fa9ecc19680f971fdaaf5d115afcbfff8
@@ -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,5 @@
1
- - 0.8.0 New syntax that is more explicit, passng generated values to blocks as parameters.
1
+ - 0.12.1 - Fixes shrinking when filtering bug.
2
+ - 0.12.0 - `PropCheck::Generators#instance`
3
+ - 0.11.0 - Improved syntax to support Ruby 2.7 and up without deprecation warnings, full support for `#where`.
4
+ - 0.10.0 - Some bugfixes, support for `#where`
5
+ - 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
 
@@ -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,11 +99,11 @@ module PropCheck
95
99
  ##
96
100
  # Creates a new Generator that returns a value by running `proc` on the output of the current Generator.
97
101
  #
98
- # >> Generators.choose(32..128).map(&:chr).call(10, Random.new(42))
102
+ # >> Generators.choose(32..128).map(&:chr).call(size: 10, rng: Random.new(42))
99
103
  # => "S"
100
104
  def map(&proc)
101
- Generator.new do |size, rng|
102
- result = self.generate(size, rng)
105
+ Generator.new do |**kwargs|
106
+ result = self.generate(**kwargs)
103
107
  result.map(&proc)
104
108
  end
105
109
  end
@@ -107,20 +111,14 @@ module PropCheck
107
111
  ##
108
112
  # Creates a new Generator that only produces a value when the block `condition` returns a truthy value.
109
113
  def where(&condition)
110
- self.map do |*result|
111
- if condition.call(*result)
114
+ self.map do |result|
115
+ # if condition.call(*result)
116
+ if PropCheck::Helper.call_splatted(result, &condition)
112
117
  result
113
118
  else
114
119
  :"_PropCheck.filter_me"
115
120
  end
116
121
  end
117
- # self.map do |*result|
118
- # if condition.call(*result)
119
- # result
120
- # else
121
- # :'_PropCheck.filter_me'
122
- # end
123
- # end
124
122
  end
125
123
  end
126
124
  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,44 @@ 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
+ # attr_accessor :name, :age
536
+ # def initialize(name: , age: )
537
+ # @name = name
538
+ # @age = age
539
+ # end
540
+ #
541
+ # def inspect
542
+ # "<User name: #{@name.inspect}, age: #{@age.inspect}>"
543
+ # end
544
+ # end
545
+ #
546
+ # >> user_gen = Generators.instance(User, name: Generators.printable_ascii_string, age: Generators.nonnegative_integer)
547
+ # >> user_gen.sample(3, rng: Random.new(42)).inspect
548
+ # => "[<User name: \"S|.g\", age: 10>, <User name: \"rvjj\", age: 10>, <User name: \"7\\\"5T!w=\", age: 5>]"
549
+ def instance(klass, *args, **kwargs)
550
+ tuple(*args).bind do |vals|
551
+ fixed_hash(**kwargs).map do |kwvals|
552
+ if kwvals == {}
553
+ klass.new(*vals)
554
+ elsif vals == []
555
+ klass.new(**kwvals)
556
+ else
557
+ klass.new(*vals, **kwvals)
558
+ end
559
+ end
560
+ end
561
+ end
486
562
  end
487
563
  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.3'
2
+ VERSION = '0.12.1'
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.3
4
+ version: 0.12.1
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.