prop_check 0.17.0 → 0.18.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/run_tests.yaml +10 -5
- data/.tool-versions +1 -1
- data/CHANGELOG.md +8 -0
- data/README.md +61 -20
- data/lib/prop_check/generator.rb +12 -3
- data/lib/prop_check/generators.rb +4 -4
- data/lib/prop_check/helper.rb +3 -0
- data/lib/prop_check/property.rb +6 -9
- data/lib/prop_check/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 117dfaa6197dcb53fd6619ae414452a277d668a34736331169407361dbdcb0bc
|
4
|
+
data.tar.gz: 86a17a6070762151d0e84010c3bdf75569df962c1428fb7008f46cfe685f312c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1d00f635551fc17b870a0f3065040662b85565a19a498d84dbdb76de106f0138d905f2da0fdb89f5ee25e666411abdc7d4d7b61831114d99ed80451471f450f0
|
7
|
+
data.tar.gz: 8ec326b8e39ceed7d587de59c6fac591e91d4429bdef0a9770542643e7a1d5323667f59cf196611cac42324288994096054afc72ea6eb2722640a68c3cc745b6
|
@@ -2,9 +2,9 @@ name: Ruby RSpec tests
|
|
2
2
|
|
3
3
|
on:
|
4
4
|
push:
|
5
|
-
branches: [
|
5
|
+
branches: [ main ]
|
6
6
|
pull_request:
|
7
|
-
branches: [
|
7
|
+
branches: [ main ]
|
8
8
|
|
9
9
|
permissions:
|
10
10
|
contents: read
|
@@ -15,7 +15,8 @@ jobs:
|
|
15
15
|
runs-on: ubuntu-latest
|
16
16
|
strategy:
|
17
17
|
matrix:
|
18
|
-
|
18
|
+
# NOTE: Ruby 3.2 is not in here, as `doctest-core` first needs to be updated to support it
|
19
|
+
ruby-version: ['2.6', '2.7', '3.0', '3.1']
|
19
20
|
|
20
21
|
steps:
|
21
22
|
- uses: actions/checkout@v3
|
@@ -27,5 +28,9 @@ jobs:
|
|
27
28
|
with:
|
28
29
|
ruby-version: ${{ matrix.ruby-version }}
|
29
30
|
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
30
|
-
- name: Run tests
|
31
|
-
|
31
|
+
- name: Run tests & push test coverage to Codeclimate
|
32
|
+
uses: paambaati/codeclimate-action@v3.2.0
|
33
|
+
env:
|
34
|
+
CC_TEST_REPORTER_ID: '9d18f5b43e49eecd6c3da64d85ea9c765d3606c129289d7c8cadf6d448713311'
|
35
|
+
with:
|
36
|
+
coverageCommand: bundle exec rake
|
data/.tool-versions
CHANGED
@@ -1 +1 @@
|
|
1
|
-
ruby
|
1
|
+
ruby 3.1.3
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
- 0.18.1
|
2
|
+
- Fixes:
|
3
|
+
- Compatibility with Ruby 3.2:
|
4
|
+
- Use `Random` instead of no-longer-available `Random::DEFAULT` on Ruby 3.x.
|
5
|
+
- Ensure when a hash is passed (such as in `PropCheck.forall(hash_of(integer, string)) { |hash| ... }` that when an empty hash is generated, `hash` is still `{}` and not `nil`. ([Ruby 3.x treats `fun(**{})` differently than Ruby 2.x](https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/#other-minor-changes-empty-hash))
|
6
|
+
- 0.18.0
|
7
|
+
- Features:
|
8
|
+
- Allows calling `PropCheck::Property#check` without a block, which will just return `self`. This is useful for writing wrapper functions that use `before/after/around/with_config` etc hooks which might themselves optionally want a block so they can be chained. (See the `forall_with_db` snippet in the README for an example)
|
1
9
|
- 0.17.0
|
2
10
|
- Features:
|
3
11
|
- Recursive generation using `PropCheck::Generators.tree`.
|
data/README.md
CHANGED
@@ -40,9 +40,9 @@ It works by generating arbitrary data matching your specification and checking t
|
|
40
40
|
|
41
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
42
|
|
43
|
-
- Your code should
|
43
|
+
- Your code should never crash.
|
44
44
|
- If you remove an object, you can no longer see it
|
45
|
-
- If you serialize and then
|
45
|
+
- If you serialize and then deserialize a value, you get the same value back.
|
46
46
|
|
47
47
|
|
48
48
|
## Implemented and still missing features
|
@@ -107,7 +107,7 @@ PropCheck.forall(G.array(G.integer)) do |numbers|
|
|
107
107
|
|
108
108
|
# Check that no number is smaller than the previous number
|
109
109
|
sorted_numbers.each_cons(2) do |former, latter|
|
110
|
-
raise "Elements are not sorted! #{latter} is < #{former}" if latter
|
110
|
+
raise "Elements are not sorted! #{latter} is < #{former}" if latter > former
|
111
111
|
end
|
112
112
|
end
|
113
113
|
```
|
@@ -122,32 +122,53 @@ def naive_average(array)
|
|
122
122
|
array.sum / array.length
|
123
123
|
end
|
124
124
|
```
|
125
|
-
```ruby
|
126
|
-
# And then in a test case:
|
127
|
-
G = PropCheck::Generators
|
128
|
-
PropCheck.forall(numbers: G.array(G.integer)) do |numbers:|
|
129
|
-
result = naive_average(numbers)
|
130
|
-
unless result.is_a?(Integer) do
|
131
|
-
raise "Expected the average to be an integer!"
|
132
|
-
end
|
133
|
-
end
|
134
125
|
|
135
|
-
|
136
|
-
|
137
|
-
|
126
|
+
The test case, using RSpec:
|
127
|
+
``` ruby
|
128
|
+
require 'rspec'
|
129
|
+
|
130
|
+
RSpec.describe "#naive_average" do
|
138
131
|
G = PropCheck::Generators
|
139
132
|
|
140
133
|
it "returns an integer for any input" do
|
141
|
-
forall(
|
142
|
-
result = naive_average(numbers)
|
134
|
+
PropCheck.forall(G.array(G.integer)) do |numbers|
|
135
|
+
result = naive_average(numbers)
|
136
|
+
|
143
137
|
expect(result).to be_a(Integer)
|
144
138
|
end
|
145
139
|
end
|
146
140
|
end
|
147
141
|
```
|
148
142
|
|
149
|
-
|
143
|
+
The test case, using MiniTest:
|
144
|
+
``` ruby
|
145
|
+
require 'minitest/autorun'
|
146
|
+
class NaiveAverageTest < MiniTest::Unit::TestCase
|
147
|
+
G = PropCheck::Generators
|
148
|
+
|
149
|
+
def test_that_it_returns_an_integer_for_any_input()
|
150
|
+
PropCheck.forall(G.array(G.integer)) do |numbers|
|
151
|
+
result = naive_average(numbers)
|
152
|
+
|
153
|
+
assert_instance_of(Integer, result)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
```
|
150
158
|
|
159
|
+
The test case, using only vanilla Ruby:
|
160
|
+
```ruby
|
161
|
+
# And then in a test case:
|
162
|
+
G = PropCheck::Generators
|
163
|
+
|
164
|
+
PropCheck.forall(G.array(G.integer)) do |numbers|
|
165
|
+
result = naive_average(numbers)
|
166
|
+
|
167
|
+
raise "Expected the average to be an integer!" unless result.is_a?(Integer)
|
168
|
+
end
|
169
|
+
```
|
170
|
+
|
171
|
+
When running this particular example PropCheck very quickly finds out that we have made a programming mistake:
|
151
172
|
```ruby
|
152
173
|
ZeroDivisionError:
|
153
174
|
(after 6 successful property test runs)
|
@@ -270,6 +291,26 @@ although above are the most generally useful ones.
|
|
270
291
|
[PropCheck::Generator documentation](https://www.rubydoc.info/github/Qqwy/ruby-prop_check/master/PropCheck/Generator)
|
271
292
|
[PropCheck::Generators documentation](https://www.rubydoc.info/github/Qqwy/ruby-prop_check/master/PropCheck/Generators)
|
272
293
|
|
294
|
+
|
295
|
+
## Usage within Rails / with a database
|
296
|
+
|
297
|
+
Using PropCheck for unit tests in a Rails, Sinatra, Hanami, etc. project is very easy.
|
298
|
+
Here are some simple recommendations for the best results:
|
299
|
+
- Tests that do not need to use the DB at all are usually 10x-100x faster. Faster tests means that you can configure PropCheck to do more test runs.
|
300
|
+
- If you do need to use the database, use the [database_cleaner](https://github.com/DatabaseCleaner/database_cleaner) gem, preferibly with the fast `:transaction` strategy if your RDBMS supports it. To make sure the DB is cleaned around each generated example, you can write the following helper:
|
301
|
+
``` ruby
|
302
|
+
# Version of PropCheck.forall
|
303
|
+
# which ensures records persisted to the DB in one generated example
|
304
|
+
# do not affect any other
|
305
|
+
def forall_with_db(*args, **kwargs, &block)
|
306
|
+
PropCheck.forall(*args, **kwargs)
|
307
|
+
.before { DatabaseCleaner.start }
|
308
|
+
.after { DatabaseCleaner.clean }
|
309
|
+
.check(&block)
|
310
|
+
end
|
311
|
+
```
|
312
|
+
- Other setup/cleanup should also usually happen around each generated example rather than around the whole test: Instead of using the hooks exposed by RSpec/MiniTest/etc., use the before/after/around hooks exposed by PropCheck.
|
313
|
+
|
273
314
|
## Development
|
274
315
|
|
275
316
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
@@ -286,7 +327,7 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
286
327
|
|
287
328
|
## Code of Conduct
|
288
329
|
|
289
|
-
Everyone interacting in the PropCheck project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/
|
330
|
+
Everyone interacting in the PropCheck project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/Qqwy/ruby-prop_check/blob/master/CODE_OF_CONDUCT.md).
|
290
331
|
|
291
332
|
## Attribution and Thanks
|
292
333
|
|
@@ -294,7 +335,7 @@ I want to thank the original creators of QuickCheck (Koen Claessen, John Hughes)
|
|
294
335
|
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),
|
295
336
|
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
337
|
|
297
|
-
The implementation and API of PropCheck takes a lot of inspiration from the following
|
338
|
+
The implementation and API of PropCheck takes a lot of inspiration from the following projects:
|
298
339
|
|
299
340
|
- Haskell's [QuickCheck](https://hackage.haskell.org/package/QuickCheck) and [Hedgehog](https://hackage.haskell.org/package/hedgehog);
|
300
341
|
- Erlang's [PropEr](https://hex.pm/packages/proper);
|
data/lib/prop_check/generator.rb
CHANGED
@@ -9,7 +9,14 @@ module PropCheck
|
|
9
9
|
# to be used during the shrinking phase.
|
10
10
|
class Generator
|
11
11
|
@@default_size = 10
|
12
|
-
@@default_rng =
|
12
|
+
@@default_rng =
|
13
|
+
# Backwards compatibility: Random::DEFAULT is deprecated in Ruby 3.x
|
14
|
+
# but required in Ruby 2.x and 1.x
|
15
|
+
if RUBY_VERSION.to_i >= 3
|
16
|
+
Random
|
17
|
+
else
|
18
|
+
Random::DEFAULT
|
19
|
+
end
|
13
20
|
@@max_consecutive_attempts = 100
|
14
21
|
@@default_kwargs = { size: @@default_size, rng: @@default_rng,
|
15
22
|
max_consecutive_attempts: @@max_consecutive_attempts }
|
@@ -115,8 +122,10 @@ module PropCheck
|
|
115
122
|
# This can be used to inspect the configuration inside a `#map` or `#where`
|
116
123
|
# and act on it.
|
117
124
|
#
|
118
|
-
# >>
|
119
|
-
#
|
125
|
+
# >> example_config = PropCheck::Property::Configuration.new(default_epoch: Date.new(2022, 11, 22))
|
126
|
+
# >> generator = Generators.choose(0..100).with_config.map { |int, conf| Date.jd(conf[:default_epoch].jd + int) }
|
127
|
+
# >> generator.call(size: 10, rng: Random.new(42), config: example_config)
|
128
|
+
# => Date.new(2023, 01, 12)
|
120
129
|
def with_config
|
121
130
|
Generator.new do |**kwargs|
|
122
131
|
result = generate(**kwargs)
|
@@ -769,7 +769,7 @@ module PropCheck
|
|
769
769
|
# DateTimes start around the given `epoch:` and deviate more when `size` increases.
|
770
770
|
# when no epoch is set, `PropCheck::Property::Configuration.default_epoch` is used, which defaults to `DateTime.now`.
|
771
771
|
#
|
772
|
-
# >> PropCheck::Generators.datetime.sample(2, rng: Random.new(42), config: PropCheck::Property::Configuration.new)
|
772
|
+
# >> PropCheck::Generators.datetime(epoch: Date.new(2022, 11, 20)).sample(2, rng: Random.new(42), config: PropCheck::Property::Configuration.new)
|
773
773
|
# => [DateTime.parse("2022-11-17 07:11:59.999983907 +0000"), DateTime.parse("2022-11-19 05:27:16.363618076 +0000")]
|
774
774
|
def datetime(epoch: nil)
|
775
775
|
datetime_from_offset(real_float, epoch: epoch)
|
@@ -785,7 +785,7 @@ module PropCheck
|
|
785
785
|
##
|
786
786
|
# Variant of `#datetime` that only generates datetimes in the future (relative to `:epoch`).
|
787
787
|
#
|
788
|
-
# >> PropCheck::Generators.future_datetime.sample(2, rng: Random.new(42), config: PropCheck::Property::Configuration.new).map(&:inspect)
|
788
|
+
# >> PropCheck::Generators.future_datetime(epoch: Date.new(2022, 11, 20)).sample(2, rng: Random.new(42), config: PropCheck::Property::Configuration.new).map(&:inspect)
|
789
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
790
|
def future_datetime(epoch: nil)
|
791
791
|
datetime_from_offset(real_positive_float, epoch: epoch)
|
@@ -794,7 +794,7 @@ module PropCheck
|
|
794
794
|
##
|
795
795
|
# Variant of `#datetime` that only generates datetimes in the past (relative to `:epoch`).
|
796
796
|
#
|
797
|
-
# >> PropCheck::Generators.past_datetime.sample(2, rng: Random.new(42), config: PropCheck::Property::Configuration.new)
|
797
|
+
# >> PropCheck::Generators.past_datetime(epoch: Date.new(2022, 11, 20)).sample(2, rng: Random.new(42), config: PropCheck::Property::Configuration.new)
|
798
798
|
# => [DateTime.parse("2022-11-17 07:11:59.999983907 +0000"), DateTime.parse("2022-11-19 05:27:16.363618076 +0000")]
|
799
799
|
def past_datetime(epoch: nil)
|
800
800
|
datetime_from_offset(real_negative_float, epoch: epoch)
|
@@ -805,7 +805,7 @@ module PropCheck
|
|
805
805
|
# Times start around the given `epoch:` and deviate more when `size` increases.
|
806
806
|
# when no epoch is set, `PropCheck::Property::Configuration.default_epoch` is used, which defaults to `DateTime.now`.
|
807
807
|
#
|
808
|
-
# >> PropCheck::Generators.time.sample(2, rng: Random.new(42), config: PropCheck::Property::Configuration.new)
|
808
|
+
# >> PropCheck::Generators.time(epoch: Date.new(2022, 11, 20)).sample(2, rng: Random.new(42), config: PropCheck::Property::Configuration.new)
|
809
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
810
|
def time(epoch: nil)
|
811
811
|
datetime(epoch: epoch).map(&:to_time)
|
data/lib/prop_check/helper.rb
CHANGED
@@ -33,6 +33,9 @@ module PropCheck
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def call_splatted(val, &block)
|
36
|
+
# Handle edge case where Ruby >= 3 behaves differently than Ruby <= 2
|
37
|
+
# c.f. https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/#other-minor-changes-empty-hash
|
38
|
+
return block.call({}) if val.is_a?(Hash) && val.empty?
|
36
39
|
return block.call(**val) if val.is_a?(Hash) && val.keys.all? { |k| k.is_a?(Symbol) }
|
37
40
|
|
38
41
|
block.call(val)
|
data/lib/prop_check/property.rb
CHANGED
@@ -42,11 +42,8 @@ module PropCheck
|
|
42
42
|
# of this class on before finally passing a block to it using `#check`.
|
43
43
|
# (so `forall(Generators.integer) do |val| ... end` and forall(Generators.integer).check do |val| ... end` are the same)
|
44
44
|
def self.forall(*bindings, **kwbindings, &block)
|
45
|
-
|
46
|
-
|
47
|
-
return property.check(&block) if block_given?
|
48
|
-
|
49
|
-
property
|
45
|
+
new(*bindings, **kwbindings)
|
46
|
+
.check(&block)
|
50
47
|
end
|
51
48
|
|
52
49
|
##
|
@@ -106,9 +103,7 @@ module PropCheck
|
|
106
103
|
duplicate.instance_variable_set(:@config, @config.merge(config))
|
107
104
|
duplicate.freeze
|
108
105
|
|
109
|
-
|
110
|
-
|
111
|
-
duplicate
|
106
|
+
duplicate.check(&block)
|
112
107
|
end
|
113
108
|
|
114
109
|
##
|
@@ -248,6 +243,8 @@ module PropCheck
|
|
248
243
|
##
|
249
244
|
# Checks the property (after settings have been altered using the other instance methods in this class.)
|
250
245
|
def check(&block)
|
246
|
+
return self unless block_given?
|
247
|
+
|
251
248
|
n_runs = 0
|
252
249
|
n_successful = 0
|
253
250
|
|
@@ -335,7 +332,7 @@ c.f. https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-k
|
|
335
332
|
end
|
336
333
|
|
337
334
|
private def raw_attempts_enum(binding_generator)
|
338
|
-
rng = Random
|
335
|
+
rng = Random.new
|
339
336
|
size = 1
|
340
337
|
(0...@config.max_generate_attempts)
|
341
338
|
.lazy
|
data/lib/prop_check/version.rb
CHANGED
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.
|
4
|
+
version: 0.18.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:
|
11
|
+
date: 2023-05-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: amazing_print
|
@@ -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.
|
86
|
+
rubygems_version: 3.3.26
|
87
87
|
signing_key:
|
88
88
|
specification_version: 4
|
89
89
|
summary: PropCheck allows you to do property-based testing, including shrinking.
|