prop_check 0.9.0 → 0.10.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: 8e5aeef61ddf82569ca885327d32b9fe8fd939f2fdbef48709cb19504b5cf041
4
- data.tar.gz: d914dcac32f7a3a976661a0280c71f0a7a665411550aed9902317b04c82ee290
3
+ metadata.gz: aaa23e40547bec5672e806d62c567947cd596e1c11ed2bc80fbe6912cd75a777
4
+ data.tar.gz: a0263cddf3efb8ee55f29395004915c38b6e78c7fc7bbf2c1b456f344013b8da
5
5
  SHA512:
6
- metadata.gz: 85c5b76ac37ba8e03b2c41c04c4cc359844fa2e52d0af8893787531c23f3a54b046eb7eed13b055fed44c8983f87cbfe492d1524746624a3744300a73caf01da
7
- data.tar.gz: 9beb7ac4e605c72cc8e9b3907e727ae532c2eec4a530b6a9b7c31445135e37c8849c9f186d4571aaf38c24e28989ebe856ed2e5e873baf2ed7b740a73561dae7
6
+ metadata.gz: b7976fb344d4c7135cd858ca86606beb97cd9e9f57c40e2bb322a3c76287de46491d4feb1dd3a65158ffef8ea98894490edc7eb9bc506d80ddb110b2f026de8d
7
+ data.tar.gz: f80bffd3b08d1a1cf120a70645b548c2f70b540ae5be317b7635f7416cc4e7be94d3a1bfca10acd117166e08fd0a0c6cd8ab08bf68097182fc064519600cfb55
data/.gitignore CHANGED
@@ -11,4 +11,5 @@
11
11
  .rspec_status
12
12
 
13
13
  # .gem version files
14
- *.gem
14
+ *.gem
15
+ Gemfile.lock
data/Gemfile CHANGED
@@ -3,6 +3,9 @@ source "https://rubygems.org"
3
3
  # Specify your gem's dependencies in prop_check.gemspec
4
4
  gemspec
5
5
 
6
- gem 'simplecov', require: false, group: :test
7
- gem 'doctest-rspec', require: false, group: :test
8
- gem 'awesome_print', require: true
6
+ gem "bundler", "~> 2.0"
7
+ gem "rake", "~> 12.3", require: false, group: :test
8
+ gem "rspec", "~> 3.0", require: false, group: :test
9
+ gem "doctest-rspec", require: false, group: :test
10
+ gem "simplecov", require: false, group: :test
11
+
data/README.md CHANGED
@@ -27,6 +27,7 @@ Before releasing this gem on Rubygems, the following things need to be finished:
27
27
  - [x] Look into customization of settings from e.g. command line arguments.
28
28
  - [x] Good, unicode-compliant, string generators.
29
29
  - [x] Filtering generator outputs.
30
+ - [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.
30
31
 
31
32
  # Nice-to-haves
32
33
 
@@ -87,7 +88,8 @@ Here we check if `naive_average` indeed always returns an integer for all arrays
87
88
  def naive_average(array)
88
89
  array.sum / array.length
89
90
  end
90
-
91
+ ```
92
+ ```ruby
91
93
  # And then in a test case:
92
94
  include PropCheck::Generators
93
95
  PropCheck.forall(numbers: array(integer)) do |numbers:|
@@ -96,6 +98,19 @@ PropCheck.forall(numbers: array(integer)) do |numbers:|
96
98
  raise "Expected the average to be an integer!"
97
99
  end
98
100
  end
101
+
102
+ # Or if you e.g. are using RSpec:
103
+ describe "#naive_average" do
104
+ include PropCheck
105
+ include PropCheck::Generators
106
+
107
+ it "returns an integer for any input" do
108
+ forall(numbers: array(integer)) do |numbers:|
109
+ result = naive_average(numbers)
110
+ expect(result).to be_a(Integer)
111
+ end
112
+ end
113
+ end
99
114
  ```
100
115
 
101
116
  When running this particular example PropCheck very quickly finds out that we have made a programming mistake:
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # Contains the logic to combine potentially many before/after/around hooks
5
+ # into a single pair of procedures called `before` and `after`.
6
+ #
7
+ # _Note: This module is an implementation detail of PropCheck._
8
+ #
9
+ # These can be invoked by manually calling `#before` and `#after`.
10
+ # Important:
11
+ # - Always call first `#before` and then `#after`.
12
+ # This is required to make sure that `around` callbacks will work properly.
13
+ # - Make sure that if you call `#before`, to also call `#after`.
14
+ # It is thus highly recommended to call `#after` inside an `ensure`.
15
+ # This is to make sure that `around` callbacks indeed perform their proper cleanup.
16
+ #
17
+ # Alternatively, check out `PropCheck::Hooks::Enumerable` which allows
18
+ # wrapping the elements of an enumerable with hooks.
19
+ class PropCheck::Hooks
20
+ # attr_reader :before, :after, :around
21
+ def initialize()
22
+ @before = proc {}
23
+ @after = proc {}
24
+ @around = proc { |*args, &block| block.call(*args) }
25
+ end
26
+
27
+ def wrap_enum(enumerable)
28
+ PropCheck::Hooks::Enumerable.new(enumerable, self)
29
+ end
30
+
31
+
32
+ ##
33
+ # Wraps a block with all hooks that were configured this far.
34
+ #
35
+ # This means that whenever the block is called,
36
+ # the before/around/after hooks are called before/around/after it.
37
+ def wrap_block(&block)
38
+ proc { |*args| call(*args, &block) }
39
+ end
40
+
41
+ ##
42
+ # Wraps a block with all hooks that were configured this far,
43
+ # and immediately calls it using the given `*args`.
44
+ #
45
+ # See also #wrap_block
46
+ def call(*args, &block)
47
+ begin
48
+ @before.call()
49
+ @around.call do
50
+ block.call(*args)
51
+ end
52
+ ensure
53
+ @after.call()
54
+ end
55
+ end
56
+
57
+ ##
58
+ # Adds `hook` to the `before` proc.
59
+ # It is called after earlier-added `before` procs.
60
+ def add_before(&hook)
61
+ old_before = @before
62
+ @before = proc {
63
+ old_before.call
64
+ hook.call
65
+ }
66
+ end
67
+
68
+ ##
69
+ # Adds `hook` to the `after` proc.
70
+ # It is called before earlier-added `after` procs.
71
+ def add_after(&hook)
72
+ old_after = @after
73
+ @after = proc {
74
+ hook.call
75
+ old_after.call
76
+ }
77
+ end
78
+
79
+ ##
80
+ # Adds `hook` to the `around` proc.
81
+ # It is called _inside_ earlier-added `around` procs.
82
+ def add_around(&hook)
83
+ old_around = @around
84
+ @around = proc do |&block|
85
+ old_around.call do |*args|
86
+ hook.call(*args, &block)
87
+ end
88
+ end
89
+ end
90
+
91
+ ##
92
+ # Wraps enumerable `inner` with a `PropCheck::Hooks` object
93
+ # such that the before/after/around hooks are called
94
+ # before/after/around each element that is fetched from `inner`.
95
+ #
96
+ # This is very helpful if you need to perform cleanup logic
97
+ # before/after/around e.g. data is generated or fetched.
98
+ #
99
+ # Note that whatever is after a `yield` in an `around` hook
100
+ # is not guaranteed to be called (for instance when a StopIteration is raised).
101
+ # Thus: make sure you use `ensure` to clean up resources.
102
+ class Enumerable
103
+ include ::Enumerable
104
+
105
+ def initialize(inner, hooks)
106
+ @inner = inner
107
+ @hooks = hooks
108
+ end
109
+
110
+ def each(&task)
111
+ return to_enum(:each) unless block_given?
112
+
113
+ enum = @inner.to_enum
114
+
115
+ wrapped_yielder = @hooks.wrap_block do
116
+ yield enum.next(&task)
117
+ end
118
+
119
+ loop(&wrapped_yielder)
120
+ end
121
+ end
122
+ end
@@ -2,6 +2,7 @@ require 'stringio'
2
2
  require "awesome_print"
3
3
 
4
4
  require 'prop_check/property/configuration'
5
+ require 'prop_check/hooks'
5
6
  module PropCheck
6
7
  ##
7
8
  # Run properties
@@ -67,6 +68,7 @@ module PropCheck
67
68
  @kwbindings = kwbindings
68
69
  @condition = proc { true }
69
70
  @config = self.class.configuration
71
+ @hooks = PropCheck::Hooks.new
70
72
  end
71
73
 
72
74
  ##
@@ -103,13 +105,28 @@ module PropCheck
103
105
  # Only filter if you have few inputs to reject. Otherwise, improve your generators.
104
106
  def where(&condition)
105
107
  original_condition = @condition.dup
106
- @condition = proc do |**kwargs|
107
- original_condition.call(**kwargs) && condition.call(**kwargs)
108
+ @condition = proc do |*args|
109
+ original_condition.call(*args) && condition.call(*args)
108
110
  end
109
111
 
110
112
  self
111
113
  end
112
114
 
115
+ def before(&hook)
116
+ @hooks.add_before(&hook)
117
+ self
118
+ end
119
+
120
+ def after(&hook)
121
+ @hooks.add_after(&hook)
122
+ self
123
+ end
124
+
125
+ def around(&hook)
126
+ @hooks.add_around(&hook)
127
+ self
128
+ end
129
+
113
130
  ##
114
131
  # Checks the property (after settings have been altered using the other instance methods in this class.)
115
132
  def check(&block)
@@ -127,7 +144,7 @@ module PropCheck
127
144
  n_successful = 0
128
145
 
129
146
  # Loop stops at first exception
130
- attempts_enumerator(binding_generator).each do |generator_result|
147
+ attempts_enum(binding_generator).each do |generator_result|
131
148
  n_runs += 1
132
149
  check_attempt(generator_result, n_successful, &block)
133
150
  n_successful += 1
@@ -182,10 +199,15 @@ module PropCheck
182
199
  raise e, output_string, e.backtrace
183
200
  end
184
201
 
185
- private def attempts_enumerator(binding_generator)
202
+ private def attempts_enum(binding_generator)
203
+ @hooks
204
+ .wrap_enum(raw_attempts_enum(binding_generator))
205
+ .lazy
206
+ .take(@config.n_runs)
207
+ end
186
208
 
209
+ private def raw_attempts_enum(binding_generator)
187
210
  rng = Random::DEFAULT
188
- n_runs = 0
189
211
  size = 1
190
212
  (0...@config.max_generate_attempts)
191
213
  .lazy
@@ -193,12 +215,10 @@ module PropCheck
193
215
  .reject { |val| val.root.any? { |elem| elem == :"_PropCheck.filter_me" }}
194
216
  .select { |val| @condition.call(*val.root) }
195
217
  .map do |result|
196
- n_runs += 1
197
218
  size += 1
198
219
 
199
220
  result
200
221
  end
201
- .take_while { n_runs <= @config.n_runs }
202
222
  end
203
223
 
204
224
  private def show_problem_output(problem, generator_results, n_successful, &block)
@@ -253,7 +273,7 @@ module PropCheck
253
273
  parent_siblings = nil
254
274
  problem_exception = nil
255
275
  shrink_steps = 0
256
- (0..@config.max_shrink_steps).each do
276
+ @hooks.wrap_enum(0..@config.max_shrink_steps).lazy.each do
257
277
  begin
258
278
  sibling = siblings.next
259
279
  rescue StopIteration
@@ -1,3 +1,3 @@
1
1
  module PropCheck
2
- VERSION = "0.9.0"
2
+ VERSION = '0.10.0'
3
3
  end
@@ -36,7 +36,5 @@ Gem::Specification.new do |spec|
36
36
 
37
37
  spec.required_ruby_version = '>= 2.5.1'
38
38
 
39
- spec.add_development_dependency "bundler", "~> 2.0"
40
- spec.add_development_dependency "rake", "~> 12.3"
41
- spec.add_development_dependency "rspec", "~> 3.0"
39
+ spec.add_dependency 'awesome_print', '~> 1.8'
42
40
  end
metadata CHANGED
@@ -1,57 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prop_check
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.10.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-07-21 00:00:00.000000000 Z
11
+ date: 2020-07-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: bundler
14
+ name: awesome_print
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '2.0'
20
- type: :development
19
+ version: '1.8'
20
+ type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '2.0'
27
- - !ruby/object:Gem::Dependency
28
- name: rake
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: '12.3'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: '12.3'
41
- - !ruby/object:Gem::Dependency
42
- name: rspec
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: '3.0'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: '3.0'
26
+ version: '1.8'
55
27
  description: PropCheck allows you to do property-based testing, including shrinking.
56
28
  (akin to Haskell's QuickCheck, Erlang's PropEr, Elixir's StreamData). This means
57
29
  that your test are run many times with different, autogenerated inputs, and as soon
@@ -71,7 +43,6 @@ files:
71
43
  - CHANGELOG.md
72
44
  - CODE_OF_CONDUCT.md
73
45
  - Gemfile
74
- - Gemfile.lock
75
46
  - LICENSE.txt
76
47
  - README.md
77
48
  - Rakefile
@@ -82,6 +53,7 @@ files:
82
53
  - lib/prop_check/generators.rb
83
54
  - lib/prop_check/helper.rb
84
55
  - lib/prop_check/helper/lazy_append.rb
56
+ - lib/prop_check/hooks.rb
85
57
  - lib/prop_check/lazy_tree.rb
86
58
  - lib/prop_check/property.rb
87
59
  - lib/prop_check/property/configuration.rb
@@ -1,50 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- prop_check (0.9.0)
5
-
6
- GEM
7
- remote: https://rubygems.org/
8
- specs:
9
- awesome_print (1.8.0)
10
- diff-lcs (1.3)
11
- docile (1.3.2)
12
- doctest-core (0.0.2)
13
- doctest-rspec (0.0.3)
14
- doctest-core (~> 0.0.2)
15
- rspec
16
- json (2.2.0)
17
- rake (12.3.3)
18
- rspec (3.8.0)
19
- rspec-core (~> 3.8.0)
20
- rspec-expectations (~> 3.8.0)
21
- rspec-mocks (~> 3.8.0)
22
- rspec-core (3.8.1)
23
- rspec-support (~> 3.8.0)
24
- rspec-expectations (3.8.4)
25
- diff-lcs (>= 1.2.0, < 2.0)
26
- rspec-support (~> 3.8.0)
27
- rspec-mocks (3.8.1)
28
- diff-lcs (>= 1.2.0, < 2.0)
29
- rspec-support (~> 3.8.0)
30
- rspec-support (3.8.2)
31
- simplecov (0.16.1)
32
- docile (~> 1.1)
33
- json (>= 1.8, < 3)
34
- simplecov-html (~> 0.10.0)
35
- simplecov-html (0.10.2)
36
-
37
- PLATFORMS
38
- ruby
39
-
40
- DEPENDENCIES
41
- awesome_print
42
- bundler (~> 2.0)
43
- doctest-rspec
44
- prop_check!
45
- rake (~> 12.3)
46
- rspec (~> 3.0)
47
- simplecov
48
-
49
- BUNDLED WITH
50
- 2.1.4