pbt 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|