prop_check 0.9.0 → 0.10.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +2 -1
- data/Gemfile +8 -3
- data/README.md +16 -1
- data/lib/prop_check/generator.rb +18 -4
- data/lib/prop_check/helper.rb +8 -0
- data/lib/prop_check/helper/lazy_append.rb +18 -18
- data/lib/prop_check/hooks.rb +124 -0
- data/lib/prop_check/lazy_tree.rb +5 -7
- data/lib/prop_check/property.rb +55 -78
- data/lib/prop_check/property/output_formatter.rb +41 -0
- data/lib/prop_check/property/shrinker.rb +79 -0
- data/lib/prop_check/version.rb +1 -1
- data/prop_check.gemspec +1 -3
- metadata +9 -35
- data/Gemfile.lock +0 -50
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f8f707bae1dadf800e667376a749c7d5de3f3d741d13e63118de79dfb7233fb5
|
4
|
+
data.tar.gz: e86ac3b096bff66cfa88c0a66b1fc8ea91dc8452ded41c417fb1452c33c2b1d7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e0985469e34e0b353aab0fac657a073fadfe135c03029187d3f7824ebded0bfd244122aa2ae4361b5ccad3fb05ca52162e3967e9f46a6e43674e7d7626a700f7
|
7
|
+
data.tar.gz: 0b0505768ea68855beaf9c8954cb973215dd392c40975e7f581e7faa9d7570c9e4fb76402227c5b0f223f96e87f72d58decfb7dffd2744897c22db3ba0959ac6
|
data/.gitignore
CHANGED
data/Gemfile
CHANGED
@@ -3,6 +3,11 @@ source "https://rubygems.org"
|
|
3
3
|
# Specify your gem's dependencies in prop_check.gemspec
|
4
4
|
gemspec
|
5
5
|
|
6
|
-
gem
|
7
|
-
|
8
|
-
|
6
|
+
gem "bundler", "~> 2.0"
|
7
|
+
|
8
|
+
group :test do
|
9
|
+
gem "rake", "~> 12.3", require: false
|
10
|
+
gem "rspec", "~> 3.0", require: false
|
11
|
+
gem "doctest-rspec", require: false
|
12
|
+
gem "simplecov", require: false
|
13
|
+
end
|
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:
|
data/lib/prop_check/generator.rb
CHANGED
@@ -10,6 +10,7 @@ module PropCheck
|
|
10
10
|
class Generator
|
11
11
|
@@default_size = 10
|
12
12
|
@@default_rng = Random.new
|
13
|
+
@@max_consecutive_attempts = 100
|
13
14
|
|
14
15
|
##
|
15
16
|
# Being a special kind of Proc, a Generator wraps a block.
|
@@ -20,8 +21,19 @@ module PropCheck
|
|
20
21
|
##
|
21
22
|
# Given a `size` (integer) and a random number generator state `rng`,
|
22
23
|
# generate a LazyTree.
|
23
|
-
def generate(size = @@default_size, rng = @@default_rng)
|
24
|
-
|
24
|
+
def generate(size = @@default_size, rng = @@default_rng, max_consecutive_attempts = @@max_consecutive_attempts)
|
25
|
+
(0..max_consecutive_attempts).each do
|
26
|
+
res = @block.call(size, rng)
|
27
|
+
next if res == :"PropCheck.filter_me"
|
28
|
+
|
29
|
+
return res
|
30
|
+
end
|
31
|
+
|
32
|
+
raise Errors::GeneratorExhaustedError, """
|
33
|
+
Exhausted #{max_consecutive_attempts} consecutive generation attempts.
|
34
|
+
|
35
|
+
Probably too few generator results were adhering to a `where` condition.
|
36
|
+
"""
|
25
37
|
end
|
26
38
|
|
27
39
|
##
|
@@ -88,14 +100,16 @@ module PropCheck
|
|
88
100
|
def map(&proc)
|
89
101
|
Generator.new do |size, rng|
|
90
102
|
result = self.generate(size, rng)
|
91
|
-
result.map
|
103
|
+
result.map do |*val|
|
104
|
+
proc.call(*val)
|
105
|
+
end
|
92
106
|
end
|
93
107
|
end
|
94
108
|
|
95
109
|
##
|
96
110
|
# Creates a new Generator that only produces a value when the block `condition` returns a truthy value.
|
97
111
|
def where(&condition)
|
98
|
-
self.map do
|
112
|
+
self.map do |*result|
|
99
113
|
if condition.call(*result)
|
100
114
|
result
|
101
115
|
else
|
data/lib/prop_check/helper.rb
CHANGED
@@ -23,5 +23,13 @@ module PropCheck
|
|
23
23
|
end
|
24
24
|
end.lazy
|
25
25
|
end
|
26
|
+
|
27
|
+
##
|
28
|
+
# allow lazy appending of two (potentially lazy) enumerators:
|
29
|
+
# >> PropCheck::Helper::LazyAppend.lazy_append([1,2,3],[4,5.6]).to_a
|
30
|
+
# => [1,2,3,4,5,6]
|
31
|
+
def lazy_append(this_enumerator, other_enumerator)
|
32
|
+
[this_enumerator, other_enumerator].lazy.flat_map(&:lazy)
|
33
|
+
end
|
26
34
|
end
|
27
35
|
end
|
@@ -1,18 +1,18 @@
|
|
1
|
-
module PropCheck
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
end
|
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
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
##
|
4
|
+
# @api private
|
5
|
+
# Contains the logic to combine potentially many before/after/around hooks
|
6
|
+
# into a single pair of procedures called `before` and `after`.
|
7
|
+
#
|
8
|
+
# _Note: This module is an implementation detail of PropCheck._
|
9
|
+
#
|
10
|
+
# These can be invoked by manually calling `#before` and `#after`.
|
11
|
+
# Important:
|
12
|
+
# - Always call first `#before` and then `#after`.
|
13
|
+
# This is required to make sure that `around` callbacks will work properly.
|
14
|
+
# - Make sure that if you call `#before`, to also call `#after`.
|
15
|
+
# It is thus highly recommended to call `#after` inside an `ensure`.
|
16
|
+
# This is to make sure that `around` callbacks indeed perform their proper cleanup.
|
17
|
+
#
|
18
|
+
# Alternatively, check out `PropCheck::Hooks::Enumerable` which allows
|
19
|
+
# wrapping the elements of an enumerable with hooks.
|
20
|
+
class PropCheck::Hooks
|
21
|
+
# attr_reader :before, :after, :around
|
22
|
+
def initialize()
|
23
|
+
@before = proc {}
|
24
|
+
@after = proc {}
|
25
|
+
@around = proc { |*args, &block| block.call(*args) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def wrap_enum(enumerable)
|
29
|
+
PropCheck::Hooks::Enumerable.new(enumerable, self)
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
##
|
34
|
+
# Wraps a block with all hooks that were configured this far.
|
35
|
+
#
|
36
|
+
# This means that whenever the block is called,
|
37
|
+
# the before/around/after hooks are called before/around/after it.
|
38
|
+
def wrap_block(&block)
|
39
|
+
proc { |*args| call(*args, &block) }
|
40
|
+
end
|
41
|
+
|
42
|
+
##
|
43
|
+
# Wraps a block with all hooks that were configured this far,
|
44
|
+
# and immediately calls it using the given `*args`.
|
45
|
+
#
|
46
|
+
# See also #wrap_block
|
47
|
+
def call(*args, &block)
|
48
|
+
begin
|
49
|
+
@before.call()
|
50
|
+
@around.call do
|
51
|
+
block.call(*args)
|
52
|
+
end
|
53
|
+
ensure
|
54
|
+
@after.call()
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
##
|
59
|
+
# Adds `hook` to the `before` proc.
|
60
|
+
# It is called after earlier-added `before` procs.
|
61
|
+
def add_before(&hook)
|
62
|
+
old_before = @before
|
63
|
+
@before = proc {
|
64
|
+
old_before.call
|
65
|
+
hook.call
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
##
|
70
|
+
# Adds `hook` to the `after` proc.
|
71
|
+
# It is called before earlier-added `after` procs.
|
72
|
+
def add_after(&hook)
|
73
|
+
old_after = @after
|
74
|
+
@after = proc {
|
75
|
+
hook.call
|
76
|
+
old_after.call
|
77
|
+
}
|
78
|
+
end
|
79
|
+
|
80
|
+
##
|
81
|
+
# Adds `hook` to the `around` proc.
|
82
|
+
# It is called _inside_ earlier-added `around` procs.
|
83
|
+
def add_around(&hook)
|
84
|
+
old_around = @around
|
85
|
+
@around = proc do |&block|
|
86
|
+
old_around.call do |*args|
|
87
|
+
hook.call(*args, &block)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
##
|
93
|
+
# @api private
|
94
|
+
# Wraps enumerable `inner` with a `PropCheck::Hooks` object
|
95
|
+
# such that the before/after/around hooks are called
|
96
|
+
# before/after/around each element that is fetched from `inner`.
|
97
|
+
#
|
98
|
+
# This is very helpful if you need to perform cleanup logic
|
99
|
+
# before/after/around e.g. data is generated or fetched.
|
100
|
+
#
|
101
|
+
# Note that whatever is after a `yield` in an `around` hook
|
102
|
+
# is not guaranteed to be called (for instance when a StopIteration is raised).
|
103
|
+
# Thus: make sure you use `ensure` to clean up resources.
|
104
|
+
class Enumerable
|
105
|
+
include ::Enumerable
|
106
|
+
|
107
|
+
def initialize(inner, hooks)
|
108
|
+
@inner = inner
|
109
|
+
@hooks = hooks
|
110
|
+
end
|
111
|
+
|
112
|
+
def each(&task)
|
113
|
+
return to_enum(:each) unless block_given?
|
114
|
+
|
115
|
+
enum = @inner.to_enum
|
116
|
+
|
117
|
+
wrapped_yielder = @hooks.wrap_block do
|
118
|
+
yield enum.next(&task)
|
119
|
+
end
|
120
|
+
|
121
|
+
loop(&wrapped_yielder)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
data/lib/prop_check/lazy_tree.rb
CHANGED
@@ -1,13 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'prop_check/helper/lazy_append'
|
4
|
-
|
5
3
|
module PropCheck
|
6
4
|
##
|
7
5
|
# A Rose tree with the root being eager,
|
8
6
|
# and the children computed lazily, on demand.
|
9
7
|
class LazyTree
|
10
|
-
|
8
|
+
require 'prop_check/helper'
|
11
9
|
|
12
10
|
attr_accessor :root, :children
|
13
11
|
def initialize(root, children = [].lazy)
|
@@ -38,7 +36,7 @@ module PropCheck
|
|
38
36
|
# root_children = root_tree.children
|
39
37
|
# flattened_children = children.map(&:flatten)
|
40
38
|
|
41
|
-
# combined_children =
|
39
|
+
# combined_children = PropCheck::Helper.lazy_append(root_children, flattened_children)
|
42
40
|
|
43
41
|
# LazyTree.new(root_root, combined_children)
|
44
42
|
# end
|
@@ -53,7 +51,7 @@ module PropCheck
|
|
53
51
|
inner_children = inner_tree.children
|
54
52
|
mapped_children = children.map { |child| child.bind(&fun) }
|
55
53
|
|
56
|
-
combined_children =
|
54
|
+
combined_children = PropCheck::Helper.lazy_append(inner_children, mapped_children)
|
57
55
|
|
58
56
|
LazyTree.new(inner_root, combined_children)
|
59
57
|
end
|
@@ -70,7 +68,7 @@ module PropCheck
|
|
70
68
|
def each(&block)
|
71
69
|
squish = lambda do |tree, list|
|
72
70
|
new_children = tree.children.reduce(list) { |acc, elem| squish.call(elem, acc) }
|
73
|
-
[tree.root]
|
71
|
+
PropCheck::Helper.lazy_append([tree.root], new_children)
|
74
72
|
end
|
75
73
|
|
76
74
|
squish
|
@@ -78,7 +76,7 @@ module PropCheck
|
|
78
76
|
|
79
77
|
# base = [root]
|
80
78
|
# recursive = children.map(&:each)
|
81
|
-
# res =
|
79
|
+
# res = PropCheck::Helper.lazy_append(base, recursive)
|
82
80
|
|
83
81
|
# return res.each(&block) if block_given?
|
84
82
|
|
data/lib/prop_check/property.rb
CHANGED
@@ -2,6 +2,9 @@ require 'stringio'
|
|
2
2
|
require "awesome_print"
|
3
3
|
|
4
4
|
require 'prop_check/property/configuration'
|
5
|
+
require 'prop_check/property/output_formatter'
|
6
|
+
require 'prop_check/property/shrinker'
|
7
|
+
require 'prop_check/hooks'
|
5
8
|
module PropCheck
|
6
9
|
##
|
7
10
|
# Run properties
|
@@ -67,6 +70,7 @@ module PropCheck
|
|
67
70
|
@kwbindings = kwbindings
|
68
71
|
@condition = proc { true }
|
69
72
|
@config = self.class.configuration
|
73
|
+
@hooks = PropCheck::Hooks.new
|
70
74
|
end
|
71
75
|
|
72
76
|
##
|
@@ -103,13 +107,51 @@ module PropCheck
|
|
103
107
|
# Only filter if you have few inputs to reject. Otherwise, improve your generators.
|
104
108
|
def where(&condition)
|
105
109
|
original_condition = @condition.dup
|
106
|
-
@condition = proc do
|
107
|
-
original_condition.call(
|
110
|
+
@condition = proc do |*args|
|
111
|
+
original_condition.call(*args) && condition.call(*args)
|
108
112
|
end
|
109
113
|
|
110
114
|
self
|
111
115
|
end
|
112
116
|
|
117
|
+
##
|
118
|
+
# Calls `hook` before each time a check is run with new data.
|
119
|
+
#
|
120
|
+
# This is useful to add setup logic
|
121
|
+
# When called multiple times, earlier-added hooks will be called _before_ `hook` is called.
|
122
|
+
def before(&hook)
|
123
|
+
@hooks.add_before(&hook)
|
124
|
+
self
|
125
|
+
end
|
126
|
+
|
127
|
+
##
|
128
|
+
# Calls `hook` after each time a check is run with new data.
|
129
|
+
#
|
130
|
+
# This is useful to add teardown logic
|
131
|
+
# When called multiple times, earlier-added hooks will be called _after_ `hook` is called.
|
132
|
+
def after(&hook)
|
133
|
+
@hooks.add_after(&hook)
|
134
|
+
self
|
135
|
+
end
|
136
|
+
|
137
|
+
##
|
138
|
+
# Calls `hook` around each time a check is run with new data.
|
139
|
+
#
|
140
|
+
# `hook` should `yield` to the passed block.
|
141
|
+
#
|
142
|
+
# When called multiple times, earlier-added hooks will be wrapped _around_ `hook`.
|
143
|
+
#
|
144
|
+
# Around hooks will be called after all `#before` hooks
|
145
|
+
# and before all `#after` hooks.
|
146
|
+
#
|
147
|
+
# Note that if the block passed to `hook` raises an exception,
|
148
|
+
# it is possible for the code after `yield` not to be called.
|
149
|
+
# So make sure that cleanup logic is wrapped with the `ensure` keyword.
|
150
|
+
def around(&hook)
|
151
|
+
@hooks.add_around(&hook)
|
152
|
+
self
|
153
|
+
end
|
154
|
+
|
113
155
|
##
|
114
156
|
# Checks the property (after settings have been altered using the other instance methods in this class.)
|
115
157
|
def check(&block)
|
@@ -127,7 +169,7 @@ module PropCheck
|
|
127
169
|
n_successful = 0
|
128
170
|
|
129
171
|
# Loop stops at first exception
|
130
|
-
|
172
|
+
attempts_enum(binding_generator).each do |generator_result|
|
131
173
|
n_runs += 1
|
132
174
|
check_attempt(generator_result, n_successful, &block)
|
133
175
|
n_successful += 1
|
@@ -182,10 +224,15 @@ module PropCheck
|
|
182
224
|
raise e, output_string, e.backtrace
|
183
225
|
end
|
184
226
|
|
185
|
-
private def
|
227
|
+
private def attempts_enum(binding_generator)
|
228
|
+
@hooks
|
229
|
+
.wrap_enum(raw_attempts_enum(binding_generator))
|
230
|
+
.lazy
|
231
|
+
.take(@config.n_runs)
|
232
|
+
end
|
186
233
|
|
234
|
+
private def raw_attempts_enum(binding_generator)
|
187
235
|
rng = Random::DEFAULT
|
188
|
-
n_runs = 0
|
189
236
|
size = 1
|
190
237
|
(0...@config.max_generate_attempts)
|
191
238
|
.lazy
|
@@ -193,93 +240,23 @@ module PropCheck
|
|
193
240
|
.reject { |val| val.root.any? { |elem| elem == :"_PropCheck.filter_me" }}
|
194
241
|
.select { |val| @condition.call(*val.root) }
|
195
242
|
.map do |result|
|
196
|
-
n_runs += 1
|
197
243
|
size += 1
|
198
244
|
|
199
245
|
result
|
200
246
|
end
|
201
|
-
.take_while { n_runs <= @config.n_runs }
|
202
247
|
end
|
203
248
|
|
204
249
|
private def show_problem_output(problem, generator_results, n_successful, &block)
|
205
250
|
output = @config.verbose ? STDOUT : StringIO.new
|
206
|
-
output = pre_output(output, n_successful, generator_results.root, problem)
|
251
|
+
output = PropCheck::Property::OutputFormatter.pre_output(output, n_successful, generator_results.root, problem)
|
207
252
|
shrunken_result, shrunken_exception, n_shrink_steps = shrink(generator_results, output, &block)
|
208
|
-
output = post_output(output, n_shrink_steps, shrunken_result, shrunken_exception)
|
253
|
+
output = PropCheck::Property::OutputFormatter.post_output(output, n_shrink_steps, shrunken_result, shrunken_exception)
|
209
254
|
|
210
255
|
[output, shrunken_result, shrunken_exception, n_shrink_steps]
|
211
256
|
end
|
212
257
|
|
213
|
-
private def pre_output(output, n_successful, generated_root, problem)
|
214
|
-
output.puts ""
|
215
|
-
output.puts "(after #{n_successful} successful property test runs)"
|
216
|
-
output.puts "Failed on: "
|
217
|
-
output.puts "`#{print_roots(generated_root)}`"
|
218
|
-
output.puts ""
|
219
|
-
output.puts "Exception message:\n---\n#{problem}"
|
220
|
-
output.puts "---"
|
221
|
-
output.puts ""
|
222
|
-
|
223
|
-
output
|
224
|
-
end
|
225
|
-
|
226
|
-
private def post_output(output, n_shrink_steps, shrunken_result, shrunken_exception)
|
227
|
-
if n_shrink_steps == 0
|
228
|
-
output.puts '(shrinking impossible)'
|
229
|
-
else
|
230
|
-
output.puts ''
|
231
|
-
output.puts "Shrunken input (after #{n_shrink_steps} shrink steps):"
|
232
|
-
output.puts "`#{print_roots(shrunken_result)}`"
|
233
|
-
output.puts ""
|
234
|
-
output.puts "Shrunken exception:\n---\n#{shrunken_exception}"
|
235
|
-
output.puts "---"
|
236
|
-
output.puts ""
|
237
|
-
end
|
238
|
-
output
|
239
|
-
end
|
240
|
-
|
241
|
-
private def print_roots(lazy_tree_val)
|
242
|
-
if lazy_tree_val.is_a?(Array) && lazy_tree_val.length == 1 && lazy_tree_val[0].is_a?(Hash)
|
243
|
-
lazy_tree_val[0].ai
|
244
|
-
else
|
245
|
-
lazy_tree_val.ai
|
246
|
-
end
|
247
|
-
end
|
248
|
-
|
249
258
|
private def shrink(bindings_tree, io, &block)
|
250
|
-
|
251
|
-
problem_child = bindings_tree
|
252
|
-
siblings = problem_child.children.lazy
|
253
|
-
parent_siblings = nil
|
254
|
-
problem_exception = nil
|
255
|
-
shrink_steps = 0
|
256
|
-
(0..@config.max_shrink_steps).each do
|
257
|
-
begin
|
258
|
-
sibling = siblings.next
|
259
|
-
rescue StopIteration
|
260
|
-
break if parent_siblings.nil?
|
261
|
-
|
262
|
-
siblings = parent_siblings.lazy
|
263
|
-
parent_siblings = nil
|
264
|
-
next
|
265
|
-
end
|
266
|
-
|
267
|
-
shrink_steps += 1
|
268
|
-
io.print '.' if @config.verbose
|
269
|
-
|
270
|
-
begin
|
271
|
-
block.call(*sibling.root)
|
272
|
-
rescue Exception => e
|
273
|
-
problem_child = sibling
|
274
|
-
parent_siblings = siblings
|
275
|
-
siblings = problem_child.children.lazy
|
276
|
-
problem_exception = e
|
277
|
-
end
|
278
|
-
end
|
279
|
-
|
280
|
-
io.puts "(Note: Exceeded #{@config.max_shrink_steps} shrinking steps, the maximum.)" if shrink_steps >= @config.max_shrink_steps
|
281
|
-
|
282
|
-
[problem_child.root, problem_exception, shrink_steps]
|
259
|
+
PropCheck::Property::Shrinker.call(bindings_tree, io, @hooks, @config, &block)
|
283
260
|
end
|
284
261
|
end
|
285
262
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
##
|
2
|
+
# @api private
|
3
|
+
module PropCheck::Property::OutputFormatter
|
4
|
+
extend self
|
5
|
+
|
6
|
+
def pre_output(output, n_successful, generated_root, problem)
|
7
|
+
output.puts ""
|
8
|
+
output.puts "(after #{n_successful} successful property test runs)"
|
9
|
+
output.puts "Failed on: "
|
10
|
+
output.puts "`#{print_roots(generated_root)}`"
|
11
|
+
output.puts ""
|
12
|
+
output.puts "Exception message:\n---\n#{problem}"
|
13
|
+
output.puts "---"
|
14
|
+
output.puts ""
|
15
|
+
|
16
|
+
output
|
17
|
+
end
|
18
|
+
|
19
|
+
def post_output(output, n_shrink_steps, shrunken_result, shrunken_exception)
|
20
|
+
if n_shrink_steps == 0
|
21
|
+
output.puts '(shrinking impossible)'
|
22
|
+
else
|
23
|
+
output.puts ''
|
24
|
+
output.puts "Shrunken input (after #{n_shrink_steps} shrink steps):"
|
25
|
+
output.puts "`#{print_roots(shrunken_result)}`"
|
26
|
+
output.puts ""
|
27
|
+
output.puts "Shrunken exception:\n---\n#{shrunken_exception}"
|
28
|
+
output.puts "---"
|
29
|
+
output.puts ""
|
30
|
+
end
|
31
|
+
output
|
32
|
+
end
|
33
|
+
|
34
|
+
def print_roots(lazy_tree_val)
|
35
|
+
if lazy_tree_val.is_a?(Array) && lazy_tree_val.length == 1 && lazy_tree_val[0].is_a?(Hash)
|
36
|
+
lazy_tree_val[0].ai
|
37
|
+
else
|
38
|
+
lazy_tree_val.ai
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
class PropCheck::Property::Shrinker
|
2
|
+
def initialize(bindings_tree, io, hooks, config)
|
3
|
+
@problem_child = bindings_tree
|
4
|
+
@io = io
|
5
|
+
@siblings = @problem_child.children.lazy
|
6
|
+
@parent_siblings = nil
|
7
|
+
@problem_exception = nil
|
8
|
+
@shrink_steps = 0
|
9
|
+
@hooks = hooks
|
10
|
+
@config = config
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.call(bindings_tree, io, hooks, config, &block)
|
14
|
+
self
|
15
|
+
.new(bindings_tree, io, hooks, config)
|
16
|
+
.call(&block)
|
17
|
+
end
|
18
|
+
|
19
|
+
def call(&block)
|
20
|
+
@io.puts 'Shrinking...' if @config.verbose
|
21
|
+
|
22
|
+
shrink(&block)
|
23
|
+
|
24
|
+
print_shrinking_exceeded_message if @shrink_steps >= @config.max_shrink_steps
|
25
|
+
|
26
|
+
[@problem_child.root, @problem_exception, @shrink_steps]
|
27
|
+
end
|
28
|
+
|
29
|
+
private def shrink(&block)
|
30
|
+
wrapped_enum.each do
|
31
|
+
instruction, sibling = safe_read_sibling
|
32
|
+
break if instruction == :break
|
33
|
+
next if instruction == :next
|
34
|
+
|
35
|
+
inc_shrink_step
|
36
|
+
|
37
|
+
safe_call_block(sibling, &block)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private def wrapped_enum
|
42
|
+
@hooks.wrap_enum(0..@config.max_shrink_steps).lazy
|
43
|
+
end
|
44
|
+
|
45
|
+
private def inc_shrink_step
|
46
|
+
@shrink_steps += 1
|
47
|
+
@io.print '.' if @config.verbose
|
48
|
+
end
|
49
|
+
|
50
|
+
private def safe_read_sibling
|
51
|
+
begin
|
52
|
+
sibling = @siblings.next
|
53
|
+
[:continue, sibling]
|
54
|
+
rescue StopIteration
|
55
|
+
return [:break, nil] if @parent_siblings.nil?
|
56
|
+
|
57
|
+
@siblings = @parent_siblings.lazy
|
58
|
+
@parent_siblings = nil
|
59
|
+
[:next, nil]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
private def safe_call_block(sibling, &block)
|
64
|
+
begin
|
65
|
+
block.call(*sibling.root)
|
66
|
+
# It is correct that we want to rescue _all_ Exceptions
|
67
|
+
# not only 'StandardError's
|
68
|
+
rescue Exception => e
|
69
|
+
@problem_child = sibling
|
70
|
+
@parent_siblings = @siblings
|
71
|
+
@siblings = @problem_child.children.lazy
|
72
|
+
@problem_exception = e
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
private def print_shrinking_exceeded_message
|
77
|
+
@io.puts "(Note: Exceeded #{@config.max_shrink_steps} shrinking steps, the maximum.)"
|
78
|
+
end
|
79
|
+
end
|
data/lib/prop_check/version.rb
CHANGED
data/prop_check.gemspec
CHANGED
@@ -36,7 +36,5 @@ Gem::Specification.new do |spec|
|
|
36
36
|
|
37
37
|
spec.required_ruby_version = '>= 2.5.1'
|
38
38
|
|
39
|
-
spec.
|
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.
|
4
|
+
version: 0.10.4
|
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-
|
11
|
+
date: 2020-08-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: awesome_print
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
20
|
-
type: :
|
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: '
|
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,9 +53,12 @@ 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
|
60
|
+
- lib/prop_check/property/output_formatter.rb
|
61
|
+
- lib/prop_check/property/shrinker.rb
|
88
62
|
- lib/prop_check/version.rb
|
89
63
|
- prop_check.gemspec
|
90
64
|
homepage: https://github.com/Qqwy/ruby-prop_check/
|
data/Gemfile.lock
DELETED
@@ -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
|