prop_check 0.11.0 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6938e33edff09860d467d560675d04173ac8f44c487f65f22a4253bcad01a465
4
- data.tar.gz: 1ed4f6b097d008461e251f285ad52fcd32dfce27cc306862b250cea45e0b954a
3
+ metadata.gz: '099a3bafe1fae426f476e3add7fb93ab333506670c72c35bb78e5e505b9f0764'
4
+ data.tar.gz: d1c433ef97043f38c84a5bf8768fb46aa5fcc97f00518da831fbebc36142d2e5
5
5
  SHA512:
6
- metadata.gz: e8d7498c00b09f17ec90fced0b130158e1919270837bad1af82ca7f4140f8904816c377cdd3489aea38d6ef36b911a128a92ca956c598daf9f0f88654ca4ac64
7
- data.tar.gz: b05e4c9364808468af3c5b9807cc8cc8e036a780e449efd0468f7b577853bd3a286371a3312073c0cf5952c5debb12dd6b13ad2d3e8d0fed0952839db680757c
6
+ metadata.gz: 782411f48a77c4bc2213d6ecaecd70d7d7c3609c13bab2125ce853dedd7d3029fd8801ac882154d8c924cea212698740d8e330f8e2b90f460a5a87484413e560
7
+ data.tar.gz: dd18135801b66e2f073e82066506f8bb26cf031a4bd8d2fc208864acd7ef3a7ec564f8364628d34e4ab40429a5444a1dbc41f329bb4c7d3a608011407bb0a923
@@ -1 +1 @@
1
- ruby 2.6.5
1
+ ruby 2.7.1
@@ -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,12 +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
- (0..max_consecutive_attempts).each do
26
- res = @block.call(size, rng)
27
- next if res == :"PropCheck.filter_me"
25
+ def generate(**kwargs)
26
+ kwargs = @@default_kwargs.merge(kwargs)
27
+ max_consecutive_attempts = kwargs[:max_consecutive_attempts]
28
28
 
29
- return res
29
+ (0..max_consecutive_attempts).each do
30
+ res = @block.call(**kwargs)
31
+ return res unless res.root == :"_PropCheck.filter_me"
30
32
  end
31
33
 
32
34
  raise Errors::GeneratorExhaustedError, """
@@ -40,18 +42,18 @@ module PropCheck
40
42
  # Generates a value, and only return this value
41
43
  # (drop information for shrinking)
42
44
  #
43
- # >> Generators.integer.call(1000, Random.new(42))
45
+ # >> Generators.integer.call(size: 1000, rng: Random.new(42))
44
46
  # => 126
45
- def call(size = @@default_size, rng = @@default_rng)
46
- generate(size, rng).root
47
+ def call(**kwargs)
48
+ generate(**@@default_kwargs.merge(kwargs)).root
47
49
  end
48
50
 
49
51
  ##
50
52
  # Returns `num_of_samples` values from calling this Generator.
51
53
  # 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)
54
+ def sample(num_of_samples = 10, **kwargs)
53
55
  num_of_samples.times.map do
54
- call(size, rng)
56
+ call(**@@default_kwargs.merge(kwargs))
55
57
  end
56
58
  end
57
59
 
@@ -61,10 +63,10 @@ module PropCheck
61
63
  #
62
64
  # Keen readers may notice this as the Monadic 'pure'/'return' implementation for Generators.
63
65
  #
64
- # >> 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))
65
67
  # => [2, 79]
66
68
  def self.wrap(val)
67
- Generator.new { |_size, _rng| LazyTree.wrap(val) }
69
+ Generator.new { LazyTree.wrap(val) }
68
70
  end
69
71
 
70
72
  ##
@@ -73,7 +75,7 @@ module PropCheck
73
75
  #
74
76
  # Keen readers may notice this as the Monadic 'bind' (sometimes known as '>>=') implementation for Generators.
75
77
  #
76
- # >> 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))
77
79
  # => [2, 79]
78
80
  def bind(&generator_proc)
79
81
  # Generator.new do |size, rng|
@@ -83,11 +85,11 @@ module PropCheck
83
85
  # inner_generator.generate(size, rng)
84
86
  # end.flatten
85
87
  # end
86
- Generator.new do |size, rng|
87
- outer_result = self.generate(size, rng)
88
+ Generator.new do |**kwargs|
89
+ outer_result = self.generate(**kwargs)
88
90
  outer_result.bind do |outer_val|
89
91
  inner_generator = generator_proc.call(outer_val)
90
- inner_generator.generate(size, rng)
92
+ inner_generator.generate(**kwargs)
91
93
  end
92
94
  end
93
95
  end
@@ -95,11 +97,11 @@ module PropCheck
95
97
  ##
96
98
  # Creates a new Generator that returns a value by running `proc` on the output of the current Generator.
97
99
  #
98
- # >> 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))
99
101
  # => "S"
100
102
  def map(&proc)
101
- Generator.new do |size, rng|
102
- result = self.generate(size, rng)
103
+ Generator.new do |**kwargs|
104
+ result = self.generate(**kwargs)
103
105
  result.map(&proc)
104
106
  end
105
107
  end
@@ -108,19 +110,13 @@ module PropCheck
108
110
  # Creates a new Generator that only produces a value when the block `condition` returns a truthy value.
109
111
  def where(&condition)
110
112
  self.map do |result|
111
- if condition.call(result)
113
+ # if condition.call(*result)
114
+ if PropCheck::Helper.call_splatted(result, &condition)
112
115
  result
113
116
  else
114
117
  :"_PropCheck.filter_me"
115
118
  end
116
119
  end
117
- # self.map do |*result|
118
- # if condition.call(*result)
119
- # result
120
- # else
121
- # :'_PropCheck.filter_me'
122
- # end
123
- # end
124
120
  end
125
121
  end
126
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 =
@@ -265,7 +266,6 @@ module PropCheck
265
266
  end
266
267
  end
267
268
 
268
-
269
269
  ##
270
270
  # Generates a hash of key->values,
271
271
  # where each of the keys is made using the `key_generator`
@@ -275,12 +275,23 @@ module PropCheck
275
275
  #
276
276
  # >> Generators.hash(Generators.printable_ascii_string, Generators.positive_integer).sample(5, size: 3, rng: Random.new(42))
277
277
  # => [{""=>2, "g\\4"=>4, "rv"=>2}, {"7"=>2}, {"!"=>1, "E!"=>1}, {"kY5"=>2}, {}]
278
- def hash(key_generator, value_generator, **kwargs)
278
+ def hash(*args, **kwargs)
279
+ if args.length == 2
280
+ hash_of(*args, **kwargs)
281
+ else
282
+ super
283
+ end
284
+ end
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)
279
291
  array(tuple(key_generator, value_generator), **kwargs)
280
292
  .map(&:to_h)
281
293
  end
282
294
 
283
-
284
295
  @alphanumeric_chars = [('a'..'z'), ('A'..'Z'), ('0'..'9')].flat_map(&:to_a).freeze
285
296
  ##
286
297
  # Generates a single-character string
@@ -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.11.0'
2
+ VERSION = '0.11.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.11.0
4
+ version: 0.11.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.