Skip to content

Commit dbad36f

Browse files
committed
reset GLOBAL_RNG state in/out at-testset for reproducibility
This is a follow-up on the recently introduced `guardsrand` functionality, first suggested at JuliaLang#16940 (comment). Each `testset` block is now implicitly wrapped in a `guardsrand` block, which is more user-friendly and more systematic (only few users know about `guardsrand`). These are essentially two new features: 1) "in": in base, tests are run with the global RNG randomly seeded, but the seed is printed in case of failure to allow reproducing the failure; but even if the failure occurs after many tests, when they alter the global RNG state, one has to re-run the whole file, which can be time-consuming; with this change, at the beginning of each `testset`, the global RNG is re-seeded with its own seed: this allows to re-run only the failing `testset`, which can be done easily in the REPL (the seeding occurs for each loop in a `testset for`; this also allows to re-arrange `testset`s in arbitrary order w.r.t. the global RNG; 2) "out": a `testset` leaves no tracks of its use of `srand` or `rand` (this "feature" should be less and less needed/useful with the generalization of the use of `testset` blocks). Example: ``` @testset begin srand(123) rand() @testset for T in (Int, Float64) # here is an implicit srand(123), by 1) rand() end rand() # this value will not be affected if the sub-`testset` block # above is removed, or if another rand() call is added therein, by 2) end ``` Note that guardsrand can't be used directly, as then the testset's body is wrapped in a function, which causes problem with overwriting loop variable ("outer"), using `using`, defining new methods, etc. So we need to duplicate guardsrand's logic.
1 parent c640381 commit dbad36f

File tree

9 files changed

+533
-508
lines changed

9 files changed

+533
-508
lines changed

stdlib/IterativeEigensolvers/test/runtests.jl

+94-94
Original file line numberDiff line numberDiff line change
@@ -4,118 +4,118 @@ using IterativeEigensolvers
44
using Test
55

66
@testset "eigs" begin
7-
guardsrand(1234) do
8-
n = 10
9-
areal = sprandn(n,n,0.4)
10-
breal = sprandn(n,n,0.4)
11-
acmplx = complex.(sprandn(n,n,0.4), sprandn(n,n,0.4))
12-
bcmplx = complex.(sprandn(n,n,0.4), sprandn(n,n,0.4))
7+
srand(1234)
8+
n = 10
9+
areal = sprandn(n,n,0.4)
10+
breal = sprandn(n,n,0.4)
11+
acmplx = complex.(sprandn(n,n,0.4), sprandn(n,n,0.4))
12+
bcmplx = complex.(sprandn(n,n,0.4), sprandn(n,n,0.4))
1313

14-
testtol = 1e-6
14+
testtol = 1e-6
1515

16-
@testset for elty in (Float64, ComplexF64)
17-
if elty == ComplexF32 || elty == ComplexF64
18-
a = acmplx
19-
b = bcmplx
20-
else
21-
a = areal
22-
b = breal
23-
end
24-
a_evs = eigvals(Array(a))
25-
a = convert(SparseMatrixCSC{elty}, a)
26-
asym = a' + a # symmetric indefinite
27-
apd = a'*a # symmetric positive-definite
16+
@testset for elty in (Float64, ComplexF64)
17+
if elty == ComplexF32 || elty == ComplexF64
18+
a = acmplx
19+
b = bcmplx
20+
else
21+
a = areal
22+
b = breal
23+
end
24+
a_evs = eigvals(Array(a))
25+
a = convert(SparseMatrixCSC{elty}, a)
26+
asym = a' + a # symmetric indefinite
27+
apd = a'*a # symmetric positive-definite
2828

29-
b = convert(SparseMatrixCSC{elty}, b)
30-
bsym = b' + b
31-
bpd = b'*b
29+
b = convert(SparseMatrixCSC{elty}, b)
30+
bsym = b' + b
31+
bpd = b'*b
3232

33-
(d,v) = eigs(a, nev=3)
34-
@test a*v[:,2] d[2]*v[:,2]
35-
@test norm(v) > testtol # eigenvectors cannot be null vectors
36-
(d,v) = eigs(a, I, nev=3) # test eigs(A, B; kwargs...)
37-
@test a*v[:,2] d[2]*v[:,2]
38-
@test norm(v) > testtol # eigenvectors cannot be null vectors
39-
@test_logs (:warn,"Use symbols instead of strings for specifying which eigenvalues to compute") eigs(a, which="LM")
40-
@test_logs (:warn,"Adjusting ncv from 1 to 4") eigs(a, ncv=1, nev=2)
41-
@test_logs (:warn,"Adjusting nev from $n to $(n-2)") eigs(a, nev=n)
42-
# (d,v) = eigs(a, b, nev=3, tol=1e-8) # not handled yet
43-
# @test a*v[:,2] ≈ d[2]*b*v[:,2] atol=testtol
44-
# @test norm(v) > testtol # eigenvectors cannot be null vectors
45-
if elty <: Base.LinAlg.BlasComplex
46-
sr_ind = indmin(real.(a_evs))
47-
(d, v) = eigs(a, nev=1, which=:SR)
48-
@test d[1] a_evs[sr_ind]
49-
si_ind = indmin(imag.(a_evs))
50-
(d, v) = eigs(a, nev=1, which=:SI)
51-
@test d[1] a_evs[si_ind]
52-
lr_ind = indmax(real.(a_evs))
53-
(d, v) = eigs(a, nev=1, which=:LR)
54-
@test d[1] a_evs[lr_ind]
55-
li_ind = indmax(imag.(a_evs))
56-
(d, v) = eigs(a, nev=1, which=:LI)
57-
@test d[1] a_evs[li_ind]
58-
end
33+
(d,v) = eigs(a, nev=3)
34+
@test a*v[:,2] d[2]*v[:,2]
35+
@test norm(v) > testtol # eigenvectors cannot be null vectors
36+
(d,v) = eigs(a, I, nev=3) # test eigs(A, B; kwargs...)
37+
@test a*v[:,2] d[2]*v[:,2]
38+
@test norm(v) > testtol # eigenvectors cannot be null vectors
39+
@test_logs (:warn,"Use symbols instead of strings for specifying which eigenvalues to compute") eigs(a, which="LM")
40+
@test_logs (:warn,"Adjusting ncv from 1 to 4") eigs(a, ncv=1, nev=2)
41+
@test_logs (:warn,"Adjusting nev from $n to $(n-2)") eigs(a, nev=n)
42+
# (d,v) = eigs(a, b, nev=3, tol=1e-8) # not handled yet
43+
# @test a*v[:,2] ≈ d[2]*b*v[:,2] atol=testtol
44+
# @test norm(v) > testtol # eigenvectors cannot be null vectors
45+
if elty <: Base.LinAlg.BlasComplex
46+
sr_ind = indmin(real.(a_evs))
47+
(d, v) = eigs(a, nev=1, which=:SR)
48+
@test d[1] a_evs[sr_ind]
49+
si_ind = indmin(imag.(a_evs))
50+
(d, v) = eigs(a, nev=1, which=:SI)
51+
@test d[1] a_evs[si_ind]
52+
lr_ind = indmax(real.(a_evs))
53+
(d, v) = eigs(a, nev=1, which=:LR)
54+
@test d[1] a_evs[lr_ind]
55+
li_ind = indmax(imag.(a_evs))
56+
(d, v) = eigs(a, nev=1, which=:LI)
57+
@test d[1] a_evs[li_ind]
58+
end
5959

60-
(d,v) = eigs(asym, nev=3)
61-
@test asym*v[:,1] d[1]*v[:,1]
62-
@test eigs(asym; nev=1, sigma=d[3])[1][1] d[3]
63-
@test norm(v) > testtol # eigenvectors cannot be null vectors
60+
(d,v) = eigs(asym, nev=3)
61+
@test asym*v[:,1] d[1]*v[:,1]
62+
@test eigs(asym; nev=1, sigma=d[3])[1][1] d[3]
63+
@test norm(v) > testtol # eigenvectors cannot be null vectors
6464

65-
(d,v) = eigs(apd, nev=3)
66-
@test apd*v[:,3] d[3]*v[:,3]
67-
@test eigs(apd; nev=1, sigma=d[3])[1][1] d[3]
65+
(d,v) = eigs(apd, nev=3)
66+
@test apd*v[:,3] d[3]*v[:,3]
67+
@test eigs(apd; nev=1, sigma=d[3])[1][1] d[3]
6868

69-
(d,v) = eigs(apd, bpd, nev=3, tol=1e-8)
70-
@test apd*v[:,2] d[2]*bpd*v[:,2] atol=testtol
71-
@test norm(v) > testtol # eigenvectors cannot be null vectors
69+
(d,v) = eigs(apd, bpd, nev=3, tol=1e-8)
70+
@test apd*v[:,2] d[2]*bpd*v[:,2] atol=testtol
71+
@test norm(v) > testtol # eigenvectors cannot be null vectors
7272

73-
@testset "(shift-and-)invert mode" begin
74-
(d,v) = eigs(apd, nev=3, sigma=0)
75-
@test apd*v[:,3] d[3]*v[:,3]
76-
@test norm(v) > testtol # eigenvectors cannot be null vectors
73+
@testset "(shift-and-)invert mode" begin
74+
(d,v) = eigs(apd, nev=3, sigma=0)
75+
@test apd*v[:,3] d[3]*v[:,3]
76+
@test norm(v) > testtol # eigenvectors cannot be null vectors
7777

78-
(d,v) = eigs(apd, bpd, nev=3, sigma=0, tol=1e-8)
79-
@test apd*v[:,1] d[1]*bpd*v[:,1] atol=testtol
80-
@test norm(v) > testtol # eigenvectors cannot be null vectors
81-
end
78+
(d,v) = eigs(apd, bpd, nev=3, sigma=0, tol=1e-8)
79+
@test apd*v[:,1] d[1]*bpd*v[:,1] atol=testtol
80+
@test norm(v) > testtol # eigenvectors cannot be null vectors
81+
end
8282

83-
@testset "ArgumentErrors" begin
84-
@test_throws ArgumentError eigs(rand(elty,2,2))
85-
@test_throws ArgumentError eigs(a, nev=-1)
86-
@test_throws ArgumentError eigs(a, which=:Z)
87-
@test_throws ArgumentError eigs(a, which=:BE)
88-
@test_throws DimensionMismatch eigs(a, v0=zeros(elty,n+2))
89-
@test_throws ArgumentError eigs(a, v0=zeros(Int,n))
90-
if elty == Float64
91-
@test_throws ArgumentError eigs(a+a.',which=:SI)
92-
@test_throws ArgumentError eigs(a+a.',which=:LI)
93-
@test_throws ArgumentError eigs(a,sigma=rand(ComplexF32))
94-
end
83+
@testset "ArgumentErrors" begin
84+
@test_throws ArgumentError eigs(rand(elty,2,2))
85+
@test_throws ArgumentError eigs(a, nev=-1)
86+
@test_throws ArgumentError eigs(a, which=:Z)
87+
@test_throws ArgumentError eigs(a, which=:BE)
88+
@test_throws DimensionMismatch eigs(a, v0=zeros(elty,n+2))
89+
@test_throws ArgumentError eigs(a, v0=zeros(Int,n))
90+
if elty == Float64
91+
@test_throws ArgumentError eigs(a+a.',which=:SI)
92+
@test_throws ArgumentError eigs(a+a.',which=:LI)
93+
@test_throws ArgumentError eigs(a,sigma=rand(ComplexF32))
9594
end
9695
end
96+
end
9797

98-
@testset "Symmetric generalized with singular B" begin
99-
n = 10
100-
k = 3
101-
A = randn(n,n); A = A'A
102-
B = randn(n,k); B = B*B'
103-
@test sort(eigs(A, B, nev = k, sigma = 1.0)[1]) sort(eigvals(A, B)[1:k])
104-
end
98+
@testset "Symmetric generalized with singular B" begin
99+
srand(124)
100+
n = 10
101+
k = 3
102+
A = randn(n,n); A = A'A
103+
B = randn(n,k); B = B*B'
104+
@test sort(eigs(A, B, nev = k, sigma = 1.0)[1]) sort(eigvals(A, B)[1:k])
105105
end
106106
end
107107

108108
# Problematic example from #6965A
109109
let A6965 = [
110-
1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0
111-
-1.0 2.0 0.0 0.0 0.0 0.0 0.0 1.0
112-
-1.0 0.0 3.0 0.0 0.0 0.0 0.0 1.0
113-
-1.0 0.0 0.0 4.0 0.0 0.0 0.0 1.0
114-
-1.0 0.0 0.0 0.0 5.0 0.0 0.0 1.0
115-
-1.0 0.0 0.0 0.0 0.0 6.0 0.0 1.0
116-
-1.0 0.0 0.0 0.0 0.0 0.0 7.0 1.0
117-
-1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 8.0
118-
]
110+
1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0
111+
-1.0 2.0 0.0 0.0 0.0 0.0 0.0 1.0
112+
-1.0 0.0 3.0 0.0 0.0 0.0 0.0 1.0
113+
-1.0 0.0 0.0 4.0 0.0 0.0 0.0 1.0
114+
-1.0 0.0 0.0 0.0 5.0 0.0 0.0 1.0
115+
-1.0 0.0 0.0 0.0 0.0 6.0 0.0 1.0
116+
-1.0 0.0 0.0 0.0 0.0 0.0 7.0 1.0
117+
-1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 8.0
118+
]
119119
d, = eigs(A6965,which=:SM,nev=2,ncv=4,tol=eps())
120120
@test d[1] 2.5346936860350002
121121
@test real(d[2]) 2.6159972444834976

stdlib/Test/src/Test.jl

+13-1
Original file line numberDiff line numberDiff line change
@@ -964,12 +964,20 @@ function testset_beginend(args, tests, source)
964964
# which is needed for backtrace scrubbing to work correctly.
965965
while false; end
966966
push_testset(ts)
967+
# we reproduce the logic of guardsrand, but this function
968+
# cannot be used as it changes slightly the semantic of @testset,
969+
# by wrapping the body in a function
970+
oldrng = copy(Base.GLOBAL_RNG)
967971
try
972+
# GLOBAL_RNG is re-seeded with its own seed to ease reproduce a failed test
973+
srand(Base.GLOBAL_RNG.seed)
968974
$(esc(tests))
969975
catch err
970976
# something in the test block threw an error. Count that as an
971977
# error in this test set
972978
record(ts, Error(:nontest_error, :(), err, catch_backtrace(), $(QuoteNode(source))))
979+
finally
980+
copy!(Base.GLOBAL_RNG, oldrng)
973981
end
974982
pop_testset()
975983
finish(ts)
@@ -1030,12 +1038,16 @@ function testset_forloop(args, testloop, source)
10301038
ts = $(testsettype)($desc; $options...)
10311039
push_testset(ts)
10321040
first_iteration = false
1041+
oldrng = copy(Base.GLOBAL_RNG)
10331042
try
1043+
srand(Base.GLOBAL_RNG.seed)
10341044
$(esc(tests))
10351045
catch err
10361046
# Something in the test block threw an error. Count that as an
10371047
# error in this test set
10381048
record(ts, Error(:nontest_error, :(), err, catch_backtrace(), $(QuoteNode(source))))
1049+
finally
1050+
copy!(Base.GLOBAL_RNG, oldrng)
10391051
end
10401052
end
10411053
quote
@@ -1477,7 +1489,7 @@ end
14771489

14781490
"`guardsrand(f, seed)` is equivalent to running `srand(seed); f()` and
14791491
then restoring the state of the global RNG as it was before."
1480-
guardsrand(f::Function, seed::Integer) = guardsrand() do
1492+
guardsrand(f::Function, seed::Union{Vector{UInt32},Integer}) = guardsrand() do
14811493
srand(seed)
14821494
f()
14831495
end

stdlib/Test/test/runtests.jl

+21-4
Original file line numberDiff line numberDiff line change
@@ -287,10 +287,8 @@ end
287287
end
288288
end
289289
@testset "some loops fail" begin
290-
guardsrand(123) do
291-
@testset for i in 1:5
292-
@test i <= rand(1:10)
293-
end
290+
@testset for i in 1:5
291+
@test i <= 4
294292
end
295293
# should add 3 errors and 3 passing tests
296294
@testset for i in 1:6
@@ -739,3 +737,22 @@ end
739737
be tested in --depwarn=error mode"""
740738
end
741739
end
740+
741+
@testset "@testset preserves GLOBAL_RNG's state, and re-seeds it" begin
742+
# i.e. it behaves as if it was wrapped in a `guardsrand(GLOBAL_RNG.seed)` block
743+
seed = rand(UInt128)
744+
srand(seed)
745+
a = rand()
746+
@testset begin
747+
# global RNG must re-seeded at the beginning of @testset
748+
@test a == rand()
749+
end
750+
@testset for i=1:3
751+
@test a == rand()
752+
end
753+
# the @testset's above must have no consequence for rand() below
754+
b = rand()
755+
srand(seed)
756+
@test a == rand()
757+
@test b == rand()
758+
end

test/linalg/lapack.jl

+25-27
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,24 @@ import Base.LinAlg.BlasInt
1010
@test_throws ArgumentError Base.LinAlg.LAPACK.chktrans('Z')
1111

1212
@testset "syevr" begin
13-
guardsrand(123) do
14-
Ainit = randn(5,5)
15-
@testset for elty in (Float32, Float64, ComplexF32, ComplexF64)
16-
if elty == ComplexF32 || elty == ComplexF64
17-
A = complex.(Ainit, Ainit)
18-
else
19-
A = Ainit
20-
end
21-
A = convert(Array{elty, 2}, A)
22-
Asym = A'A
23-
vals, Z = LAPACK.syevr!('V', copy(Asym))
24-
@test Z*(Diagonal(vals)*Z') Asym
25-
@test all(vals .> 0.0)
26-
@test LAPACK.syevr!('N','V','U',copy(Asym),0.0,1.0,4,5,-1.0)[1] vals[vals .< 1.0]
27-
@test LAPACK.syevr!('N','I','U',copy(Asym),0.0,1.0,4,5,-1.0)[1] vals[4:5]
28-
@test vals LAPACK.syev!('N','U',copy(Asym))
29-
30-
@test_throws DimensionMismatch LAPACK.sygvd!(1,'V','U',copy(Asym),ones(elty,6,6))
13+
srand(123)
14+
Ainit = randn(5,5)
15+
@testset for elty in (Float32, Float64, ComplexF32, ComplexF64)
16+
if elty == Complex64 || elty == Complex128
17+
A = complex.(Ainit, Ainit)
18+
else
19+
A = Ainit
3120
end
21+
A = convert(Array{elty, 2}, A)
22+
Asym = A'A
23+
vals, Z = LAPACK.syevr!('V', copy(Asym))
24+
@test Z*(Diagonal(vals)*Z') Asym
25+
@test all(vals .> 0.0)
26+
@test LAPACK.syevr!('N','V','U',copy(Asym),0.0,1.0,4,5,-1.0)[1] vals[vals .< 1.0]
27+
@test LAPACK.syevr!('N','I','U',copy(Asym),0.0,1.0,4,5,-1.0)[1] vals[4:5]
28+
@test vals LAPACK.syev!('N','U',copy(Asym))
29+
30+
@test_throws DimensionMismatch LAPACK.sygvd!(1,'V','U',copy(Asym),ones(elty,6,6))
3231
end
3332
end
3433

@@ -435,15 +434,14 @@ end
435434

436435
@testset "sysv" begin
437436
@testset for elty in (Float32, Float64, ComplexF32, ComplexF64)
438-
guardsrand(123) do
439-
A = rand(elty,10,10)
440-
A = A + A.' #symmetric!
441-
b = rand(elty,10)
442-
c = A \ b
443-
b,A = LAPACK.sysv!('U',A,b)
444-
@test b c
445-
@test_throws DimensionMismatch LAPACK.sysv!('U',A,rand(elty,11))
446-
end
437+
srand(123)
438+
A = rand(elty,10,10)
439+
A = A + A.' #symmetric!
440+
b = rand(elty,10)
441+
c = A \ b
442+
b,A = LAPACK.sysv!('U',A,b)
443+
@test b c
444+
@test_throws DimensionMismatch LAPACK.sysv!('U',A,rand(elty,11))
447445
end
448446
end
449447

test/linalg/lu.jl

+1
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ dimg = randn(n)/2
195195
end
196196

197197
@testset "conversion" begin
198+
srand(3)
198199
a = Tridiagonal(rand(9),rand(10),rand(9))
199200
fa = Array(a)
200201
falu = lufact(fa)

0 commit comments

Comments
 (0)