Skip to content

Commit 28a11ff

Browse files
authored
Add test_warn macro to Base.Test for checking for warnings (#19903)
* add test_warn macro to Base.Test for verifying warning messages * use test_warn in compilation tests * add test for #18725 * doc links for test_warn * compile test no longer needs to save STDERR * rewrite pkg tests in terms of test_nowarn
1 parent 2f223b7 commit 28a11ff

File tree

7 files changed

+137
-178
lines changed

7 files changed

+137
-178
lines changed

NEWS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,9 @@ Library improvements
146146
implemented; the semantics are as if the `Nullable` were a container with
147147
zero or one elements ([#16961]).
148148

149+
* New `@test_warn` and `@test_nowarn` macros in the `Base.Test` module to
150+
test for the presence or absence of warning messages ([#19903]).
151+
149152
* `logging` can be used to redirect `info`, `warn`, and `error` messages
150153
either universally or on a per-module/function basis ([#16213]).
151154

base/test.jl

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ and summarize them at the end of the test set with `@testset`.
1313
"""
1414
module Test
1515

16-
export @test, @test_throws, @test_broken, @test_skip
16+
export @test, @test_throws, @test_broken, @test_skip, @test_warn, @test_nowarn
1717
export @testset
1818
# Legacy approximate testing functions, yet to be included
1919
export @test_approx_eq_eps, @inferred
@@ -356,6 +356,57 @@ function do_test_throws(result::ExecutionResult, orig_expr, extype)
356356
record(get_testset(), testres)
357357
end
358358

359+
#-----------------------------------------------------------------------
360+
# Test for warning messages
361+
362+
ismatch_warn(s::AbstractString, output) = contains(output, s)
363+
ismatch_warn(s::Regex, output) = ismatch(s, output)
364+
ismatch_warn(s::Function, output) = s(output)
365+
ismatch_warn(S::Union{AbstractArray,Tuple}, output) = all(s -> ismatch_warn(s, output), S)
366+
367+
"""
368+
@test_warn msg expr
369+
370+
Test whether evaluating `expr` results in [`STDERR`](@ref) output that contains
371+
the `msg` string or matches the `msg` regular expression. If `msg` is
372+
a boolean function, tests whether `msg(output)` returns `true`. If `msg` is a
373+
tuple or array, checks that the error output contains/matches each item in `msg`.
374+
Returns the result of evaluating `expr`.
375+
376+
See also [`@test_nowarn`](@ref) to check for the absence of error output.
377+
"""
378+
macro test_warn(msg, expr)
379+
quote
380+
let fname = tempname(), have_color = Base.have_color
381+
try
382+
eval(Base, :(have_color = false))
383+
ret = open(fname, "w") do f
384+
redirect_stderr(f) do
385+
$(esc(expr))
386+
end
387+
end
388+
@test ismatch_warn($(esc(msg)), readstring(fname))
389+
ret
390+
finally
391+
eval(Base, Expr(:(=), :have_color, have_color))
392+
rm(fname, force=true)
393+
end
394+
end
395+
end
396+
end
397+
398+
"""
399+
@test_nowarn expr
400+
401+
Test whether evaluating `expr` results in empty [`STDERR`](@ref) output
402+
(no warnings or other messages). Returns the result of evaluating `expr`.
403+
"""
404+
macro test_nowarn(expr)
405+
quote
406+
@test_warn r"^(?!.)"s $expr
407+
end
408+
end
409+
359410
#-----------------------------------------------------------------------
360411

361412
# The AbstractTestSet interface is defined by two methods:

doc/src/stdlib/test.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,8 @@ checks using either `@test a ≈ b` (where `≈`, typed via tab completion of `\
171171

172172
```@docs
173173
Base.Test.@inferred
174+
Base.Test.@test_warn
175+
Base.Test.@test_nowarn
174176
```
175177

176178
## Broken Tests

test/compile.jl

Lines changed: 5 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,6 @@
22

33
using Base.Test
44

5-
function redirected_stderr(expected)
6-
rd, wr = redirect_stderr()
7-
t = @async begin
8-
read = readstring(rd) # also makes sure the kernel isn't being forced to buffer the output
9-
if !contains(read, expected)
10-
@show expected
11-
@show read
12-
@test false
13-
end
14-
nothing
15-
end
16-
return t
17-
end
18-
195
Foo_module = :Foo4b3a94a1a081a8cb
206
FooBase_module = :FooBase4b3a94a1a081a8cb
217
@eval module ConflictingBindings
@@ -29,7 +15,6 @@ using .ConflictingBindings
2915
# so we disable it for the tests below
3016
withenv( "JULIA_DEBUG_LOADING" => nothing ) do
3117

32-
olderr = STDERR
3318
dir = mktempdir()
3419
dir2 = mktempdir()
3520
insert!(LOAD_PATH, 1, dir)
@@ -136,14 +121,9 @@ try
136121

137122
# use _require_from_serialized to ensure that the test fails if
138123
# the module doesn't reload from the image:
139-
t = redirected_stderr("WARNING: replacing module Foo4b3a94a1a081a8cb.\nWARNING: Method definition ")
140-
try
124+
@test_warn "WARNING: replacing module Foo4b3a94a1a081a8cb.\nWARNING: Method definition " begin
141125
@test isa(Base._require_from_serialized(myid(), Foo_module, cachefile, #=broadcast-load=#false), Array{Any,1})
142-
finally
143-
close(STDERR)
144-
redirect_stderr(olderr)
145126
end
146-
wait(t)
147127

148128
let Foo = getfield(Main, Foo_module)
149129
@test_throws MethodError Foo.foo(17) # world shouldn't be visible yet
@@ -217,17 +197,13 @@ try
217197
end
218198
""")
219199

220-
t = redirected_stderr("ERROR: LoadError: Declaring __precompile__(false) is not allowed in files that are being precompiled.\nStacktrace:\n [1] __precompile__")
221-
try
200+
@test_warn "ERROR: LoadError: Declaring __precompile__(false) is not allowed in files that are being precompiled.\nStacktrace:\n [1] __precompile__" try
222201
Base.compilecache("Baz") # from __precompile__(false)
223202
error("__precompile__ disabled test failed")
224203
catch exc
225-
close(STDERR)
226-
redirect_stderr(olderr)
227204
isa(exc, ErrorException) || rethrow(exc)
228205
!isempty(search(exc.msg, "__precompile__(false)")) && rethrow(exc)
229206
end
230-
wait(t)
231207

232208
# Issue #12720
233209
FooBar1_file = joinpath(dir, "FooBar1.jl")
@@ -273,14 +249,7 @@ try
273249
fb_uuid1 = Base.module_uuid(Main.FooBar1)
274250
@test fb_uuid != fb_uuid1
275251

276-
t = redirected_stderr("WARNING: replacing module FooBar.")
277-
try
278-
reload("FooBar")
279-
finally
280-
close(STDERR)
281-
redirect_stderr(olderr)
282-
end
283-
wait(t)
252+
@test_warn "WARNING: replacing module FooBar." reload("FooBar")
284253
@test fb_uuid != Base.module_uuid(Main.FooBar)
285254
@test fb_uuid1 == Base.module_uuid(Main.FooBar1)
286255
fb_uuid = Base.module_uuid(Main.FooBar)
@@ -289,14 +258,7 @@ try
289258
@test !Base.stale_cachefile(FooBar1_file, joinpath(dir2, "FooBar1.ji"))
290259
@test !Base.stale_cachefile(FooBar_file, joinpath(dir2, "FooBar.ji"))
291260

292-
t = redirected_stderr("WARNING: replacing module FooBar1.")
293-
try
294-
reload("FooBar1")
295-
finally
296-
close(STDERR)
297-
redirect_stderr(olderr)
298-
end
299-
wait(t)
261+
@test_warn "WARNING: replacing module FooBar1." reload("FooBar1")
300262
@test fb_uuid == Base.module_uuid(Main.FooBar)
301263
@test fb_uuid1 != Base.module_uuid(Main.FooBar1)
302264

@@ -314,22 +276,14 @@ try
314276
error("break me")
315277
end
316278
""")
317-
t = redirected_stderr("ERROR: LoadError: break me\nStacktrace:\n [1] error")
318-
try
279+
@test_warn "ERROR: LoadError: break me\nStacktrace:\n [1] error" try
319280
Base.require(:FooBar)
320281
error("\"LoadError: break me\" test failed")
321282
catch exc
322-
close(STDERR)
323-
redirect_stderr(olderr)
324283
isa(exc, ErrorException) || rethrow(exc)
325284
!isempty(search(exc.msg, "ERROR: LoadError: break me")) && rethrow(exc)
326285
end
327-
wait(t)
328286
finally
329-
if STDERR != olderr
330-
close(STDERR)
331-
redirect_stderr(olderr)
332-
end
333287
splice!(Base.LOAD_CACHE_PATH, 1:2)
334288
splice!(LOAD_PATH, 1)
335289
rm(dir, recursive=true)

test/core.jl

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4822,6 +4822,17 @@ end
48224822
@test f14893() == 14893
48234823
@test M14893.f14893() == 14893
48244824

4825+
# issue #18725
4826+
@test_nowarn eval(Main, :(begin
4827+
f18725(x) = 1
4828+
f18725(x) = 2
4829+
end))
4830+
@test Main.f18725(0) == 2
4831+
@test_warn "WARNING: Method definition f18725(Any) in module Module18725" eval(Main, :(module Module18725
4832+
f18725(x) = 1
4833+
f18725(x) = 2
4834+
end))
4835+
48254836
# issue #19599
48264837
f19599{T}(x::((S)->Vector{S})(T)...) = 1
48274838
@test f19599([1],[1]) == 1

0 commit comments

Comments
 (0)