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

JuliaLang / julia / #38006

12 Feb 2025 02:14AM UTC coverage: 20.346% (+0.09%) from 20.256%
#38006

push

local

web-flow
Bump Documenter to v1.8.1 (#57362)

This should get fix the binding world-age warnings in our `doctest` CI

9908 of 48698 relevant lines covered (20.35%)

105075.55 hits per line

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

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

3
"""
4
Simple unit testing functionality:
5

6
* `@test`
7
* `@test_throws`
8

9
All tests belong to a *test set*. There is a default, task-level
10
test set that throws on the first failure. Users can choose to wrap
11
their tests in (possibly nested) test sets that will store results
12
and summarize them at the end of the test set with `@testset`.
13
"""
14
module Test
15

16
export @test, @test_throws, @test_broken, @test_skip,
17
    @test_warn, @test_nowarn,
18
    @test_logs, @test_deprecated
19

20
export @testset
21
export @inferred
22
export detect_ambiguities, detect_unbound_args
23
export GenericString, GenericSet, GenericDict, GenericArray, GenericOrder
24
export TestSetException
25
export TestLogger, LogRecord
26

27
using Random
28
using Random: AbstractRNG, default_rng
29
using InteractiveUtils: gen_call_with_extracted_types
30
using Base: typesplit, remove_linenums!
31
using Serialization: Serialization
32

33
const DISPLAY_FAILED = (
34
    :isequal,
35
    :isapprox,
36
    :≈,
37
    :occursin,
38
    :startswith,
39
    :endswith,
40
    :isempty,
41
    :contains
42
)
43

44
const FAIL_FAST = Ref{Bool}(false)
45

46
#-----------------------------------------------------------------------
47

48
# Backtrace utility functions
49
function ip_has_file_and_func(ip, file, funcs)
50
    return any(fr -> (in_file(fr, file) && fr.func in funcs), StackTraces.lookup(ip))
107✔
51
end
52
in_file(frame, file) = string(frame.file) == file
65✔
53

54
function test_location(bt, file_ts, file_t)
55
    if (isnothing(file_ts) || isnothing(file_t))
×
56
        return macrocall_location(bt, something(file_ts, @__FILE__))
1✔
57
    else
58
        return test_callsite(bt, file_ts, file_t)
×
59
    end
60
end
61

62
function test_callsite(bt, file_ts, file_t)
×
63
    # We avoid duplicate calls to `StackTraces.lookup`, as it is an expensive call.
64
    # For that, we retrieve locations from lower to higher stack elements
65
    # and only traverse parts of the backtrace which we haven't traversed before.
66
    # The order will always be <internal functions> -> `@test` -> `@testset`.
67
    internal = @something(macrocall_location(bt, @__FILE__), return nothing)
×
68
    test = internal - 1 + @something(findfirst(ip -> any(frame -> in_file(frame, file_t), StackTraces.lookup(ip)), @view bt[internal:end]), return nothing)
×
69
    testset = test - 1 + @something(macrocall_location(@view(bt[test:end]), file_ts), return nothing)
×
70

71
    # If stacktrace locations differ, include frames until the `@testset` appears.
72
    test != testset && return testset
×
73
    # `@test` and `@testset` occurred at the same stacktrace location.
74
    # This may happen if `@test` occurred directly in scope of the testset,
75
    # or if `@test` occurred in a function that has been inlined in the testset.
76
    frames = StackTraces.lookup(bt[testset])
×
77
    outer_frame = findfirst(frame -> in_file(frame, file_ts) && frame.func == Symbol("macro expansion"), frames)
×
78
    isnothing(outer_frame) && return nothing
×
79
    # The `@test` call occurred directly in scope of a `@testset`.
80
    # The __source__ from `@test` will be printed in the test message upon failure.
81
    # There is no need to include more frames, but always include at least the internal macrocall location in the stacktrace.
82
    in_file(frames[outer_frame], file_t) && return internal
×
83
    # The `@test` call was inlined, so we still need to include the callsite.
84
    return testset
×
85
end
86

87
macrocall_location(bt, file) = findfirst(ip -> ip_has_file_and_func(ip, file, (Symbol("macro expansion"),)), bt)
10✔
88

89
function scrub_backtrace(bt, file_ts, file_t)
1✔
90
    do_test_ind = findfirst(ip -> ip_has_file_and_func(ip, @__FILE__, (:do_test, :do_test_throws)), bt)
34✔
91
    if do_test_ind !== nothing && length(bt) > do_test_ind
1✔
92
        bt = bt[do_test_ind + 1:end]
×
93
    end
94
    stop_at = test_location(bt, file_ts, file_t)
1✔
95
    !isnothing(stop_at) && !isempty(bt) && return bt[1:stop_at]
2✔
96
    return bt
×
97
end
98

99
function scrub_exc_stack(stack, file_ts, file_t)
1✔
100
    return Any[ (x[1], scrub_backtrace(x[2]::Vector{Union{Ptr{Nothing},Base.InterpreterIP}}, file_ts, file_t)) for x in stack ]
1✔
101
end
102

103
# define most of the test infrastructure without type specialization
104
@nospecialize
105

106
"""
107
    Test.Result
108

109
All tests produce a result object. This object may or may not be
110
stored, depending on whether the test is part of a test set.
111
"""
112
abstract type Result end
113

114
"""
115
    Test.Pass <: Test.Result
116

117
The test condition was true, i.e. the expression evaluated to true or
118
the correct exception was thrown.
119
"""
120
struct Pass <: Result
121
    test_type::Symbol
122
    orig_expr
123
    data
124
    value
125
    source::Union{Nothing,LineNumberNode}
126
    message_only::Bool
127
    function Pass(test_type::Symbol, orig_expr, data, thrown, source::Union{Nothing,LineNumberNode}=nothing, message_only::Bool=false)
128
        return new(test_type, orig_expr, data, thrown, source, message_only)
1,552✔
129
    end
130
end
131

132
function Base.show(io::IO, t::Pass)
×
133
    printstyled(io, "Test Passed"; bold = true, color=:green)
×
134
    if t.test_type === :test_throws
×
135
        # The correct type of exception was thrown
136
        if t.message_only
×
137
            print(io, "\n     Message: ", t.value)
×
138
        else
139
            print(io, "\n      Thrown: ", typeof(t.value))
×
140
        end
141
    end
142
end
143

144
"""
145
    Test.Fail <: Test.Result
146

147
The test condition was false, i.e. the expression evaluated to false or
148
the correct exception was not thrown.
149
"""
150
struct Fail <: Result
151
    test_type::Symbol
152
    orig_expr::String
153
    data::Union{Nothing, String}
154
    value::String
155
    context::Union{Nothing, String}
156
    source::LineNumberNode
157
    message_only::Bool
158
    backtrace::Union{Nothing, String}
159
    function Fail(test_type::Symbol, orig_expr, data, value, context, source::LineNumberNode, message_only::Bool, backtrace=nothing)
1✔
160
        return new(test_type,
2✔
161
            string(orig_expr),
162
            data === nothing ? nothing : string(data),
163
            string(isa(data, Type) ? typeof(value) : value),
164
            context,
165
            source,
166
            message_only,
167
            backtrace)
168
    end
169
end
170

171
# Deprecated fallback constructor without `context` argument (added in Julia 1.9). Remove in Julia 2.0.
172
Fail(test_type::Symbol, orig_expr, data, value, source::LineNumberNode, message_only::Bool=false) =
×
173
    Fail(test_type, orig_expr, data, value, nothing, source, message_only)
174

175
function Base.show(io::IO, t::Fail)
2✔
176
    printstyled(io, "Test Failed"; bold=true, color=Base.error_color())
2✔
177
    print(io, " at ")
2✔
178
    printstyled(io, something(t.source.file, :none), ":", t.source.line, "\n"; bold=true, color=:default)
4✔
179
    print(io, "  Expression: ", t.orig_expr)
2✔
180
    value, data = t.value, t.data
2✔
181
    if t.test_type === :test_throws_wrong
2✔
182
        # An exception was thrown, but it was of the wrong type
183
        if t.message_only
×
184
            print(io, "\n    Expected: ", data)
×
185
            print(io, "\n     Message: ", value)
×
186
        else
187
            print(io, "\n    Expected: ", data)
×
188
            print(io, "\n      Thrown: ", value)
×
189
            print(io, "\n")
×
190
            if t.backtrace !== nothing
×
191
                # Capture error message and indent to match
192
                join(io, ("      " * line for line in split(t.backtrace, "\n")), "\n")
×
193
            end
194
        end
195
    elseif t.test_type === :test_throws_nothing
2✔
196
        # An exception was expected, but no exception was thrown
197
        print(io, "\n    Expected: ", data)
×
198
        print(io, "\n  No exception thrown")
×
199
    elseif t.test_type === :test
2✔
200
        if data !== nothing
2✔
201
            # The test was an expression, so display the term-by-term
202
            # evaluated version as well
203
            print(io, "\n   Evaluated: ", data)
2✔
204
        end
205
        if t.context !== nothing
2✔
206
            print(io, "\n     Context: ", t.context)
×
207
        end
208
    end
209
    println(io) # add some visual space to separate sequential failures
2✔
210
end
211

212
"""
213
    Test.Error <: Test.Result
214

215
The test condition couldn't be evaluated due to an exception, or
216
it evaluated to something other than a [`Bool`](@ref).
217
In the case of `@test_broken` it is used to indicate that an
218
unexpected `Pass` `Result` occurred.
219
"""
220
struct Error <: Result
221
    test_type::Symbol
222
    orig_expr::String
223
    value::String
224
    backtrace::String
225
    source::LineNumberNode
226

227
    function Error(test_type::Symbol, orig_expr, value, bt, source::LineNumberNode)
1✔
228
        if test_type === :test_error
1✔
229
            bt = scrub_exc_stack(bt, nothing, extract_file(source))
2✔
230
        end
231
        if test_type === :test_error || test_type === :nontest_error
1✔
232
            bt_str = try # try the latest world for this, since we might have eval'd new code for show
1✔
233
                    Base.invokelatest(sprint, Base.show_exception_stack, bt; context=stdout)
1✔
234
                catch ex
235
                    "#=ERROR showing exception stack=# " *
×
236
                        try
237
                            sprint(Base.showerror, ex, catch_backtrace(); context=stdout)
×
238
                        catch
239
                            "of type " * string(typeof(ex))
1✔
240
                        end
241
                end
242
        else
243
            bt_str = ""
×
244
        end
245
        value = try # try the latest world for this, since we might have eval'd new code for show
1✔
246
                Base.invokelatest(sprint, show, value, context = :limit => true)
1✔
247
            catch ex
248
                "#=ERROR showing error of type " * string(typeof(value)) * "=# " *
×
249
                    try
250
                        sprint(Base.showerror, ex, catch_backtrace(); context=stdout)
×
251
                    catch
252
                        "of type " * string(typeof(ex))
1✔
253
                    end
254
            end
255
        return new(test_type,
1✔
256
            string(orig_expr),
257
            value,
258
            bt_str,
259
            source)
260
    end
261
end
262

263
function Base.show(io::IO, t::Error)
2✔
264
    if t.test_type === :test_interrupted
2✔
265
        printstyled(io, "Interrupted", color=Base.error_color())
×
266
        return
×
267
    end
268
    printstyled(io, "Error During Test"; bold=true, color=Base.error_color())
2✔
269
    print(io, " at ")
2✔
270
    printstyled(io, something(t.source.file, :none), ":", t.source.line, "\n"; bold=true, color=:default)
4✔
271
    if t.test_type === :test_nonbool
2✔
272
        println(io, "  Expression evaluated to non-Boolean")
×
273
        println(io, "  Expression: ", t.orig_expr)
×
274
        print(  io, "       Value: ", t.value)
×
275
    elseif t.test_type === :test_error
2✔
276
        println(io, "  Test threw exception")
2✔
277
        println(io, "  Expression: ", t.orig_expr)
2✔
278
        # Capture error message and indent to match
279
        join(io, ("  " * line for line in split(t.backtrace, "\n")), "\n")
2✔
280
    elseif t.test_type === :test_unbroken
×
281
        # A test that was expected to fail did not
282
        println(io, " Unexpected Pass")
×
283
        println(io, " Expression: ", t.orig_expr)
×
284
        println(io, " Got correct result, please change to @test if no longer broken.")
×
285
    elseif t.test_type === :nontest_error
×
286
        # we had an error outside of a @test
287
        println(io, "  Got exception outside of a @test")
×
288
        # Capture error message and indent to match
289
        join(io, ("  " * line for line in split(t.backtrace, "\n")), "\n")
×
290
    end
291
end
292

293
"""
294
    Test.Broken <: Test.Result
295

296
The test condition is the expected (failed) result of a broken test,
297
or was explicitly skipped with `@test_skip`.
298
"""
299
struct Broken <: Result
300
    test_type::Symbol
10✔
301
    orig_expr
302
end
303

304
function Base.show(io::IO, t::Broken)
×
305
    printstyled(io, "Test Broken\n"; bold=true, color=Base.warn_color())
×
306
    if t.test_type === :skipped && !(t.orig_expr === nothing)
×
307
        print(io, "  Skipped: ", t.orig_expr)
×
308
    elseif !(t.orig_expr === nothing)
×
309
        print(io, "  Expression: ", t.orig_expr)
×
310
    end
311
end
312

313
# Types that appear in TestSetException.errors_and_fails we convert eagerly into strings
314
# other types we convert lazily
315
function Serialization.serialize(s::Serialization.AbstractSerializer, t::Pass)
×
316
    Serialization.serialize_type(s, typeof(t))
×
317
    Serialization.serialize(s, t.test_type)
×
318
    Serialization.serialize(s, t.orig_expr === nothing ? nothing : string(t.orig_expr))
×
319
    Serialization.serialize(s, t.data === nothing ? nothing : string(t.data))
×
320
    Serialization.serialize(s, string(t.value))
×
321
    Serialization.serialize(s, t.source === nothing ? nothing : t.source)
×
322
    Serialization.serialize(s, t.message_only)
×
323
    nothing
×
324
end
325

326
function Serialization.serialize(s::Serialization.AbstractSerializer, t::Broken)
×
327
    Serialization.serialize_type(s, typeof(t))
×
328
    Serialization.serialize(s, t.test_type)
×
329
    Serialization.serialize(s, t.orig_expr === nothing ? nothing : string(t.orig_expr))
×
330
    nothing
×
331
end
332

333

334
#-----------------------------------------------------------------------
335

336
abstract type ExecutionResult end
337

338
struct Returned <: ExecutionResult
339
    value
395✔
340
    data
341
    source::LineNumberNode
342
end
343

344
struct Threw <: ExecutionResult
345
    exception
1✔
346
    backtrace::Union{Nothing,Vector{Any}}
347
    source::LineNumberNode
348
end
349

350
function eval_test(evaluated::Expr, quoted::Expr, source::LineNumberNode, negate::Bool=false)
296✔
351
    evaled_args = evaluated.args
296✔
352
    quoted_args = quoted.args
296✔
353
    n = length(evaled_args)
296✔
354
    kw_suffix = ""
296✔
355
    if evaluated.head === :comparison
296✔
356
        args = evaled_args
204✔
357
        res = true
204✔
358
        i = 1
204✔
359
        while i < n
416✔
360
            a, op, b = args[i], args[i+1], args[i+2]
212✔
361
            if res
212✔
362
                res = op(a, b)
212✔
363
            end
364
            quoted_args[i] = a
212✔
365
            quoted_args[i+2] = b
212✔
366
            i += 2
212✔
367
        end
212✔
368

369
    elseif evaluated.head === :call
92✔
370
        op = evaled_args[1]
92✔
371
        kwargs = (evaled_args[2]::Expr).args  # Keyword arguments from `Expr(:parameters, ...)`
92✔
372
        args = evaled_args[3:n]
184✔
373

374
        res = op(args...; kwargs...)
92✔
375

376
        # Create "Evaluated" expression which looks like the original call but has all of
377
        # the arguments evaluated
378
        func_sym = quoted_args[1]::Union{Symbol,Expr}
92✔
379
        if isempty(kwargs)
92✔
380
            quoted = Expr(:call, func_sym, args...)
92✔
381
        elseif func_sym === :≈ && !res
×
382
            quoted = Expr(:call, func_sym, args...)
×
383
            kw_suffix = " ($(join(["$k=$v" for (k, v) in kwargs], ", ")))"
×
384
        else
385
            kwargs_expr = Expr(:parameters, [Expr(:kw, k, v) for (k, v) in kwargs]...)
×
386
            quoted = Expr(:call, func_sym, kwargs_expr, args...)
×
387
        end
388
    else
389
        throw(ArgumentError("Unhandled expression type: $(evaluated.head)"))
×
390
    end
391

392
    if negate
296✔
393
        res = !res
22✔
394
        quoted = Expr(:call, :!, quoted)
22✔
395
    end
396

397
    Returned(res,
296✔
398
             # stringify arguments in case of failure, for easy remote printing
399
             res === true ? quoted : sprint(print, quoted, context=(:limit => true)) * kw_suffix,
400
             source)
401
end
402

403
const comparison_prec = Base.operator_precedence(:(==))
404

405
"""
406
    test_expr!(ex, kws...)
407

408
Preprocess test expressions of function calls with trailing keyword arguments
409
so that e.g. `@test a ≈ b atol=ε` means `@test ≈(a, b, atol=ε)`.
410
"""
411
test_expr!(m, ex) = ex
316✔
412

413
function test_expr!(m, ex, kws...)
×
414
    ex isa Expr && ex.head === :call || @goto fail
×
415
    for kw in kws
×
416
        kw isa Expr && kw.head === :(=) || @goto fail
×
417
        kw.head = :kw
×
418
        push!(ex.args, kw)
×
419
    end
×
420
    return ex
×
421
@label fail
×
422
    error("invalid test macro call: $m $ex $(join(kws," "))")
×
423
end
424

425
# @test - check if the expression evaluates to true
426
"""
427
    @test ex
428
    @test f(args...) key=val ...
429
    @test ex broken=true
430
    @test ex skip=true
431

432
Test that the expression `ex` evaluates to `true`.
433
If executed inside a `@testset`, return a `Pass` `Result` if it does, a `Fail` `Result` if it is
434
`false`, and an `Error` `Result` if it could not be evaluated.
435
If executed outside a `@testset`, throw an exception instead of returning `Fail` or `Error`.
436

437
# Examples
438
```jldoctest
439
julia> @test true
440
Test Passed
441

442
julia> @test [1, 2] + [2, 1] == [3, 3]
443
Test Passed
444
```
445

446
The `@test f(args...) key=val...` form is equivalent to writing
447
`@test f(args..., key=val...)` which can be useful when the expression
448
is a call using infix syntax such as approximate comparisons:
449

450
```jldoctest
451
julia> @test π ≈ 3.14 atol=0.01
452
Test Passed
453
```
454

455
This is equivalent to the uglier test `@test ≈(π, 3.14, atol=0.01)`.
456
It is an error to supply more than one expression unless the first
457
is a call expression and the rest are assignments (`k=v`).
458

459
You can use any key for the `key=val` arguments, except for `broken` and `skip`,
460
which have special meanings in the context of `@test`:
461

462
* `broken=cond` indicates a test that should pass but currently consistently
463
  fails when `cond==true`.  Tests that the expression `ex` evaluates to `false`
464
  or causes an exception.  Returns a `Broken` `Result` if it does, or an `Error`
465
  `Result` if the expression evaluates to `true`.  Regular `@test ex` is
466
  evaluated when `cond==false`.
467
* `skip=cond` marks a test that should not be executed but should be included in
468
  test summary reporting as `Broken`, when `cond==true`.  This can be useful for
469
  tests that intermittently fail, or tests of not-yet-implemented functionality.
470
  Regular `@test ex` is evaluated when `cond==false`.
471

472
# Examples
473

474
```jldoctest
475
julia> @test 2 + 2 ≈ 6 atol=1 broken=true
476
Test Broken
477
  Expression: ≈(2 + 2, 6, atol = 1)
478

479
julia> @test 2 + 2 ≈ 5 atol=1 broken=false
480
Test Passed
481

482
julia> @test 2 + 2 == 5 skip=true
483
Test Broken
484
  Skipped: 2 + 2 == 5
485

486
julia> @test 2 + 2 == 4 skip=false
487
Test Passed
488
```
489

490
!!! compat "Julia 1.7"
491
     The `broken` and `skip` keyword arguments require at least Julia 1.7.
492
"""
493
macro test(ex, kws...)
311✔
494
    # Collect the broken/skip keywords and remove them from the rest of keywords
495
    broken = [kw.args[2] for kw in kws if kw.args[1] === :broken]
311✔
496
    skip = [kw.args[2] for kw in kws if kw.args[1] === :skip]
311✔
497
    kws = filter(kw -> kw.args[1] ∉ (:skip, :broken), kws)
311✔
498
    # Validation of broken/skip keywords
499
    for (kw, name) in ((broken, :broken), (skip, :skip))
311✔
500
        if length(kw) > 1
622✔
501
            error("invalid test macro call: cannot set $(name) keyword multiple times")
×
502
        end
503
    end
622✔
504
    if length(skip) > 0 && length(broken) > 0
311✔
505
        error("invalid test macro call: cannot set both skip and broken keywords")
×
506
    end
507

508
    # Build the test expression
509
    test_expr!("@test", ex, kws...)
311✔
510

511
    result = get_test_result(ex, __source__)
311✔
512

513
    ex = Expr(:inert, ex)
311✔
514
    result = quote
622✔
515
        if $(length(skip) > 0 && esc(skip[1]))
302✔
516
            record(get_testset(), Broken(:skipped, $ex))
×
517
        else
518
            let _do = $(length(broken) > 0 && esc(broken[1])) ? do_broken_test : do_test
432✔
519
                _do($result, $ex)
302✔
520
            end
521
        end
522
    end
523
    return result
311✔
524
end
525

526
"""
527
    @test_broken ex
528
    @test_broken f(args...) key=val ...
529

530
Indicates a test that should pass but currently consistently fails.
531
Tests that the expression `ex` evaluates to `false` or causes an
532
exception. Returns a `Broken` `Result` if it does, or an `Error` `Result`
533
if the expression evaluates to `true`.  This is equivalent to
534
[`@test ex broken=true`](@ref @test).
535

536
The `@test_broken f(args...) key=val...` form works as for the `@test` macro.
537

538
# Examples
539
```jldoctest
540
julia> @test_broken 1 == 2
541
Test Broken
542
  Expression: 1 == 2
543

544
julia> @test_broken 1 == 2 atol=0.1
545
Test Broken
546
  Expression: ==(1, 2, atol = 0.1)
547
```
548
"""
549
macro test_broken(ex, kws...)
3✔
550
    test_expr!("@test_broken", ex, kws...)
3✔
551
    result = get_test_result(ex, __source__)
3✔
552
    # code to call do_test with execution result and original expr
553
    ex = Expr(:inert, ex)
3✔
554
    return :(do_broken_test($result, $ex))
3✔
555
end
556

557
"""
558
    @test_skip ex
559
    @test_skip f(args...) key=val ...
560

561
Marks a test that should not be executed but should be included in test
562
summary reporting as `Broken`. This can be useful for tests that intermittently
563
fail, or tests of not-yet-implemented functionality.  This is equivalent to
564
[`@test ex skip=true`](@ref @test).
565

566
The `@test_skip f(args...) key=val...` form works as for the `@test` macro.
567

568
# Examples
569
```jldoctest
570
julia> @test_skip 1 == 2
571
Test Broken
572
  Skipped: 1 == 2
573

574
julia> @test_skip 1 == 2 atol=0.1
575
Test Broken
576
  Skipped: ==(1, 2, atol = 0.1)
577
```
578
"""
579
macro test_skip(ex, kws...)
2✔
580
    test_expr!("@test_skip", ex, kws...)
2✔
581
    ex = Expr(:inert, ex)
2✔
582
    testres = :(Broken(:skipped, $ex))
2✔
583
    return :(record(get_testset(), $testres))
2✔
584
end
585

586
# An internal function, called by the code generated by the @test
587
# macro to get results of the test expression.
588
# In the special case of a comparison, e.g. x == 5, generate code to
589
# evaluate each term in the comparison individually so the results
590
# can be displayed nicely.
591
function get_test_result(ex, source)
314✔
592
    negate = QuoteNode(false)
314✔
593
    orig_ex = ex
314✔
594
    # Evaluate `not` wrapped functions separately for pretty-printing failures
595
    if isa(ex, Expr) && ex.head === :call && length(ex.args) == 2 && ex.args[1] === :!
314✔
596
        negate = QuoteNode(true)
16✔
597
        ex = ex.args[2]
16✔
598
    end
599
    # Normalize non-dot comparison operator calls to :comparison expressions
600
    is_splat = x -> isa(x, Expr) && x.head === :...
934✔
601
    if isa(ex, Expr) && ex.head === :call && length(ex.args) == 3 &&
363✔
602
        first(string(ex.args[1])) != '.' && !is_splat(ex.args[2]) && !is_splat(ex.args[3]) &&
603
        (ex.args[1] === :(==) || Base.operator_precedence(ex.args[1]) == comparison_prec)
604
        ex = Expr(:comparison, ex.args[2], ex.args[1], ex.args[3])
182✔
605

606
    # Mark <: and >: as :comparison expressions
607
    elseif isa(ex, Expr) && length(ex.args) == 2 &&
132✔
608
        !is_splat(ex.args[1]) && !is_splat(ex.args[2]) &&
609
        Base.operator_precedence(ex.head) == comparison_prec
610
        ex = Expr(:comparison, ex.args[1], ex.head, ex.args[2])
×
611
    end
612
    if isa(ex, Expr) && ex.head === :comparison
314✔
613
        # pass all terms of the comparison to `eval_comparison`, as an Expr
614
        escaped_terms = [esc(arg) for arg in ex.args]
185✔
615
        quoted_terms = [QuoteNode(arg) for arg in ex.args]
370✔
616
        testret = :(eval_test(
185✔
617
            Expr(:comparison, $(escaped_terms...)),
618
            Expr(:comparison, $(quoted_terms...)),
619
            $(QuoteNode(source)),
620
            $negate,
621
        ))
622
    elseif isa(ex, Expr) && ex.head === :call && ex.args[1] in DISPLAY_FAILED
129✔
623
        escaped_func = esc(ex.args[1])
48✔
624
        quoted_func = QuoteNode(ex.args[1])
48✔
625

626
        escaped_args = []
48✔
627
        escaped_kwargs = []
48✔
628

629
        # Keywords that occur before `;`. Note that the keywords are being revised into
630
        # a form we can splat.
631
        for a in ex.args[2:end]
48✔
632
            if isa(a, Expr) && a.head === :kw
89✔
633
                push!(escaped_kwargs, Expr(:call, :(=>), QuoteNode(a.args[1]), esc(a.args[2])))
×
634
            end
635
        end
89✔
636

637
        # Keywords that occur after ';'
638
        parameters_expr = ex.args[2]
48✔
639
        if isa(parameters_expr, Expr) && parameters_expr.head === :parameters
48✔
640
            for a in parameters_expr.args
×
641
                if isa(a, Expr) && a.head === :kw
×
642
                    push!(escaped_kwargs, Expr(:call, :(=>), QuoteNode(a.args[1]), esc(a.args[2])))
×
643
                elseif isa(a, Expr) && a.head === :...
×
644
                    push!(escaped_kwargs, Expr(:..., esc(a.args[1])))
×
645
                elseif isa(a, Expr) && a.head === :.
×
646
                    push!(escaped_kwargs, Expr(:call, :(=>), QuoteNode(a.args[2].value), esc(Expr(:., a.args[1], QuoteNode(a.args[2].value)))))
×
647
                elseif isa(a, Symbol)
×
648
                    push!(escaped_kwargs, Expr(:call, :(=>), QuoteNode(a), esc(a)))
×
649
                end
650
            end
×
651
        end
652

653
        # Positional arguments
654
        for a in ex.args[2:end]
48✔
655
            isa(a, Expr) && a.head in (:kw, :parameters) && continue
89✔
656

657
            if isa(a, Expr) && a.head === :...
89✔
658
                push!(escaped_args, Expr(:..., esc(a.args[1])))
×
659
            else
660
                push!(escaped_args, esc(a))
89✔
661
            end
662
        end
89✔
663

664
        testret = :(eval_test(
48✔
665
            Expr(:call, $escaped_func, Expr(:parameters, $(escaped_kwargs...)), $(escaped_args...)),
666
            Expr(:call, $quoted_func),
667
            $(QuoteNode(source)),
668
            $negate,
669
        ))
670
    else
671
        ex = Expr(:block, source, esc(orig_ex))
81✔
672
        testret = :(Returned($ex, nothing, $(QuoteNode(source))))
81✔
673
    end
674
    result = quote
314✔
675
        try
305✔
676
            $testret
366✔
677
        catch _e
678
            _e isa InterruptException && rethrow()
×
679
            Threw(_e, Base.current_exceptions(), $(QuoteNode(source)))
305✔
680
        end
681
    end
682
    result
314✔
683
end
684

685
# An internal function, called by the code generated by the @test
686
# macro to actually perform the evaluation and manage the result.
687
function do_test(result::ExecutionResult, orig_expr)
393✔
688
    # get_testset() returns the most recently added test set
689
    # We then call record() with this test set and the test result
690
    if isa(result, Returned)
393✔
691
        # expr, in the case of a comparison, will contain the
692
        # comparison with evaluated values of each term spliced in.
693
        # For anything else, just contains the test expression.
694
        # value is the evaluated value of the whole test expression.
695
        # Ideally it is true, but it may be false or non-Boolean.
696
        value = result.value
392✔
697
        testres = if isa(value, Bool)
392✔
698
            # a true value Passes
699
            value ? Pass(:test, orig_expr, result.data, value, result.source) :
393✔
700
                    Fail(:test, orig_expr, result.data, value, nothing, result.source, false)
701
        else
702
            # If the result is non-Boolean, this counts as an Error
703
            Error(:test_nonbool, orig_expr, value, nothing, result.source)
392✔
704
        end
705
    else
706
        # The predicate couldn't be evaluated without throwing an
707
        # exception, so that is an Error and not a Fail
708
        @assert isa(result, Threw)
1✔
709
        testres = Error(:test_error, orig_expr, result.exception, result.backtrace::Vector{Any}, result.source)
1✔
710
    end
711
    isa(testres, Pass) || trigger_test_failure_break(result)
395✔
712
    record(get_testset(), testres)
393✔
713
end
714

715
function do_broken_test(result::ExecutionResult, orig_expr)
3✔
716
    testres = Broken(:test, orig_expr)
3✔
717
    # Assume the test is broken and only change if the result is true
718
    if isa(result, Returned)
3✔
719
        value = result.value
3✔
720
        if isa(value, Bool)
3✔
721
            if value
3✔
722
                testres = Error(:test_unbroken, orig_expr, value, nothing, result.source)
×
723
            end
724
        else
725
            # If the result is non-Boolean, this counts as an Error
726
            testres = Error(:test_nonbool, orig_expr, value, nothing, result.source)
×
727
        end
728
    end
729
    record(get_testset(), testres)
3✔
730
end
731

732
#-----------------------------------------------------------------------
733

734
"""
735
    @test_throws exception expr
736

737
Tests that the expression `expr` throws `exception`.
738
The exception may specify either a type,
739
a string, regular expression, or list of strings occurring in the displayed error message,
740
a matching function,
741
or a value (which will be tested for equality by comparing fields).
742
Note that `@test_throws` does not support a trailing keyword form.
743

744
!!! compat "Julia 1.8"
745
    The ability to specify anything other than a type or a value as `exception` requires Julia v1.8 or later.
746

747
# Examples
748
```jldoctest
749
julia> @test_throws BoundsError [1, 2, 3][4]
750
Test Passed
751
      Thrown: BoundsError
752

753
julia> @test_throws DimensionMismatch [1, 2, 3] + [1, 2]
754
Test Passed
755
      Thrown: DimensionMismatch
756

757
julia> @test_throws "Try sqrt(Complex" sqrt(-1)
758
Test Passed
759
     Message: "DomainError with -1.0:\\nsqrt was called with a negative real argument but will only return a complex result if called with a complex argument. Try sqrt(Complex(x))."
760
```
761

762
In the final example, instead of matching a single string it could alternatively have been performed with:
763

764
- `["Try", "Complex"]` (a list of strings)
765
- `r"Try sqrt\\([Cc]omplex"` (a regular expression)
766
- `str -> occursin("complex", str)` (a matching function)
767
"""
768
macro test_throws(extype, ex)
769
    orig_ex = Expr(:inert, ex)
770
    ex = Expr(:block, __source__, esc(ex))
771
    result = quote
772
        try
773
            Returned($ex, nothing, $(QuoteNode(__source__)))
774
        catch _e
775
            if $(esc(extype)) != InterruptException && _e isa InterruptException
776
                rethrow()
777
            end
778
            Threw(_e, Base.current_exceptions(), $(QuoteNode(__source__)))
779
        end
780
    end
781
    return :(do_test_throws($result, $orig_ex, $(esc(extype))))
782
end
783

784
const MACROEXPAND_LIKE = Symbol.(("@macroexpand", "@macroexpand1", "macroexpand"))
785

786
function isequalexception(@nospecialize(a), @nospecialize(b))
×
787
    for fld in 1:nfields(b)
×
788
        if !isequal(getfield(a, fld), getfield(b, fld))
×
789
            return false
×
790
        end
791
    end
×
792
    return true
×
793
end
794
function isequalexception(a::UndefVarError, b::UndefVarError)
×
795
    # Ignore different world ages
796
    return isequal(a.var, b.var) && isequal(a.scope, b.scope)
×
797
end
798

799
# An internal function, called by the code generated by @test_throws
800
# to evaluate and catch the thrown exception - if it exists
801
function do_test_throws(result::ExecutionResult, orig_expr, extype)
×
802
    if isa(result, Threw)
×
803
        # Check that the right type of exception was thrown
804
        success = false
×
805
        message_only = false
×
806
        exc = result.exception
×
807
        # NB: Throwing LoadError from macroexpands is deprecated, but in order to limit
808
        # the breakage in package tests we add extra logic here.
809
        from_macroexpand =
×
810
            orig_expr isa Expr &&
811
            orig_expr.head in (:call, :macrocall) &&
812
            orig_expr.args[1] in MACROEXPAND_LIKE
813
        if isa(extype, Type)
×
814
            success =
×
815
                if from_macroexpand && extype == LoadError && exc isa Exception
816
                    Base.depwarn("macroexpand no longer throws a LoadError so `@test_throws LoadError ...` is deprecated and passed without checking the error type!", :do_test_throws)
×
817
                    true
×
818
                elseif extype == ErrorException && isa(exc, FieldError)
×
819
                    Base.depwarn(lazy"ErrorException should no longer be used to test field access; FieldError should be used instead!", :do_test_throws)
×
820
                    true
×
821
                else
822
                    isa(exc, extype)
×
823
                end
824
        elseif isa(extype, Exception) || !isa(exc, Exception)
×
825
            if extype isa LoadError && !(exc isa LoadError) && typeof(extype.error) == typeof(exc)
×
826
                extype = extype.error # deprecated
×
827
            end
828
            # Support `UndefVarError(:x)` meaning `UndefVarError(:x, scope)` for any `scope`.
829
            # Retains the behaviour from pre-v1.11 when `UndefVarError` didn't have `scope`.
830
            if isa(extype, UndefVarError) && !isdefined(extype, :scope)
×
831
                success = exc isa UndefVarError && exc.var == extype.var
×
832
            else isa(exc, typeof(extype))
×
833
                success = isequalexception(exc, extype)
×
834
            end
835
        else
836
            message_only = true
×
837
            exc = sprint(showerror, exc)
×
838
            success = contains_warn(exc, extype)
×
839
            exc = repr(exc)
×
840
            if isa(extype, AbstractString)
×
841
                extype = repr(extype)
×
842
            elseif isa(extype, Function)
×
843
                extype = "< match function >"
×
844
            end
845
        end
846
        if success
×
847
            testres = Pass(:test_throws, orig_expr, extype, exc, result.source, message_only)
×
848
        else
849
            if result.backtrace !== nothing
×
850
                bt = scrub_exc_stack(result.backtrace, nothing, extract_file(result.source))
×
851
                bt_str = try # try the latest world for this, since we might have eval'd new code for show
×
852
                    Base.invokelatest(sprint, Base.show_exception_stack, bt; context=stdout)
×
853
                catch ex
854
                    "#=ERROR showing exception stack=# " *
×
855
                        try
856
                            sprint(Base.showerror, ex, catch_backtrace(); context=stdout)
×
857
                        catch
858
                            "of type " * string(typeof(ex))
×
859
                        end
860
                end
861
            else
862
                bt_str = nothing
×
863
            end
864
            testres = Fail(:test_throws_wrong, orig_expr, extype, exc, nothing, result.source, message_only, bt_str)
×
865
        end
866
    else
867
        testres = Fail(:test_throws_nothing, orig_expr, extype, nothing, nothing, result.source, false)
×
868
    end
869
    record(get_testset(), testres)
×
870
end
871

872
#-----------------------------------------------------------------------
873
# Test for log messages
874

875
# Test for warning messages (deprecated)
876

877
contains_warn(output, s::AbstractString) = occursin(s, output)
2✔
878
contains_warn(output, s::Regex) = occursin(s, output)
×
879
contains_warn(output, s::Function) = s(output)
2✔
880
contains_warn(output, S::Union{AbstractArray,Tuple}) = all(s -> contains_warn(output, s), S)
×
881

882
"""
883
    @test_warn msg expr
884

885
Test whether evaluating `expr` results in [`stderr`](@ref) output that contains
886
the `msg` string or matches the `msg` regular expression.  If `msg` is
887
a boolean function, tests whether `msg(output)` returns `true`.  If `msg` is a
888
tuple or array, checks that the error output contains/matches each item in `msg`.
889
Returns the result of evaluating `expr`.
890

891
See also [`@test_nowarn`](@ref) to check for the absence of error output.
892

893
Note: Warnings generated by `@warn` cannot be tested with this macro. Use
894
[`@test_logs`](@ref) instead.
895
"""
896
macro test_warn(msg, expr)
2✔
897
    test_warn_expr(expr, msg)
2✔
898
end
899

900
"""
901
    @test_nowarn expr
902

903
Test whether evaluating `expr` results in empty [`stderr`](@ref) output
904
(no warnings or other messages).  Returns the result of evaluating `expr`.
905

906
Note: The absence of warnings generated by `@warn` cannot be tested
907
with this macro. Use [`@test_logs`](@ref) instead.
908
"""
909
macro test_nowarn(expr)
2✔
910
    # allow printing the content of `stderr` again to `stderr` here while suppressing it
911
    # for `@test_warn`. If that shouldn't be used, this could just be `test_warn_expr(expr, #=msg=#isempty)`
912
    test_warn_expr(expr, function (s)
4✔
913
        print(stderr, s) # this is helpful for debugging
2✔
914
        isempty(s)
2✔
915
    end)
916
end
917

918
function test_warn_expr(@nospecialize(expr), @nospecialize(msg))
4✔
919
    return :(let fname = tempname()
4✔
920
        try
921
            f = open(fname, "w")
922
            stdold = stderr
923
            redirect_stderr(f)
924
            ret = try
925
                # We deliberately don't use the thunk versions of open/redirect
926
                # to ensure that adding the macro does not change the toplevel-ness
927
                # of the resulting expression.
928
                $(esc(expr))
929
            finally
930
                redirect_stderr(stdold)
931
                close(f)
932
            end
933
            @test contains_warn(read(fname, String), $(esc(msg)))
934
            ret
935
        finally
936
            rm(fname, force=true)
937
        end
938
    end)
939
end
940

941
#-----------------------------------------------------------------------
942

943
# The AbstractTestSet interface is defined by two methods:
944
# record(AbstractTestSet, Result)
945
#   Called by do_test after a test is evaluated
946
# finish(AbstractTestSet)
947
#   Called after the test set has been popped from the test set stack
948
abstract type AbstractTestSet end
949

950
"""
951
    record(ts::AbstractTestSet, res::Result)
952

953
Record a result to a testset. This function is called by the `@testset`
954
infrastructure each time a contained `@test` macro completes, and is given the
955
test result (which could be an `Error`). This will also be called with an `Error`
956
if an exception is thrown inside the test block but outside of a `@test` context.
957
"""
958
function record end
959

960
"""
961
    finish(ts::AbstractTestSet)
962

963
Do any final processing necessary for the given testset. This is called by the
964
`@testset` infrastructure after a test block executes.
965

966
Custom `AbstractTestSet` subtypes should call `record` on their parent (if there
967
is one) to add themselves to the tree of test results. This might be implemented
968
as:
969

970
```julia
971
if get_testset_depth() != 0
972
    # Attach this test set to the parent test set
973
    parent_ts = get_testset()
974
    record(parent_ts, self)
975
    return self
976
end
977
```
978
"""
979
function finish end
980

981
"""
982
    TestSetException
983

984
Thrown when a test set finishes and not all tests passed.
985
"""
986
struct TestSetException <: Exception
987
    pass::Int
2✔
988
    fail::Int
989
    error::Int
990
    broken::Int
991
    errors_and_fails::Vector{Union{Fail, Error}}
992
end
993

994
function Base.show(io::IO, ex::TestSetException)
×
995
    print(io, "Some tests did not pass: ")
×
996
    print(io, ex.pass,  " passed, ")
×
997
    print(io, ex.fail,  " failed, ")
×
998
    print(io, ex.error, " errored, ")
×
999
    print(io, ex.broken, " broken.")
×
1000
end
1001

1002
function Base.showerror(io::IO, ex::TestSetException, bt; backtrace=true)
×
1003
    printstyled(io, string(ex), color=Base.error_color())
×
1004
end
1005

1006
#-----------------------------------------------------------------------
1007

1008
"""
1009
    FallbackTestSet
1010

1011
A simple fallback test set that throws immediately on a failure.
1012
"""
1013
struct FallbackTestSet <: AbstractTestSet end
1014
fallback_testset = FallbackTestSet()
1015

1016
struct FallbackTestSetException <: Exception
1017
    msg::String
1✔
1018
end
1019

1020
function Base.showerror(io::IO, ex::FallbackTestSetException, bt; backtrace=true)
2✔
1021
    printstyled(io, ex.msg, color=Base.error_color())
1✔
1022
end
1023

1024
# Records nothing, and throws an error immediately whenever a Fail or
1025
# Error occurs. Takes no action in the event of a Pass or Broken result
1026
record(ts::FallbackTestSet, t::Union{Pass, Broken}) = t
×
1027
function record(ts::FallbackTestSet, t::Union{Fail, Error})
×
1028
    println(t)
×
1029
    throw(FallbackTestSetException("There was an error during testing"))
×
1030
end
1031
# We don't need to do anything as we don't record anything
1032
finish(ts::FallbackTestSet) = ts
×
1033

1034
#-----------------------------------------------------------------------
1035

1036
"""
1037
    ContextTestSet
1038

1039
Passes test failures through to the parent test set, while adding information
1040
about a context object that is being tested.
1041
"""
1042
struct ContextTestSet <: AbstractTestSet
1043
    parent_ts::AbstractTestSet
7✔
1044
    context_name::Union{Symbol, Expr}
1045
    context::Any
1046
end
1047

1048
function ContextTestSet(name::Union{Symbol, Expr}, @nospecialize(context))
7✔
1049
    if (name isa Expr) && (name.head != :tuple)
7✔
1050
        error("Invalid syntax: $(name)")
×
1051
    end
1052
    return ContextTestSet(get_testset(), name, context)
7✔
1053
end
1054
record(c::ContextTestSet, t) = record(c.parent_ts, t)
50✔
1055
function record(c::ContextTestSet, t::Fail)
×
1056
    context = string(c.context_name, " = ", c.context)
×
1057
    context = t.context === nothing ? context : string(t.context, "\n              ", context)
×
1058
    record(c.parent_ts, Fail(t.test_type, t.orig_expr, t.data, t.value, context, t.source, t.message_only))
×
1059
end
1060

1061
#-----------------------------------------------------------------------
1062

1063
"""
1064
    DefaultTestSet
1065

1066
If using the DefaultTestSet, the test results will be recorded. If there
1067
are any `Fail`s or `Error`s, an exception will be thrown only at the end,
1068
along with a summary of the test results.
1069
"""
1070
mutable struct DefaultTestSet <: AbstractTestSet
1071
    description::String
71✔
1072
    results::Vector{Any}
1073
    n_passed::Int
1074
    anynonpass::Bool
1075
    verbose::Bool
1076
    showtiming::Bool
1077
    time_start::Float64
1078
    time_end::Union{Float64,Nothing}
1079
    failfast::Bool
1080
    file::Union{String,Nothing}
1081
    rng::Union{Nothing,AbstractRNG}
1082
end
1083
function DefaultTestSet(desc::AbstractString; verbose::Bool = false, showtiming::Bool = true, failfast::Union{Nothing,Bool} = nothing, source = nothing, rng = nothing)
142✔
1084
    if isnothing(failfast)
71✔
1085
        # pass failfast state into child testsets
1086
        parent_ts = get_testset()
71✔
1087
        if parent_ts isa DefaultTestSet
71✔
1088
            failfast = parent_ts.failfast
69✔
1089
        else
1090
            failfast = false
2✔
1091
        end
1092
    end
1093
    return DefaultTestSet(String(desc)::String, [], 0, false, verbose, showtiming, time(), nothing, failfast, extract_file(source), rng)
71✔
1094
end
1095
extract_file(source::LineNumberNode) = extract_file(source.file)
2✔
1096
extract_file(file::Symbol) = string(file)
70✔
1097
extract_file(::Nothing) = nothing
2✔
1098

1099
struct FailFastError <: Exception end
×
1100

1101
# For a broken result, simply store the result
1102
record(ts::DefaultTestSet, t::Broken) = (push!(ts.results, t); t)
10✔
1103
# For a passed result, do not store the result since it uses a lot of memory
1104
record(ts::DefaultTestSet, t::Pass) = (ts.n_passed += 1; t)
770✔
1105

1106
# For the other result types, immediately print the error message
1107
# but do not terminate. Print a backtrace.
1108
function record(ts::DefaultTestSet, t::Union{Fail, Error}; print_result::Bool=TESTSET_PRINT_ENABLE[])
6✔
1109
    if print_result
4✔
1110
        print(ts.description, ": ")
×
1111
        # don't print for interrupted tests
1112
        if !(t isa Error) || t.test_type !== :test_interrupted
×
1113
            print(t)
×
1114
            if !isa(t, Error) # if not gets printed in the show method
×
1115
                Base.show_backtrace(stdout, scrub_backtrace(backtrace(), ts.file, extract_file(t.source)))
×
1116
            end
1117
            println()
×
1118
        end
1119
    end
1120
    push!(ts.results, t)
6✔
1121
    (FAIL_FAST[] || ts.failfast) && throw(FailFastError())
4✔
1122
    return t
4✔
1123
end
1124

1125
"""
1126
    print_verbose(::AbstractTestSet) -> Bool
1127

1128
Whether printing involving this `AbstractTestSet` should be verbose or not.
1129

1130
Defaults to `false`.
1131
"""
1132
function print_verbose end
1133

1134
"""
1135
    results(::AbstractTestSet)
1136

1137
Return an iterator of results aggregated by this `AbstractTestSet`, if any were recorded.
1138

1139
Defaults to the empty tuple.
1140
"""
1141
function results end
1142

1143
print_verbose(ts::DefaultTestSet) = ts.verbose
×
1144
results(ts::DefaultTestSet) = ts.results
4✔
1145

1146
# When a DefaultTestSet finishes, it records itself to its parent
1147
# testset, if there is one. This allows for recursive printing of
1148
# the results at the end of the tests
1149
record(ts::DefaultTestSet, t::AbstractTestSet) = push!(ts.results, t)
69✔
1150

1151
@specialize
1152

1153
"""
1154
    print_test_errors(::AbstractTestSet)
1155

1156
Prints the errors that were recorded by this `AbstractTestSet` after it
1157
was `finish`ed.
1158
"""
1159
function print_test_errors(ts::AbstractTestSet)
2✔
1160
    for t in results(ts)
2✔
1161
        if isa(t, Error) || isa(t, Fail)
15✔
1162
            println("Error in testset $(ts.description):")
2✔
1163
            show(t)
3✔
1164
            println()
2✔
1165
        elseif isa(t, AbstractTestSet)
6✔
1166
            print_test_errors(t)
1✔
1167
        end
1168
    end
8✔
1169
end
1170

1171
"""
1172
    print_test_results(ts::AbstractTestSet, depth_pad=0)
1173

1174
Print the results of an `AbstractTestSet` as a formatted table.
1175

1176
`depth_pad` refers to how much padding should be added in front of all output.
1177

1178
Called inside of `Test.finish`, if the `finish`ed testset is the topmost
1179
testset.
1180
"""
1181
function print_test_results(ts::AbstractTestSet, depth_pad=0)
1✔
1182
    # Calculate the overall number for each type so each of
1183
    # the test result types are aligned
1184
    tc = get_test_counts(ts)
1✔
1185
    total_pass   = tc.passes + tc.cumulative_passes
1✔
1186
    total_fail   = tc.fails  + tc.cumulative_fails
1✔
1187
    total_error  = tc.errors + tc.cumulative_errors
1✔
1188
    total_broken = tc.broken + tc.cumulative_broken
1✔
1189
    dig_pass   = total_pass   > 0 ? ndigits(total_pass)   : 0
1✔
1190
    dig_fail   = total_fail   > 0 ? ndigits(total_fail)   : 0
1✔
1191
    dig_error  = total_error  > 0 ? ndigits(total_error)  : 0
1✔
1192
    dig_broken = total_broken > 0 ? ndigits(total_broken) : 0
1✔
1193
    total = total_pass + total_fail + total_error + total_broken
1✔
1194
    dig_total = total > 0 ? ndigits(total) : 0
1✔
1195
    # For each category, take max of digits and header width if there are
1196
    # tests of that type
1197
    pass_width   = dig_pass   > 0 ? max(length("Pass"),   dig_pass)   : 0
1✔
1198
    fail_width   = dig_fail   > 0 ? max(length("Fail"),   dig_fail)   : 0
1✔
1199
    error_width  = dig_error  > 0 ? max(length("Error"),  dig_error)  : 0
1✔
1200
    broken_width = dig_broken > 0 ? max(length("Broken"), dig_broken) : 0
1✔
1201
    total_width  = max(textwidth("Total"),  dig_total)
1✔
1202
    duration_width = max(textwidth("Time"), textwidth(tc.duration))
1✔
1203
    # Calculate the alignment of the test result counts by
1204
    # recursively walking the tree of test sets
1205
    align = max(get_alignment(ts, depth_pad), textwidth("Test Summary:"))
1✔
1206
    # Print the outer test set header once
1207
    printstyled(rpad("Test Summary:", align, " "), " |", " "; bold=true)
1✔
1208
    if pass_width > 0
1✔
1209
        printstyled(lpad("Pass", pass_width, " "), "  "; bold=true, color=:green)
1✔
1210
    end
1211
    if fail_width > 0
1✔
1212
        printstyled(lpad("Fail", fail_width, " "), "  "; bold=true, color=Base.error_color())
2✔
1213
    end
1214
    if error_width > 0
1✔
1215
        printstyled(lpad("Error", error_width, " "), "  "; bold=true, color=Base.error_color())
2✔
1216
    end
1217
    if broken_width > 0
1✔
1218
        printstyled(lpad("Broken", broken_width, " "), "  "; bold=true, color=Base.warn_color())
2✔
1219
    end
1220
    if total_width > 0 || total == 0
1✔
1221
        printstyled(lpad("Total", total_width, " "), "  "; bold=true, color=Base.info_color())
2✔
1222
    end
1223
    timing = isdefined(ts, :showtiming) ? ts.showtiming : false
1✔
1224
    if timing
1✔
1225
        printstyled(lpad("Time", duration_width, " "); bold=true)
1✔
1226
    end
1227
    println()
1✔
1228
    # Recursively print a summary at every level
1229
    print_counts(ts, depth_pad, align, pass_width, fail_width, error_width, broken_width, total_width, duration_width, timing)
1✔
1230
    # Print the RNG of the outer testset if there are failures
1231
    if total != total_pass + total_broken
1✔
1232
        rng = get_rng(ts)
1✔
1233
        if !isnothing(rng)
1✔
1234
            println("RNG of the outermost testset: ", rng)
×
1235
        end
1236
    end
1237
end
1238

1239

1240
const TESTSET_PRINT_ENABLE = Ref(true)
1241

1242
# Called at the end of a @testset, behaviour depends on whether
1243
# this is a child of another testset, or the "root" testset
1244
function finish(ts::DefaultTestSet; print_results::Bool=TESTSET_PRINT_ENABLE[])
138✔
1245
    ts.time_end = time()
69✔
1246
    # If we are a nested test set, do not print a full summary
1247
    # now - let the parent test set do the printing
1248
    if get_testset_depth() != 0
69✔
1249
        # Attach this test set to the parent test set
1250
        parent_ts = get_testset()
68✔
1251
        record(parent_ts, ts)
68✔
1252
        return ts
68✔
1253
    end
1254
    tc = get_test_counts(ts)
1✔
1255
    total_pass   = tc.passes + tc.cumulative_passes
1✔
1256
    total_fail   = tc.fails  + tc.cumulative_fails
1✔
1257
    total_error  = tc.errors + tc.cumulative_errors
1✔
1258
    total_broken = tc.broken + tc.cumulative_broken
1✔
1259
    total = total_pass + total_fail + total_error + total_broken
1✔
1260

1261
    if print_results
1✔
1262
        print_test_results(ts)
×
1263
    end
1264

1265
    # Finally throw an error as we are the outermost test set
1266
    if total != total_pass + total_broken
1✔
1267
        # Get all the error/failures and bring them along for the ride
1268
        efs = filter_errors(ts)
1✔
1269
        throw(TestSetException(total_pass, total_fail, total_error, total_broken, efs))
1✔
1270
    end
1271

1272
    # return the testset so it is returned from the @testset macro
1273
    ts
×
1274
end
1275

1276
# Recursive function that finds the column that the result counts
1277
# can begin at by taking into account the width of the descriptions
1278
# and the amount of indentation. If a test set had no failures, and
1279
# no failures in child test sets, there is no need to include those
1280
# in calculating the alignment
1281
function get_alignment(ts::DefaultTestSet, depth::Int)
2✔
1282
    # The minimum width at this depth is
1283
    ts_width = 2*depth + length(ts.description)
2✔
1284
    # If not verbose and all passing, no need to look at children
1285
    !ts.verbose && !ts.anynonpass && return ts_width
2✔
1286
    # Return the maximum of this width and the minimum width
1287
    # for all children (if they exist)
1288
    isempty(ts.results) && return ts_width
2✔
1289
    child_widths = map(t->get_alignment(t, depth+1), ts.results)
10✔
1290
    return max(ts_width, maximum(child_widths))
2✔
1291
end
1292
get_alignment(ts, depth::Int) = 0
×
1293

1294
# Recursive function that fetches backtraces for any and all errors
1295
# or failures the testset and its children encountered
1296
function filter_errors(ts::DefaultTestSet)
69✔
1297
    efs = []
69✔
1298
    for t in ts.results
69✔
1299
        if isa(t, DefaultTestSet)
75✔
1300
            append!(efs, filter_errors(t))
68✔
1301
        elseif isa(t, Union{Fail, Error})
7✔
1302
            append!(efs, [t])
2✔
1303
        end
1304
    end
75✔
1305
    efs
69✔
1306
end
1307

1308
"""
1309
    Test.get_rng(ts::AbstractTestSet) -> Union{Nothing,AbstractRNG}
1310

1311
Return the global random number generator (RNG) associated to the input testset `ts`.
1312
If no RNG is associated to it, return `nothing`.
1313
"""
1314
get_rng(::AbstractTestSet) = nothing
×
1315
get_rng(ts::DefaultTestSet) = ts.rng
48✔
1316
"""
1317
    Test.set_rng!(ts::AbstractTestSet, rng::AbstractRNG) -> AbstractRNG
1318

1319
Set the global random number generator (RNG) associated to the input testset `ts` to `rng`.
1320
If no RNG is associated to it, do nothing.
1321
In any case, always return the input `rng`.
1322
"""
1323
set_rng!(::AbstractTestSet, rng::AbstractRNG) = rng
×
1324
set_rng!(ts::DefaultTestSet, rng::AbstractRNG) = ts.rng = rng
47✔
1325

1326
"""
1327
    TestCounts
1328

1329
Holds the state for recursively gathering the results of a test set for display purposes.
1330

1331
Fields:
1332

1333
 * `customized`: Whether the function `get_test_counts` was customized for the `AbstractTestSet`
1334
                 this counts object is for. If a custom method was defined, always pass `true`
1335
                 to the constructor.
1336
 * `passes`: The number of passing `@test` invocations.
1337
 * `fails`: The number of failing `@test` invocations.
1338
 * `errors`: The number of erroring `@test` invocations.
1339
 * `broken`: The number of broken `@test` invocations.
1340
 * `passes`: The cumulative number of passing `@test` invocations.
1341
 * `fails`: The cumulative number of failing `@test` invocations.
1342
 * `errors`: The cumulative number of erroring `@test` invocations.
1343
 * `broken`: The cumulative number of broken `@test` invocations.
1344
 * `duration`: The total duration the `AbstractTestSet` in question ran for, as a formatted `String`.
1345
"""
1346
struct TestCounts
1347
    customized::Bool
74✔
1348
    passes::Int
1349
    fails::Int
1350
    errors::Int
1351
    broken::Int
1352
    cumulative_passes::Int
1353
    cumulative_fails::Int
1354
    cumulative_errors::Int
1355
    cumulative_broken::Int
1356
    duration::String
1357
end
1358

1359
""""
1360
    get_test_counts(::AbstractTestSet) -> TestCounts
1361

1362
Recursive function that counts the number of test results of each
1363
type directly in the testset, and totals across the child testsets.
1364

1365
Custom `AbstractTestSet` should implement this function to get their totals
1366
counted & displayed with `DefaultTestSet` as well.
1367

1368
If this is not implemented for a custom `TestSet`, the printing falls back to
1369
reporting `x` for failures and `?s` for the duration.
1370
"""
1371
function get_test_counts end
1372

1373
get_test_counts(ts::AbstractTestSet) = TestCounts(false, 0,0,0,0,0,0,0,0, format_duration(ts))
×
1374

1375
function get_test_counts(ts::DefaultTestSet)
74✔
1376
    passes, fails, errors, broken = ts.n_passed, 0, 0, 0
74✔
1377
    # cumulative results
1378
    c_passes, c_fails, c_errors, c_broken = 0, 0, 0, 0
74✔
1379
    for t in ts.results
74✔
1380
        isa(t, Fail)   && (fails  += 1)
98✔
1381
        isa(t, Error)  && (errors += 1)
98✔
1382
        isa(t, Broken) && (broken += 1)
98✔
1383
        if isa(t, AbstractTestSet)
98✔
1384
            tc = get_test_counts(t)::TestCounts
70✔
1385
            c_passes += tc.passes + tc.cumulative_passes
70✔
1386
            c_fails  += tc.fails + tc.cumulative_fails
70✔
1387
            c_errors += tc.errors + tc.cumulative_errors
70✔
1388
            c_broken += tc.broken + tc.cumulative_broken
70✔
1389
        end
1390
    end
98✔
1391
    duration = format_duration(ts)
74✔
1392
    ts.anynonpass = (fails + errors + c_fails + c_errors > 0)
74✔
1393
    return TestCounts(true, passes, fails, errors, broken, c_passes, c_fails, c_errors, c_broken, duration)
74✔
1394
end
1395

1396
"""
1397
    format_duration(::AbstractTestSet)
1398

1399
Return a formatted string for printing the duration the testset ran for.
1400

1401
If not defined, falls back to `"?s"`.
1402
"""
1403
format_duration(::AbstractTestSet) = "?s"
×
1404

1405
function format_duration(ts::DefaultTestSet)
74✔
1406
    (; time_start, time_end) = ts
74✔
1407
    isnothing(time_end) && return ""
148✔
1408

1409
    dur_s = time_end - time_start
74✔
1410
    if dur_s < 60
74✔
1411
        string(round(dur_s, digits = 1), "s")
68✔
1412
    else
1413
        m, s = divrem(dur_s, 60)
6✔
1414
        s = lpad(string(round(s, digits = 1)), 4, "0")
6✔
1415
        string(round(Int, m), "m", s, "s")
6✔
1416
    end
1417
end
1418

1419
print_verbose(::AbstractTestSet) = false
×
1420
results(::AbstractTestSet) = ()
×
1421

1422
# Recursive function that prints out the results at each level of
1423
# the tree of test sets
1424
function print_counts(ts::AbstractTestSet, depth, align,
2✔
1425
                      pass_width, fail_width, error_width, broken_width, total_width, duration_width, showtiming)
1426
    # Count results by each type at this level, and recursively
1427
    # through any child test sets
1428
    tc = get_test_counts(ts)
2✔
1429
    fallbackstr = tc.customized ? " " : "x"
2✔
1430
    subtotal = tc.passes + tc.fails + tc.errors + tc.broken +
2✔
1431
               tc.cumulative_passes + tc.cumulative_fails + tc.cumulative_errors + tc.cumulative_broken
1432
    # Print test set header, with an alignment that ensures all
1433
    # the test results appear above each other
1434
    print(rpad(string("  "^depth, ts.description), align, " "), " | ")
2✔
1435

1436
    n_passes = tc.passes + tc.cumulative_passes
2✔
1437
    if n_passes > 0
2✔
1438
        printstyled(lpad(string(n_passes), pass_width, " "), "  ", color=:green)
2✔
1439
    elseif pass_width > 0
×
1440
        # No passes at this level, but some at another level
1441
        printstyled(lpad(fallbackstr, pass_width, " "), "  ", color=:green)
×
1442
    end
1443

1444
    n_fails = tc.fails + tc.cumulative_fails
2✔
1445
    if n_fails > 0
2✔
1446
        printstyled(lpad(string(n_fails), fail_width, " "), "  ", color=Base.error_color())
4✔
1447
    elseif fail_width > 0
×
1448
        # No fails at this level, but some at another level
1449
        printstyled(lpad(fallbackstr, fail_width, " "), "  ", color=Base.error_color())
×
1450
    end
1451

1452
    n_errors = tc.errors + tc.cumulative_errors
2✔
1453
    if n_errors > 0
2✔
1454
        printstyled(lpad(string(n_errors), error_width, " "), "  ", color=Base.error_color())
4✔
1455
    elseif error_width > 0
×
1456
        # No errors at this level, but some at another level
1457
        printstyled(lpad(fallbackstr, error_width, " "), "  ", color=Base.error_color())
×
1458
    end
1459

1460
    n_broken = tc.broken + tc.cumulative_broken
2✔
1461
    if n_broken > 0
2✔
1462
        printstyled(lpad(string(n_broken), broken_width, " "), "  ", color=Base.warn_color())
4✔
1463
    elseif broken_width > 0
×
1464
        # None broken at this level, but some at another level
1465
        printstyled(lpad(fallbackstr, broken_width, " "), "  ", color=Base.warn_color())
×
1466
    end
1467

1468
    if n_passes == 0 && n_fails == 0 && n_errors == 0 && n_broken == 0
2✔
1469
        total_str = tc.customized ? string(subtotal) : "?"
×
1470
        printstyled(lpad(total_str, total_width, " "), "  ", color=Base.info_color())
×
1471
    else
1472
        printstyled(lpad(string(subtotal), total_width, " "), "  ", color=Base.info_color())
2✔
1473
    end
1474

1475
    if showtiming
2✔
1476
        printstyled(lpad(tc.duration, duration_width, " "))
2✔
1477
    end
1478
    println()
2✔
1479

1480
    # Only print results at lower levels if we had failures or if the user
1481
    # wants. Requires the given `AbstractTestSet` to have a vector of results
1482
    if ((n_passes + n_broken != subtotal) || print_verbose(ts))
2✔
1483
        for t in results(ts)
2✔
1484
            if isa(t, AbstractTestSet)
8✔
1485
                print_counts(t, depth + 1, align,
1✔
1486
                    pass_width, fail_width, error_width, broken_width, total_width, duration_width, ts.showtiming)
1487
            end
1488
        end
8✔
1489
    end
1490
end
1491

1492
#-----------------------------------------------------------------------
1493

1494
function _check_testset(testsettype, testsetname)
68✔
1495
    if !(testsettype isa Type && testsettype <: AbstractTestSet)
69✔
1496
        error("Expected `$testsetname` to be an AbstractTestSet, it is a ",
×
1497
              typeof(testsettype), ". ",
1498
              typeof(testsettype) == String ?
1499
                  """
1500
                  To use `$testsetname` as a testset name, interpolate it into a string, e.g:
1501
                      @testset "\$$testsetname" begin
1502
                          ...
1503
                      end"""
1504
             :
1505
                  ""
1506
            )
1507
    end
1508
end
1509

1510
"""
1511
    @testset [CustomTestSet] [options...] ["description"] begin test_ex end
1512
    @testset [CustomTestSet] [options...] ["description \$v"] for v in itr test_ex end
1513
    @testset [CustomTestSet] [options...] ["description \$v, \$w"] for v in itrv, w in itrw test_ex end
1514
    @testset [CustomTestSet] [options...] ["description"] test_func()
1515
    @testset let v = v, w = w; test_ex; end
1516

1517
# With begin/end or function call
1518

1519
When @testset is used, with begin/end or a single function call, the macro
1520
starts a new test set in which to evaluate the given expression.
1521

1522
If no custom testset type is given it defaults to creating a `DefaultTestSet`.
1523
`DefaultTestSet` records all the results and, if there are any `Fail`s or
1524
`Error`s, throws an exception at the end of the top-level (non-nested) test set,
1525
along with a summary of the test results.
1526

1527
Any custom testset type (subtype of `AbstractTestSet`) can be given and it will
1528
also be used for any nested `@testset` invocations. The given options are only
1529
applied to the test set where they are given. The default test set type
1530
accepts the following options:
1531
- `verbose::Bool`: if `true`, the result summary of the nested testsets is shown even
1532
  when they all pass (the default is `false`).
1533
- `showtiming::Bool`: if `true`, the duration of each displayed testset is shown
1534
  (the default is `true`).
1535
- `failfast::Bool`: if `true`, any test failure or error will cause the testset and any
1536
  child testsets to return immediately (the default is `false`).
1537
  This can also be set globally via the env var `JULIA_TEST_FAILFAST`.
1538
- `rng::Random.AbstractRNG`: use the given random number generator (RNG) as the global one
1539
  for the testset.  `rng` must be `copy!`-able.  This option can be useful to locally
1540
  reproduce stochastic test failures which only depend on the state of the global RNG.
1541

1542
!!! compat "Julia 1.8"
1543
    `@testset test_func()` requires at least Julia 1.8.
1544

1545
!!! compat "Julia 1.9"
1546
    `failfast` requires at least Julia 1.9.
1547

1548
!!! compat "Julia 1.12"
1549
    The `rng` option requires at least Julia 1.12.
1550

1551
The description string accepts interpolation from the loop indices.
1552
If no description is provided, one is constructed based on the variables.
1553
If a function call is provided, its name will be used.
1554
Explicit description strings override this behavior.
1555

1556
By default the `@testset` macro will return the testset object itself, though
1557
this behavior can be customized in other testset types. If a `for` loop is used
1558
then the macro collects and returns a list of the return values of the `finish`
1559
method, which by default will return a list of the testset objects used in
1560
each iteration.
1561

1562
Before the execution of the body of a `@testset`, there is an implicit
1563
call to `copy!(Random.default_rng(), rng)` where `rng` is the RNG of the current task, or
1564
the value of the RNG passed via the `rng` option.
1565
Moreover, after the execution of the body, the state of the global RNG is
1566
restored to what it was before the `@testset`. This is meant to ease
1567
reproducibility in case of failure, and to allow seamless
1568
re-arrangements of `@testset`s regardless of their side-effect on the
1569
global RNG state.
1570

1571
!!! note "RNG of nested testsets"
1572
    Unless changed with the `rng` option, the same RNG is set at the beginning of all
1573
    nested testsets.  The RNG printed to screen when a testset has failures is the global RNG of
1574
    the outermost testset even if inner testsets have different RNGs manually set by the user.
1575

1576
## Examples
1577
```jldoctest; filter = r"trigonometric identities |    4      4  [0-9\\.]+s"
1578
julia> @testset "trigonometric identities" begin
1579
           θ = 2/3*π
1580
           @test sin(-θ) ≈ -sin(θ)
1581
           @test cos(-θ) ≈ cos(θ)
1582
           @test sin(2θ) ≈ 2*sin(θ)*cos(θ)
1583
           @test cos(2θ) ≈ cos(θ)^2 - sin(θ)^2
1584
       end;
1585
Test Summary:            | Pass  Total  Time
1586
trigonometric identities |    4      4  0.2s
1587
```
1588

1589
# `@testset for`
1590

1591
When `@testset for` is used, the macro starts a new test for each iteration of
1592
the provided loop. The semantics of each test set are otherwise identical to that
1593
of that `begin/end` case (as if used for each loop iteration).
1594

1595
# `@testset let`
1596

1597
When `@testset let` is used, the macro starts a *transparent* test set with
1598
the given object added as a context object to any failing test contained
1599
therein. This is useful when performing a set of related tests on one larger
1600
object and it is desirable to print this larger object when any of the
1601
individual tests fail. Transparent test sets do not introduce additional levels
1602
of nesting in the test set hierarchy and are passed through directly to the
1603
parent test set (with the context object appended to any failing tests.)
1604

1605
!!! compat "Julia 1.9"
1606
    `@testset let` requires at least Julia 1.9.
1607

1608
!!! compat "Julia 1.10"
1609
    Multiple `let` assignments are supported since Julia 1.10.
1610

1611
# Special implicit world age increment for `@testset begin`
1612

1613
World age inside `@testset begin` increments implicitly after every statement.
1614
This matches the behavior of ordinary toplevel code, but not that of ordinary
1615
`begin/end` blocks, i.e. with respect to world age, `@testset begin` behaves
1616
as if the body of the `begin/end` block was written at toplevel.
1617

1618
## Examples
1619
```jldoctest
1620
julia> @testset let logi = log(im)
1621
           @test imag(logi) == π/2
1622
           @test !iszero(real(logi))
1623
       end
1624
Test Failed at none:3
1625
  Expression: !(iszero(real(logi)))
1626
     Context: logi = 0.0 + 1.5707963267948966im
1627

1628
ERROR: There was an error during testing
1629

1630
julia> @testset let logi = log(im), op = !iszero
1631
           @test imag(logi) == π/2
1632
           @test op(real(logi))
1633
       end
1634
Test Failed at none:3
1635
  Expression: op(real(logi))
1636
     Context: logi = 0.0 + 1.5707963267948966im
1637
              op = !iszero
1638

1639
ERROR: There was an error during testing
1640
```
1641
"""
1642
macro testset(args...)
15✔
1643
    isempty(args) && error("No arguments to @testset")
15✔
1644

1645
    tests = args[end]
15✔
1646

1647
    # Determine if a single block or for-loop style
1648
    if !isa(tests,Expr) || (tests.head !== :for && tests.head !== :block && tests.head !== :call && tests.head !== :let)
30✔
1649

1650
        error("Expected function call, begin/end block or for loop as argument to @testset")
×
1651
    end
1652

1653
    FAIL_FAST[] = Base.get_bool_env("JULIA_TEST_FAILFAST", false)
15✔
1654

1655
    if tests.head === :for
15✔
1656
        return testset_forloop(args, tests, __source__)
2✔
1657
    elseif tests.head === :let
13✔
1658
        return testset_context(args, tests, __source__)
5✔
1659
    else
1660
        return testset_beginend_call(args, tests, __source__)
8✔
1661
    end
1662
end
1663

1664
trigger_test_failure_break(@nospecialize(err)) =
2✔
1665
    ccall(:jl_test_failure_breakpoint, Cvoid, (Any,), err)
1666

1667
"""
1668
Generate the code for an `@testset` with a `let` argument.
1669
"""
1670
function testset_context(args, ex, source)
5✔
1671
    desc, testsettype, options = parse_testset_args(args[1:end-1])
5✔
1672
    if desc !== nothing || testsettype !== nothing
5✔
1673
        # Reserve this syntax if we ever want to allow this, but for now,
1674
        # just do the transparent context test set.
1675
        error("@testset with a `let` argument cannot be customized")
×
1676
    end
1677

1678
    let_ex = ex.args[1]
5✔
1679

1680
    if Meta.isexpr(let_ex, :(=))
5✔
1681
        contexts = Any[let_ex.args[1]]
5✔
1682
    elseif Meta.isexpr(let_ex, :block)
×
1683
        contexts = Any[]
×
1684
        for assign_ex in let_ex.args
×
1685
            if Meta.isexpr(assign_ex, :(=))
×
1686
                push!(contexts, assign_ex.args[1])
×
1687
            else
1688
                error("Malformed `let` expression is given")
×
1689
            end
1690
        end
×
1691
    else
1692
        error("Malformed `let` expression is given")
×
1693
    end
1694
    reverse!(contexts)
5✔
1695

1696
    test_ex = ex.args[2]
5✔
1697

1698
    ex.args[2] = quote
5✔
1699
        $(map(contexts) do context
7✔
1700
            :($push_testset($(ContextTestSet)($(QuoteNode(context)), $context; $options...)))
5✔
1701
        end...)
1702
        try
7✔
1703
            $(test_ex)
7✔
1704
        finally
1705
            $(map(_->:($pop_testset()), contexts)...)
12✔
1706
        end
1707
    end
1708

1709
    return esc(ex)
5✔
1710
end
1711

1712
function insert_toplevel_latestworld(@nospecialize(tests))
8✔
1713
    isa(tests, Expr) || return tests
8✔
1714
    (tests.head !== :block) && return tests
8✔
1715
    ret = Expr(:block)
8✔
1716
    for arg in tests.args
8✔
1717
        push!(ret.args, arg)
98✔
1718
        if isa(arg, LineNumberNode) ||
147✔
1719
          (isa(arg, Expr) && arg.head in (:latestworld, :var"latestworld-if-toplevel"))
1720
            continue
49✔
1721
        end
1722
        push!(ret.args, Expr(:var"latestworld-if-toplevel"))
49✔
1723
    end
98✔
1724
    return ret
8✔
1725
end
1726

1727
"""
1728
Generate the code for a `@testset` with a function call or `begin`/`end` argument
1729
"""
1730
function testset_beginend_call(args, tests, source)
8✔
1731
    desc, testsettype, options = parse_testset_args(args[1:end-1])
8✔
1732
    if desc === nothing
8✔
1733
        if tests.head === :call
×
1734
            desc = string(tests.args[1]) # use the function name as test name
×
1735
        else
1736
            desc = "test set"
×
1737
        end
1738
    end
1739
    # If we're at the top level we'll default to DefaultTestSet. Otherwise
1740
    # default to the type of the parent testset
1741
    if testsettype === nothing
8✔
1742
        testsettype = :(get_testset_depth() == 0 ? DefaultTestSet : typeof(get_testset()))
8✔
1743
    end
1744

1745
    tests = insert_toplevel_latestworld(tests)
8✔
1746

1747
    # Generate a block of code that initializes a new testset, adds
1748
    # it to the task local storage, evaluates the test(s), before
1749
    # finally removing the testset and giving it a chance to take
1750
    # action (such as reporting the results)
1751
    ex = quote
16✔
1752
        _check_testset($testsettype, $(QuoteNode(testsettype.args[1])))
5✔
1753
        local ret
1754
        local ts = if ($testsettype === $DefaultTestSet) && $(isa(source, LineNumberNode))
5✔
1755
            $(testsettype)($desc; source=$(QuoteNode(source.file)), $options...)
5✔
1756
        else
1757
            $(testsettype)($desc; $options...)
3✔
1758
        end
1759
        push_testset(ts)
3✔
1760
        # we reproduce the logic of guardseed, but this function
1761
        # cannot be used as it changes slightly the semantic of @testset,
1762
        # by wrapping the body in a function
1763
        local default_rng_orig = copy(default_rng())
3✔
1764
        local tls_seed_orig = copy(Random.get_tls_seed())
3✔
1765
        local tls_seed = isnothing(get_rng(ts)) ? set_rng!(ts, tls_seed_orig) : get_rng(ts)
3✔
1766
        try
3✔
1767
            # default RNG is reset to its state from last `seed!()` to ease reproduce a failed test
1768
            copy!(Random.default_rng(), tls_seed)
4✔
1769
            copy!(Random.get_tls_seed(), Random.default_rng())
3✔
1770
            let
3✔
1771
                $(esc(tests))
3✔
1772
            end
1773
        catch err
1774
            err isa InterruptException && rethrow()
×
1775
            # something in the test block threw an error. Count that as an
1776
            # error in this test set
1777
            trigger_test_failure_break(err)
×
1778
            if err isa FailFastError
×
1779
                get_testset_depth() > 1 ? rethrow() : failfast_print()
×
1780
            else
1781
                record(ts, Error(:nontest_error, Expr(:tuple), err, Base.current_exceptions(), $(QuoteNode(source))))
3✔
1782
            end
1783
        finally
1784
            copy!(default_rng(), default_rng_orig)
3✔
1785
            copy!(Random.get_tls_seed(), tls_seed_orig)
3✔
1786
            pop_testset()
3✔
1787
            ret = finish(ts)
3✔
1788
        end
1789
        ret
2✔
1790
    end
1791
    # preserve outer location if possible
1792
    if tests isa Expr && tests.head === :block && !isempty(tests.args) && tests.args[1] isa LineNumberNode
8✔
1793
        ex = Expr(:block, tests.args[1], ex)
8✔
1794
    end
1795
    return ex
8✔
1796
end
1797

1798
function failfast_print()
×
1799
    printstyled("\nFail-fast enabled:"; color = Base.error_color(), bold=true)
×
1800
    printstyled(" Fail or Error occurred\n\n"; color = Base.error_color())
×
1801
end
1802

1803
"""
1804
Generate the code for a `@testset` with a `for` loop argument
1805
"""
1806
function testset_forloop(args, testloop, source)
2✔
1807
    # Pull out the loop variables. We might need them for generating the
1808
    # description and we'll definitely need them for generating the
1809
    # comprehension expression at the end
1810
    loopvars = Expr[]
2✔
1811
    if testloop.args[1].head === :(=)
2✔
1812
        push!(loopvars, testloop.args[1])
2✔
1813
    elseif testloop.args[1].head === :block
×
1814
        for loopvar in testloop.args[1].args
×
1815
            push!(loopvars, loopvar)
×
1816
        end
×
1817
    else
1818
        error("Unexpected argument to @testset")
×
1819
    end
1820

1821
    desc, testsettype, options = parse_testset_args(args[1:end-1])
2✔
1822

1823
    if desc === nothing
2✔
1824
        # No description provided. Generate from the loop variable names
1825
        v = loopvars[1].args[1]
×
1826
        desc = Expr(:string, "$v = ", esc(v)) # first variable
×
1827
        for l = loopvars[2:end]
×
1828
            v = l.args[1]
×
1829
            push!(desc.args, ", $v = ")
×
1830
            push!(desc.args, esc(v))
×
1831
        end
×
1832
    end
1833

1834
    if testsettype === nothing
2✔
1835
        testsettype = :(get_testset_depth() == 0 ? DefaultTestSet : typeof(get_testset()))
2✔
1836
    end
1837

1838
    # Uses a similar block as for `@testset`, except that it is
1839
    # wrapped in the outer loop provided by the user
1840
    tests = testloop.args[2]
2✔
1841
    blk = quote
4✔
1842
        _check_testset($testsettype, $(QuoteNode(testsettype.args[1])))
44✔
1843
        # Trick to handle `break` and `continue` in the test code before
1844
        # they can be handled properly by `finally` lowering.
1845
        if !first_iteration
22✔
1846
            pop_testset()
20✔
1847
            finish_errored = true
20✔
1848
            push!(arr, finish(ts))
20✔
1849
            finish_errored = false
20✔
1850
            copy!(default_rng(), tls_seed)
20✔
1851
        end
1852
        ts = if ($testsettype === $DefaultTestSet) && $(isa(source, LineNumberNode))
44✔
1853
            $(testsettype)($desc; source=$(QuoteNode(source.file)), $options..., rng=tls_seed)
44✔
1854
        else
1855
            $(testsettype)($desc; $options...)
22✔
1856
        end
1857
        push_testset(ts)
22✔
1858
        first_iteration = false
22✔
1859
        try
22✔
1860
            $(esc(tests))
22✔
1861
        catch err
1862
            err isa InterruptException && rethrow()
×
1863
            # Something in the test block threw an error. Count that as an
1864
            # error in this test set
1865
            trigger_test_failure_break(err)
×
1866
            if !isa(err, FailFastError)
×
1867
                record(ts, Error(:nontest_error, Expr(:tuple), err, Base.current_exceptions(), $(QuoteNode(source))))
22✔
1868
            end
1869
        end
1870
    end
1871
    quote
2✔
1872
        local arr = Vector{Any}()
2✔
1873
        local first_iteration = true
2✔
1874
        local ts
1875
        local rng_option = get($(options), :rng, nothing)
2✔
1876
        local finish_errored = false
2✔
1877
        local default_rng_orig = copy(default_rng())
2✔
1878
        local tls_seed_orig = copy(Random.get_tls_seed())
2✔
1879
        local tls_seed = isnothing(rng_option) ? copy(Random.get_tls_seed()) : rng_option
2✔
1880
        copy!(Random.default_rng(), tls_seed)
2✔
1881
        try
2✔
1882
            let
1883
                $(Expr(:for, Expr(:block, [esc(v) for v in loopvars]...), blk))
2✔
1884
            end
1885
        finally
1886
            # Handle `return` in test body
1887
            if !first_iteration && !finish_errored
2✔
1888
                pop_testset()
2✔
1889
                push!(arr, finish(ts))
2✔
1890
            end
1891
            copy!(default_rng(), default_rng_orig)
2✔
1892
            copy!(Random.get_tls_seed(), tls_seed_orig)
2✔
1893
        end
1894
        arr
2✔
1895
    end
1896
end
1897

1898
"""
1899
Parse the arguments to the `@testset` macro to pull out the description,
1900
Testset Type, and options. Generally this should be called with all the macro
1901
arguments except the last one, which is the test expression itself.
1902
"""
1903
function parse_testset_args(args)
10✔
1904
    desc = nothing
15✔
1905
    testsettype = nothing
15✔
1906
    options = :(Dict{Symbol, Any}())
15✔
1907
    for arg in args
15✔
1908
        # a standalone symbol is assumed to be the test set we should use
1909
        # the same is true for a symbol that's not exported from a module
1910
        if isa(arg, Symbol) || Base.isexpr(arg, :.)
10✔
1911
            if testsettype !== nothing
×
1912
                msg = """Multiple testset types provided to @testset. \
×
1913
                    This is deprecated and may error in the future."""
1914
                Base.depwarn(msg, :testset_multiple_testset_types; force=true)
×
1915
            end
1916
            testsettype = esc(arg)
×
1917
        # a string is the description
1918
        elseif isa(arg, AbstractString) || (isa(arg, Expr) && arg.head === :string)
10✔
1919
            if desc !== nothing
10✔
1920
                msg = """Multiple descriptions provided to @testset. \
×
1921
                    This is deprecated and may error in the future."""
1922
                Base.depwarn(msg, :testset_multiple_descriptions; force=true)
×
1923
            end
1924
            desc = esc(arg)
10✔
1925
        # an assignment is an option
1926
        elseif isa(arg, Expr) && arg.head === :(=)
×
1927
            # we're building up a Dict literal here
1928
            key = Expr(:quote, arg.args[1])
×
1929
            push!(options.args, Expr(:call, :(=>), key, esc(arg.args[2])))
×
1930
        else
1931
            error("Unexpected argument $arg to @testset")
×
1932
        end
1933
    end
10✔
1934

1935
    (desc, testsettype, options)
15✔
1936
end
1937

1938
#-----------------------------------------------------------------------
1939
# Various helper methods for test sets
1940

1941
"""
1942
    get_testset()
1943

1944
Retrieve the active test set from the task's local storage. If no
1945
test set is active, use the fallback default test set.
1946
"""
1947
function get_testset()
748✔
1948
    testsets = get(task_local_storage(), :__BASETESTNEXT__, AbstractTestSet[])
748✔
1949
    return isempty(testsets) ? fallback_testset : testsets[end]
748✔
1950
end
1951

1952
"""
1953
    push_testset(ts::AbstractTestSet)
1954

1955
Adds the test set to the `task_local_storage`.
1956
"""
1957
function push_testset(ts::AbstractTestSet)
78✔
1958
    testsets = get(task_local_storage(), :__BASETESTNEXT__, AbstractTestSet[])
78✔
1959
    push!(testsets, ts)
78✔
1960
    setindex!(task_local_storage(), testsets, :__BASETESTNEXT__)
78✔
1961
end
1962

1963
"""
1964
    pop_testset()
1965

1966
Pops the last test set added to the `task_local_storage`. If there are no
1967
active test sets, returns the fallback default test set.
1968
"""
1969
function pop_testset()
77✔
1970
    testsets = get(task_local_storage(), :__BASETESTNEXT__, AbstractTestSet[])
77✔
1971
    ret = isempty(testsets) ? fallback_testset : pop!(testsets)
154✔
1972
    setindex!(task_local_storage(), testsets, :__BASETESTNEXT__)
77✔
1973
    return ret
77✔
1974
end
1975

1976
"""
1977
    get_testset_depth()
1978

1979
Return the number of active test sets, not including the default test set
1980
"""
1981
function get_testset_depth()
276✔
1982
    testsets = get(task_local_storage(), :__BASETESTNEXT__, AbstractTestSet[])
276✔
1983
    return length(testsets)
276✔
1984
end
1985

1986
_args_and_call((args..., f)...; kwargs...) = (args, kwargs, f(args...; kwargs...))
×
1987
_materialize_broadcasted(f, args...) = Broadcast.materialize(Broadcast.broadcasted(f, args...))
×
1988

1989
"""
1990
    @inferred [AllowedType] f(x)
1991

1992
Tests that the call expression `f(x)` returns a value of the same type inferred by the
1993
compiler. It is useful to check for type stability.
1994

1995
`f(x)` can be any call expression. Returns the result of `f(x)` if the types match, and an
1996
`Error` `Result` if it finds different types.
1997

1998
Optionally, `AllowedType` relaxes the test, by making it pass when either the type of `f(x)`
1999
matches the inferred type modulo `AllowedType`, or when the return type is a subtype of
2000
`AllowedType`. This is useful when testing type stability of functions returning a small
2001
union such as `Union{Nothing, T}` or `Union{Missing, T}`.
2002

2003
```jldoctest; setup = :(using InteractiveUtils; using Base: >), filter = r"begin\\n(.|\\n)*end"
2004
julia> f(a) = a > 1 ? 1 : 1.0
2005
f (generic function with 1 method)
2006

2007
julia> typeof(f(2))
2008
Int64
2009

2010
julia> @code_warntype f(2)
2011
MethodInstance for f(::Int64)
2012
  from f(a) @ Main none:1
2013
Arguments
2014
  #self#::Core.Const(f)
2015
  a::Int64
2016
Body::UNION{FLOAT64, INT64}
2017
1 ─ %1 = :>::Core.Const(>)
2018
│   %2 = (%1)(a, 1)::Bool
2019
└──      goto #3 if not %2
2020
2 ─      return 1
2021
3 ─      return 1.0
2022

2023
julia> @inferred f(2)
2024
ERROR: return type Int64 does not match inferred return type Union{Float64, Int64}
2025
[...]
2026

2027
julia> @inferred max(1, 2)
2028
2
2029

2030
julia> g(a) = a < 10 ? missing : 1.0
2031
g (generic function with 1 method)
2032

2033
julia> @inferred g(20)
2034
ERROR: return type Float64 does not match inferred return type Union{Missing, Float64}
2035
[...]
2036

2037
julia> @inferred Missing g(20)
2038
1.0
2039

2040
julia> h(a) = a < 10 ? missing : f(a)
2041
h (generic function with 1 method)
2042

2043
julia> @inferred Missing h(20)
2044
ERROR: return type Int64 does not match inferred return type Union{Missing, Float64, Int64}
2045
[...]
2046
```
2047
"""
2048
macro inferred(ex)
2049
    _inferred(ex, __module__)
2050
end
2051
macro inferred(allow, ex)
2052
    _inferred(ex, __module__, allow)
2053
end
2054
function _inferred(ex, mod, allow = :(Union{}))
×
2055
    if Meta.isexpr(ex, :ref)
×
2056
        ex = Expr(:call, :getindex, ex.args...)
×
2057
    end
2058
    Meta.isexpr(ex, :call)|| error("@inferred requires a call expression")
×
2059
    farg = ex.args[1]
×
2060
    if isa(farg, Symbol) && farg !== :.. && first(string(farg)) == '.'
×
2061
        farg = Symbol(string(farg)[2:end])
×
2062
        ex = Expr(:call, GlobalRef(Test, :_materialize_broadcasted),
×
2063
            farg, ex.args[2:end]...)
2064
    end
2065
    result = let ex = ex
×
2066
        quote
×
2067
            let allow = $(esc(allow))
×
2068
                allow isa Type || throw(ArgumentError("@inferred requires a type as second argument"))
×
2069
                $(if any(@nospecialize(a)->(Meta.isexpr(a, :kw) || Meta.isexpr(a, :parameters)), ex.args)
×
2070
                    # Has keywords
2071
                    args = gensym()
×
2072
                    kwargs = gensym()
×
2073
                    quote
×
2074
                        $(esc(args)), $(esc(kwargs)), result = $(esc(Expr(:call, _args_and_call, ex.args[2:end]..., ex.args[1])))
×
2075
                        inftype = $(gen_call_with_extracted_types(mod, Base.infer_return_type, :($(ex.args[1])($(args)...; $(kwargs)...))))
×
2076
                    end
2077
                else
2078
                    # No keywords
2079
                    quote
×
2080
                        args = ($([esc(ex.args[i]) for i = 2:length(ex.args)]...),)
×
2081
                        result = $(esc(ex.args[1]))(args...)
×
2082
                        inftype = Base.infer_return_type($(esc(ex.args[1])), Base.typesof(args...))
×
2083
                    end
2084
                end)
2085
                rettype = result isa Type ? Type{result} : typeof(result)
×
2086
                rettype <: allow || rettype == typesplit(inftype, allow) || error("return type $rettype does not match inferred return type $inftype")
×
2087
                result
×
2088
            end
2089
        end
2090
    end
2091
    return remove_linenums!(result)
×
2092
end
2093

2094
function is_in_mods(m::Module, recursive::Bool, mods)
×
2095
    while true
×
2096
        m in mods && return true
×
2097
        recursive || return false
×
2098
        p = parentmodule(m)
×
2099
        p === m && return false
×
2100
        m = p
×
2101
    end
×
2102
end
2103

2104
"""
2105
    detect_ambiguities(mod1, mod2...; recursive=false,
2106
                                      ambiguous_bottom=false,
2107
                                      allowed_undefineds=nothing)
2108

2109
Return a vector of `(Method,Method)` pairs of ambiguous methods
2110
defined in the specified modules.
2111
Use `recursive=true` to test in all submodules.
2112

2113
`ambiguous_bottom` controls whether ambiguities triggered only by
2114
`Union{}` type parameters are included; in most cases you probably
2115
want to set this to `false`. See [`Base.isambiguous`](@ref).
2116

2117
See [`Test.detect_unbound_args`](@ref) for an explanation of
2118
`allowed_undefineds`.
2119

2120
!!! compat "Julia 1.8"
2121
    `allowed_undefineds` requires at least Julia 1.8.
2122
"""
2123
function detect_ambiguities(mods::Module...;
×
2124
                            recursive::Bool = false,
2125
                            ambiguous_bottom::Bool = false,
2126
                            allowed_undefineds = nothing)
2127
    @nospecialize
×
2128
    ambs = Set{Tuple{Method,Method}}()
×
2129
    mods = collect(mods)::Vector{Module}
×
2130
    function sortdefs(m1::Method, m2::Method)
×
2131
        ord12 = cmp(m1.file, m2.file)
×
2132
        if ord12 == 0
×
2133
            ord12 = cmp(m1.line, m2.line)
×
2134
        end
2135
        return ord12 <= 0 ? (m1, m2) : (m2, m1)
×
2136
    end
2137
    function examine(mt::Core.MethodTable)
×
2138
        for m in Base.MethodList(mt)
×
2139
            m.sig == Tuple && continue # ignore Builtins
×
2140
            is_in_mods(parentmodule(m), recursive, mods) || continue
×
2141
            world = Base.get_world_counter()
×
2142
            ambig = Ref{Int32}(0)
×
2143
            ms = Base._methods_by_ftype(m.sig, nothing, -1, world, true, Ref(typemin(UInt)), Ref(typemax(UInt)), ambig)::Vector
×
2144
            ambig[] == 0 && continue
×
2145
            for match2 in ms
×
2146
                match2 = match2::Core.MethodMatch
×
2147
                m2 = match2.method
×
2148
                 if !(m === m2 || Base.morespecific(m2.sig, m.sig))
×
2149
                    if Base.isambiguous(m, m2; ambiguous_bottom)
×
2150
                        push!(ambs, sortdefs(m, m2))
×
2151
                    end
2152
                end
2153
            end
×
2154
        end
×
2155
    end
2156
    work = Base.loaded_modules_array()
×
2157
    filter!(mod -> mod === parentmodule(mod), work) # some items in loaded_modules_array are not top modules (really just Base)
×
2158
    while !isempty(work)
×
2159
        mod = pop!(work)
×
2160
        for n in names(mod, all = true)
×
2161
            Base.isdeprecated(mod, n) && continue
×
2162
            if !isdefined(mod, n)
×
2163
                if is_in_mods(mod, recursive, mods)
×
2164
                    if allowed_undefineds === nothing || GlobalRef(mod, n) ∉ allowed_undefineds
×
2165
                        println("Skipping ", mod, '.', n)  # typically stale exports
×
2166
                    end
2167
                end
2168
                continue
×
2169
            end
2170
            f = Base.unwrap_unionall(getfield(mod, n))
×
2171
            if isa(f, Module) && f !== mod && parentmodule(f) === mod && nameof(f) === n
×
2172
                push!(work, f)
×
2173
            elseif isa(f, DataType) && isdefined(f.name, :mt) && parentmodule(f) === mod && nameof(f) === n && f.name.mt !== Symbol.name.mt && f.name.mt !== DataType.name.mt
×
2174
                examine(f.name.mt)
×
2175
            end
2176
        end
×
2177
    end
×
2178
    examine(Symbol.name.mt)
×
2179
    examine(DataType.name.mt)
×
2180
    return collect(ambs)
×
2181
end
2182

2183
"""
2184
    detect_unbound_args(mod1, mod2...; recursive=false, allowed_undefineds=nothing)
2185

2186
Return a vector of `Method`s which may have unbound type parameters.
2187
Use `recursive=true` to test in all submodules.
2188

2189
By default, any undefined symbols trigger a warning. This warning can
2190
be suppressed by supplying a collection of `GlobalRef`s for which
2191
the warning can be skipped. For example, setting
2192

2193
```
2194
allowed_undefineds = Set([GlobalRef(Base, :active_repl),
2195
                          GlobalRef(Base, :active_repl_backend)])
2196
```
2197

2198
would suppress warnings about `Base.active_repl` and
2199
`Base.active_repl_backend`.
2200

2201
!!! compat "Julia 1.8"
2202
    `allowed_undefineds` requires at least Julia 1.8.
2203
"""
2204
function detect_unbound_args(mods...;
×
2205
                             recursive::Bool = false,
2206
                             allowed_undefineds=nothing)
2207
    @nospecialize mods
×
2208
    ambs = Set{Method}()
×
2209
    mods = collect(mods)::Vector{Module}
×
2210
    function examine(mt::Core.MethodTable)
×
2211
        for m in Base.MethodList(mt)
×
2212
            is_in_mods(parentmodule(m), recursive, mods) || continue
×
2213
            has_unbound_vars(m.sig) || continue
×
2214
            tuple_sig = Base.unwrap_unionall(m.sig)::DataType
×
2215
            if Base.isvatuple(tuple_sig)
×
2216
                params = tuple_sig.parameters[1:(end - 1)]
×
2217
                tuple_sig = Base.rewrap_unionall(Tuple{params...}, m.sig)
×
2218
                world = Base.get_world_counter()
×
2219
                mf = ccall(:jl_gf_invoke_lookup, Any, (Any, Any, UInt), tuple_sig, nothing, world)
×
2220
                if mf !== nothing && mf !== m && mf.sig <: tuple_sig
×
2221
                    continue
×
2222
                end
2223
            end
2224
            push!(ambs, m)
×
2225
        end
×
2226
    end
2227
    work = Base.loaded_modules_array()
×
2228
    filter!(mod -> mod === parentmodule(mod), work) # some items in loaded_modules_array are not top modules (really just Base)
×
2229
    while !isempty(work)
×
2230
        mod = pop!(work)
×
2231
        for n in names(mod, all = true)
×
2232
            Base.isdeprecated(mod, n) && continue
×
2233
            if !isdefined(mod, n)
×
2234
                if is_in_mods(mod, recursive, mods)
×
2235
                    if allowed_undefineds === nothing || GlobalRef(mod, n) ∉ allowed_undefineds
×
2236
                        println("Skipping ", mod, '.', n)  # typically stale exports
×
2237
                    end
2238
                end
2239
                continue
×
2240
            end
2241
            f = Base.unwrap_unionall(getfield(mod, n))
×
2242
            if isa(f, Module) && f !== mod && parentmodule(f) === mod && nameof(f) === n
×
2243
                push!(work, f)
×
2244
            elseif isa(f, DataType) && isdefined(f.name, :mt) && parentmodule(f) === mod && nameof(f) === n && f.name.mt !== Symbol.name.mt && f.name.mt !== DataType.name.mt
×
2245
                examine(f.name.mt)
×
2246
            end
2247
        end
×
2248
    end
×
2249
    examine(Symbol.name.mt)
×
2250
    examine(DataType.name.mt)
×
2251
    return collect(ambs)
×
2252
end
2253

2254
function has_unbound_vars(@nospecialize sig)
×
2255
    while sig isa UnionAll
×
2256
        var = sig.var
×
2257
        sig = sig.body
×
2258
        if !Core.Compiler.constrains_param(var, sig, #=covariant=#true, #=type_constrains=#true)
×
2259
            return true
×
2260
        end
2261
    end
×
2262
    return false
×
2263
end
2264

2265

2266
"""
2267
The `GenericString` can be used to test generic string APIs that program to
2268
the `AbstractString` interface, in order to ensure that functions can work
2269
with string types besides the standard `String` type.
2270
"""
2271
struct GenericString <: AbstractString
2272
    string::AbstractString
2273
end
2274
Base.ncodeunits(s::GenericString) = ncodeunits(s.string)::Int
×
2275
Base.codeunit(s::GenericString) = codeunit(s.string)::Type{<:Union{UInt8, UInt16, UInt32}}
×
2276
Base.codeunit(s::GenericString, i::Integer) = codeunit(s.string, i)::Union{UInt8, UInt16, UInt32}
×
2277
Base.isvalid(s::GenericString, i::Integer) = isvalid(s.string, i)::Bool
×
2278
Base.iterate(s::GenericString, i::Integer=1) = iterate(s.string, i)::Union{Nothing,Tuple{AbstractChar,Int}}
×
2279
Base.reverse(s::GenericString) = GenericString(reverse(s.string))
×
2280
Base.reverse(s::SubString{GenericString}) =
×
2281
    GenericString(typeof(s.string)(reverse(String(s))))
2282

2283
"""
2284
The `GenericSet` can be used to test generic set APIs that program to
2285
the `AbstractSet` interface, in order to ensure that functions can work
2286
with set types besides the standard `Set` and `BitSet` types.
2287
"""
2288
struct GenericSet{T} <: AbstractSet{T}
2289
    s::AbstractSet{T}
2290
end
2291

2292
"""
2293
The `GenericDict` can be used to test generic dict APIs that program to
2294
the `AbstractDict` interface, in order to ensure that functions can work
2295
with associative types besides the standard `Dict` type.
2296
"""
2297
struct GenericDict{K,V} <: AbstractDict{K,V}
2298
    s::AbstractDict{K,V}
2299
end
2300

2301
for G in (GenericSet, GenericDict)
2302
    @eval begin
2303
        Base.iterate(s::$G, state...) = iterate(s.s, state...)
×
2304
    end
2305
    for f in (:isempty, :length)
2306
        @eval begin
2307
            Base.$f(s::$G) = $f(s.s)
×
2308
        end
2309
    end
2310
end
2311

2312
Base.get(s::GenericDict, x, y) = get(s.s, x, y)
×
2313
Base.pop!(s::GenericDict, k) = pop!(s.s, k)
×
2314
Base.setindex!(s::GenericDict, v, k) = setindex!(s.s, v, k)
×
2315

2316
"""
2317
The `GenericArray` can be used to test generic array APIs that program to
2318
the `AbstractArray` interface, in order to ensure that functions can work
2319
with array types besides the standard `Array` type.
2320
"""
2321
struct GenericArray{T,N} <: AbstractArray{T,N}
2322
    a::Array{T,N}
2323
end
2324

2325
GenericArray{T}(args...) where {T} = GenericArray(Array{T}(args...))
×
2326
GenericArray{T,N}(args...) where {T,N} = GenericArray(Array{T,N}(args...))
×
2327

2328
"""
2329
The `GenericOrder` can be used to test APIs for their support
2330
of generic ordered types.
2331
"""
2332
struct GenericOrder{T}
2333
    val::T
2334
end
2335
Base.isless(x::GenericOrder, y::GenericOrder) = isless(x.val,y.val)
×
2336

2337
Base.keys(a::GenericArray) = keys(a.a)
×
2338
Base.axes(a::GenericArray) = axes(a.a)
×
2339
Base.length(a::GenericArray) = length(a.a)
×
2340
Base.size(a::GenericArray) = size(a.a)
×
2341
Base.IndexStyle(::Type{<:GenericArray}) = IndexLinear()
×
2342
Base.getindex(a::GenericArray, i::Int) = a.a[i]
×
2343
Base.setindex!(a::GenericArray, x, i::Int) = a.a[i] = x
×
2344

2345
Base.similar(A::GenericArray, s::Integer...) = GenericArray(similar(A.a, s...))
×
2346

2347
"`guardseed(f)` runs the function `f()` and then restores the
2348
state of the global RNG as it was before."
2349
function guardseed(f::Function, r::AbstractRNG=default_rng())
×
2350
    old = copy(r)
×
2351
    try
×
2352
        f()
×
2353
    finally
2354
        copy!(r, old)
×
2355
    end
2356
end
2357

2358
"`guardseed(f, seed)` is equivalent to running `Random.seed!(seed); f()` and
2359
then restoring the state of the global RNG as it was before."
2360
guardseed(f::Function, seed::Union{Vector{UInt64},Vector{UInt32},Integer,NTuple{4,UInt64}}) = guardseed() do
×
2361
    Random.seed!(seed)
×
2362
    f()
×
2363
end
2364

2365
function _check_bitarray_consistency(B::BitArray{N}) where N
×
2366
    n = length(B)
×
2367
    if N ≠ 1
×
2368
        all(d ≥ 0 for d in B.dims) || (@warn("Negative d in dims: $(B.dims)"); return false)
×
2369
        prod(B.dims) ≠ n && (@warn("Inconsistent dims/len: prod(dims)=$(prod(B.dims)) len=$n"); return false)
×
2370
    end
2371
    Bc = B.chunks
×
2372
    nc = length(Bc)
×
2373
    nc == Base.num_bit_chunks(n) || (@warn("Incorrect chunks length for length $n: expected=$(Base.num_bit_chunks(n)) actual=$nc"); return false)
×
2374
    n == 0 && return true
×
2375
    Bc[end] & Base._msk_end(n) == Bc[end] || (@warn("Nonzero bits in chunk after `BitArray` end"); return false)
×
2376
    return true
×
2377
end
2378

2379
include("logging.jl")
2380
include("precompile.jl")
2381

2382
end # module
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

© 2025 Coveralls, Inc