prop_check 0.11.0 → 0.11.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: 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.