prop_check 0.10.4 → 0.13.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: 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.