pbt 0.0.1 → 0.1.0
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/.standard.yml +1 -1
- data/CHANGELOG.md +17 -2
- data/README.md +282 -37
- data/lib/pbt/arbitrary/arbitrary.rb +58 -0
- data/lib/pbt/arbitrary/arbitrary_methods.rb +307 -0
- data/lib/pbt/arbitrary/array_arbitrary.rb +55 -0
- data/lib/pbt/arbitrary/choose_arbitrary.rb +25 -0
- data/lib/pbt/arbitrary/constant.rb +37 -0
- data/lib/pbt/arbitrary/constant_arbitrary.rb +23 -0
- data/lib/pbt/arbitrary/filter_arbitrary.rb +36 -0
- data/lib/pbt/arbitrary/fixed_hash_arbitrary.rb +30 -0
- data/lib/pbt/arbitrary/integer_arbitrary.rb +40 -0
- data/lib/pbt/arbitrary/map_arbitrary.rb +31 -0
- data/lib/pbt/arbitrary/one_of_arbitrary.rb +29 -0
- data/lib/pbt/arbitrary/tuple_arbitrary.rb +32 -0
- data/lib/pbt/check/case.rb +8 -0
- data/lib/pbt/check/configuration.rb +58 -0
- data/lib/pbt/check/property.rb +50 -0
- data/lib/pbt/check/runner_iterator.rb +59 -0
- data/lib/pbt/check/runner_methods.rb +177 -0
- data/lib/pbt/check/tosser.rb +32 -0
- data/lib/pbt/reporter/run_details.rb +21 -0
- data/lib/pbt/reporter/run_details_reporter.rb +39 -0
- data/lib/pbt/reporter/run_execution.rb +81 -0
- data/lib/pbt/version.rb +1 -1
- data/lib/pbt.rb +65 -5
- metadata +23 -4
- data/lib/pbt/generator.rb +0 -15
- data/lib/pbt/runner.rb +0 -41
@@ -0,0 +1,307 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pbt/arbitrary/arbitrary"
|
4
|
+
require "pbt/arbitrary/constant"
|
5
|
+
require "pbt/arbitrary/constant_arbitrary"
|
6
|
+
require "pbt/arbitrary/array_arbitrary"
|
7
|
+
require "pbt/arbitrary/integer_arbitrary"
|
8
|
+
require "pbt/arbitrary/tuple_arbitrary"
|
9
|
+
require "pbt/arbitrary/fixed_hash_arbitrary"
|
10
|
+
require "pbt/arbitrary/choose_arbitrary"
|
11
|
+
require "pbt/arbitrary/one_of_arbitrary"
|
12
|
+
require "pbt/arbitrary/map_arbitrary"
|
13
|
+
require "pbt/arbitrary/filter_arbitrary"
|
14
|
+
|
15
|
+
module Pbt
|
16
|
+
module Arbitrary
|
17
|
+
module ArbitraryMethods
|
18
|
+
# For integers between min (included) and max (included).
|
19
|
+
#
|
20
|
+
# @param min [Integer] Lower limit for the generated integers (included).
|
21
|
+
# @param max [Integer] Upper limit for the generated integers (included).
|
22
|
+
# @return [Arbitrary<Integer>]
|
23
|
+
def integer(min: -1000000, max: 1000000)
|
24
|
+
IntegerArbitrary.new(min, max)
|
25
|
+
end
|
26
|
+
|
27
|
+
# For natural numbers (non-negative integers) between 0 (included) and max (included).
|
28
|
+
#
|
29
|
+
# @param max [Integer] Upper limit for the generated integers (included).
|
30
|
+
# @return [Arbitrary<Integer>]
|
31
|
+
def nat(max: nil)
|
32
|
+
integer(min: 0, max: max)
|
33
|
+
end
|
34
|
+
|
35
|
+
# For arrays of values generated by `arb`.
|
36
|
+
#
|
37
|
+
# @param arb [Arbitrary<T>] Arbitrary used to generate the values of the array.
|
38
|
+
# @param min [Integer] Lower limit of the generated array size.
|
39
|
+
# @param max [Integer] Upper limit of the generated array size.
|
40
|
+
# @param empty [Boolean] Whether the array can be empty or not.
|
41
|
+
# @return [Arbitrary<T>]
|
42
|
+
def array(arb, min: 0, max: 10, empty: true)
|
43
|
+
raise ArgumentError if min < 0
|
44
|
+
min = 1 if min.zero? && !empty
|
45
|
+
|
46
|
+
ArrayArbitrary.new(arb, min, max)
|
47
|
+
end
|
48
|
+
|
49
|
+
# For tuples of values generated by `arbs`.
|
50
|
+
#
|
51
|
+
# @param arbs [Array<Arbitrary<...T>>] Arbitraries used to generate the values of the tuple.
|
52
|
+
# @return [Arbitrary<Array<...T>>]
|
53
|
+
def tuple(*arbs)
|
54
|
+
TupleArbitrary.new(*arbs)
|
55
|
+
end
|
56
|
+
|
57
|
+
# For fixed hashes of values generated by `hash`.
|
58
|
+
#
|
59
|
+
# @example
|
60
|
+
# arb = Pbt.fixed_hash(x: Pbt.integer, y: Pbt.integer)
|
61
|
+
# arb.generate(Random.new) # => {x: -450108, y: 42}
|
62
|
+
#
|
63
|
+
# @param hash [Hash<Object, Arbitrary<T>>] Hash with any keys and arbitraries as values.
|
64
|
+
# @return [Arbitrary<Hash<Object, T>>]
|
65
|
+
def fixed_hash(hash)
|
66
|
+
FixedHashArbitrary.new(hash)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Picks a random integer in the given range.
|
70
|
+
#
|
71
|
+
# @see Pbt.one_of
|
72
|
+
# @param range [Range<Integer>] Range of integers to choose from.
|
73
|
+
# @return [Arbitrary<Integer>]
|
74
|
+
def choose(range)
|
75
|
+
ChooseArbitrary.new(range)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Picks a random element from the given choices.
|
79
|
+
# The choices can be of any type.
|
80
|
+
#
|
81
|
+
# @see Pbt.one_of
|
82
|
+
# @param choices [Array<Object>] Array of choices.
|
83
|
+
# @return [Arbitrary<Object>]
|
84
|
+
def one_of(*choices)
|
85
|
+
OneOfArbitrary.new(choices)
|
86
|
+
end
|
87
|
+
|
88
|
+
# For a lowercase hexadecimal character.
|
89
|
+
#
|
90
|
+
# @return [Arbitrary<String>]
|
91
|
+
def hexa
|
92
|
+
one_of(*HEXA_CHARS)
|
93
|
+
end
|
94
|
+
|
95
|
+
# For lowercase hexadecimal stings.
|
96
|
+
#
|
97
|
+
# @see Pbt.array
|
98
|
+
# @param kwargs [Hash] Options for ArrayArbitrary. See `.array` for more information.
|
99
|
+
# @return [Arbitrary<String>]
|
100
|
+
def hexa_string(**kwargs)
|
101
|
+
array(hexa, **kwargs).map(STRING_MAPPER, STRING_UNMAPPER)
|
102
|
+
end
|
103
|
+
|
104
|
+
# For a single unicode character (including printable and non-printable).
|
105
|
+
#
|
106
|
+
# @return [Arbitrary<String>]
|
107
|
+
def char
|
108
|
+
choose(CHAR_RANGE).map(CHAR_MAPPER, CHAR_UNMAPPER)
|
109
|
+
end
|
110
|
+
|
111
|
+
# For an alphanumeric character.
|
112
|
+
#
|
113
|
+
# @return [Arbitrary<String>]
|
114
|
+
def alphanumeric_char
|
115
|
+
one_of(*ALPHANUMERIC_CHARS)
|
116
|
+
end
|
117
|
+
|
118
|
+
# For alphanumeric strings.
|
119
|
+
#
|
120
|
+
# @see Pbt.array
|
121
|
+
# @param kwargs [Hash] Options for ArrayArbitrary. See `.array` for more information.
|
122
|
+
# @return [Arbitrary<String>]
|
123
|
+
def alphanumeric_string(**kwargs)
|
124
|
+
array(alphanumeric_char, **kwargs).map(STRING_MAPPER, STRING_UNMAPPER)
|
125
|
+
end
|
126
|
+
|
127
|
+
# For an ascii character.
|
128
|
+
#
|
129
|
+
# @return [Arbitrary<String>]
|
130
|
+
def ascii_char
|
131
|
+
one_of(*ASCII_CHARS)
|
132
|
+
end
|
133
|
+
|
134
|
+
# For ascii strings.
|
135
|
+
#
|
136
|
+
# @see Pbt.array
|
137
|
+
# @param kwargs [Hash] Options for ArrayArbitrary. See `.array` for more information.
|
138
|
+
# @return [Arbitrary<String>]
|
139
|
+
def ascii_string(**kwargs)
|
140
|
+
array(ascii_char, **kwargs).map(STRING_MAPPER, STRING_UNMAPPER)
|
141
|
+
end
|
142
|
+
|
143
|
+
# For a printable ascii character.
|
144
|
+
#
|
145
|
+
# @return [Arbitrary<String>]
|
146
|
+
def printable_ascii_char
|
147
|
+
one_of(*PRINTABLE_ASCII_CHARS)
|
148
|
+
end
|
149
|
+
|
150
|
+
# For printable ascii strings.
|
151
|
+
#
|
152
|
+
# @see Pbt.array
|
153
|
+
# @param kwargs [Hash] Options for ArrayArbitrary. See `.array` for more information.
|
154
|
+
# @return [Arbitrary<String>]
|
155
|
+
def printable_ascii_string(**kwargs)
|
156
|
+
array(printable_ascii_char, **kwargs).map(STRING_MAPPER, STRING_UNMAPPER)
|
157
|
+
end
|
158
|
+
|
159
|
+
# For a printable character.
|
160
|
+
#
|
161
|
+
# @return [Arbitrary<String>]
|
162
|
+
def printable_char
|
163
|
+
one_of(*PRINTABLE_CHARS)
|
164
|
+
end
|
165
|
+
|
166
|
+
# For printable strings.
|
167
|
+
#
|
168
|
+
# @see Pbt.array
|
169
|
+
# @param kwargs [Hash] Options for ArrayArbitrary. See `.array` for more information.
|
170
|
+
# @return [Arbitrary<String>]
|
171
|
+
def printable_string(**kwargs)
|
172
|
+
array(printable_char, **kwargs).map(STRING_MAPPER, STRING_UNMAPPER)
|
173
|
+
end
|
174
|
+
|
175
|
+
# For symbols.
|
176
|
+
#
|
177
|
+
# @see Pbt.array
|
178
|
+
# @param kwargs [Hash] Options for ArrayArbitrary. See `.array` for more information.
|
179
|
+
# @return [Arbitrary<Symbol>]
|
180
|
+
def symbol(**kwargs)
|
181
|
+
array(one_of(*SYMBOL_SAFE_CHARS), empty: false, **kwargs).map(SYMBOL_MAPPER, SYMBOL_UNMAPPER)
|
182
|
+
end
|
183
|
+
|
184
|
+
# For floats.
|
185
|
+
#
|
186
|
+
# @return [Arbitrary<Float>]
|
187
|
+
def float
|
188
|
+
tuple(integer, integer).map(FLOAT_MAPPER, FLOAT_UNMAPPER)
|
189
|
+
end
|
190
|
+
|
191
|
+
# For symbols.
|
192
|
+
#
|
193
|
+
# @param arb [Arbitrary<T>] Arbitrary used to generate the values of the array.
|
194
|
+
# @param min [Integer] Lower limit of the generated set size.
|
195
|
+
# @param max [Integer] Upper limit of the generated set size.
|
196
|
+
# @param empty [Boolean] Whether the array can be empty or not.
|
197
|
+
# @return [Arbitrary<T>]
|
198
|
+
def set(arb, min: 0, max: 10, empty: true)
|
199
|
+
array(arb, min: min, max: max, empty: empty).map(SET_MAPPER, SET_UNMAPPER)
|
200
|
+
end
|
201
|
+
|
202
|
+
# For hashes of any keys and values.
|
203
|
+
# If you want to call `Object#hash` for `Pbt`, call this method without arguments.
|
204
|
+
#
|
205
|
+
# @example
|
206
|
+
# hash_generator = Pbt.hash(Pbt.symbol, Pbt.integer)
|
207
|
+
# hash_generator.generate(Random.new) # => {:buo=>466214, :cwftzvglq=>331431, :wweccnzg=>-848867}
|
208
|
+
#
|
209
|
+
# @see Pbt.array
|
210
|
+
# @param args [Array<Arbitrary<T,U>>] Arbitraries to generate Hash. First one is for key and second is for value.
|
211
|
+
# @param kwargs [Hash] Options for ArrayArbitrary. See `.array` for more information.
|
212
|
+
# @return [Arbitrary<Hash<T,U>>]
|
213
|
+
def hash(*args, **kwargs)
|
214
|
+
if args.size == 2
|
215
|
+
key_arbitrary, value_arbitrary = args
|
216
|
+
array(tuple(key_arbitrary, value_arbitrary), **kwargs).map(HASH_MAPPER, HASH_UNMAPPER)
|
217
|
+
else
|
218
|
+
super # call `Object#hash`
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
# For booleans.
|
223
|
+
#
|
224
|
+
# @return [Arbitrary<Boolean>]
|
225
|
+
def boolean
|
226
|
+
one_of(true, false)
|
227
|
+
end
|
228
|
+
|
229
|
+
# For any constant values.
|
230
|
+
# It's useful when you want to use a constant value that behaves like an arbitrary.
|
231
|
+
#
|
232
|
+
# @example
|
233
|
+
# Pbt.constant(42).generate(Random.new) # => 42
|
234
|
+
#
|
235
|
+
# @param val [Object]
|
236
|
+
# @return [Arbitrary<Object>]
|
237
|
+
def constant(val)
|
238
|
+
ConstantArbitrary.new(val)
|
239
|
+
end
|
240
|
+
|
241
|
+
# For nil.
|
242
|
+
#
|
243
|
+
# @return [Arbitrary<nil>]
|
244
|
+
def nil
|
245
|
+
constant(nil)
|
246
|
+
end
|
247
|
+
|
248
|
+
# For dates between `base_date + past_offset_days` and `base_date + future_offset_days`.
|
249
|
+
#
|
250
|
+
# @param base_date [Date] Base date for the generated dates.
|
251
|
+
# @param past_offset_days [Integer] Offset days for the past. Default is -18250 (about 50 years).
|
252
|
+
# @param future_offset_days [Integer] Offset days for the future. Default is 18250 (about 50 years).
|
253
|
+
# @return [Arbitrary<Date>]
|
254
|
+
def date(base_date: Date.today, past_offset_days: -18250, future_offset_days: 18250)
|
255
|
+
offset_arb = integer(min: past_offset_days, max: future_offset_days)
|
256
|
+
offset_arb.map(DATE_MAPPER.call(base_date), DATE_UNMAPPER.call(base_date))
|
257
|
+
end
|
258
|
+
|
259
|
+
# For past dates between `base_date - past_offset_days` and `base_date`.
|
260
|
+
#
|
261
|
+
# @param base_date [Date] Base date for the generated dates.
|
262
|
+
# @param past_offset_days [Integer] Offset days for the past. Default is -18250 (about 50 years).
|
263
|
+
# @return [Arbitrary<Date>]
|
264
|
+
def past_date(base_date: Date.today, past_offset_days: -18250)
|
265
|
+
date(base_date: base_date, past_offset_days: past_offset_days, future_offset_days: 0)
|
266
|
+
end
|
267
|
+
|
268
|
+
# For future dates between `base_date` and `base_date - future_offset_days`.
|
269
|
+
#
|
270
|
+
# @param base_date [Date] Base date for the generated dates.
|
271
|
+
# @param future_offset_days [Integer] Offset days for the future. Default is 18250 (about 50 years).
|
272
|
+
# @return [Arbitrary<Date>]
|
273
|
+
def future_date(base_date: Date.today, future_offset_days: 18250)
|
274
|
+
date(base_date: base_date, past_offset_days: 0, future_offset_days: future_offset_days)
|
275
|
+
end
|
276
|
+
|
277
|
+
# For times between `base_time + past_offset_seconds` and `base_time + future_offset_seconds`.
|
278
|
+
#
|
279
|
+
# @param base_time [Date] Base time for the generated times.
|
280
|
+
# @param past_offset_seconds [Integer] Offset seconds for the past. Default is -1576800000 (about 50 years).
|
281
|
+
# @param future_offset_seconds [Integer] Offset seconds for the future. Default is 1576800000 (about 50 years).
|
282
|
+
# @return [Arbitrary<Time>]
|
283
|
+
def time(base_time: Time.now, past_offset_seconds: -1576800000, future_offset_seconds: 1576800000)
|
284
|
+
offset_arb = integer(min: past_offset_seconds, max: future_offset_seconds)
|
285
|
+
offset_arb.map(TIME_MAPPER.call(base_time), TIME_UNMAPPER.call(base_time))
|
286
|
+
end
|
287
|
+
|
288
|
+
# For past times between `base_time + past_offset_seconds` and `base_time`
|
289
|
+
#
|
290
|
+
# @param base_time [Date] Base time for the generated times.
|
291
|
+
# @param past_offset_seconds [Integer] Offset seconds for the past. Default is -1576800000 (about 50 years).
|
292
|
+
# @return [Arbitrary<Time>]
|
293
|
+
def past_time(base_time: Time.now, past_offset_seconds: -1576800000)
|
294
|
+
time(base_time: base_time, past_offset_seconds: past_offset_seconds, future_offset_seconds: 0)
|
295
|
+
end
|
296
|
+
|
297
|
+
# For future times between `base_time` and `base_time + future_offset_seconds`.
|
298
|
+
#
|
299
|
+
# @param base_time [Date] Base time for the generated times.
|
300
|
+
# @param future_offset_seconds [Integer] Offset seconds for the future. Default is 1576800000 (about 50 years).
|
301
|
+
# @return [Arbitrary<Time>]
|
302
|
+
def future_time(base_time: Time.now, future_offset_seconds: 1576800000)
|
303
|
+
time(base_time: base_time, past_offset_seconds: 0, future_offset_seconds: future_offset_seconds)
|
304
|
+
end
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pbt
|
4
|
+
module Arbitrary
|
5
|
+
# Generates arrays of values generated by `value_arb` with type `T`.
|
6
|
+
class ArrayArbitrary < Arbitrary
|
7
|
+
DEFAULT_MAX_SIZE = 10
|
8
|
+
private_constant :DEFAULT_MAX_SIZE
|
9
|
+
|
10
|
+
# @param value_arb [Arbitrary] Arbitrary to generate values.
|
11
|
+
# @param min_length [Integer] Minimum length of the generated array. Default is 0.
|
12
|
+
# @param max_length [Integer] Maximum length of the generated array. Default is 10.
|
13
|
+
def initialize(value_arb, min_length = 0, max_length = DEFAULT_MAX_SIZE)
|
14
|
+
raise ArgumentError, "min_length must be zero or positive number" if min_length < 0
|
15
|
+
|
16
|
+
@min_length = min_length
|
17
|
+
@max_length = max_length
|
18
|
+
@value_arb = value_arb
|
19
|
+
@length_arb = IntegerArbitrary.new(min_length, max_length)
|
20
|
+
end
|
21
|
+
|
22
|
+
# @see Arbitrary#generate
|
23
|
+
def generate(rng)
|
24
|
+
length = @length_arb.generate(rng)
|
25
|
+
length.times.map { @value_arb.generate(rng) }
|
26
|
+
end
|
27
|
+
|
28
|
+
# @see Arbitrary#shrink
|
29
|
+
def shrink(current)
|
30
|
+
return Enumerator.new { |_| } if current.size == @min_length
|
31
|
+
|
32
|
+
Enumerator.new do |y|
|
33
|
+
# First, shrink the length of the array and try combinations of elements.
|
34
|
+
# But this doesn't try all possible combinations since it'd be too huge and slow.
|
35
|
+
@length_arb.shrink(current.size).each do |length|
|
36
|
+
if length == 0
|
37
|
+
y.yield []
|
38
|
+
next
|
39
|
+
end
|
40
|
+
current.each_cons(length) do |con|
|
41
|
+
y.yield con
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Second, shrink each element of the array.
|
46
|
+
current.each_with_index do |item, i|
|
47
|
+
@value_arb.shrink(item).each do |val|
|
48
|
+
y.yield [*current[...i], val, *current[i + 1..]]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pbt
|
4
|
+
module Arbitrary
|
5
|
+
# Generates a random value from a range.
|
6
|
+
class ChooseArbitrary < Arbitrary
|
7
|
+
# @param range [Range<Integer>]
|
8
|
+
def initialize(range)
|
9
|
+
@range = range
|
10
|
+
end
|
11
|
+
|
12
|
+
# @see Arbitrary#generate
|
13
|
+
def generate(rng)
|
14
|
+
rng.rand(@range)
|
15
|
+
end
|
16
|
+
|
17
|
+
# @see Arbitrary#shrink
|
18
|
+
def shrink(current)
|
19
|
+
# Range is ordered from min to max, so we can just shrink towards min.
|
20
|
+
min, max = [@range.begin, @range.end].sort
|
21
|
+
IntegerArbitrary.new(min, max).shrink(current, target: min)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "date"
|
4
|
+
require "set" # For 3.1 or prior. Set became built-in library since Ruby 3.2.0
|
5
|
+
|
6
|
+
module Pbt
|
7
|
+
module Arbitrary
|
8
|
+
HEXA_CHARS = [*("0".."9"), *("a".."f")].freeze
|
9
|
+
CHAR_RANGE = (0..0x10FFFF)
|
10
|
+
SYMBOL_SAFE_CHARS = [*("a".."z"), "-"].freeze
|
11
|
+
ALPHANUMERIC_CHARS = [*("a".."z"), *("A".."Z"), *("0".."9")].freeze
|
12
|
+
PRINTABLE_ASCII_CHARS = [*(" ".."~")].freeze
|
13
|
+
ASCII_CHARS = [*PRINTABLE_ASCII_CHARS, "\n", "\r", "\t", "\v", "\b", "\f", "\e", "\d", "\a"].freeze
|
14
|
+
PRINTABLE_CHARS = [
|
15
|
+
*ASCII_CHARS,
|
16
|
+
*("\u{A0}".."\u{D7FF}"),
|
17
|
+
*("\u{E000}".."\u{FFFD}"),
|
18
|
+
*("\u{10000}".."\u{10FFFF}")
|
19
|
+
].freeze
|
20
|
+
CHAR_MAPPER = ->(v) { [v].pack("U") }
|
21
|
+
CHAR_UNMAPPER = ->(v) { v.unpack1("U") }
|
22
|
+
STRING_MAPPER = ->(v) { v.join }
|
23
|
+
STRING_UNMAPPER = ->(v) { v.chars }
|
24
|
+
SYMBOL_MAPPER = ->(v) { v.join.to_sym }
|
25
|
+
SYMBOL_UNMAPPER = ->(v) { v.to_s.chars }
|
26
|
+
FLOAT_MAPPER = ->((x, y)) { x.to_f / (y.to_f.abs + 1.0) }
|
27
|
+
FLOAT_UNMAPPER = ->(v) { [v, 0] }
|
28
|
+
SET_MAPPER = ->(v) { v.to_set }
|
29
|
+
SET_UNMAPPER = ->(v) { v.to_a }
|
30
|
+
HASH_MAPPER = ->(v) { v.to_h }
|
31
|
+
HASH_UNMAPPER = ->(v) { v.to_a }
|
32
|
+
DATE_MAPPER = ->(epoch) { ->(v) { epoch + v } }
|
33
|
+
DATE_UNMAPPER = ->(epoch) { ->(v) { (v - epoch).to_i } }
|
34
|
+
TIME_MAPPER = DATE_MAPPER
|
35
|
+
TIME_UNMAPPER = DATE_UNMAPPER
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pbt
|
4
|
+
module Arbitrary
|
5
|
+
# Generates a constant value.
|
6
|
+
class ConstantArbitrary < Arbitrary
|
7
|
+
# @param val [Object]
|
8
|
+
def initialize(val)
|
9
|
+
@val = val
|
10
|
+
end
|
11
|
+
|
12
|
+
# @see Arbitrary#generate
|
13
|
+
def generate(rng)
|
14
|
+
@val
|
15
|
+
end
|
16
|
+
|
17
|
+
# @see Arbitrary#shrink
|
18
|
+
def shrink(current)
|
19
|
+
Enumerator.new {}
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pbt
|
4
|
+
module Arbitrary
|
5
|
+
# Generates values from another arbitrary, but only if they pass a predicate.
|
6
|
+
class FilterArbitrary < Arbitrary
|
7
|
+
# @param arb [Arbitrary] Arbitrary to generate values to be filtered.
|
8
|
+
# @param refinement [Proc] Predicate proc to test each produced element. Return true to keep the element, false otherwise.
|
9
|
+
def initialize(arb, &refinement)
|
10
|
+
@arb = arb
|
11
|
+
@refinement = refinement
|
12
|
+
end
|
13
|
+
|
14
|
+
# @see Arbitrary#generate
|
15
|
+
def generate(rng)
|
16
|
+
loop do
|
17
|
+
val = @arb.generate(rng)
|
18
|
+
return val if @refinement.call(val)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# @see Arbitrary#shrink
|
23
|
+
def shrink(current)
|
24
|
+
Enumerator.new do |y|
|
25
|
+
@arb.shrink(current).each do |v|
|
26
|
+
if @refinement.call(v)
|
27
|
+
y.yield v
|
28
|
+
else
|
29
|
+
next
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pbt
|
4
|
+
module Arbitrary
|
5
|
+
# Generates a hash with fixed keys and arbitrary values.
|
6
|
+
class FixedHashArbitrary < Arbitrary
|
7
|
+
# @param hash [Hash<Object, Arbitrary<T>>] Hash with any keys and arbitraries as values.
|
8
|
+
def initialize(hash)
|
9
|
+
@keys = hash.keys
|
10
|
+
@arb = TupleArbitrary.new(*hash.values)
|
11
|
+
end
|
12
|
+
|
13
|
+
# @see Arbitrary#generate
|
14
|
+
def generate(rng)
|
15
|
+
values = @arb.generate(rng)
|
16
|
+
@keys.zip(values).to_h
|
17
|
+
end
|
18
|
+
|
19
|
+
# @see Arbitrary#shrink
|
20
|
+
def shrink(current)
|
21
|
+
# This is not the most comprehensive but allows a reasonable number of entries in the shrink
|
22
|
+
Enumerator.new do |y|
|
23
|
+
@arb.shrink(current.values).each do |next_values|
|
24
|
+
y << @keys.zip(next_values).to_h
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pbt
|
4
|
+
module Arbitrary
|
5
|
+
# Generates random integers between `min` and `max`.
|
6
|
+
class IntegerArbitrary < Arbitrary
|
7
|
+
DEFAULT_TARGET = 0
|
8
|
+
private_constant :DEFAULT_TARGET
|
9
|
+
|
10
|
+
# @param min [Integer] Minimum value to generate.
|
11
|
+
# @param max [Integer] Maximum value to generate.
|
12
|
+
def initialize(min, max)
|
13
|
+
@min = min
|
14
|
+
@max = max
|
15
|
+
end
|
16
|
+
|
17
|
+
# @see Arbitrary#generate
|
18
|
+
def generate(rng)
|
19
|
+
rng.rand(@min..@max)
|
20
|
+
end
|
21
|
+
|
22
|
+
# @see Arbitrary#shrink
|
23
|
+
def shrink(current, target: DEFAULT_TARGET)
|
24
|
+
gap = current - target
|
25
|
+
return Enumerator.new { |_| } if gap == 0
|
26
|
+
|
27
|
+
is_positive_gap = gap > 0
|
28
|
+
|
29
|
+
Enumerator.new do |y|
|
30
|
+
while (diff = (current - target).abs) > 1
|
31
|
+
halved = diff / 2
|
32
|
+
current -= is_positive_gap ? halved : -halved
|
33
|
+
y.yield current
|
34
|
+
end
|
35
|
+
y.yield target # no diff here
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pbt
|
4
|
+
module Arbitrary
|
5
|
+
# Generates a mapped value from another arbitrary.
|
6
|
+
class MapArbitrary < Arbitrary
|
7
|
+
# @param arb [Arbitrary] Arbitrary to generate values to be mapped.
|
8
|
+
# @param mapper [Proc] Proc to map generated values. Mainly used for generation.
|
9
|
+
# @param unmapper [Proc] Proc to unmap generated values. Used for shrinking.
|
10
|
+
def initialize(arb, mapper, unmapper)
|
11
|
+
@arb = arb
|
12
|
+
@mapper = mapper
|
13
|
+
@unmapper = unmapper
|
14
|
+
end
|
15
|
+
|
16
|
+
# @see Arbitrary#generate
|
17
|
+
def generate(rng)
|
18
|
+
@mapper.call(@arb.generate(rng))
|
19
|
+
end
|
20
|
+
|
21
|
+
# @see Arbitrary#shrink
|
22
|
+
def shrink(current)
|
23
|
+
Enumerator.new do |y|
|
24
|
+
@arb.shrink(@unmapper.call(current)).each do |v|
|
25
|
+
y.yield @mapper.call(v)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pbt
|
4
|
+
module Arbitrary
|
5
|
+
# Generates one of the given choices.
|
6
|
+
class OneOfArbitrary < Arbitrary
|
7
|
+
# @param choices [Array] List of choices.
|
8
|
+
def initialize(choices)
|
9
|
+
@choices = choices
|
10
|
+
@idx_arb = IntegerArbitrary.new(0, choices.size - 1)
|
11
|
+
end
|
12
|
+
|
13
|
+
# @see Arbitrary#generate
|
14
|
+
def generate(rng)
|
15
|
+
@choices[@idx_arb.generate(rng)]
|
16
|
+
end
|
17
|
+
|
18
|
+
# @see Arbitrary#shrink
|
19
|
+
def shrink(current)
|
20
|
+
# Shrinks to values earlier in the list of `choices`.
|
21
|
+
Enumerator.new do |y|
|
22
|
+
@idx_arb.shrink(@choices.index(current)).map do |idx|
|
23
|
+
y << @choices[idx]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pbt
|
4
|
+
module Arbitrary
|
5
|
+
# Generates a tuple of arbitrary values.
|
6
|
+
class TupleArbitrary < Arbitrary
|
7
|
+
# @param arbs [Array<Arbitrary>] Arbitraries used to generate the values of the tuple.
|
8
|
+
def initialize(*arbs)
|
9
|
+
@arbs = arbs
|
10
|
+
end
|
11
|
+
|
12
|
+
# @see Arbitrary#generate
|
13
|
+
def generate(rng)
|
14
|
+
@arbs.map { |arb| arb.generate(rng) }
|
15
|
+
end
|
16
|
+
|
17
|
+
# @see Arbitrary#shrink
|
18
|
+
def shrink(current)
|
19
|
+
# This is not the most comprehensive but allows a reasonable number of entries in the shrink.
|
20
|
+
Enumerator.new do |y|
|
21
|
+
@arbs.each_with_index do |arb, idx|
|
22
|
+
arb.shrink(current[idx]).each do |v|
|
23
|
+
next_values = current.dup
|
24
|
+
next_values[idx] = v
|
25
|
+
y << next_values
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|