-
Notifications
You must be signed in to change notification settings - Fork 0
/
runtests.jl
459 lines (436 loc) · 16.2 KB
/
runtests.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
using Test
using PropCheck
using PropCheck: getSubtypes, numTests, Tree
const PC = PropCheck
using RequiredInterfaces: RequiredInterfaces
const RI = RequiredInterfaces
using Random: default_rng, randperm
@info "RNG state is:" RNG=copy(default_rng())
"""
Tests the fallback shrinking for numbers to shrink towards zero.
"""
function numsToZero(T)
# are there other properties of the shrinkers themselves we can test?
# there are a lot of numbers, so crank up the tests
check(itype(T); ntests=10_000) do x
shrunks = shrink(x)
if iszero(x) || isnan(x) || isinf(x)
isempty(shrunks)
else
if !isempty(shrunks)
# abs(Int8(-128)) === Int8(-128)
x == typemin(x) || all( y -> zero(T) <= abs(y) < abs(x), shrunks)
else
false
end
end
end
end
function stringsFromTypeToEmpty()
@test check(itype(String)) do str
shrunks = shrink(str)
if isempty(str)
isempty(shrunks)
else
!isempty(str) && all(shrunks) do shr
length(shr) < length(str) ||
count(Base.splat(!=), zip(str, shr)) == 1
end
end
end
end
function interleaveFilterInvariant()
s = filter(l->length(l)>=5, PC.vector(isample(0:10), itype(UInt8)), true)
res = check(s) do s2
length(s2) < 5
end
@test res == zeros(UInt8, 5)
end
function interleaveMapInvariant()
s = map(l -> push!(l, 0x0), PC.vector(isample(0:2), itype(UInt8)))
res = check(s) do s2
!isempty(s2) && last(s2) != 0x0
end
@test res == [0x0]
end
function initialVectorLengthIsBoundedByRange()
i = PC.vector(isample(5:10), itype(Int8))
@test check(i) do v
5 <= length(v) <= 10
end
end
"""
Tests that when a given predicate holds for the parent, it also holds for its subtrees (or at least the first 100).
"""
function predicateHoldsForSubtrees(p, T)
g = filter(p, itype(T))
toCheck = Iterators.filter(!isnothing, Iterators.take(g, numTests[]))
checkedValues = false
res = all(toCheck) do x
checkedValues = true
all(p ∘ root, Iterators.take(subtrees(x), 100))
end
checkedValues && res
end
guaranteeEven(x) = div(x,0x2)*0x2
function mappedGeneratorsObserveProperty(T)
g = map(guaranteeEven, itype(T))
toCheck = Iterators.filter(!isnothing, Iterators.take(g, numTests[]))
checkedValues = false
res = all(toCheck) do x
checkedValues = true
all(iseven ∘ root, Iterators.take(subtrees(x), 100))
end
checkedValues && res
end
function throwingProperty(x)
if x < 5
return true
else
throw(ArgumentError("x not smaller than 5"))
end
end
function floatTear(T, x)
x === PC.assemble(T, PC.tear(x)...)
end
function assembleInf(T)
check(itype(Bool)) do b
inttype = PC.uint(T)
f = PC.assemble(T, inttype(b), typemax(inttype), zero(inttype))
b == signbit(f)
end
end
const numTypes = union(getSubtypes(Integer), getSubtypes(AbstractFloat))
@time @testset "All Tests" begin
@testset "Interfaces" begin
RI.check_implementations(PC.AbstractIntegrated)
extent_types = filter!(!=(PC.IntegratedVal), RI.nonabstract_subtypes(PC.ExtentIntegrated))
RI.check_implementations(PC.ExtentIntegrated, extent_types)
# only this type implements extent
num_ival = PC.IntegratedVal{PC.Tree{Number}}
@test RI.check_interface_implemented(PC.ExtentIntegrated, num_ival)
end
@testset "Tear $T & reassemble, floating point generators" for T in getSubtypes(Base.IEEEFloat)
@testset "assembleInf" begin
assembleInf(T)
end
@test check(x -> floatTear(T, x), itype(T))
@test check(isinf, PC.ifloatinf(T); transform=bitstring)
@test check(isnan, PC.ifloatnan(T); transform=bitstring)
@test check(PC.ifloatinfnan(T); transform=bitstring) do v
isnan(v) | isinf(v)
end
@test check(PC.ifloat(T)) do v
!(isnan(v) | isinf(v))
end
@testset "Special numbers: $x)" for x in (Inf, -Inf, NaN, -0.0, 0.0)
@test floatTear(T, T(x))
end
end
@testset "Integer generators" begin
@testset for T in (getSubtypes(Base.BitSigned))
@test check(>=(zero(T)), PC.iposint(T))
@test check(<(zero(T)), PC.inegint(T))
end
end
@testset "numsToZero" begin
@testset "$T" for T in numTypes
if VERSION >= v"1.7"
@test numsToZero(T) broken=(T == BigInt || T == BigFloat)
else
if T != BigInt && T != BigFloat
@test numsToZero(T)
end
end
end
end
@testset "filter predicates hold for shrunk values" begin
for p in (iseven, isodd)
@testset "$p($T)" for T in (UInt8, UInt16, UInt32, UInt64)
@test predicateHoldsForSubtrees(p, T)
end
end
end
@testset "shrunk values of mapped generator are also even" begin
@testset "mappedGeneratorsObserveProperty($T)" for T in (UInt8, UInt16, UInt32, UInt64)
@test mappedGeneratorsObserveProperty(T)
end
end
@testset "random vectors are sorted" begin
@test check(issorted, PC.vector(isample(0:10), itype(UInt8))) == [0x1, 0x0]
end
@testset "all even numbers are less than 5" begin
@test check(<(5), filter(iseven, itype(UInt8))) == 0x6
end
@testset "there are only even numbers" begin
@test check(iseven, itype(UInt8)) == 0x1
end
@testset "throwing properties still shrink" begin
@test check(throwingProperty, itype(UInt8)) == (0x05, ArgumentError("x not smaller than 5"))
end
@testset "stringsFromTypeToEmpty" begin
stringsFromTypeToEmpty()
end
@testset "interleave preserves invariants" begin
@testset "interleaveFilterInvariant" begin
interleaveFilterInvariant()
end
@testset "interleaveMapInvariant" begin
interleaveMapInvariant()
end
end
@testset "initialVectorLengthIsboundedByRange" begin
initialVectorLengthIsBoundedByRange()
end
@testset "`Integrated` can be `collect`ed" begin
@test all(x -> x isa Tree{Int}, collect(Iterators.take(itype(Int), 5)))
end
@testset "convert Trees: $T" for (f,T) in (((x -> x % 0x3), Int8),
((x -> x % 3), Int))
t = map(f, itype(Int8))
itr = Iterators.take(t, 5)
@test eltype(itr) == Tree{T}
@test eltype(collect(itr)) == Tree{T}
end
if VERSION >= v"1.9"
# this sadly doesn't infer before then, so we can't really test it :/
@testset "type unstable constructors" begin
grade = map(Base.splat(Pair), PC.interleave(itype(String), isample(0:100)))
# it's type unstable because the empty dispatch returns `Dict{Any,Any}`!
# I'd love to propagate the lowerbound of `1` to make this type stable, but that is hard.
# maybe that needs dependent types?
gradegen = map(Base.splat(Dict), PC.tuple(isample(0:10), grade))
@test eltype(gradegen) == Union{PC.Tree{Dict{Any, Any}}, PC.Tree{Dict{String, Int64}}}
end
end
@testset "$f preserves invariants" for f in (PC.str, PC.vector)
# 3 hex characters is plenty - this is already 3^11 == 177147 possible strings
# of couse, it would be better to generate some variations, but.. CI time is not fixing
# & writing code time ¯\_(ツ)_/¯ Plus, I like to be able to test my code locally in a reasonable time
strlen = 3
strgen = f(iconst(strlen), isample('a':'f'))
work = [generate(strgen)]
res = false
while !isempty(work)
cur = pop!(work)
res |= strlen == length(root(cur))
!res && (@test root(cur); break)
append!(work, subtrees(cur))
end
@test res
end
@testset "IntegratedUnique" begin
colgen = PC.vector(isample(0:10), itype(Int8))
# Taking as many elements as the source collection has
# produces the source collection again
@test check(colgen) do col
iu = PC.IntegratedUnique(copy(col), shrink)
sort!(col)
iucol = sort!(map(root, Iterators.take(iu, length(col))))
firstiteration = all(Base.splat(==), zip(col, iucol))
iucol = sort!(map(root, Iterators.take(iu, length(col))))
seconditeration = all(Base.splat(==), zip(col, iucol))
iucol = sort!(map(root, Iterators.take(iu, length(col))))
thirditeration = all(Base.splat(==), zip(col, iucol))
firstiteration && seconditeration && thirditeration
end
end
@testset "IntegratedConst" begin
valgen = itype(Int8)
# Only & exactly the root is produced, without shrunk values
@test check(valgen) do v
genv = generate(iconst(v))
v == root(genv) && isempty(subtrees(genv))
end
end
@testset "IntegratedVal" begin
valgen = itype(Int8)
shrinkgen = iunique([shrink, PC.noshrink], PC.noshrink)
constgen = interleave(valgen, shrinkgen)
# The root is always the given element
@test check(constgen) do (v, s)
genv = generate(PC.ival(v, s))
v == root(genv)
end
# The shrunk values of the root are consistent with the given shrinking function
@test check(constgen) do (v, s)
genv = generate(PC.ival(v, s))
shrunks = sort!(s(v))
gensubs = sort!(map(root, subtrees(genv)))
length(shrunks) == length(gensubs) && all(Base.splat(==), zip(shrunks, gensubs))
end
end
@testset "FiniteIntegrated" begin
@testset "IntegratedOnce" begin
valgen = itype(Int8)
# The value will only be generated exactly once
@test check(valgen) do v
gen = PC.IntegratedOnce(v)
v == root(generate(gen)) && nothing == generate(gen)
end
end
@testset "IntegratedFiniteIterator" begin
function integratedFinitePreservesLength(itr)
# generation preserves the length
ifi = PC.IntegratedFiniteIterator(itr)
length(itr) == length(ifi)
end
function integratedFinitePreservesOrder(itr)
# generation preserves the original iteration order
ifi = PC.IntegratedFiniteIterator(itr)
# `iterate` calls `generate`
all(zip(itr, ifi)) do (a,b)
a == root(b)
end
end
function integratedFiniteGetsExhausted(itr)
# After `length(itr)` calls to `generate`, the shrinker is exhausted
ifi = PC.IntegratedFiniteIterator(itr)
for _ in 1:length(itr)
generate(ifi)
end
Base.isdone(ifi) && generate(ifi) === nothing
end
lengen = isample(0:10)
elgen = itype(Int8)
vecgen = PC.vector(lengen, elgen)
tupgen = PC.tuple(lengen, elgen)
@testset for prop in (integratedFinitePreservesLength,
integratedFinitePreservesOrder,
integratedFiniteGetsExhausted)
@testset for gen in (vecgen, tupgen)
@test check(prop, gen)
end
end
end
@testset "IntegratedLengthBounded" begin
function givenLengthCorrectForInfinite(len)
gen = PC.IntegratedLengthBounded(itype(Int8), len)
all(zip(gen, 1:(len+1))) do (genval, counter)
if counter > len
genval isa Nothing
else
genval isa Tree{Int8}
end
end
end
function givenLengthUpperboundForFinite(len)
targetLen = div(len, 2) + 1
sourceels = rand(Int8, targetLen)
elgen = PC.IntegratedFiniteIterator(sourceels)
gen = PC.IntegratedLengthBounded(elgen, len)
all(zip(gen, 1:max(targetLen, len))) do (genval, counter)
if counter > min(targetLen, len)
genval isa Nothing
else
genval isa Tree{Int8} && sourceels[counter] == root(genval)
end
end
end
@test check(givenLengthCorrectForInfinite, isample(0:20))
@test check(givenLengthUpperboundForFinite, isample(0:20))
end
gens = [
PC.IntegratedOnce(6),
PC.IntegratedFiniteIterator(1:11),
PC.IntegratedLengthBounded(PC.iposint(Int8), 5)
]
@testset for gen in gens
@testset "`filter` FiniteIntegrated" begin
@test check(isodd, filter(isodd, gen))
end
@testset "`map` FiniteIntegrated" begin
mgen = map(gen) do v
v, sqrt(v)
end
@test check(mgen) do (a,b)
sqrt(a) ≈ b
end
end
end
@testset "`interleave` FiniteIntegrated" begin
@testset "IntegratedOnce" begin
gen = PC.IntegratedOnce(6)
woven = interleave(gen, deepcopy(gen))
@test begin
res = generate(woven)
res isa Tree{Tuple{Int, Int}} && root(res) == (6,6)
end
@test generate(woven) isa Nothing
end
@testset "IntegratedFiniteIterator" begin
src = 1:11
gen = PC.IntegratedFiniteIterator(src)
woven = interleave(gen, deepcopy(gen))
for i in src
res = generate(woven)
@test res isa Tree{Tuple{Int, Int}} && root(res) == (i,i)
end
@test generate(woven) isa Nothing
end
@testset "IntegratedLengthBounded" begin
bound = PC.iposint(Int8)
@test check(bound) do v
gen = PC.IntegratedLengthBounded(itype(Int8), v)
woven = interleave(gen, deepcopy(gen))
count(woven) do t
t isa Tree{Tuple{Int8, Int8}}
end == v && generate(woven) isa Nothing
end
end
end
@testset "`check` on finite integrated reaches second generated value" begin
gen = PC.IntegratedFiniteIterator(1:2)
@test check(gen) do v
v != 2
end == 2
end
end
@testset "IntegratedBoundedRec" begin
@testset "Regular repeated generation" begin
irb = PC.IntegratedBoundedRec(10, itype(Int8))
irbvec = PC.vector(isample(10:20), irb)
@test check(irbvec) do v
length(v) >= 10 && all(v) do e
e isa Int8
end
end
end
@testset "Mutually recursive generation" begin
# This is _incredibly_ awkward to test!
foocount_correct(s) = 2*count(==('f'), s) == count(==('o'), s)
@testset "with Choice" begin
irb = PC.IntegratedBoundedRec{String}(10);
a = map(join, interleave(ival("foo", PC.noshrink), irb))
b = PC.IntegratedChoice(a, a, a, a, a, ival("bar", PC.noshrink))
PC.bind!(irb, b)
@test check(foocount_correct, b)
end
@testset "map" begin
irc = PC.IntegratedBoundedRec{String}(10);
rec = map(join, interleave(iconst("foo"), irc))
c = map(interleave(rec, iconst("bar"))) do t
t = join(t)
t[randperm(length(t))]::String
end
PC.bind!(irc, c)
@test_broken check(foocount_correct, c)
end
@testset "interleave" begin
ird = PC.IntegratedBoundedRec{Any}(5);
red = PC.IntegratedChoice(ird, iconst("foo"))
d = interleave(red, iconst("bar"))
PC.bind!(ird, d)
@test check(foocount_correct, d)
end
end
end
@testset "Inference" begin
@testset for gen in (
itype(Int8), PC.iposint(Int8), PC.inegint(Int8),
PC.ifloat(Float16), PC.ifloatinf(Float16), PC.ifloatnan(Float16),
)
@test @inferred(generate(gen)) isa PC.Tree
end
end
end