@@ -27,15 +27,22 @@ using .Testset: Testset, @testsetr
27
27
__init__ () = INLINE_TEST[] = gensym ()
28
28
29
29
30
+ struct TestsetExpr
31
+ desc:: Union{String,Expr}
32
+ loops:: Union{Expr,Nothing}
33
+ body:: Expr
34
+ final:: Bool
35
+ end
36
+
30
37
function tests (m)
31
38
inline_test:: Symbol = m ∈ (InlineTest, InlineTest. InlineTestTest) ? :__INLINE_TEST__ : INLINE_TEST[]
32
39
if ! isdefined (m, inline_test)
33
- @eval m $ inline_test = Tuple{Expr,Union{String,Missing},Bool} []
40
+ @eval m $ inline_test = []
34
41
end
35
42
getfield (m, inline_test)
36
43
end
37
44
38
- replacetestset (x) = x, false
45
+ replacetestset (x) = ( x, missing , false )
39
46
40
47
# replace unqualified `@testset` by @testsetr
41
48
# return also (as 3nd element) whether the expression contains a (possibly nested) @testset
@@ -50,21 +57,41 @@ function replacetestset(x::Expr)
50
57
:($ (Testset. FINAL[]) = $ final),
51
58
Expr (:macrocall , Expr (:., :InlineTest , QuoteNode (Symbol (" @testsetr" ))),
52
59
map (first, body)... )),
53
- final, true )
60
+ final,
61
+ true )
54
62
else
55
63
body = map (replacetestset, x. args)
56
64
(Expr (x. head, map (first, body)... ),
57
- missing , any (last, body)) # missing: a non-testset doesn't have a "final" attribute...
65
+ missing , # missing: a non-testset doesn't have a "final" attribute...
66
+ any (last, body))
58
67
end
59
68
end
60
69
61
70
function addtest (args:: Tuple , m:: Module )
62
- desc = args[1 ] isa String ? args[1 ] : missing
63
- # args[1] might not be a string if none was passed, or for a testset-for with
64
- # interpolated loop variable (in which case it's difficult to statically know the
65
- # final description)
66
- ts, final, _ = replacetestset (:(@testset ($ (args... ))))
67
- push! (tests (m), (ts, desc, final))
71
+ length (args) == 2 || error (" unsupported @testset" )
72
+
73
+ desc = args[1 ]
74
+ desc isa String || Meta. isexpr (desc, :string ) || error (" unsupported @testset" )
75
+
76
+ body = args[2 ]
77
+ isa (body, Expr) || error (" Expected begin/end block or for loop as argument to @testset" )
78
+ if body. head === :for
79
+ isloop = true
80
+ elseif body. head === :block
81
+ isloop = false
82
+ else
83
+ error (" Expected begin/end block or for loop as argument to @testset" )
84
+ end
85
+
86
+ if isloop
87
+ loops = body. args[1 ]
88
+ expr, _, has_testset = replacetestset (body. args[2 ])
89
+ final = ! has_testset
90
+ push! (tests (m), TestsetExpr (desc, loops, expr, final))
91
+ else
92
+ ts, final, _ = replacetestset (:(@testset $ desc $ body))
93
+ push! (tests (m), TestsetExpr (desc, nothing , ts, final))
94
+ end
68
95
nothing
69
96
end
70
97
@@ -95,27 +122,66 @@ in which it was written (e.g. `m`, when specified).
95
122
"""
96
123
function runtests (m:: Module , regex:: Regex = r" " ; wrap:: Bool = false )
97
124
partial = partialize (regex)
125
+ matches (desc, final) = Testset. partialoccursin ((partial, regex)[1 + final], desc)
126
+
98
127
if wrap
99
128
Core. eval (m, :(InlineTest. Test. @testset $ (" Tests for module $m " ) begin
100
- let $ (Testset. REGEX[]) = ($ partial, $ regex)
101
- $ (map (first, tests (m))... )
102
- end
129
+ $ (map (ts -> wrap_ts (partial, regex, ts), tests (m))... )
103
130
end ))
104
131
else
105
- for (ts, desc, final) in tests (m)
106
- # bypass evaluation if we know statically that testset won't be run
107
- if desc isa String && ! Testset. partialoccursin ((partial, regex)[1 + final], desc)
108
- continue
109
- end
132
+ for ts in tests (m)
110
133
# it's faster to evel in a loop than to eval a block containing tests(m)
111
- Core. eval (m, :(let $ (Testset. REGEX[]) = ($ partial, $ regex)
112
- $ ts
113
- end ))
134
+ desc = ts. desc
135
+ if ts. loops === nothing # begin/end testset
136
+ @assert desc isa String
137
+ # bypass evaluation if we know statically that testset won't be run
138
+ matches (desc, ts. final) ||
139
+ continue
140
+ Core. eval (m, wrap_ts (partial, regex, ts))
141
+ else # for-loop testset
142
+ loops = ts. loops
143
+ xs = Core. eval (m, loops. args[2 ]) # loop values
144
+ # we eval the description to a string for each values, and if at least one matches
145
+ # the filtering-regex, then we must instantiate the testset (otherwise, it's skipped)
146
+ skip = true
147
+ for x in xs
148
+ # TODO : do not assign loop.args[1] in m ?
149
+ Core. eval (m, Expr (:(= ), loops. args[1 ], x))
150
+ descx = Core. eval (m, desc)
151
+ if matches (descx, ts. final)
152
+ skip = false
153
+ break
154
+ end
155
+ end
156
+ skip && continue
157
+ Core. eval (m, wrap_ts (partial, regex, ts, xs))
158
+ end
114
159
end
115
160
end
116
161
nothing
117
162
end
118
163
164
+ function wrap_ts (partial, regex, ts:: TestsetExpr , loopvals= nothing )
165
+ if ts. loops === nothing
166
+ quote
167
+ let $ (Testset. REGEX[]) = ($ partial, $ regex)
168
+ $ (ts. body)
169
+ end
170
+ end
171
+ else
172
+ loopvals = something (loopvals, ts. loops. args[2 ])
173
+ quote
174
+ let $ (Testset. REGEX[]) = ($ partial, $ regex),
175
+ $ (Testset. FINAL[]) = $ (ts. final)
176
+
177
+ InlineTest. @testsetr $ (ts. desc) for $ (ts. loops. args[1 ]) in $ loopvals
178
+ $ (ts. body)
179
+ end
180
+ end
181
+ end
182
+ end
183
+ end
184
+
119
185
function runtests (; wrap:: Bool = true )
120
186
foreach (values (Base. loaded_modules)) do m
121
187
if isdefined (m, INLINE_TEST[]) # will automatically skip InlineTest and InlineTest.InlineTestTest
0 commit comments