• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

JuliaLang / julia / #37919

29 Sep 2024 09:41AM UTC coverage: 86.232% (-0.3%) from 86.484%
#37919

push

local

web-flow
fix rawbigints OOB issues (#55917)

Fixes issues introduced in #50691 and found in #55906:
* use `@inbounds` and `@boundscheck` macros in rawbigints, for catching
OOB with `--check-bounds=yes`
* fix OOB in `truncate`

12 of 13 new or added lines in 1 file covered. (92.31%)

1287 existing lines in 41 files now uncovered.

77245 of 89578 relevant lines covered (86.23%)

15686161.83 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

67.71
/stdlib/Test/src/logging.jl
1
# This file is a part of Julia. License is MIT: https://julialang.org/license
2

3
using Logging: Logging, AbstractLogger, LogLevel, Info, with_logger
4
import Base: occursin
5
using Base: @lock
6

7
#-------------------------------------------------------------------------------
8
"""
9
    LogRecord
10

11
Stores the results of a single log event. Fields:
12

13
* `level`: the [`LogLevel`](@ref) of the log message
14
* `message`: the textual content of the log message
15
* `_module`: the module of the log event
16
* `group`: the logging group (by default, the name of the file containing the log event)
17
* `id`: the ID of the log event
18
* `file`: the file containing the log event
19
* `line`: the line within the file of the log event
20
* `kwargs`: any keyword arguments passed to the log event
21
"""
22
struct LogRecord
23
    level
1,007✔
24
    message
25
    _module
26
    group
27
    id
28
    file
29
    line
30
    kwargs
31
end
32
LogRecord(args...; kwargs...) = LogRecord(args..., kwargs)
×
33

34
struct Ignored ; end
11✔
35

36
#-------------------------------------------------------------------------------
37
# Logger with extra test-related state
38
mutable struct TestLogger <: AbstractLogger
39
    lock::ReentrantLock
1,005✔
40
    logs::Vector{LogRecord}  # Guarded by lock.
41
    min_level::LogLevel
42
    catch_exceptions::Bool
43
    # Note: shouldlog_args only maintains the info for the most recent log message, which
44
    # may not be meaningful in a multithreaded program. See:
45
    # https://github.com/JuliaLang/julia/pull/54497#discussion_r1603691606
46
    shouldlog_args  # Guarded by lock.
47
    message_limits::Dict{Any,Int}  # Guarded by lock.
48
    respect_maxlog::Bool
49
end
50

51
"""
52
    TestLogger(; min_level=Info, catch_exceptions=false)
53

54
Create a `TestLogger` which captures logged messages in its `logs::Vector{LogRecord}` field.
55

56
Set `min_level` to control the `LogLevel`, `catch_exceptions` for whether or not exceptions
57
thrown as part of log event generation should be caught, and `respect_maxlog` for whether
58
or not to follow the convention of logging messages with `maxlog=n` for some integer `n` at
59
most `n` times.
60

61
See also: [`LogRecord`](@ref).
62

63
## Examples
64

65
```jldoctest
66
julia> using Test, Logging
67

68
julia> f() = @info "Hi" number=5;
69

70
julia> test_logger = TestLogger();
71

72
julia> with_logger(test_logger) do
73
           f()
74
           @info "Bye!"
75
       end
76

77
julia> @test test_logger.logs[1].message == "Hi"
78
Test Passed
79

80
julia> @test test_logger.logs[1].kwargs[:number] == 5
81
Test Passed
82

83
julia> @test test_logger.logs[2].message == "Bye!"
84
Test Passed
85
```
86
"""
87
TestLogger(; min_level=Info, catch_exceptions=false, respect_maxlog=true) =
2,010✔
88
    TestLogger(ReentrantLock(), LogRecord[], min_level, catch_exceptions, nothing, Dict{Any, Int}(), respect_maxlog)
89
Logging.min_enabled_level(logger::TestLogger) = logger.min_level
1,005✔
90

91
function Logging.shouldlog(logger::TestLogger, level, _module, group, id)
1,013✔
92
    @lock logger.lock begin
1,013✔
93
        if get(logger.message_limits, id, 1) > 0
1,014✔
94
            logger.shouldlog_args = (level, _module, group, id)
1,012✔
95
            return true
1,012✔
96
        else
97
            return false
1✔
98
        end
99
    end
100
end
101

102
function Logging.handle_message(logger::TestLogger, level, msg, _module,
2,014✔
103
                                group, id, file, line; kwargs...)
104
    @nospecialize
×
105
    if logger.respect_maxlog
1,007✔
106
        maxlog = get(kwargs, :maxlog, nothing)
1,007✔
107
        if maxlog isa Core.BuiltinInts
1,007✔
108
            @lock logger.lock begin
3✔
109
                remaining = get!(logger.message_limits, id, Int(maxlog)::Int)
6✔
110
                logger.message_limits[id] = remaining - 1
3✔
111
                remaining > 0 || return
3✔
112
            end
113
        end
114
    end
115
    r = LogRecord(level, msg, _module, group, id, file, line, kwargs)
1,007✔
116
    @lock logger.lock begin
1,007✔
117
        push!(logger.logs, r)
1,007✔
118
    end
119
end
120

121
# Catch exceptions for the test logger only if specified
122
Logging.catch_exceptions(logger::TestLogger) = logger.catch_exceptions
14✔
123

124
function collect_test_logs(f; kwargs...)
1,003✔
125
    logger = TestLogger(; kwargs...)
999✔
126
    value = with_logger(f, logger)
999✔
127
    @lock logger.lock begin
994✔
128
        return copy(logger.logs), value
994✔
129
    end
130
end
131

132

133
#-------------------------------------------------------------------------------
134
# Log testing tools
135

136
# Failure result type for log testing
137
struct LogTestFailure <: Result
UNCOV
138
    orig_expr
×
139
    source::LineNumberNode
140
    patterns
141
    logs
142
end
143
function Base.show(io::IO, t::LogTestFailure)
×
144
    printstyled(io, "Log Test Failed"; bold=true, color=Base.error_color())
×
145
    print(io, " at ")
×
146
    printstyled(io, something(t.source.file, :none), ":", t.source.line, "\n"; bold=true, color=:default)
×
147
    println(io, "  Expression: ", t.orig_expr)
×
148
    println(io, "  Log Pattern: ", join(t.patterns, " "))
×
149
    println(io, "  Captured Logs: ")
×
150
    for l in t.logs
×
151
        println(io, "    ", l)
×
152
    end
×
153
end
154

155
# Patch support for LogTestFailure into Base.Test test set types
156
# TODO: Would be better if `Test` itself allowed us to handle this more neatly.
157
function record(::FallbackTestSet, t::LogTestFailure)
×
158
    println(t)
×
159
    throw(FallbackTestSetException("There was an error during testing"))
×
160
end
161

162
function record(ts::DefaultTestSet, t::LogTestFailure)
×
163
    if TESTSET_PRINT_ENABLE[]
×
164
        printstyled(ts.description, ": ", color=:white)
×
165
        print(t)
×
166
        Base.show_backtrace(stdout, scrub_backtrace(backtrace(), ts.file, extract_file(t.source)))
×
167
        println()
×
168
    end
169
    # Hack: convert to `Fail` so that test summarization works correctly
170
    push!(ts.results, Fail(:test, t.orig_expr, t.logs, nothing, nothing, t.source, false))
×
171
    return t
×
172
end
173

174
"""
175
    @test_logs [log_patterns...] [keywords] expression
176

177
Collect a list of log records generated by `expression` using
178
`collect_test_logs`, check that they match the sequence `log_patterns`, and
179
return the value of `expression`.  The `keywords` provide some simple filtering
180
of log records: the `min_level` keyword controls the minimum log level which
181
will be collected for the test, the `match_mode` keyword defines how matching
182
will be performed (the default `:all` checks that all logs and patterns match
183
pairwise; use `:any` to check that the pattern matches at least once somewhere
184
in the sequence.)
185

186
The most useful log pattern is a simple tuple of the form `(level,message)`.
187
A different number of tuple elements may be used to match other log metadata,
188
corresponding to the arguments to passed to `AbstractLogger` via the
189
`handle_message` function: `(level,message,module,group,id,file,line)`.
190
Elements which are present will be matched pairwise with the log record fields
191
using `==` by default, with the special cases that `Symbol`s may be used for
192
the standard log levels, and `Regex`s in the pattern will match string or
193
Symbol fields using `occursin`.
194

195
# Examples
196

197
Consider a function which logs a warning, and several debug messages:
198

199
    function foo(n)
200
        @info "Doing foo with n=\$n"
201
        for i=1:n
202
            @debug "Iteration \$i"
203
        end
204
        42
205
    end
206

207
We can test the info message using
208

209
    @test_logs (:info,"Doing foo with n=2") foo(2)
210

211
If we also wanted to test the debug messages, these need to be enabled with the
212
`min_level` keyword:
213

214
    using Logging
215
    @test_logs (:info,"Doing foo with n=2") (:debug,"Iteration 1") (:debug,"Iteration 2") min_level=Logging.Debug foo(2)
216

217
If you want to test that some particular messages are generated while ignoring the rest,
218
you can set the keyword `match_mode=:any`:
219

220
    using Logging
221
    @test_logs (:info,) (:debug,"Iteration 42") min_level=Logging.Debug match_mode=:any foo(100)
222

223
The macro may be chained with `@test` to also test the returned value:
224

225
    @test (@test_logs (:info,"Doing foo with n=2") foo(2)) == 42
226

227
If you want to test for the absence of warnings, you can omit specifying log
228
patterns and set the `min_level` accordingly:
229

230
    # test that the expression logs no messages when the logger level is warn:
231
    @test_logs min_level=Logging.Warn @info("Some information") # passes
232
    @test_logs min_level=Logging.Warn @warn("Some information") # fails
233

234
If you want to test the absence of warnings (or error messages) in
235
[`stderr`](@ref) which are not generated by `@warn`, see [`@test_nowarn`](@ref).
236
"""
237
macro test_logs(exs...)
44✔
238
    length(exs) >= 1 || throw(ArgumentError("""`@test_logs` needs at least one arguments.
44✔
239
                               Usage: `@test_logs [msgs...] expr_to_run`"""))
240
    patterns = Any[]
44✔
241
    kwargs = Any[]
44✔
242
    for e in exs[1:end-1]
44✔
243
        if e isa Expr && e.head === :(=)
62✔
244
            push!(kwargs, esc(Expr(:kw, e.args...)))
12✔
245
        else
246
            push!(patterns, esc(e))
50✔
247
        end
248
    end
62✔
249
    expression = exs[end]
44✔
250
    orig_expr = QuoteNode(expression)
44✔
251
    sourceloc = QuoteNode(__source__)
44✔
252
    Base.remove_linenums!(quote
44✔
253
        let testres=nothing, value=nothing
254
            try
255
                didmatch,logs,value = match_logs($(patterns...); $(kwargs...)) do
256
                    $(esc(expression))
×
257
                end
258
                if didmatch
259
                    testres = Pass(:test, $orig_expr, nothing, value, $sourceloc)
260
                else
261
                    testres = LogTestFailure($orig_expr, $sourceloc,
262
                                             $(QuoteNode(exs[1:end-1])), logs)
263
                end
264
            catch e
265
                testres = Error(:test_error, $orig_expr, e, Base.current_exceptions(), $sourceloc)
266
            end
267
            Test.record(Test.get_testset(), testres)
268
            value
269
        end
270
    end)
271
end
272

273
function match_logs(f, patterns...; match_mode::Symbol=:all, kwargs...)
992✔
274
    logs,value = collect_test_logs(f; kwargs...)
982✔
275
    if match_mode === :all
982✔
276
        didmatch = length(logs) == length(patterns) &&
991✔
277
            all(occursin(p, l) for (p,l) in zip(patterns, logs))
UNCOV
278
    elseif match_mode === :any
×
UNCOV
279
        didmatch = all(any(occursin(p, l) for l in logs) for p in patterns)
×
280
    end
281
    didmatch,logs,value
982✔
282
end
283

284
# TODO: Use a version of parse_level from stdlib/Logging, when it exists.
285
function parse_level(level::Symbol)
286
    if      level === :belowminlevel  return  Logging.BelowMinLevel
957✔
287
    elseif  level === :debug          return  Logging.Debug
957✔
288
    elseif  level === :info           return  Logging.Info
957✔
289
    elseif  level === :warn           return  Logging.Warn
953✔
290
    elseif  level === :error          return  Logging.Error
938✔
291
    elseif  level === :abovemaxlevel  return  Logging.AboveMaxLevel
×
292
    else
293
        throw(ArgumentError("Unknown log level $level"))
×
294
    end
295
end
296

297
logfield_contains(a, b) = a == b
85✔
298
logfield_contains(a, r::Regex) = occursin(r, a)
945✔
299
logfield_contains(a::Symbol, r::Regex) = occursin(r, String(a))
×
300
logfield_contains(a::LogLevel, b::Symbol) = a == parse_level(b)
1,914✔
301
logfield_contains(a, b::Ignored) = true
×
302

303
function occursin(pattern::Tuple, r::LogRecord)
990✔
304
    stdfields = (r.level, r.message, r._module, r.group, r.id, r.file, r.line)
990✔
305
    all(logfield_contains(f, p) for (f, p) in zip(stdfields[1:length(pattern)], pattern))
990✔
306
end
307

308
"""
309
    @test_deprecated [pattern] expression
310

311
When `--depwarn=yes`, test that `expression` emits a deprecation warning and
312
return the value of `expression`.  The log message string will be matched
313
against `pattern` which defaults to `r"deprecated"i`.
314

315
When `--depwarn=no`, simply return the result of executing `expression`.  When
316
`--depwarn=error`, check that an ErrorException is thrown.
317

318
# Examples
319

320
```
321
# Deprecated in julia 0.7
322
@test_deprecated num2hex(1)
323

324
# The returned value can be tested by chaining with @test:
325
@test (@test_deprecated num2hex(1)) == "0000000000000001"
326
```
327
"""
328
macro test_deprecated(exs...)
2✔
329
    1 <= length(exs) <= 2 || throw(ArgumentError("""`@test_deprecated` expects one or two arguments.
2✔
330
                               Usage: `@test_deprecated [pattern] expr_to_run`"""))
331
    pattern = length(exs) == 1 ? r"deprecated"i : esc(exs[1])
2✔
332
    expression = esc(exs[end])
2✔
333
    res = quote
2✔
334
        dw = Base.JLOptions().depwarn
335
        if dw == 2
336
            # TODO: Remove --depwarn=error if possible and replace with a more
337
            # flexible mechanism so we don't have to do this.
338
            @test_throws ErrorException $expression
339
        elseif dw == 1
340
            @test_logs (:warn, $pattern, Ignored(), :depwarn) match_mode=:any $expression
341
        else
342
            $expression
343
        end
344
    end
345
    # Propagate source code location of @test_logs to @test macro
346
    # FIXME: Use rewrite_sourceloc!() for this - see #22623
347
    res.args[4].args[2].args[2].args[2] = __source__
2✔
348
    res.args[4].args[3].args[2].args[2].args[2] = __source__
2✔
349
    res
2✔
350
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc