prop_check 0.16.0 → 0.18.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: 912a84633a6ab5b7f209555be0086e4dcafd371f337b289113003edf765fb668
4
- data.tar.gz: 92146dcaf693c20f4cb3021178e881495de8b63f3819c254aea186a3780891c0
3
+ metadata.gz: ba078df3937d6f069a4ed24e9aaed9617dc1eb03139be0189e51b060b11a75ba
4
+ data.tar.gz: 0fe77f31d282754df6712aafae7995e82e99d9ee86f8ad12bd060596147dafe1
5
5
  SHA512:
6
- metadata.gz: 192ed9c6887dc19f29fc3d8aba9278eeb0fd389aab9fae546c940e2831833ea8064ea53e1e903c599d7d7ec2a8fe5bdc04a2c3122c2374a2d1d933b2184a2ab0
7
- data.tar.gz: 63005af74e5f0d1757930f64ce031f5cc922e34b4e876cdaebbbf139280e9e923d134440f20ab63129c4fd1647d1e524b07b6ed3800828b29d22ad7e2cc5e394
6
+ metadata.gz: e49791f3e5a380e822f4d86f39444a09e26828c4b1ea7c90d298c9eedcc14d8c6fdc05864439e1984b0cead4183beb1ae108bee82c7ce6dccbe11aae5efbd248
7
+ data.tar.gz: cddd15cf550e166ec93d15fa2ffdf07a68646aa6750d544b6a5cf64c9bfb44177b638e6ff1df2ce25e28105df05aa838534e6eaafadefb7688aed32dae9d3bbc
@@ -2,9 +2,9 @@ name: Ruby RSpec tests
2
2
 
3
3
  on:
4
4
  push:
5
- branches: [ master ]
5
+ branches: [ main ]
6
6
  pull_request:
7
- branches: [ master ]
7
+ branches: [ main ]
8
8
 
9
9
  permissions:
10
10
  contents: read
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ - 0.18.0
2
+ - Features:
3
+ - 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)
4
+ - 0.17.0
5
+ - Features:
6
+ - Recursive generation using `PropCheck::Generators.tree`.
1
7
  - 0.16.0
2
8
  - Features:
3
9
  - New option in `PropCheck::Property::Configuration` to resize all generators at once.
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 not throw an exception, or only a particular type of exception.
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 deserializea value, you get the same value back.
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
@@ -64,7 +64,7 @@ Before releasing v1.0, we want to finish the following:
64
64
  - [x] Builtin generation of `Set`s
65
65
  - [x] Builtin generation of `Date`s, `Time`s and `DateTime`s.
66
66
  - [x] Configuration option to resize all generators given to a particular Property instance.
67
- - [ ] A simple way to create recursive generators
67
+ - [x] A simple way to create recursive generators
68
68
  - [ ] A usage guide.
69
69
 
70
70
  ## Nice-to-haves
@@ -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 < former
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
- # Or if you e.g. are using RSpec:
136
- describe "#naive_average" do
137
- include PropCheck
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(numbers: G.array(G.integer)) do |numbers:|
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
- When running this particular example PropCheck very quickly finds out that we have made a programming mistake:
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/[USERNAME]/prop_check/blob/master/CODE_OF_CONDUCT.md).
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 pre-existing libraries:
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);
@@ -116,7 +116,7 @@ module PropCheck
116
116
  # and act on it.
117
117
  #
118
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)
119
+ # => Date.new(2023, 01, 12)
120
120
  def with_config
121
121
  Generator.new do |**kwargs|
122
122
  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)
@@ -872,5 +872,75 @@ module PropCheck
872
872
  end
873
873
  end
874
874
  end
875
+
876
+ ##
877
+ # Helper to build recursive generators
878
+ #
879
+ # Given a `leaf_generator`
880
+ # and a block which:
881
+ # - is given a generator that generates subtrees.
882
+ # - it should return the generator for intermediate tree nodes.
883
+ #
884
+ # This is best explained with an example.
885
+ # Say we want to generate a binary tree of integers.
886
+ #
887
+ # If we have a struct representing internal nodes:
888
+ # ```ruby
889
+ # Branch = Struct.new(:left, :right, keyword_init: true)
890
+ # ```
891
+ # we can generate trees like so:
892
+ # ```ruby
893
+ # Generators.tree(Generators.integer) do |subtree_gen|
894
+ # G.instance(Branch, left: subtree_gen, right: subtree_gen)
895
+ # end
896
+ # ```
897
+ #
898
+ # As another example, consider generating lists of integers:
899
+ #
900
+ # >> G = PropCheck::Generators
901
+ # >> G.tree(G.integer) {|child_gen| G.array(child_gen) }.sample(5, size: 37, rng: Random.new(42))
902
+ # => [[7, [2, 3], -10], [[-2], [-2, [3]], [[2, 3]]], [], [0, [-2, -3]], [[1], -19, [], [1, -1], [1], [-1, -1], [1]]]
903
+ #
904
+ # And finally, here is how one could create a simple generator for parsed JSON data:
905
+ #
906
+ # ```ruby`
907
+ # G = PropCheck::Generators
908
+ # def json
909
+ # G.tree(G.one_of(G.boolean, G.real_float, G.ascii_string)) do |json_gen|
910
+ # G.one_of(G.array(json_gen), G.hash_of(G.ascii_string, json_gen))
911
+ # end
912
+ # end
913
+ # ```
914
+ #
915
+ def tree(leaf_generator, &block)
916
+ # Implementation is based on
917
+ # https://hexdocs.pm/stream_data/StreamData.html#tree/2
918
+ Generator.new do |size:, rng:, **other_kwargs|
919
+ nodes_on_each_level = random_pseudofactors(size.pow(1.1).to_i, rng)
920
+ result = nodes_on_each_level.reduce(leaf_generator) do |subtree_generator, nodes_on_this_level|
921
+ frequency(1 => subtree_generator,
922
+ 2 => block.call(subtree_generator).resize { |_size| nodes_on_this_level })
923
+ end
924
+
925
+ result.generate(size: size, rng: rng, **other_kwargs)
926
+ end
927
+ end
928
+
929
+ private def random_pseudofactors(size, rng)
930
+ return [size].to_enum if size < 2
931
+
932
+ Enumerator.new do |yielder|
933
+ loop do
934
+ factor = rng.rand(1..(Math.log2(size).to_i))
935
+ if factor == 1
936
+ yielder << size
937
+ break
938
+ else
939
+ yielder << factor
940
+ size /= factor
941
+ end
942
+ end
943
+ end
944
+ end
875
945
  end
876
946
  end
@@ -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
- property = new(*bindings, **kwbindings)
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
- return duplicate.check(&block) if block_given?
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
 
@@ -1,3 +1,3 @@
1
1
  module PropCheck
2
- VERSION = '0.16.0'
2
+ VERSION = '0.18.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.16.0
4
+ version: 0.18.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: 2022-11-20 00:00:00.000000000 Z
11
+ date: 2022-11-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: amazing_print