prop_check 0.11.1 → 0.14.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '099a3bafe1fae426f476e3add7fb93ab333506670c72c35bb78e5e505b9f0764'
4
- data.tar.gz: d1c433ef97043f38c84a5bf8768fb46aa5fcc97f00518da831fbebc36142d2e5
3
+ metadata.gz: bd78cbcc636fbb2089c175708a6be52ebb8124b1f21758ee513f8a84fce33f97
4
+ data.tar.gz: d3bc244740e31fda8d3dbcdcafc3b7f2bd43676c13f945413bae852fbd82de6f
5
5
  SHA512:
6
- metadata.gz: 782411f48a77c4bc2213d6ecaecd70d7d7c3609c13bab2125ce853dedd7d3029fd8801ac882154d8c924cea212698740d8e330f8e2b90f460a5a87484413e560
7
- data.tar.gz: dd18135801b66e2f073e82066506f8bb26cf031a4bd8d2fc208864acd7ef3a7ec564f8364628d34e4ab40429a5444a1dbc41f329bb4c7d3a608011407bb0a923
6
+ metadata.gz: eda47d7e95d30de4a4b12fe18063b4eb234d7e2bafa2c071613c12c882e71a777da288504330990edde061a8247db33bdb8a7a829419b2111912b7499cef9749
7
+ data.tar.gz: d44b56680e431af41f2df178795a576d25ccf42e0fb2af086398a18a8b39ccb39eb071acfd88323705346ce5fd528361d3e049509d924be44300a9951d1260e3
@@ -1,3 +1,5 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.7
1
3
  Metrics/LineLength:
2
4
  Max: 120
3
5
  Style/AccessModifierDeclarations:
@@ -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,8 @@
1
- - 0.8.0 New syntax that is more explicit, passng generated values to blocks as parameters.
1
+ - 0.14.1 - Swap `awesome_print` for `amazing_print` which is a fork of the former that is actively maintained.
2
+ - 0.14.0 - Adds `uniq: true` option to `Generators.array`. Makes `PropCheck::Property` an immutable object that returns copies that have changes whenever reconfiguring, allowing re-usable configuration.
3
+ - 0.13.0 - Adds Generator#resize
4
+ - 0.12.1 - Fixes shrinking when filtering bug.
5
+ - 0.12.0 - `PropCheck::Generators#instance`
6
+ - 0.11.0 - Improved syntax to support Ruby 2.7 and up without deprecation warnings, full support for `#where`.
7
+ - 0.10.0 - Some bugfixes, support for `#where`
8
+ - 0.8.0 - New syntax that is more explicit, passng generated values to blocks as parameters.
data/README.md CHANGED
@@ -24,10 +24,12 @@ Before releasing this gem on Rubygems, the following things need to be finished:
24
24
  - [x] Filtering generators.
25
25
  - [x] Customize the max. of samples to run.
26
26
  - [x] Stop after a ludicrous amount of generator runs, to prevent malfunctioning (infinitely looping) generators from blowing up someone's computer.
27
- - [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")
@@ -28,7 +28,9 @@ module PropCheck
28
28
 
29
29
  (0..max_consecutive_attempts).each do
30
30
  res = @block.call(**kwargs)
31
- return res unless res.root == :"_PropCheck.filter_me"
31
+ next if res.root == :"_PropCheck.filter_me"
32
+
33
+ return res
32
34
  end
33
35
 
34
36
  raise Errors::GeneratorExhaustedError, """
@@ -118,5 +120,19 @@ module PropCheck
118
120
  end
119
121
  end
120
122
  end
123
+
124
+ ##
125
+ # Resizes the generator to either grow faster or smaller than normal.
126
+ #
127
+ # `proc` takes the current size as input and is expected to return the new size.
128
+ # a size should always be a nonnegative integer.
129
+ #
130
+ # >> Generators.integer.resize{}
131
+ def resize(&proc)
132
+ Generator.new do |size:, **other_kwargs|
133
+ new_size = proc.call(size)
134
+ self.generate(**other_kwargs, size: new_size)
135
+ end
136
+ end
121
137
  end
122
138
  end
@@ -9,7 +9,7 @@ module PropCheck
9
9
  # Use this module by including it in the class (e.g. in your test suite)
10
10
  # where you want to use them.
11
11
  module Generators
12
- extend self
12
+ module_function
13
13
 
14
14
  ##
15
15
  # Always returns the same value, regardless of `size` or `rng` (random number generator state)
@@ -82,11 +82,19 @@ module PropCheck
82
82
  # => [-4205, -19140, 18158, -8716, -13735, -3150, 17194, 1962, -3977, -18315]
83
83
  def integer
84
84
  Generator.new do |size:, rng:, **|
85
+ ensure_proper_size!(size)
86
+
85
87
  val = rng.rand(-size..size)
86
88
  LazyTree.new(val, integer_shrink(val))
87
89
  end
88
90
  end
89
91
 
92
+ private def ensure_proper_size!(size)
93
+ return if size.is_a?(Integer) && size >= 0
94
+
95
+ raise ArgumentError, "`size:` should be a nonnegative integer but got `#{size.inspect}`"
96
+ end
97
+
90
98
  ##
91
99
  # Only returns integers that are zero or larger.
92
100
  # See `integer` for more information.
@@ -138,7 +146,7 @@ module PropCheck
138
146
  end
139
147
  end
140
148
 
141
- @special_floats = [Float::NAN, Float::INFINITY, -Float::INFINITY, Float::MAX, Float::MIN, 0.0.next_float, 0.0.prev_float]
149
+ @@special_floats = [Float::NAN, Float::INFINITY, -Float::INFINITY, Float::MAX, Float::MIN, 0.0.next_float, 0.0.prev_float]
142
150
  ##
143
151
  # Generates floating-point numbers
144
152
  # Will generate NaN, Infinity, -Infinity,
@@ -151,7 +159,7 @@ module PropCheck
151
159
  # >> Generators.float().sample(10, size: 10, rng: Random.new(42))
152
160
  # => [4.0, 9.555555555555555, 0.0, -Float::INFINITY, 5.5, -5.818181818181818, 1.1428571428571428, 0.0, 8.0, 7.857142857142858]
153
161
  def float
154
- frequency(99 => real_float, 1 => one_of(*@special_floats.map(&method(:constant))))
162
+ frequency(99 => real_float, 1 => one_of(*@@special_floats.map(&method(:constant))))
155
163
  end
156
164
 
157
165
  ##
@@ -233,6 +241,12 @@ module PropCheck
233
241
  # `empty:` When false, behaves the same as `min: 1`
234
242
  # `min:` Ensures at least this many elements are generated. (default: 0)
235
243
  # `max:` Ensures at most this many elements are generated. When nil, an arbitrary count is used instead. (default: nil)
244
+ # `uniq:` When `true`, ensures that all elements in the array are unique.
245
+ # When given a proc, uses the result of this proc to check for uniqueness.
246
+ # (matching the behaviour of `Array#uniq`)
247
+ # If it is not possible to generate another unique value after the configured `max_consecutive_attempts`
248
+ # an `PropCheck::Errors::GeneratorExhaustedError` will be raised.
249
+ # (default: `false`)
236
250
  #
237
251
  #
238
252
  # >> Generators.array(Generators.positive_integer).sample(5, size: 1, rng: Random.new(42))
@@ -244,25 +258,68 @@ module PropCheck
244
258
  # => [[], [2], [], [], [2]]
245
259
  # >> Generators.array(Generators.positive_integer, empty: false).sample(5, size: 1, rng: Random.new(1))
246
260
  # => [[2], [1], [2], [1], [1]]
261
+ #
262
+ # >> Generators.array(Generators.boolean, uniq: true).sample(5, rng: Random.new(1))
263
+ # => [[true, false], [false, true], [true, false], [false, true], [false, true]]
247
264
 
248
-
249
- def array(element_generator, min: 0, max: nil, empty: true)
265
+ def array(element_generator, min: 0, max: nil, empty: true, uniq: false)
250
266
  min = 1 if min.zero? && !empty
267
+ uniq = proc { |x| x } if uniq == true
251
268
 
252
- 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
256
- element_generator.clone
257
- end
269
+ if max.nil?
270
+ nonnegative_integer.bind { |count| make_array(element_generator, min, count, uniq) }
271
+ else
272
+ make_array(element_generator, min, max, uniq)
273
+ end
274
+ end
275
+
276
+ private def make_array(element_generator, min, count, uniq)
277
+ amount = min if count < min
278
+ amount = min if count == min && min != 0
279
+ amount ||= (count - min)
280
+
281
+ # Simple, optimized implementation:
282
+ return make_array_simple(element_generator, amount) unless uniq
283
+
284
+ # More complex implementation that filters duplicates
285
+ make_array_uniq(element_generator, min, amount, uniq)
286
+ end
258
287
 
259
- tuple(*generators)
288
+ private def make_array_simple(element_generator, amount)
289
+ generators = amount.times.map do
290
+ element_generator.clone
260
291
  end
261
292
 
262
- if max.nil?
263
- nonnegative_integer.bind(&res)
264
- else
265
- proc.call(max)
293
+ tuple(*generators)
294
+ end
295
+
296
+ private def make_array_uniq(element_generator, min, amount, uniq_fun)
297
+ Generator.new do |**kwargs|
298
+ arr = []
299
+ uniques = Set.new
300
+ count = 0
301
+ (0..).lazy.map do
302
+ elem = element_generator.clone.generate(**kwargs)
303
+ if uniques.add?(uniq_fun.call(elem.root))
304
+ arr.push(elem)
305
+ count = 0
306
+ else
307
+ count += 1
308
+ end
309
+
310
+ if count > kwargs[:max_consecutive_attempts]
311
+ if arr.size >= min
312
+ # Give up and return shorter array in this case
313
+ amount = min
314
+ else
315
+ raise Errors::GeneratorExhaustedError, "Too many consecutive elements filtered by 'uniq:'."
316
+ end
317
+ end
318
+ end
319
+ .take_while { arr.size < amount }
320
+ .force
321
+
322
+ LazyTree.zip(arr).map { |array| array.uniq(&uniq_fun) }
266
323
  end
267
324
  end
268
325
 
@@ -292,7 +349,7 @@ module PropCheck
292
349
  .map(&:to_h)
293
350
  end
294
351
 
295
- @alphanumeric_chars = [('a'..'z'), ('A'..'Z'), ('0'..'9')].flat_map(&:to_a).freeze
352
+ @@alphanumeric_chars = [('a'..'z'), ('A'..'Z'), ('0'..'9')].flat_map(&:to_a).freeze
296
353
  ##
297
354
  # Generates a single-character string
298
355
  # containing one of a..z, A..Z, 0..9
@@ -302,7 +359,7 @@ module PropCheck
302
359
  # >> Generators.alphanumeric_char.sample(5, size: 10, rng: Random.new(42))
303
360
  # => ["M", "Z", "C", "o", "Q"]
304
361
  def alphanumeric_char
305
- one_of(*@alphanumeric_chars.map(&method(:constant)))
362
+ one_of(*@@alphanumeric_chars.map(&method(:constant)))
306
363
  end
307
364
 
308
365
  ##
@@ -313,11 +370,13 @@ module PropCheck
313
370
  #
314
371
  # >> Generators.alphanumeric_string.sample(5, size: 10, rng: Random.new(42))
315
372
  # => ["ZCoQ", "8uM", "wkkx0JNx", "v0bxRDLb", "Gl5v8RyWA6"]
373
+ #
374
+ # Accepts the same options as `array`
316
375
  def alphanumeric_string(**kwargs)
317
376
  array(alphanumeric_char, **kwargs).map(&:join)
318
377
  end
319
378
 
320
- @printable_ascii_chars = (' '..'~').to_a.freeze
379
+ @@printable_ascii_chars = (' '..'~').to_a.freeze
321
380
 
322
381
  ##
323
382
  # Generates a single-character string
@@ -328,7 +387,7 @@ module PropCheck
328
387
  # >> Generators.printable_ascii_char.sample(size: 10, rng: Random.new(42))
329
388
  # => ["S", "|", ".", "g", "\\", "4", "r", "v", "j", "j"]
330
389
  def printable_ascii_char
331
- one_of(*@printable_ascii_chars.map(&method(:constant)))
390
+ one_of(*@@printable_ascii_chars.map(&method(:constant)))
332
391
  end
333
392
 
334
393
  ##
@@ -339,12 +398,14 @@ module PropCheck
339
398
  #
340
399
  # >> Generators.printable_ascii_string.sample(5, size: 10, rng: Random.new(42))
341
400
  # => ["S|.g", "rvjjw7\"5T!", "=", "!_[4@", "Y"]
401
+ #
402
+ # Accepts the same options as `array`
342
403
  def printable_ascii_string(**kwargs)
343
404
  array(printable_ascii_char, **kwargs).map(&:join)
344
405
  end
345
406
 
346
- @ascii_chars = [
347
- @printable_ascii_chars,
407
+ @@ascii_chars = [
408
+ @@printable_ascii_chars,
348
409
  [
349
410
  "\n",
350
411
  "\r",
@@ -367,7 +428,7 @@ module PropCheck
367
428
  # >> Generators.ascii_char.sample(size: 10, rng: Random.new(42))
368
429
  # => ["d", "S", "|", ".", "g", "\\", "4", "d", "r", "v"]
369
430
  def ascii_char
370
- one_of(*@ascii_chars.map(&method(:constant)))
431
+ one_of(*@@ascii_chars.map(&method(:constant)))
371
432
  end
372
433
 
373
434
  ##
@@ -378,12 +439,14 @@ module PropCheck
378
439
  #
379
440
  # >> Generators.ascii_string.sample(5, size: 10, rng: Random.new(42))
380
441
  # => ["S|.g", "drvjjw\b\a7\"", "!w=E!_[4@k", "x", "zZI{[o"]
442
+ #
443
+ # Accepts the same options as `array`
381
444
  def ascii_string(**kwargs)
382
445
  array(ascii_char, **kwargs).map(&:join)
383
446
  end
384
447
 
385
- @printable_chars = [
386
- @ascii_chars,
448
+ @@printable_chars = [
449
+ @@ascii_chars,
387
450
  "\u{A0}".."\u{D7FF}",
388
451
  "\u{E000}".."\u{FFFD}",
389
452
  "\u{10000}".."\u{10FFFF}"
@@ -398,7 +461,7 @@ module PropCheck
398
461
  # >> Generators.printable_char.sample(size: 10, rng: Random.new(42))
399
462
  # => ["吏", "", "", "", "", "", "", "", "", "Ȍ"]
400
463
  def printable_char
401
- one_of(*@printable_chars.map(&method(:constant)))
464
+ one_of(*@@printable_chars.map(&method(:constant)))
402
465
  end
403
466
 
404
467
  ##
@@ -409,6 +472,8 @@ module PropCheck
409
472
  #
410
473
  # >> Generators.printable_string.sample(5, size: 10, rng: Random.new(42))
411
474
  # => ["", "Ȍ", "𐁂", "Ȕ", ""]
475
+ #
476
+ # Accepts the same options as `array`
412
477
  def printable_string(**kwargs)
413
478
  array(printable_char, **kwargs).map(&:join)
414
479
  end
@@ -435,6 +500,8 @@ module PropCheck
435
500
  #
436
501
  # >> Generators.string.sample(5, size: 10, rng: Random.new(42))
437
502
  # => ["\u{A3DB3}𠍜\u{3F46A}\u{1AEBC}", "􍙦𡡹󴇒\u{DED74}𪱣\u{43E97}ꂂ\u{50695}􏴴\u{C0301}", "\u{4FD9D}", "\u{C14BF}\u{193BB}𭇋󱣼\u{76B58}", "𦐺\u{9FDDB}\u{80ABB}\u{9E3CF}𐂽\u{14AAE}"]
503
+ #
504
+ # Accepts the same options as `array`
438
505
  def string(**kwargs)
439
506
  array(char, **kwargs).map(&:join)
440
507
  end
@@ -520,5 +587,44 @@ module PropCheck
520
587
  def nillable(other_generator)
521
588
  frequency(9 => other_generator, 1 => constant(nil))
522
589
  end
590
+
591
+ ##
592
+ # Generates an instance of `klass`
593
+ # using `args` and/or `kwargs`
594
+ # as generators for the arguments that are passed to `klass.new`
595
+ #
596
+ # ## Example:
597
+ #
598
+ # Given a class like this:
599
+ #
600
+ #
601
+ # class User
602
+ # attr_accessor :name, :age
603
+ # def initialize(name: , age: )
604
+ # @name = name
605
+ # @age = age
606
+ # end
607
+ #
608
+ # def inspect
609
+ # "<User name: #{@name.inspect}, age: #{@age.inspect}>"
610
+ # end
611
+ # end
612
+ #
613
+ # >> user_gen = Generators.instance(User, name: Generators.printable_ascii_string, age: Generators.nonnegative_integer)
614
+ # >> user_gen.sample(3, rng: Random.new(42)).inspect
615
+ # => "[<User name: \"S|.g\", age: 10>, <User name: \"rvjj\", age: 10>, <User name: \"7\\\"5T!w=\", age: 5>]"
616
+ def instance(klass, *args, **kwargs)
617
+ tuple(*args).bind do |vals|
618
+ fixed_hash(**kwargs).map do |kwvals|
619
+ if kwvals == {}
620
+ klass.new(*vals)
621
+ elsif vals == []
622
+ klass.new(**kwvals)
623
+ else
624
+ klass.new(*vals, **kwvals)
625
+ end
626
+ end
627
+ end
628
+ end
523
629
  end
524
630
  end
@@ -19,10 +19,11 @@
19
19
  # wrapping the elements of an enumerable with hooks.
20
20
  class PropCheck::Hooks
21
21
  # attr_reader :before, :after, :around
22
- def initialize()
23
- @before = proc {}
24
- @after = proc {}
25
- @around = proc { |*args, &block| block.call(*args) }
22
+ def initialize(before: proc {}, after: proc {}, around: proc { |*args, &block| block.call(*args) })
23
+ @before = before
24
+ @after = after
25
+ @around = around
26
+ freeze
26
27
  end
27
28
 
28
29
  def wrap_enum(enumerable)
@@ -59,34 +60,40 @@ class PropCheck::Hooks
59
60
  # Adds `hook` to the `before` proc.
60
61
  # It is called after earlier-added `before` procs.
61
62
  def add_before(&hook)
62
- old_before = @before
63
- @before = proc {
64
- old_before.call
63
+ # old_before = @before
64
+ new_before = proc {
65
+ @before.call
65
66
  hook.call
66
67
  }
68
+ # self
69
+ self.class.new(before: new_before, after: @after, around: @around)
67
70
  end
68
71
 
69
72
  ##
70
73
  # Adds `hook` to the `after` proc.
71
74
  # It is called before earlier-added `after` procs.
72
75
  def add_after(&hook)
73
- old_after = @after
74
- @after = proc {
76
+ # old_after = @after
77
+ new_after = proc {
75
78
  hook.call
76
- old_after.call
79
+ @after.call
77
80
  }
81
+ # self
82
+ self.class.new(before: @before, after: new_after, around: @around)
78
83
  end
79
84
 
80
85
  ##
81
86
  # Adds `hook` to the `around` proc.
82
87
  # It is called _inside_ earlier-added `around` procs.
83
88
  def add_around(&hook)
84
- old_around = @around
85
- @around = proc do |&block|
86
- old_around.call do |*args|
89
+ # old_around = @around
90
+ new_around = proc do |&block|
91
+ @around.call do |*args|
87
92
  hook.call(*args, &block)
88
93
  end
89
94
  end
95
+ # self
96
+ self.class.new(before: @before, after: @after, around: new_around)
90
97
  end
91
98
 
92
99
  ##
@@ -9,12 +9,16 @@ module PropCheck
9
9
 
10
10
  include Enumerable
11
11
 
12
- attr_accessor :root, :children
12
+ attr_accessor :root
13
13
  def initialize(root, children = [].lazy)
14
14
  @root = root
15
15
  @children = children
16
16
  end
17
17
 
18
+ def children
19
+ @children.reject { |child| child.root == :"_PropCheck.filter_me" }
20
+ end
21
+
18
22
  ##
19
23
  # Maps `block` eagerly over `root` and lazily over `children`, returning a new LazyTree as result.
20
24
  #
@@ -121,5 +125,6 @@ module PropCheck
121
125
  end
122
126
  end
123
127
  end
128
+
124
129
  end
125
130
  end
@@ -1,5 +1,5 @@
1
1
  require 'stringio'
2
- require "awesome_print"
2
+ require 'amazing_print'
3
3
 
4
4
  require 'prop_check/property/configuration'
5
5
  require 'prop_check/property/output_formatter'
@@ -7,9 +7,15 @@ require 'prop_check/property/shrinker'
7
7
  require 'prop_check/hooks'
8
8
  module PropCheck
9
9
  ##
10
- # Run properties
10
+ # Create and run property-checks.
11
+ #
12
+ # For simple usage, see `.forall`.
13
+ #
14
+ # For advanced usage, call `PropCheck::Property.new(...)` and then configure it to your liking
15
+ # using e.g. `#with_config`, `#before`, `#after`, `#around` etc.
16
+ # Each of these methods will return a new `Property`, so earlier properties are not mutated.
17
+ # This allows you to re-use configuration and hooks between multiple tests.
11
18
  class Property
12
-
13
19
  ##
14
20
  # Main entry-point to create (and possibly immediately run) a property-test.
15
21
  #
@@ -36,7 +42,6 @@ module PropCheck
36
42
  # of this class on before finally passing a block to it using `#check`.
37
43
  # (so `forall(Generators.integer) do |val| ... end` and forall(Generators.integer).check do |val| ... end` are the same)
38
44
  def self.forall(*bindings, **kwbindings, &block)
39
-
40
45
  property = new(*bindings, **kwbindings)
41
46
 
42
47
  return property.check(&block) if block_given?
@@ -61,19 +66,25 @@ module PropCheck
61
66
  yield(configuration)
62
67
  end
63
68
 
64
- attr_reader :bindings, :condition
65
-
66
69
  def initialize(*bindings, **kwbindings)
67
- raise ArgumentError, 'No bindings specified!' if bindings.empty? && kwbindings.empty?
68
-
69
- # @bindings = bindings
70
- # @kwbindings = kwbindings
71
- @gen = gen_from_bindings(bindings, kwbindings)
72
- @condition = proc { true }
73
70
  @config = self.class.configuration
74
71
  @hooks = PropCheck::Hooks.new
72
+
73
+ @gen = gen_from_bindings(bindings, kwbindings) unless bindings.empty? && kwbindings.empty?
74
+ freeze
75
75
  end
76
76
 
77
+ # [:condition, :config, :hooks, :gen].each do |symbol|
78
+ # define_method(symbol) do
79
+ # self.instance_variable_get("@#{symbol}")
80
+ # end
81
+
82
+ # protected define_method("#{symbol}=") do |value|
83
+ # duplicate = self.dup
84
+ # duplicate.instance_variable_set("@#{symbol}", value)
85
+ # duplicate
86
+ # end
87
+
77
88
  ##
78
89
  # Returns the configuration of this property
79
90
  # for introspection.
@@ -91,11 +102,22 @@ module PropCheck
91
102
  # you can immediately pass a block to this method.
92
103
  # (so `forall(a: Generators.integer).with_config(verbose: true) do ... end` is the same as `forall(a: Generators.integer).with_config(verbose: true).check do ... end`)
93
104
  def with_config(**config, &block)
94
- @config = @config.merge(config)
105
+ duplicate = self.dup
106
+ duplicate.instance_variable_set(:@config, @config.merge(config))
107
+ duplicate.freeze
108
+
109
+ return duplicate.check(&block) if block_given?
110
+
111
+ duplicate
112
+ end
95
113
 
96
- return self.check(&block) if block_given?
114
+ def with_bindings(*bindings, **kwbindings)
115
+ raise ArgumentError, 'No bindings specified!' if bindings.empty? && kwbindings.empty?
97
116
 
98
- self
117
+ duplicate = self.dup
118
+ duplicate.instance_variable_set(:@gen, gen_from_bindings(bindings, kwbindings))
119
+ duplicate.freeze
120
+ duplicate
99
121
  end
100
122
 
101
123
  ##
@@ -107,14 +129,12 @@ module PropCheck
107
129
  # you might encounter a GeneratorExhaustedError.
108
130
  # Only filter if you have few inputs to reject. Otherwise, improve your generators.
109
131
  def where(&condition)
110
- # 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)
116
-
117
- self
132
+ raise ArgumentError, 'No generator bindings specified! #where should be called after `#forall` or `#with_bindings`.' unless @gen
133
+
134
+ duplicate = self.dup
135
+ duplicate.instance_variable_set(:@gen, @gen.where(&condition))
136
+ duplicate.freeze
137
+ duplicate
118
138
  end
119
139
 
120
140
 
@@ -124,8 +144,10 @@ module PropCheck
124
144
  # This is useful to add setup logic
125
145
  # When called multiple times, earlier-added hooks will be called _before_ `hook` is called.
126
146
  def before(&hook)
127
- @hooks.add_before(&hook)
128
- self
147
+ duplicate = self.dup
148
+ duplicate.instance_variable_set(:@hooks, @hooks.add_before(&hook))
149
+ duplicate.freeze
150
+ duplicate
129
151
  end
130
152
 
131
153
  ##
@@ -134,8 +156,10 @@ module PropCheck
134
156
  # This is useful to add teardown logic
135
157
  # When called multiple times, earlier-added hooks will be called _after_ `hook` is called.
136
158
  def after(&hook)
137
- @hooks.add_after(&hook)
138
- self
159
+ duplicate = self.dup
160
+ duplicate.instance_variable_set(:@hooks, @hooks.add_after(&hook))
161
+ duplicate.freeze
162
+ duplicate
139
163
  end
140
164
 
141
165
  ##
@@ -152,8 +176,10 @@ module PropCheck
152
176
  # it is possible for the code after `yield` not to be called.
153
177
  # So make sure that cleanup logic is wrapped with the `ensure` keyword.
154
178
  def around(&hook)
155
- @hooks.add_around(&hook)
156
- self
179
+ duplicate = self.dup
180
+ duplicate.instance_variable_set(:@hooks, @hooks.add_around(&hook))
181
+ duplicate.freeze
182
+ duplicate
157
183
  end
158
184
 
159
185
  ##
@@ -10,7 +10,7 @@ module PropCheck
10
10
 
11
11
  def initialize(
12
12
  verbose: false,
13
- n_runs: 1_000,
13
+ n_runs: 100,
14
14
  max_generate_attempts: 10_000,
15
15
  max_shrink_steps: 10_000,
16
16
  max_consecutive_attempts: 30
@@ -1,3 +1,3 @@
1
1
  module PropCheck
2
- VERSION = '0.11.1'
2
+ VERSION = '0.14.1'
3
3
  end
@@ -36,5 +36,5 @@ Gem::Specification.new do |spec|
36
36
 
37
37
  spec.required_ruby_version = '>= 2.5.1'
38
38
 
39
- spec.add_dependency 'awesome_print', '~> 1.8'
39
+ spec.add_dependency 'amazing_print', '~> 1.2'
40
40
  end
metadata CHANGED
@@ -1,29 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prop_check
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.1
4
+ version: 0.14.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Qqwy/Wiebe-Marten Wijnja
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-08-02 00:00:00.000000000 Z
11
+ date: 2020-08-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: awesome_print
14
+ name: amazing_print
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.8'
19
+ version: '1.2'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.8'
26
+ version: '1.2'
27
27
  description: PropCheck allows you to do property-based testing, including shrinking.
28
28
  (akin to Haskell's QuickCheck, Erlang's PropEr, Elixir's StreamData). This means
29
29
  that your test are run many times with different, autogenerated inputs, and as soon
@@ -47,6 +47,7 @@ files:
47
47
  - README.md
48
48
  - Rakefile
49
49
  - bin/console
50
+ - bin/rspec
50
51
  - bin/setup
51
52
  - lib/prop_check.rb
52
53
  - lib/prop_check/generator.rb