prop_check 0.14.1 → 0.16.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: bd78cbcc636fbb2089c175708a6be52ebb8124b1f21758ee513f8a84fce33f97
4
- data.tar.gz: d3bc244740e31fda8d3dbcdcafc3b7f2bd43676c13f945413bae852fbd82de6f
3
+ metadata.gz: 912a84633a6ab5b7f209555be0086e4dcafd371f337b289113003edf765fb668
4
+ data.tar.gz: 92146dcaf693c20f4cb3021178e881495de8b63f3819c254aea186a3780891c0
5
5
  SHA512:
6
- metadata.gz: eda47d7e95d30de4a4b12fe18063b4eb234d7e2bafa2c071613c12c882e71a777da288504330990edde061a8247db33bdb8a7a829419b2111912b7499cef9749
7
- data.tar.gz: d44b56680e431af41f2df178795a576d25ccf42e0fb2af086398a18a8b39ccb39eb071acfd88323705346ce5fd528361d3e049509d924be44300a9951d1260e3
6
+ metadata.gz: 192ed9c6887dc19f29fc3d8aba9278eeb0fd389aab9fae546c940e2831833ea8064ea53e1e903c599d7d7ec2a8fe5bdc04a2c3122c2374a2d1d933b2184a2ab0
7
+ data.tar.gz: 63005af74e5f0d1757930f64ce031f5cc922e34b4e876cdaebbbf139280e9e923d134440f20ab63129c4fd1647d1e524b07b6ed3800828b29d22ad7e2cc5e394
@@ -0,0 +1,31 @@
1
+ name: Ruby RSpec tests
2
+
3
+ on:
4
+ push:
5
+ branches: [ master ]
6
+ pull_request:
7
+ branches: [ master ]
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ test:
14
+
15
+ runs-on: ubuntu-latest
16
+ strategy:
17
+ matrix:
18
+ ruby-version: ['2.5', '2.6', '2.7', '3.0']
19
+
20
+ steps:
21
+ - uses: actions/checkout@v3
22
+ - name: Set up Ruby
23
+ # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
24
+ # change this to (see https://github.com/ruby/setup-ruby#versioning):
25
+ # uses: ruby/setup-ruby@v1
26
+ uses: ruby/setup-ruby@0a29871fe2b0200a17a4497bae54fe5df0d973aa # v1.115.3
27
+ with:
28
+ ruby-version: ${{ matrix.ruby-version }}
29
+ bundler-cache: true # runs 'bundle install' and caches installed gems automatically
30
+ - name: Run tests
31
+ run: bundle exec rake
data/CHANGELOG.md CHANGED
@@ -1,3 +1,22 @@
1
+ - 0.16.0
2
+ - Features:
3
+ - New option in `PropCheck::Property::Configuration` to resize all generators at once.
4
+ - Wrapper functions to modify this easily in `PropCheck::Property` called `#resize`, `#grow_fast`, `#grow_slowly`, `#grow_exponentially`, `#grow_quadratically`, `#grow_logarithmically`.
5
+ - 0.15.0
6
+ - Features:
7
+ - Generators for `Date`, `Time` and `DateTime`.
8
+ - Basic work done by @Haniyya. Thank you very much!
9
+ - Extra functions to generate dates/times/datetimes in the future or the past.
10
+ - Allow overriding the epoch that is used.
11
+ - A new option in `PropCheck::Property::Configuration` to set the default epoch.
12
+ - Generator to generate `Set`s.
13
+ - New builtin float generators (positive, negative, nonzero, nonnegative, nonpositive). Both in 'normal' flavor and in 'real' flavor (that will never generate infinity or other special values).
14
+ - `PropCheck::Generator#with_config` which enables the possibility to inspect and act on the current `PropCheck::Property::Configuration` while generating values.
15
+ - Fixes:
16
+ - Preserve backwards compatibility with Ruby 2.5 by not using infinite ranges internally (c.f. #8, thank you, @hlaf!)
17
+ - Make a flaky test deterministic by fixing the RNG. (c.f. #9, thank you, @hlaf!)
18
+ - Fix a crash when using a hash where not all keys are symbols. (c.f. #7, thank you, @Haniyya!)
19
+ - Fix situations in which `PropCheck::Generators.array` would for certain config values never generate empty arrays.
1
20
  - 0.14.1 - Swap `awesome_print` for `amazing_print` which is a fork of the former that is actively maintained.
2
21
  - 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
22
  - 0.13.0 - Adds Generator#resize
data/README.md CHANGED
@@ -3,22 +3,53 @@
3
3
  PropCheck allows you to do Property Testing in Ruby.
4
4
 
5
5
  [![Gem](https://img.shields.io/gem/v/prop_check.svg)](https://rubygems.org/gems/prop_check)
6
- [![Build Status](https://travis-ci.org/Qqwy/ruby-prop_check.svg?branch=master)](https://travis-ci.org/Qqwy/ruby-prop_check)
6
+ [![Ruby RSpec tests build status](https://github.com/Qqwy/ruby-prop_check/actions/workflows/run_tests.yaml/badge.svg)](https://github.com/Qqwy/ruby-prop_check/actions/workflows/run_tests.yaml)
7
7
  [![Maintainability](https://api.codeclimate.com/v1/badges/71897f5e6193a5124a53/maintainability)](https://codeclimate.com/github/Qqwy/ruby-prop_check/maintainability)
8
- [![RubyDoc](https://img.shields.io/badge/%F0%9F%93%9ARubyDoc-documentation-informational.svg)](https://www.rubydoc.info/github/Qqwy/ruby-prop_check/master/PropCheck)
8
+ [![RubyDoc](https://img.shields.io/badge/%F0%9F%93%9ARubyDoc-documentation-informational.svg)](https://www.rubydoc.info/github/Qqwy/ruby-prop_check/master/)
9
9
 
10
10
  It features:
11
11
 
12
- - Generators for common datatypes.
13
- - An easy DSL to define your own generators (by combining existing ones, or completely custom).
12
+ - Generators for most common Ruby datatypes.
13
+ - An easy DSL to define your own generators (by combining existing ones, as well as completely custom ones).
14
14
  - Shrinking to a minimal counter-example on failure.
15
+ - Hooks to perform extra set-up/cleanup logic before/after every example case.
15
16
 
17
+ ## What is PropCheck?
16
18
 
17
- ## TODOs before stable release
19
+ PropCheck is a Ruby library to create unit tests which are simpler to write and more powerful when run, finding edge-cases in your code you wouldn't have thought to look for.
18
20
 
19
- Before releasing this gem on Rubygems, the following things need to be finished:
21
+ It works by letting you write tests that assert that something should be true for _every_ case, rather than just the ones you happen to think of.
20
22
 
21
- - [x] Finalize the testing DSL.
23
+
24
+ A normal unit test looks something like the following:
25
+
26
+ 1. Set up some data.
27
+ 2. Perform some operations on the data.
28
+ 3. Assert something about the result
29
+
30
+ PropCheck lets you write tests which instead look like this:
31
+
32
+ 1. For all data matching some specification.
33
+ 2. Perform some operations on the data.
34
+ 3. Assert something about the result.
35
+
36
+ This is often called property-based testing. It was popularised by the Haskell library [QuickCheck](https://hackage.haskell.org/package/QuickCheck).
37
+ PropCheck takes further inspiration from Erlang's [PropEr](https://hex.pm/packages/proper), Elixir's [StreamData](https://hex.pm/packages/stream_data) and Python's [Hypothesis](https://hypothesis.works/).
38
+
39
+ It works by generating arbitrary data matching your specification and checking that your assertions still hold in that case. If it finds an example where they do not, it takes that example and shrinks it down, simplifying it to find the smallest example that still causes the problem.
40
+
41
+ Writing these kinds of tests usually consists of deciding on guarantees that your code should have -- properties that should always hold true, regardless of wat the world throws at you. Some examples are:
42
+
43
+ - Your code should not throw an exception, or only a particular type of exception.
44
+ - If you remove an object, you can no longer see it
45
+ - If you serialize and then deserializea value, you get the same value back.
46
+
47
+
48
+ ## Implemented and still missing features
49
+
50
+ Before releasing v1.0, we want to finish the following:
51
+
52
+ - [x] Finalize the testing DSL.
22
53
  - [x] Testing the library itself (against known 'true' axiomatically correct Ruby code.)
23
54
  - [x] Customization of common settings
24
55
  - [x] Filtering generators.
@@ -28,15 +59,17 @@ Before releasing this gem on Rubygems, the following things need to be finished:
28
59
  - [x] Good, unicode-compliant, string generators.
29
60
  - [x] Filtering generator outputs.
30
61
  - [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.
62
+ - [x] Possibility to resize generators.
31
63
  - [x] `#instance` generator to allow the easy creation of generators for custom datatypes.
64
+ - [x] Builtin generation of `Set`s
65
+ - [x] Builtin generation of `Date`s, `Time`s and `DateTime`s.
66
+ - [x] Configuration option to resize all generators given to a particular Property instance.
67
+ - [ ] A simple way to create recursive generators
32
68
  - [ ] A usage guide.
33
69
 
34
- # Nice-to-haves
70
+ ## Nice-to-haves
35
71
 
36
- - [x] Basic integration with RSpec. See also https://groups.google.com/forum/#!msg/rspec/U-LmL0OnO-Y/iW_Jcd6JBAAJ for progress on this.
37
- - [ ] `aggregate` , `resize` and similar generator-modifying calls (c.f. PropEr's variants of these) which will help with introspection/metrics.
38
- - [ ] Integration with other Ruby test frameworks.
39
- - Stateful property testing. If implemented at some point, will probably happen in a separate add-on library.
72
+ - Stateful property testing. If implemented at some point, will probably happen in a separate add-on library.
40
73
 
41
74
 
42
75
  ## Installation
@@ -61,17 +94,15 @@ Or install it yourself as:
61
94
  ### Using PropCheck for basic testing
62
95
 
63
96
  Propcheck exposes the `forall` method.
64
- It takes generators as keyword arguments and a block to run.
65
- Inside the block, each of the names in the keyword-argument-list is available by its name.
66
-
67
- _(to be precise: a method on the execution context is defined which returns the current generated value for that name)_
97
+ It takes any number of generators as arguments (or keyword arguments), as well as a block to run.
98
+ The value(s) generated from the generator(s) passed to the `forall` will be given to the block as arguments.
68
99
 
69
100
  Raise an exception from the block if there is a problem. If there is no problem, just return normally.
70
101
 
71
102
  ```ruby
72
- include PropCheck::Generators
103
+ G = PropCheck::Generators
73
104
  # testing that Enumerable#sort sorts in ascending order
74
- PropCheck.forall(array(integer)) do |numbers|
105
+ PropCheck.forall(G.array(G.integer)) do |numbers|
75
106
  sorted_numbers = numbers.sort
76
107
 
77
108
  # Check that no number is smaller than the previous number
@@ -93,8 +124,8 @@ end
93
124
  ```
94
125
  ```ruby
95
126
  # And then in a test case:
96
- include PropCheck::Generators
97
- PropCheck.forall(numbers: array(integer)) do |numbers:|
127
+ G = PropCheck::Generators
128
+ PropCheck.forall(numbers: G.array(G.integer)) do |numbers:|
98
129
  result = naive_average(numbers)
99
130
  unless result.is_a?(Integer) do
100
131
  raise "Expected the average to be an integer!"
@@ -104,10 +135,10 @@ end
104
135
  # Or if you e.g. are using RSpec:
105
136
  describe "#naive_average" do
106
137
  include PropCheck
107
- include PropCheck::Generators
138
+ G = PropCheck::Generators
108
139
 
109
140
  it "returns an integer for any input" do
110
- forall(numbers: array(integer)) do |numbers:|
141
+ forall(numbers: G.array(G.integer)) do |numbers:|
111
142
  result = naive_average(numbers)
112
143
  expect(result).to be_a(Integer)
113
144
  end
@@ -177,11 +208,12 @@ It contains generators for:
177
208
  - (any, only real-valued) floats,
178
209
  - (any, printable only, alphanumeric only, etc) strings and symbols
179
210
  - fixed-size arrays and hashes
180
- - as well as varying-size arrays and hashes.
211
+ - as well as varying-size arrays, hashes and sets.
212
+ - dates, times, datetimes.
181
213
  - and many more!
182
214
 
183
- It is common to call `include PropCheck::Generators` in e.g. your testing-suite files to be able to use these.
184
- If you want to be more explicit (but somewhat more verbose) when calling these functions. feel free to e.g. create a module-alias (like `PG = PropCheck::Generators`) instead.
215
+ It is common and recommended to set up a module alias by using `G = PropCheck::Generators` in e.g. your testing-suite files to be able to refer to all of them.
216
+ _(Earlier versions of the library recommended including the module instead. But this will make it very simple to accidentally shadow a generator with a local variable named `float` or `array` and similar.)_
185
217
 
186
218
  ### Writing Custom Generators
187
219
 
@@ -197,33 +229,37 @@ Always returns the given value. No shrinking.
197
229
 
198
230
  Allows you to take the result of one generator and transform it into something else.
199
231
 
200
- >> Generators.choose(32..128).map(&:chr).call(10, Random.new(42))
201
- => "S"
232
+ >> G.choose(32..128).map(&:chr).sample(1, size: 10, Random.new(42))
233
+ => ["S"]
202
234
 
203
235
  #### Generator#bind
204
236
 
205
237
  Allows you to create one or another generator conditionally on the output of another generator.
206
238
 
207
- >> Generators.integer.bind { |a| Generators.integer.bind { |b| Generator.wrap([a , b]) } }.call(100, Random.new(42))
208
- => [2, 79]
239
+ >> G.integer.bind { |a| G.integer.bind { |b| G.constant([a , b]) } }.sample(1, size: 100, rng: Random.new(42)
240
+ => [[2, 79]]
241
+
242
+ This is an advanced feature. Often, you can use a combination of `Generators.tuple` and `Generator#map` instead:
209
243
 
244
+ >> G.tuple(integer, integer).sample(1, size: 100, rng: Random.new(42)
245
+ => [[2, 79]]
210
246
 
211
247
  #### Generators.one_of
212
248
 
213
249
  Useful if you want to be able to generate a value to be one of multiple possibilities:
214
250
 
215
251
 
216
- >> Generators.one_of(Generators.constant(true), Generators.constant(false)).sample(5, size: 10, rng: Random.new(42))
252
+ >> G.one_of(G.constant(true), G.constant(false)).sample(5, size: 10, rng: Random.new(42))
217
253
  => [true, false, true, true, true]
218
254
 
219
- (note that for this example, you can also use `Generators.boolean`. The example happens to show how it is implemented under the hood.)
255
+ (Note that for this example, you can also use `G.boolean`. The example happens to show how it is implemented under the hood.)
220
256
 
221
257
  #### Generators.frequency
222
258
 
223
259
  If `one_of` does not give you enough flexibility because you want some results to be more common than others,
224
260
  you can use `Generators.frequency` which takes a hash of (integer_frequency => generator) keypairs.
225
261
 
226
- >> Generators.frequency(5 => Generators.integer, 1 => Generators.printable_ascii_char).sample(size: 10, rng: Random.new(42))
262
+ >> G.frequency(5 => G.integer, 1 => G.printable_ascii_char).sample(size: 10, rng: Random.new(42))
227
263
  => [4, -3, 10, 8, 0, -7, 10, 1, "E", 10]
228
264
 
229
265
  #### Others
@@ -257,3 +293,10 @@ Everyone interacting in the PropCheck project’s codebases, issue trackers, cha
257
293
  I want to thank the original creators of QuickCheck (Koen Claessen, John Hughes) as well as the authors of many great property testing libraries that I was/am able to use as inspiration.
258
294
  I also want to greatly thank Thomasz Kowal who made me excited about property based testing [with his great talk about stateful property testing](https://www.youtube.com/watch?v=q0wZzFUYCuM),
259
295
  as well as Fred Herbert for his great book [Property-Based Testing with PropEr, Erlang and Elixir](https://propertesting.com/) which is really worth the read (regardless of what language you are using).
296
+
297
+ The implementation and API of PropCheck takes a lot of inspiration from the following pre-existing libraries:
298
+
299
+ - Haskell's [QuickCheck](https://hackage.haskell.org/package/QuickCheck) and [Hedgehog](https://hackage.haskell.org/package/hedgehog);
300
+ - Erlang's [PropEr](https://hex.pm/packages/proper);
301
+ - Elixir's [StreamData](https://hex.pm/packages/stream_data);
302
+ - Python's [Hypothesis](https://hypothesis.works/).
@@ -11,7 +11,8 @@ 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
+ @@default_kwargs = { size: @@default_size, rng: @@default_rng,
15
+ max_consecutive_attempts: @@max_consecutive_attempts }
15
16
 
16
17
  ##
17
18
  # Being a special kind of Proc, a Generator wraps a block.
@@ -33,11 +34,11 @@ module PropCheck
33
34
  return res
34
35
  end
35
36
 
36
- raise Errors::GeneratorExhaustedError, """
37
+ raise Errors::GeneratorExhaustedError, ''"
37
38
  Exhausted #{max_consecutive_attempts} consecutive generation attempts.
38
39
 
39
40
  Probably too few generator results were adhering to a `where` condition.
40
- """
41
+ "''
41
42
  end
42
43
 
43
44
  ##
@@ -88,7 +89,7 @@ module PropCheck
88
89
  # end.flatten
89
90
  # end
90
91
  Generator.new do |**kwargs|
91
- outer_result = self.generate(**kwargs)
92
+ outer_result = generate(**kwargs)
92
93
  outer_result.bind do |outer_val|
93
94
  inner_generator = generator_proc.call(outer_val)
94
95
  inner_generator.generate(**kwargs)
@@ -103,15 +104,30 @@ module PropCheck
103
104
  # => "S"
104
105
  def map(&proc)
105
106
  Generator.new do |**kwargs|
106
- result = self.generate(**kwargs)
107
+ result = generate(**kwargs)
107
108
  result.map(&proc)
108
109
  end
109
110
  end
110
111
 
112
+ ##
113
+ # Turns a generator returning `x` into a generator returning `[x, config]`
114
+ # where `config` is the current `PropCheck::Property::Configuration`.
115
+ # This can be used to inspect the configuration inside a `#map` or `#where`
116
+ # and act on it.
117
+ #
118
+ # >> Generators.choose(0..100).with_config.map { |int, conf| Date.jd(conf[:default_epoch].jd + int) }.call(size: 10, rng: Random.new(42), config: PropCheck::Property::Configuration.new)
119
+ # => Date.new(2023, 01, 10)
120
+ def with_config
121
+ Generator.new do |**kwargs|
122
+ result = generate(**kwargs)
123
+ result.map { |val| [val, kwargs[:config]] }
124
+ end
125
+ end
126
+
111
127
  ##
112
128
  # Creates a new Generator that only produces a value when the block `condition` returns a truthy value.
113
129
  def where(&condition)
114
- self.map do |result|
130
+ map do |result|
115
131
  # if condition.call(*result)
116
132
  if PropCheck::Helper.call_splatted(result, &condition)
117
133
  result
@@ -131,7 +147,7 @@ module PropCheck
131
147
  def resize(&proc)
132
148
  Generator.new do |size:, **other_kwargs|
133
149
  new_size = proc.call(size)
134
- self.generate(**other_kwargs, size: new_size)
150
+ generate(**other_kwargs, size: new_size)
135
151
  end
136
152
  end
137
153
  end
@@ -1,6 +1,6 @@
1
- # coding: utf-8
2
1
  # frozen_string_literal: true
3
2
 
3
+ require 'date'
4
4
  require 'prop_check/generator'
5
5
  require 'prop_check/lazy_tree'
6
6
  module PropCheck
@@ -136,7 +136,8 @@ module PropCheck
136
136
  # that is: no infinity, no NaN,
137
137
  # no numbers testing the limits of floating-point arithmetic.
138
138
  #
139
- # Shrinks to numbers closer to zero.
139
+ # Shrinks towards zero.
140
+ # The shrinking strategy also moves towards 'simpler' floats (like `1.0`) from 'complicated' floats (like `3.76543`).
140
141
  #
141
142
  # >> Generators.real_float().sample(10, size: 10, rng: Random.new(42))
142
143
  # => [-2.2, -0.2727272727272727, 4.0, 1.25, -3.7272727272727275, -8.833333333333334, -8.090909090909092, 1.1428571428571428, 0.0, 8.0]
@@ -146,20 +147,130 @@ module PropCheck
146
147
  end
147
148
  end
148
149
 
149
- @@special_floats = [Float::NAN, Float::INFINITY, -Float::INFINITY, Float::MAX, Float::MIN, 0.0.next_float, 0.0.prev_float]
150
+ ##
151
+ # Generates any real floating-point numbers,
152
+ # but will never generate zero.
153
+ # c.f. #real_float
154
+ #
155
+ # >> Generators.real_nonzero_float().sample(10, size: 10, rng: Random.new(43))
156
+ # => [-7.25, 7.125, -7.636363636363637, -3.0, -8.444444444444445, -6.857142857142857, 2.4545454545454546, 3.0, -7.454545454545455, -6.25]
157
+ def real_nonzero_float
158
+ real_float.where { |val| val != 0.0 }
159
+ end
160
+
161
+ ##
162
+ # Generates real floating-point numbers which are never negative.
163
+ # Shrinks towards 0
164
+ # c.f. #real_float
165
+ #
166
+ # >> Generators.real_nonnegative_float().sample(10, size: 10, rng: Random.new(43))
167
+ # => [7.25, 7.125, 7.636363636363637, 3.0, 8.444444444444445, 0.0, 6.857142857142857, 2.4545454545454546, 3.0, 7.454545454545455]
168
+ def real_nonnegative_float
169
+ real_float.map(&:abs)
170
+ end
171
+
172
+ ##
173
+ # Generates real floating-point numbers which are never positive.
174
+ # Shrinks towards 0
175
+ # c.f. #real_float
176
+ #
177
+ # >> Generators.real_nonpositive_float().sample(10, size: 10, rng: Random.new(44))
178
+ # => [-9.125, -2.3636363636363638, -8.833333333333334, -1.75, -8.4, -2.4, -3.5714285714285716, -1.0, -6.111111111111111, -4.0]
179
+ def real_nonpositive_float
180
+ real_nonnegative_float.map(&:-@)
181
+ end
182
+
183
+ ##
184
+ # Generates real floating-point numbers which are always positive
185
+ # Shrinks towards Float::MIN
186
+ #
187
+ # Does not consider denormals.
188
+ # c.f. #real_float
189
+ #
190
+ # >> Generators.real_positive_float().sample(10, size: 10, rng: Random.new(42))
191
+ # => [2.2, 0.2727272727272727, 4.0, 1.25, 3.7272727272727275, 8.833333333333334, 8.090909090909092, 1.1428571428571428, 2.2250738585072014e-308, 8.0]
192
+ def real_positive_float
193
+ real_nonnegative_float.map { |val| val + Float::MIN }
194
+ end
195
+
196
+ ##
197
+ # Generates real floating-point numbers which are always negative
198
+ # Shrinks towards -Float::MIN
199
+ #
200
+ # Does not consider denormals.
201
+ # c.f. #real_float
202
+ #
203
+ # >> Generators.real_negative_float().sample(10, size: 10, rng: Random.new(42))
204
+ # => [-2.2, -0.2727272727272727, -4.0, -1.25, -3.7272727272727275, -8.833333333333334, -8.090909090909092, -1.1428571428571428, -2.2250738585072014e-308, -8.0]
205
+ def real_negative_float
206
+ real_positive_float.map(&:-@)
207
+ end
208
+
209
+ @@special_floats = [Float::NAN,
210
+ Float::INFINITY,
211
+ -Float::INFINITY,
212
+ Float::MAX,
213
+ -Float::MAX,
214
+ Float::MIN,
215
+ -Float::MIN,
216
+ Float::EPSILON,
217
+ -Float::EPSILON,
218
+ 0.0.next_float,
219
+ 0.0.prev_float]
150
220
  ##
151
221
  # Generates floating-point numbers
152
222
  # Will generate NaN, Infinity, -Infinity,
153
223
  # as well as Float::EPSILON, Float::MAX, Float::MIN,
154
224
  # 0.0.next_float, 0.0.prev_float,
155
225
  # to test the handling of floating-point edge cases.
156
- # Approx. 1/100 generated numbers is a special one.
226
+ # Approx. 1/50 generated numbers is a special one.
157
227
  #
158
228
  # Shrinks to smaller, real floats.
159
229
  # >> Generators.float().sample(10, size: 10, rng: Random.new(42))
160
- # => [4.0, 9.555555555555555, 0.0, -Float::INFINITY, 5.5, -5.818181818181818, 1.1428571428571428, 0.0, 8.0, 7.857142857142858]
230
+ # >> Generators.float().sample(10, size: 10, rng: Random.new(4))
231
+ # => [-8.0, 2.0, 2.7142857142857144, -4.0, -10.2, -6.666666666666667, -Float::INFINITY, -10.2, 2.1818181818181817, -6.2]
161
232
  def float
162
- frequency(99 => real_float, 1 => one_of(*@@special_floats.map(&method(:constant))))
233
+ frequency(49 => real_float, 1 => one_of(*@@special_floats.map(&method(:constant))))
234
+ end
235
+
236
+ ##
237
+ # Generates any nonzerno floating-point number.
238
+ # Will generate special floats (except NaN) from time to time.
239
+ # c.f. #float
240
+ def nonzero_float
241
+ float.where { |val| val != 0.0 && val }
242
+ end
243
+
244
+ ##
245
+ # Generates nonnegative floating point numbers
246
+ # Will generate special floats (except NaN) from time to time.
247
+ # c.f. #float
248
+ def nonnegative_float
249
+ float.map(&:abs).where { |val| val != Float::NAN }
250
+ end
251
+
252
+ ##
253
+ # Generates nonpositive floating point numbers
254
+ # Will generate special floats (except NaN) from time to time.
255
+ # c.f. #float
256
+ def nonpositive_float
257
+ nonnegative_float.map(&:-@)
258
+ end
259
+
260
+ ##
261
+ # Generates positive floating point numbers
262
+ # Will generate special floats (except NaN) from time to time.
263
+ # c.f. #float
264
+ def positive_float
265
+ nonnegative_float.where { |val| val != 0.0 && val }
266
+ end
267
+
268
+ ##
269
+ # Generates positive floating point numbers
270
+ # Will generate special floats (except NaN) from time to time.
271
+ # c.f. #float
272
+ def negative_float
273
+ positive_float.map(&:-@).where { |val| val != 0.0 }
163
274
  end
164
275
 
165
276
  ##
@@ -269,7 +380,7 @@ module PropCheck
269
380
  if max.nil?
270
381
  nonnegative_integer.bind { |count| make_array(element_generator, min, count, uniq) }
271
382
  else
272
- make_array(element_generator, min, max, uniq)
383
+ choose(min..max).bind { |count| make_array(element_generator, min, count, uniq) }
273
384
  end
274
385
  end
275
386
 
@@ -298,31 +409,57 @@ module PropCheck
298
409
  arr = []
299
410
  uniques = Set.new
300
411
  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
412
 
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
413
+ if amount == 0
414
+ LazyTree.new([])
415
+ else
416
+ 0.step.lazy.map do
417
+ elem = element_generator.clone.generate(**kwargs)
418
+ if uniques.add?(uniq_fun.call(elem.root))
419
+ arr.push(elem)
420
+ count = 0
314
421
  else
315
- raise Errors::GeneratorExhaustedError, "Too many consecutive elements filtered by 'uniq:'."
422
+ count += 1
423
+ end
424
+
425
+ if count > kwargs[:max_consecutive_attempts]
426
+ if arr.size >= min
427
+ # Give up and return shorter array in this case
428
+ amount = min
429
+ else
430
+ raise Errors::GeneratorExhaustedError, "Too many consecutive elements filtered by 'uniq:'."
431
+ end
316
432
  end
317
433
  end
318
- end
319
- .take_while { arr.size < amount }
320
- .force
434
+ .take_while { arr.size < amount }
435
+ .force
321
436
 
322
- LazyTree.zip(arr).map { |array| array.uniq(&uniq_fun) }
437
+ LazyTree.zip(arr).map { |array| array.uniq(&uniq_fun) }
438
+ end
323
439
  end
324
440
  end
325
441
 
442
+ ##
443
+ # Generates a set of elements, where each of the elements
444
+ # is generated by `element_generator`.
445
+ #
446
+ # Shrinks to smaller sets (with shrunken elements).
447
+ # Accepted keyword arguments:
448
+ #
449
+ # `empty:` When false, behaves the same as `min: 1`
450
+ # `min:` Ensures at least this many elements are generated. (default: 0)
451
+ # `max:` Ensures at most this many elements are generated. When nil, an arbitrary count is used instead. (default: nil)
452
+ #
453
+ # In the set, elements are always unique.
454
+ # If it is not possible to generate another unique value after the configured `max_consecutive_attempts`
455
+ # a `PropCheck::Errors::GeneratorExhaustedError` will be raised.
456
+ #
457
+ # >> Generators.set(Generators.positive_integer).sample(5, size: 4, rng: Random.new(42))
458
+ # => [Set[2, 4], Set[], Set[3, 4], Set[], Set[4]]
459
+ def set(element_generator, min: 0, max: nil, empty: true)
460
+ array(element_generator, min: min, max: max, empty: empty, uniq: true).map(&:to_set)
461
+ end
462
+
326
463
  ##
327
464
  # Generates a hash of key->values,
328
465
  # where each of the keys is made using the `key_generator`
@@ -559,8 +696,8 @@ module PropCheck
559
696
  #
560
697
  # Shrinks towards simpler terms, like `true`, an empty array, a single character or an integer.
561
698
  #
562
- # >> Generators.truthy.sample(5, size: 10, rng: Random.new(42))
563
- # => [[4, 0, -3, 10, -4, 8, 0, 0, 10], -3, [5.5, -5.818181818181818, 1.1428571428571428, 0.0, 8.0, 7.857142857142858, -0.6666666666666665, 5.25], [], ["\u{9E553}\u{DD56E}\u{A5BBB}\u{8BDAB}\u{3E9FC}\u{C4307}\u{DAFAE}\u{1A022}\u{938CD}\u{70631}", "\u{C4C01}\u{32D85}\u{425DC}"]]
699
+ # >> Generators.truthy.sample(5, size: 2, rng: Random.new(42))
700
+ # => [[2], {:gz=>0, :""=>0}, [1.0, 0.5], 0.6666666666666667, {"𦐺\u{9FDDB}"=>1, ""=>1}]
564
701
  def truthy
565
702
  one_of(constant(true),
566
703
  constant([]),
@@ -574,8 +711,7 @@ module PropCheck
574
711
  array(string),
575
712
  hash(simple_symbol, integer),
576
713
  hash(string, integer),
577
- hash(string, string)
578
- )
714
+ hash(string, string))
579
715
  end
580
716
 
581
717
  ##
@@ -588,6 +724,116 @@ module PropCheck
588
724
  frequency(9 => other_generator, 1 => constant(nil))
589
725
  end
590
726
 
727
+ ##
728
+ # Generates `Date` objects.
729
+ # DateTimes start around the given `epoch:` and deviate more when `size` increases.
730
+ # when no epoch is set, `PropCheck::Property::Configuration.default_epoch` is used, which defaults to `DateTime.now.to_date`.
731
+ #
732
+ # >> Generators.date(epoch: Date.new(2022, 01, 01)).sample(2, rng: Random.new(42))
733
+ # => [Date.new(2021, 12, 28), Date.new(2022, 01, 10)]
734
+ def date(epoch: nil)
735
+ date_from_offset(integer, epoch: epoch)
736
+ end
737
+
738
+ ##
739
+ # variant of #date that only generates dates in the future (relative to `:epoch`).
740
+ #
741
+ # >> Generators.future_date(epoch: Date.new(2022, 01, 01)).sample(2, rng: Random.new(42))
742
+ # => [Date.new(2022, 01, 06), Date.new(2022, 01, 11)]
743
+ def future_date(epoch: Date.today)
744
+ date_from_offset(positive_integer, epoch: epoch)
745
+ end
746
+
747
+ ##
748
+ # variant of #date that only generates dates in the past (relative to `:epoch`).
749
+ #
750
+ # >> Generators.past_date(epoch: Date.new(2022, 01, 01)).sample(2, rng: Random.new(42))
751
+ # => [Date.new(2021, 12, 27), Date.new(2021, 12, 22)]
752
+ def past_date(epoch: Date.today)
753
+ date_from_offset(negative_integer, epoch: epoch)
754
+ end
755
+
756
+ private def date_from_offset(offset_gen, epoch:)
757
+ if epoch
758
+ offset_gen.map { |offset| Date.jd(epoch.jd + offset) }
759
+ else
760
+ offset_gen.with_config.map do |offset, config|
761
+ epoch = config.default_epoch.to_date
762
+ Date.jd(epoch.jd + offset)
763
+ end
764
+ end
765
+ end
766
+
767
+ ##
768
+ # Generates `DateTime` objects.
769
+ # DateTimes start around the given `epoch:` and deviate more when `size` increases.
770
+ # when no epoch is set, `PropCheck::Property::Configuration.default_epoch` is used, which defaults to `DateTime.now`.
771
+ #
772
+ # >> PropCheck::Generators.datetime.sample(2, rng: Random.new(42), config: PropCheck::Property::Configuration.new)
773
+ # => [DateTime.parse("2022-11-17 07:11:59.999983907 +0000"), DateTime.parse("2022-11-19 05:27:16.363618076 +0000")]
774
+ def datetime(epoch: nil)
775
+ datetime_from_offset(real_float, epoch: epoch)
776
+ end
777
+
778
+ ##
779
+ # alias for `#datetime`, for backwards compatibility.
780
+ # Prefer using `datetime`!
781
+ def date_time(epoch: nil)
782
+ datetime(epoch: epoch)
783
+ end
784
+
785
+ ##
786
+ # Variant of `#datetime` that only generates datetimes in the future (relative to `:epoch`).
787
+ #
788
+ # >> PropCheck::Generators.future_datetime.sample(2, rng: Random.new(42), config: PropCheck::Property::Configuration.new).map(&:inspect)
789
+ # => ["#<DateTime: 2022-11-21T16:48:00+00:00 ((2459905j,60480s,16093n),+0s,2299161j)>", "#<DateTime: 2022-11-19T18:32:43+00:00 ((2459903j,66763s,636381924n),+0s,2299161j)>"]
790
+ def future_datetime(epoch: nil)
791
+ datetime_from_offset(real_positive_float, epoch: epoch)
792
+ end
793
+
794
+ ##
795
+ # Variant of `#datetime` that only generates datetimes in the past (relative to `:epoch`).
796
+ #
797
+ # >> PropCheck::Generators.past_datetime.sample(2, rng: Random.new(42), config: PropCheck::Property::Configuration.new)
798
+ # => [DateTime.parse("2022-11-17 07:11:59.999983907 +0000"), DateTime.parse("2022-11-19 05:27:16.363618076 +0000")]
799
+ def past_datetime(epoch: nil)
800
+ datetime_from_offset(real_negative_float, epoch: epoch)
801
+ end
802
+
803
+ ##
804
+ # Generates `Time` objects.
805
+ # Times start around the given `epoch:` and deviate more when `size` increases.
806
+ # when no epoch is set, `PropCheck::Property::Configuration.default_epoch` is used, which defaults to `DateTime.now`.
807
+ #
808
+ # >> PropCheck::Generators.time.sample(2, rng: Random.new(42), config: PropCheck::Property::Configuration.new)
809
+ # => [DateTime.parse("2022-11-17 07:11:59.999983907 +0000").to_time, DateTime.parse("2022-11-19 05:27:16.363618076 +0000").to_time]
810
+ def time(epoch: nil)
811
+ datetime(epoch: epoch).map(&:to_time)
812
+ end
813
+
814
+ ##
815
+ # Variant of `#time` that only generates datetimes in the future (relative to `:epoch`).
816
+ def future_time(epoch: nil)
817
+ future_datetime(epoch: epoch).map(&:to_time)
818
+ end
819
+
820
+ ##
821
+ # Variant of `#time` that only generates datetimes in the past (relative to `:epoch`).
822
+ def past_time(epoch: nil)
823
+ past_datetime(epoch: epoch).map(&:to_time)
824
+ end
825
+
826
+ private def datetime_from_offset(offset_gen, epoch:)
827
+ if epoch
828
+ offset_gen.map { |offset| DateTime.jd(epoch.ajd + offset) }
829
+ else
830
+ offset_gen.with_config.map do |offset, config|
831
+ epoch = config.default_epoch.to_date
832
+ DateTime.jd(epoch.ajd + offset)
833
+ end
834
+ end
835
+ end
836
+
591
837
  ##
592
838
  # Generates an instance of `klass`
593
839
  # using `args` and/or `kwargs`
@@ -33,17 +33,9 @@ module PropCheck
33
33
  end
34
34
 
35
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
36
+ return block.call(**val) if val.is_a?(Hash) && val.keys.all? { |k| k.is_a?(Symbol) }
37
+
38
+ block.call(val)
47
39
  end
48
40
  end
49
41
  end
@@ -1,25 +1,50 @@
1
1
  module PropCheck
2
2
  class Property
3
+ ## Configure PropCheck
4
+ #
5
+ # Configurations can be set globally,
6
+ # but also overridden on a per-generator basis.
7
+ # c.f. PropCheck.configure, PropCheck.configuration and PropCheck::Property#with_config
8
+ #
9
+ # ## Available options
10
+ # - `verbose:` When true, shows detailed options of the data generation and shrinking process. (Default: false)
11
+ # - `n_runs:` The amount of iterations each `forall` is being run.
12
+ # - `max_generate_attempts:` The amount of times the library tries a generator in total
13
+ # before raising `Errors::GeneratorExhaustedError`. c.f. `PropCheck::Generator#where`. (Default: 10_000)
14
+ # - `max_shrink_steps:` The amount of times shrinking is attempted. (Default: 10_000)
15
+ # - `max_consecutive_attempts:`
16
+ # - `max_consecutive_attempts:` The amount of times the library tries a filtered generator consecutively
17
+ # again before raising `Errors::GeneratorExhaustedError`. c.f. `PropCheck::Generator#where`. (Default: 10_000)
18
+ # - `default_epoch:` The 'base' value to use for date/time generators like
19
+ # `PropCheck::Generators#date` `PropCheck::Generators#future_date` `PropCheck::Generators#time`, etc.
20
+ # (Default: `DateTime.now`)
21
+ # - `resize_function` A proc that can be used to resize _all_ generators.
22
+ # Takes the current size as integer and should return a new integer.
23
+ # (Default: `proc { |size| size }`)
3
24
  Configuration = Struct.new(
4
25
  :verbose,
5
26
  :n_runs,
6
27
  :max_generate_attempts,
7
28
  :max_shrink_steps,
8
29
  :max_consecutive_attempts,
9
- keyword_init: true) do
10
-
30
+ :default_epoch,
31
+ :resize_function,
32
+ keyword_init: true
33
+ ) do
11
34
  def initialize(
12
- verbose: false,
13
- n_runs: 100,
14
- max_generate_attempts: 10_000,
15
- max_shrink_steps: 10_000,
16
- max_consecutive_attempts: 30
17
- )
35
+ verbose: false,
36
+ n_runs: 100,
37
+ max_generate_attempts: 10_000,
38
+ max_shrink_steps: 10_000,
39
+ max_consecutive_attempts: 30,
40
+ default_epoch: DateTime.now,
41
+ resize_function: proc { |size| size }
42
+ )
18
43
  super
19
44
  end
20
45
 
21
46
  def merge(other)
22
- Configuration.new(**self.to_h.merge(other.to_h))
47
+ Configuration.new(**to_h.merge(other.to_h))
23
48
  end
24
49
  end
25
50
  end
@@ -102,7 +102,7 @@ module PropCheck
102
102
  # you can immediately pass a block to this method.
103
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`)
104
104
  def with_config(**config, &block)
105
- duplicate = self.dup
105
+ duplicate = dup
106
106
  duplicate.instance_variable_set(:@config, @config.merge(config))
107
107
  duplicate.freeze
108
108
 
@@ -111,10 +111,71 @@ module PropCheck
111
111
  duplicate
112
112
  end
113
113
 
114
+ ##
115
+ # Resizes all generators in this property with the given function.
116
+ #
117
+ # Shorthand for manually wrapping `PropCheck::Property::Configuration.resize_function` with the new function.
118
+ def resize(&block)
119
+ raise '#resize called without a block' unless block_given?
120
+
121
+ orig_fun = @config.resize_function
122
+ with_config(resize_function: block)
123
+ end
124
+
125
+ ##
126
+ # Resizes all generators in this property. The new size is `2.pow(orig_size)`
127
+ #
128
+ # c.f. #resize
129
+ def growing_exponentially(&block)
130
+ orig_fun = @config.resize_function
131
+ fun = proc { |size| 2.pow(orig_fun.call(size)) }
132
+ with_config(resize_function: fun, &block)
133
+ end
134
+
135
+ ##
136
+ # Resizes all generators in this property. The new size is `orig_size * orig_size`
137
+ #
138
+ # c.f. #resize
139
+ def growing_quadratically(&block)
140
+ orig_fun = @config.resize_function
141
+ fun = proc { |size| orig_fun.call(size).pow(2) }
142
+ with_config(resize_function: fun, &block)
143
+ end
144
+
145
+ ##
146
+ # Resizes all generators in this property. The new size is `2 * orig_size`
147
+ #
148
+ # c.f. #resize
149
+ def growing_fast(&block)
150
+ orig_fun = @config.resize_function
151
+ fun = proc { |size| orig_fun.call(size) * 2 }
152
+ with_config(resize_function: fun, &block)
153
+ end
154
+
155
+ ##
156
+ # Resizes all generators in this property. The new size is `0.5 * orig_size`
157
+ #
158
+ # c.f. #resize
159
+ def growing_slowly(&block)
160
+ orig_fun = @config.resize_function
161
+ fun = proc { |size| orig_fun.call(size) * 0.5 }
162
+ with_config(resize_function: fun, &block)
163
+ end
164
+
165
+ ##
166
+ # Resizes all generators in this property. The new size is `Math.log2(orig_size)`
167
+ #
168
+ # c.f. #resize
169
+ def growing_logarithmically(&block)
170
+ orig_fun = @config.resize_function
171
+ fun = proc { |size| Math.log2(orig_fun.call(size)) }
172
+ with_config(resize_function: fun, &block)
173
+ end
174
+
114
175
  def with_bindings(*bindings, **kwbindings)
115
176
  raise ArgumentError, 'No bindings specified!' if bindings.empty? && kwbindings.empty?
116
177
 
117
- duplicate = self.dup
178
+ duplicate = dup
118
179
  duplicate.instance_variable_set(:@gen, gen_from_bindings(bindings, kwbindings))
119
180
  duplicate.freeze
120
181
  duplicate
@@ -129,22 +190,24 @@ module PropCheck
129
190
  # you might encounter a GeneratorExhaustedError.
130
191
  # Only filter if you have few inputs to reject. Otherwise, improve your generators.
131
192
  def where(&condition)
132
- raise ArgumentError, 'No generator bindings specified! #where should be called after `#forall` or `#with_bindings`.' unless @gen
193
+ unless @gen
194
+ raise ArgumentError,
195
+ 'No generator bindings specified! #where should be called after `#forall` or `#with_bindings`.'
196
+ end
133
197
 
134
- duplicate = self.dup
198
+ duplicate = dup
135
199
  duplicate.instance_variable_set(:@gen, @gen.where(&condition))
136
200
  duplicate.freeze
137
201
  duplicate
138
202
  end
139
203
 
140
-
141
204
  ##
142
205
  # Calls `hook` before each time a check is run with new data.
143
206
  #
144
207
  # This is useful to add setup logic
145
208
  # When called multiple times, earlier-added hooks will be called _before_ `hook` is called.
146
209
  def before(&hook)
147
- duplicate = self.dup
210
+ duplicate = dup
148
211
  duplicate.instance_variable_set(:@hooks, @hooks.add_before(&hook))
149
212
  duplicate.freeze
150
213
  duplicate
@@ -156,7 +219,7 @@ module PropCheck
156
219
  # This is useful to add teardown logic
157
220
  # When called multiple times, earlier-added hooks will be called _after_ `hook` is called.
158
221
  def after(&hook)
159
- duplicate = self.dup
222
+ duplicate = dup
160
223
  duplicate.instance_variable_set(:@hooks, @hooks.add_after(&hook))
161
224
  duplicate.freeze
162
225
  duplicate
@@ -176,7 +239,7 @@ module PropCheck
176
239
  # it is possible for the code after `yield` not to be called.
177
240
  # So make sure that cleanup logic is wrapped with the `ensure` keyword.
178
241
  def around(&hook)
179
- duplicate = self.dup
242
+ duplicate = dup
180
243
  duplicate.instance_variable_set(:@hooks, @hooks.add_around(&hook))
181
244
  duplicate.freeze
182
245
  duplicate
@@ -223,15 +286,15 @@ c.f. https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-k
223
286
  raise_generator_exhausted!
224
287
  end
225
288
 
226
- private def raise_generator_exhausted!()
227
- raise Errors::GeneratorExhaustedError, """
289
+ private def raise_generator_exhausted!
290
+ raise Errors::GeneratorExhaustedError, ''"
228
291
  Could not perform `n_runs = #{@config.n_runs}` runs,
229
292
  (exhausted #{@config.max_generate_attempts} tries)
230
293
  because too few generator results were adhering to
231
294
  the `where` condition.
232
295
 
233
296
  Try refining your generators instead.
234
- """
297
+ "''
235
298
  end
236
299
 
237
300
  private def check_attempt(generator_result, n_successful, &block)
@@ -246,7 +309,8 @@ c.f. https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-k
246
309
  # so we can shrink to find their cause.
247
310
  # don't worry: they all get reraised
248
311
  rescue Exception => e
249
- output, shrunken_result, shrunken_exception, n_shrink_steps = show_problem_output(e, generator_result, n_successful, &block)
312
+ output, shrunken_result, shrunken_exception, n_shrink_steps = show_problem_output(e, generator_result,
313
+ n_successful, &block)
250
314
  output_string = output.is_a?(StringIO) ? output.string : e.message
251
315
 
252
316
  e.define_singleton_method :prop_check_info do
@@ -264,7 +328,7 @@ c.f. https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-k
264
328
  end
265
329
 
266
330
  private def attempts_enum(binding_generator)
267
- @hooks
331
+ @hooks
268
332
  .wrap_enum(raw_attempts_enum(binding_generator))
269
333
  .lazy
270
334
  .take(@config.n_runs)
@@ -275,7 +339,15 @@ c.f. https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-k
275
339
  size = 1
276
340
  (0...@config.max_generate_attempts)
277
341
  .lazy
278
- .map { binding_generator.generate(size: size, rng: rng, max_consecutive_attempts: @config.max_consecutive_attempts) }
342
+ .map do
343
+ generator_size = @config.resize_function.call(size).to_i
344
+ binding_generator.generate(
345
+ size: generator_size,
346
+ rng: rng,
347
+ max_consecutive_attempts: @config.max_consecutive_attempts,
348
+ config: @config
349
+ )
350
+ end
279
351
  .map do |result|
280
352
  size += 1
281
353
 
@@ -287,7 +359,8 @@ c.f. https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-k
287
359
  output = @config.verbose ? STDOUT : StringIO.new
288
360
  output = PropCheck::Property::OutputFormatter.pre_output(output, n_successful, generator_results.root, problem)
289
361
  shrunken_result, shrunken_exception, n_shrink_steps = shrink(generator_results, output, &block)
290
- output = PropCheck::Property::OutputFormatter.post_output(output, n_shrink_steps, shrunken_result, shrunken_exception)
362
+ output = PropCheck::Property::OutputFormatter.post_output(output, n_shrink_steps, shrunken_result,
363
+ shrunken_exception)
291
364
 
292
365
  [output, shrunken_result, shrunken_exception, n_shrink_steps]
293
366
  end
@@ -1,3 +1,3 @@
1
1
  module PropCheck
2
- VERSION = '0.14.1'
2
+ VERSION = '0.16.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.14.1
4
+ version: 0.16.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-09 00:00:00.000000000 Z
11
+ date: 2022-11-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: amazing_print
@@ -35,11 +35,11 @@ executables: []
35
35
  extensions: []
36
36
  extra_rdoc_files: []
37
37
  files:
38
+ - ".github/workflows/run_tests.yaml"
38
39
  - ".gitignore"
39
40
  - ".rspec"
40
41
  - ".rubocop.yml"
41
42
  - ".tool-versions"
42
- - ".travis.yml"
43
43
  - CHANGELOG.md
44
44
  - CODE_OF_CONDUCT.md
45
45
  - Gemfile
@@ -53,7 +53,6 @@ files:
53
53
  - lib/prop_check/generator.rb
54
54
  - lib/prop_check/generators.rb
55
55
  - lib/prop_check/helper.rb
56
- - lib/prop_check/helper/lazy_append.rb
57
56
  - lib/prop_check/hooks.rb
58
57
  - lib/prop_check/lazy_tree.rb
59
58
  - lib/prop_check/property.rb
@@ -84,7 +83,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
84
83
  - !ruby/object:Gem::Version
85
84
  version: '0'
86
85
  requirements: []
87
- rubygems_version: 3.1.2
86
+ rubygems_version: 3.2.3
88
87
  signing_key:
89
88
  specification_version: 4
90
89
  summary: PropCheck allows you to do property-based testing, including shrinking.
data/.travis.yml DELETED
@@ -1,18 +0,0 @@
1
- ---
2
- sudo: false
3
- language: ruby
4
- cache: bundler
5
- rvm:
6
- - 2.6.5
7
- before_install: gem install bundler -v 2.0.2
8
- env:
9
- global:
10
- - CC_TEST_REPORTER_ID=9d18f5b43e49eecd6c3da64d85ea9c765d3606c129289d7c8cadf6d448713311
11
- before_script:
12
- - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
13
- - chmod +x ./cc-test-reporter
14
- - ./cc-test-reporter before-build
15
- script:
16
- - bundle exec rspec
17
- after_script:
18
- - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
@@ -1,18 +0,0 @@
1
- # module PropCheck
2
- # module Helper
3
- # ##
4
- # # A refinement for enumerators
5
- # # to allow lazy appending of two (potentially lazy) enumerators:
6
- # # >> [1,2,3].lazy_append([4,5.6]).to_a
7
- # # => [1,2,3,4,5,6]
8
- # module LazyAppend
9
- # refine Enumerable do
10
- # ## >> [1,2,3].lazy_append([4,5.6]).to_a
11
- # ## => [1,2,3,4,5,6]
12
- # def lazy_append(other_enumerator)
13
- # [self, other_enumerator].lazy.flat_map(&:lazy)
14
- # end
15
- # end
16
- # end
17
- # end
18
- # end