/
githubmirror
/
julia
Обзор
Документация
Войти
/
githubmirror
/
julia
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
stdlib/Test/test/runtests.jl
2 553 строки
88 KB
Andy Dienes
Test: restore implicit world age increment in `@testset for` (#62605)
03 авг 2026, 03:37
Не верифицирован
03 авг 2026, 03:37
1ce0686
Код
Авторство
О чём код?
# This file is a part of Julia. License is MIT: https://julialang.org/license using Test, Random using Test: guardseed, _should_escape_call, _escape_call using Serialization using Distributed: RemoteException import Logging: Debug, Info, Warn, with_logger @test isempty(Test.detect_closure_boxes(Test)) @testset "@test" begin atol = 1 a = (; atol=2) @test true @test 1 == 1 @test 1 != 2 @test ==(1, 1) @test ==((1, 1)...) @test 1 ≈ 2 atol=1 @test strip("\t hi \n") == "hi" @test strip("\t this should fail \n") != "hi" @test isequal(1, 1) @test isapprox(1, 1, atol=0.1) @test isapprox(1, 1; atol=0.1) @test isapprox(1, 1; [(:atol, 0)]...) @test isapprox(1, 2; atol) @test isapprox(1, 3; a.atol) # Test custom .. operator (not a broadcast operator) ..(x, y) = x == y @test 'a' .. 'a' @test !('a' .. 'b') end module ClosureBoxTest function boxed() x = 1 inner() = (x += 1) inner() end module Sub function boxed_sub() x = 0 inner() = (x += 1) inner() end end end module ClosureBoxRedefTest function boxed() x = 1 inner() = (x += 1) inner() end end @testset "detect_closure_boxes" begin boxes = Test.detect_closure_boxes(ClosureBoxTest) @test any(p -> p.first.name === :boxed, boxes) @test any(p -> p.first.name === :boxed_sub, boxes) sub_boxes = Test.detect_closure_boxes(ClosureBoxTest.Sub) @test any(p -> p.first.name === :boxed_sub, sub_boxes) @test all(p -> parentmodule(p.first) === ClosureBoxTest.Sub, sub_boxes) @test isempty(Test.detect_closure_boxes()) # _all version checks all loaded modules all_boxes = Test.detect_closure_boxes_all_modules() @test any(p -> p.first.name === :boxed, all_boxes) # Redefinition should drop closure boxes from shadowed methods. @test !isempty(Test.detect_closure_boxes(ClosureBoxRedefTest)) @eval ClosureBoxRedefTest begin function boxed() x = 1 inner() = x + 1 inner() end end @test isempty(Test.detect_closure_boxes(ClosureBoxRedefTest)) end @testset "@test with skip/broken kwargs" begin # Make sure the local variables can be used in conditions a = 1 @test 2 + 2 == 4 broken=false @test error() broken=true @test !Sys.iswindows() broken=Sys.iswindows() @test 1 ≈ 2 atol=1 broken=a==2 @test false skip=true @test true skip=false @test Grogu skip=isone(a) @test 41 ≈ 42 rtol=1 skip=false end @testset "@test keyword precedence" begin atol = 2 # post-semicolon keyword, suffix keyword, pre-semicolon keyword @test isapprox(1, 2, atol=0) atol=1 @test isapprox(1, 3, atol=0; atol=2) atol=1 @test isapprox(1, 2, atol=0; atol) @test isapprox(1, 3, atol=0; atol) atol=1 end @testset "@test should only evaluate the arguments once" begin g = Int[] f = (x) -> (push!(g, x); x) @test f(1) == 1 @test g == [1] empty!(g) @test isequal(f(2), 2) @test g == [2] end @testset "@test_broken with fail" begin @test_broken false @test_broken 1 == 2 @test_broken 1 != 1 @test_broken strip("\t hi \n") != "hi" @test_broken strip("\t this should fail \n") == "hi" end @testset "@test_broken with errors" begin @test_broken error() @test_broken absolute_nonsense end @testset "@test_skip" begin @test_skip error() @test_skip true @test_skip false @test_skip gobbeldygook end @testset "@test_warn" begin @test 1234 === @test_nowarn(1234) @test 5678 === @test_warn("WARNING: foo", begin println(stderr, "WARNING: foo"); 5678; end) let a @test_throws UndefVarError(:a, :local) a @test_nowarn a = 1 @test a === 1 end end @testset "@test and elements of an array" begin a = Array{Float64, 5}(undef, 2, 2, 2, 2, 2) a[1, 1, 1, 1, 1] = 10 @test a[1, 1, 1, 1, 1] == 10 @test a[1, 1, 1, 1, 1] != 2 end @test rand() != rand() @testset "Pass - exception" begin @test endswith(sprint(show, @test_throws ErrorException error()), "Thrown: ErrorException") @test endswith(sprint(show, @test_throws ErrorException("test") error("test")), "Thrown: ErrorException") @test endswith(sprint(show, @test_throws "a test" error("a test")), "Message: \"a test\"") @test occursin("Message: \"DomainError", sprint(show, @test_throws r"sqrt\([Cc]omplex" sqrt(-1))) @test endswith(sprint(show, @test_throws str->occursin("a t", str) error("a test")), "Message: \"a test\"") @test endswith(sprint(show, @test_throws ["BoundsError", "access", "1-element", "at index [2]"] [1][2]), "Message: \"BoundsError: attempt to access 1-element Vector{$Int} at index [2]\"") @test_throws "\"" throw("\"") @test_throws Returns(false) throw(Returns(false)) end @testset "Pass - exception with pattern (3-arg form)" begin # Test 3-argument form: @test_throws ExceptionType pattern expr @test_throws ErrorException "error foo" error("error foo 1") @test_throws DomainError r"sqrt.*negative" sqrt(-1) @test_throws BoundsError "at index [2]" [1][2] @test_throws ErrorException ["error", "foo"] error("error foo bar") # Test with function pattern @test_throws ErrorException (s -> occursin("foo", s)) error("error foo bar") # Test output format let result = @test_throws ErrorException "error foo" error("error foo 1") output = sprint(show, result) @test occursin("Test Passed", output) @test occursin("Thrown: ErrorException", output) end end # Test printing of Fail results include("nothrow_testset.jl") # Test @test_throws with broken/skip keywords @testset "@test_throws broken keyword" begin # broken=false should behave normally @test_throws ErrorException error("test") broken=false # broken=true with no exception should record as Broken let results = @testset NoThrowTestSet begin @test_throws ErrorException 1+1 broken=true end @test length(results) == 1 @test results[1] isa Test.Broken @test results[1].test_type === :test_throws end # broken=true with wrong exception type should record as Broken let results = @testset NoThrowTestSet begin @test_throws BoundsError error("wrong type") broken=true end @test length(results) == 1 @test results[1] isa Test.Broken end # broken=true with correct exception should record as Error (unexpected pass) let results = @testset NoThrowTestSet begin @test_throws ErrorException error("test") broken=true end @test length(results) == 1 @test results[1] isa Test.Error @test results[1].test_type === :test_unbroken end end @testset "@test_throws skip keyword" begin # skip=true should record Broken(:skipped) and not execute let results = @testset NoThrowTestSet begin @test_throws ErrorException error("should not run") skip=true end @test length(results) == 1 @test results[1] isa Test.Broken @test results[1].test_type === :skipped end # skip=false should behave normally @test_throws ErrorException error("test") skip=false end @testset "@test_throws context keyword" begin # context should be included in failure output let fails = @testset NoThrowTestSet begin @test_throws ErrorException 1+1 context="extra info" end @test length(fails) == 1 @test fails[1] isa Test.Fail @test fails[1].context == "\"extra info\"" end end @testset "@test_warn/@test_nowarn broken/skip keywords" begin # @test_warn with broken=true when test fails (no warning) should be Broken let results = @testset NoThrowTestSet begin @test_warn "expected warning" 1+1 broken=true end @test length(results) == 1 @test results[1] isa Test.Broken end # @test_warn with broken=true when test passes should be Error (unexpected pass) let results = @testset NoThrowTestSet begin @test_warn "foo" (println(stderr, "foo"); 1) broken=true end @test length(results) == 1 @test results[1] isa Test.Error @test results[1].test_type === :test_unbroken end # @test_nowarn with broken=true when test fails (has warning) should be Broken let results = @testset NoThrowTestSet begin @test_nowarn (println(stderr, "oops"); 1) broken=true end @test length(results) == 1 @test results[1] isa Test.Broken end # @test_nowarn with broken=true when test passes should be Error (unexpected pass) let results = @testset NoThrowTestSet begin @test_nowarn 1+1 broken=true end @test length(results) == 1 @test results[1] isa Test.Error @test results[1].test_type === :test_unbroken end # skip=true should record Broken(:skipped) and not execute let results = @testset NoThrowTestSet begin @test_warn "foo" error("should not run") skip=true end @test length(results) == 1 @test results[1] isa Test.Broken @test results[1].test_type === :skipped end let results = @testset NoThrowTestSet begin @test_nowarn error("should not run") skip=true end @test length(results) == 1 @test results[1] isa Test.Broken @test results[1].test_type === :skipped end end @testset "@test_warn/@test_nowarn failure display" begin # @test_warn with string pattern - failure shows expected and captured stderr let results = @testset NoThrowTestSet begin @test_warn "expected" println(stderr, "wrong") end @test length(results) == 1 fail = results[1] @test fail isa Test.Fail @test fail.test_type === :test_warn @test fail.data == "\"expected\" (occursin)" @test fail.value == "\"wrong\\n\"" str = sprint(show, fail) @test occursin("Expected stderr:", str) @test occursin("Captured stderr:", str) end # @test_warn with regex pattern let results = @testset NoThrowTestSet begin @test_warn r"expected" println(stderr, "wrong") end @test length(results) == 1 fail = results[1] @test fail isa Test.Fail @test fail.test_type === :test_warn @test fail.data == "r\"expected\" (occursin)" end # @test_warn with array pattern let results = @testset NoThrowTestSet begin @test_warn ["foo", "bar"] println(stderr, "only foo") end @test length(results) == 1 fail = results[1] @test fail isa Test.Fail @test fail.test_type === :test_warn @test occursin("(all, occursin)", fail.data) end # @test_warn with function pattern let results = @testset NoThrowTestSet begin @test_warn (s -> occursin("expected", s)) println(stderr, "wrong") end @test length(results) == 1 fail = results[1] @test fail isa Test.Fail @test fail.test_type === :test_warn @test occursin("s->occursin(\"expected\", s)", fail.data) # Shows the function expression end # @test_nowarn failure shows expected "" and captured stderr let results = @testset NoThrowTestSet begin @test_nowarn println(stderr, "oops") end @test length(results) == 1 fail = results[1] @test fail isa Test.Fail @test fail.test_type === :test_nowarn @test fail.data == "\"\" (nowarn)" @test fail.value == "\"oops\\n\"" str = sprint(show, fail) @test occursin("Expected stderr: \"\" (nowarn)", str) @test occursin("Captured stderr: \"oops\\n\"", str) end end let fails = @testset NoThrowTestSet begin # 1 - Fail - wrong exception @test_throws OverflowError error() # 2 - Fail - no exception @test_throws OverflowError 1 + 1 # 3 & 4 - Fail - comparison @test 1 == 2 @test 1+1 == 2+2 # 5 - Fail - approximate comparison @test 1/1 ≈ 2/1 # 6 - Fail - chained comparison @test 1+0 == 2+0 == 3+0 # 7 - Fail - comparison call @test ==(1 - 2, 2 - 1) # 8 - Fail - splatting @test ==(1:2...) # 9 & 10 - Fail - broadcast @test 1*1 .== 2*2 @test (==).(1*1, 2*2) # 11 & 12 - Fail qualified functions @test Base.:(==)(1*1, 2*2) @test Base.:(==).(1*1, 2*2) # 13 - Fail - isequal @test isequal(0 / 0, 1 / 0) # 14 - Fail - function splatting @test isequal(1:2...) # 15 - Fail - isapprox @test isapprox(0 / 1, -1 / 0) # 16 & 17 - Fail - function with keyword @test isapprox(1 / 2, 2 / 1, atol=1 / 1) @test isapprox(1 - 2, 2 - 1; atol=1 - 1) # 18 - Fail - function keyword splatting k = [(:atol, 0), (:nans, true)] @test isapprox(1, 2; k...) # 19 - Fail - call negation @test !isequal(1, 2 - 1) # 20 - Fail - comparison negation @test !(2 + 3 == 1 + 4) # 21 - Fail - chained negation @test !(2 + 3 == 1 + 4 == 5) # 22 - Fail - isempty nonempty = [1, 2, 3] @test isempty(nonempty) str1 = "Hello" str2 = "World" # 23 - Fail - occursin @test occursin(str1, str2) # 24 - Fail - startswith @test startswith(str1, str2) # 25 - Fail - endswith @test endswith(str1, str2) # 26 - Fail - contains @test Base.contains(str1, str2) # 27 - Fail - issetequal @test issetequal([2, 3] .- 1, [1, 3]) # 28 - Fail - Type Comparison @test typeof(1) <: typeof("julia") # 29 - Fail - assignment @test (i = length([1, 2])) == 3 # 30 - Fail - symbol comparison @test 1 + 2 == :sym # 31 - Fail - symbol in function call @test isequal(1 + 2, :sym) # 32 - 35 - Fail - wrong message @test_throws "A test" error("a test") @test_throws r"sqrt\([Cc]omplx" sqrt(-1) @test_throws str->occursin("a T", str) error("a test") @test_throws ["BoundsError", "acquire", "1-element", "at index [2]"] [1][2] end for fail in fails @test fail isa Test.Fail end let str = sprint(show, fails[1]) @test occursin("Expression: error()", str) @test occursin("Thrown: ErrorException", str) end let str = sprint(show, fails[2]) @test occursin("Expression: 1 + 1", str) @test occursin("No exception thrown", str) end let str = sprint(show, fails[3]) @test occursin("Expression: 1 == 2", str) @test !occursin("Evaluated", str) end let str = sprint(show, fails[4]) @test occursin("Expression: 1 + 1 == 2 + 2", str) @test occursin("Evaluated: 2 == 4", str) end let str = sprint(show, fails[5]) @test occursin("Expression: 1 / 1 ≈ 2 / 1", str) @test occursin("Evaluated: 1.0 ≈ 2.0", str) end let str = sprint(show, fails[6]) @test occursin("Expression: 1 + 0 == 2 + 0 == 3 + 0", str) @test occursin("Evaluated: 1 == 2 == 3", str) end let str = sprint(show, fails[7]) @test occursin("Expression: 1 - 2 == 2 - 1", str) @test occursin("Evaluated: -1 == 1", str) end let str = sprint(show, fails[8]) @test occursin("Expression: (==)(1:2...)", str) @test occursin("Evaluated: 1 == 2", str) end let str = sprint(show, fails[9]) @test occursin("Expression: 1 * 1 .== 2 * 2", str) @test occursin("Evaluated: 1 .== 4", str) end let str = sprint(show, fails[10]) @test occursin("Expression: (==).(1 * 1, 2 * 2)", str) @test occursin("Evaluated: (==).(1, 4)", str) end let str = sprint(show, fails[11]) @test occursin("Expression: Base.:(==)(1 * 1, 2 * 2)", str) @test occursin("Evaluated: Base.:(==)(1, 4)", str) end let str = sprint(show, fails[12]) @test occursin("Expression: Base.:(==).(1 * 1, 2 * 2)", str) @test occursin("Evaluated: Base.:(==).(1, 4)", str) end let str = sprint(show, fails[13]) @test occursin("Expression: isequal(0 / 0, 1 / 0)", str) @test occursin("Evaluated: isequal(NaN, Inf)", str) end let str = sprint(show, fails[14]) @test occursin("Expression: isequal(1:2...)", str) @test occursin("Evaluated: isequal(1, 2)", str) end let str = sprint(show, fails[15]) @test occursin("Expression: isapprox(0 / 1, -1 / 0)", str) @test occursin("Evaluated: isapprox(0.0, -Inf)", str) end let str = sprint(show, fails[16]) @test occursin("Expression: isapprox(1 / 2, 2 / 1, atol = 1 / 1)", str) @test occursin("Evaluated: isapprox(0.5, 2.0; atol = 1.0)", str) end let str = sprint(show, fails[17]) @test occursin("Expression: isapprox(1 - 2, 2 - 1; atol = 1 - 1)", str) @test occursin("Evaluated: isapprox(-1, 1; atol = 0)", str) end let str = sprint(show, fails[18]) @test occursin("Expression: isapprox(1, 2; k...)", str) @test occursin("Evaluated: isapprox(1, 2; atol = 0, nans = true)", str) end let str = sprint(show, fails[19]) @test occursin("Expression: !(isequal(1, 2 - 1))", str) @test occursin("Evaluated: !(isequal(1, 1))", str) end let str = sprint(show, fails[20]) @test occursin("Expression: !(2 + 3 == 1 + 4)", str) @test occursin("Evaluated: !(5 == 5)", str) end let str = sprint(show, fails[21]) @test occursin("Expression: !(2 + 3 == 1 + 4 == 5)", str) @test occursin("Evaluated: !(5 == 5 == 5)", str) end let str = sprint(show, fails[22]) @test occursin("Expression: isempty(nonempty)", str) @test occursin("Evaluated: isempty([1, 2, 3])", str) end let str = sprint(show, fails[23]) @test occursin("Expression: occursin(str1, str2)", str) @test occursin("Evaluated: occursin(\"Hello\", \"World\")", str) end let str = sprint(show, fails[24]) @test occursin("Expression: startswith(str1, str2)", str) @test occursin("Evaluated: startswith(\"Hello\", \"World\")", str) end let str = sprint(show, fails[25]) @test occursin("Expression: endswith(str1, str2)", str) @test occursin("Evaluated: endswith(\"Hello\", \"World\")", str) end let str = sprint(show, fails[26]) @test occursin("Expression: Base.contains(str1, str2)", str) @test occursin("Evaluated: Base.contains(\"Hello\", \"World\")", str) end let str = sprint(show, fails[27]) @test occursin("Expression: issetequal([2, 3] .- 1, [1, 3])", str) @test occursin("Evaluated: issetequal([1, 2], [1, 3])", str) end let str = sprint(show, fails[28]) @test occursin("Expression: typeof(1) <: typeof(\"julia\")", str) @test occursin("Evaluated: $(typeof(1)) <: $(typeof("julia"))", str) end let str = sprint(show, fails[29]) @test occursin("Expression: (i = length([1, 2])) == 3", str) @test occursin("Evaluated: 2 == 3", str) end # Test that symbols are printed with : prefix let str = sprint(show, fails[30]) @test occursin("Evaluated: 3 == :sym", str) end let str = sprint(show, fails[31]) @test occursin("Evaluated: isequal(3, :sym)", str) end let str = sprint(show, fails[32]) @test occursin("Expected: \"A test\"", str) @test occursin("Message: \"a test\"", str) end let str = sprint(show, fails[33]) @test occursin("Expected: r\"sqrt\\([Cc]omplx\"", str) @test occursin(r"Message: .*Try sqrt\(Complex", str) end let str = sprint(show, fails[34]) @test occursin("Expected: < match function >", str) @test occursin("Message: \"a test\"", str) end let str = sprint(show, fails[35]) @test occursin("Expected: [\"BoundsError\", \"acquire\", \"1-element\", \"at index [2]\"]", str) @test occursin(r"Message: \"BoundsError.* 1-element.*at index \[2\]", str) end end struct BadError <: Exception end Base.show(io::IO, ::BadError) = throw("I am a bad error") let errors = @testset NoThrowTestSet begin # 1 - Error - unexpected pass @test_broken true # 2 - Error - converting a call into a comparison @test ==(1, 1:2...) # 3 - Error - objects with broken show @test throw(BadError()) @test BadError() throw(BadError()) end for err in errors @test err isa Test.Error end let str = sprint(show, errors[1]) @test occursin("Unexpected Pass", str) @test occursin("Expression: true", str) end let str = sprint(show, errors[2]) @test occursin("Expression: ==(1, 1:2...)", str) @test occursin("MethodError: no method matching ==(::$Int, ::$Int, ::$Int)", str) end let str = sprint(show, errors[3]) @test occursin("Expression: throw(BadError())\n #=ERROR showing exception stack=# \"I am a bad error\"\n Stacktrace:\n", str) end let str = sprint(show, errors[4]) @test occursin("Expression: BadError()\n Value: #=ERROR showing error of type $BadError=# \"I am a bad error\"\nStacktrace:\n", str) end let str = sprint(show, errors[5]) @test occursin("Got exception outside of a @test\n #=ERROR showing exception stack=# \"I am a bad error\"\n Stacktrace:\n", str) end end let retval_tests = @testset NoThrowTestSet begin ts = Test.DefaultTestSet("Mock for testing retval of record(::DefaultTestSet, ::T <: Result) methods") pass_mock = Test.Pass(:test, 1, 2, 3, LineNumberNode(0, "A Pass Mock")) @test Test.record(ts, pass_mock) isa Test.Pass error_mock = Test.Error(:test, 1, 2, nothing, LineNumberNode(0, "An Error Mock"), nothing) @test Test.record(ts, error_mock; print_result=false) isa Test.Error fail_mock = Test.Fail(:test, 1, 2, 3, nothing, LineNumberNode(0, "A Fail Mock"), false) @test Test.record(ts, fail_mock; print_result=false) isa Test.Fail broken_mock = Test.Broken(:test, LineNumberNode(0, "A Broken Mock")) @test Test.record(ts, broken_mock) isa Test.Broken end for retval_test in retval_tests @test retval_test isa Test.Pass end end @testset "Fail - exception with pattern (3-arg form)" begin # Test type mismatch let fails = @testset NoThrowTestSet begin @test_throws ArgumentError "error foo" error("error foo 1") # Wrong type end @test length(fails) == 1 @test fails[1] isa Test.Fail @test fails[1].test_type === :test_throws_wrong @test occursin("ArgumentError with pattern \"error foo\"", fails[1].data) end # Test pattern mismatch let fails = @testset NoThrowTestSet begin @test_throws ErrorException "wrong pattern" error("error foo 1") # Wrong pattern end @test length(fails) == 1 @test fails[1] isa Test.Fail @test fails[1].test_type === :test_throws_wrong @test occursin("ErrorException with pattern \"wrong pattern\"", fails[1].data) end # Test no exception thrown let fails = @testset NoThrowTestSet begin @test_throws ErrorException "error foo" 1 + 1 # No exception end @test length(fails) == 1 @test fails[1] isa Test.Fail @test fails[1].test_type === :test_throws_nothing @test occursin("ErrorException with pattern \"error foo\"", fails[1].data) end # Test first argument must be a type let fails = @testset NoThrowTestSet begin @test_throws "not a type" "error foo" error("error foo 1") # First arg not a type end @test length(fails) == 1 @test fails[1] isa Test.Fail @test fails[1].test_type === :test_throws_wrong end end # Tests for context keyword @testset "@test with context keyword" begin # Test that context appears in Fail let f = Test.Fail(:test, "false", nothing, "false", "(sin, Float64)", LineNumberNode(1), false) @test occursin("Context: (sin, Float64)", sprint(show, f)) end # Test that context is evaluated and shown in @test let fails = @testset NoThrowTestSet begin @test false context=(sin, Float64) end @test length(fails) == 1 @test fails[1] isa Test.Fail @test occursin("(sin, Float64)", fails[1].context) end # Test context with broken=true (should record as Broken) let results = @testset NoThrowTestSet begin @test false context="info" broken=true end @test length(results) == 1 @test results[1] isa Test.Broken end # Test context with Error (exception thrown) let errors = @testset NoThrowTestSet begin @test error("boom") context="error info" end @test length(errors) == 1 @test errors[1] isa Test.Error @test errors[1].context == "\"error info\"" end end @testset "@test_throws with context keyword" begin # Test context appears when wrong exception thrown let fails = @testset NoThrowTestSet begin @test_throws ArgumentError error("wrong") context="extra info" end @test length(fails) == 1 @test fails[1] isa Test.Fail @test fails[1].context == "\"extra info\"" end # Test context with three-arg form let fails = @testset NoThrowTestSet begin @test_throws ErrorException "pattern" error("wrong msg") context=(1, 2) end @test length(fails) == 1 @test fails[1] isa Test.Fail @test occursin("(1, 2)", fails[1].context) end # Test context when no exception thrown let fails = @testset NoThrowTestSet begin @test_throws ErrorException 1 + 1 context="no throw info" end @test length(fails) == 1 @test fails[1] isa Test.Fail @test fails[1].test_type === :test_throws_nothing @test fails[1].context == "\"no throw info\"" end end @testset "@test_broken with context keyword" begin # Test context with @test_broken when test unexpectedly passes let errors = @testset NoThrowTestSet begin @test_broken true context="broken info" end @test length(errors) == 1 @test errors[1] isa Test.Error @test errors[1].test_type === :test_unbroken @test errors[1].context == "\"broken info\"" end end @testset "printing of a TestSetException" begin tse_str = sprint(show, Test.TestSetException(1, 2, 3, 4, Vector{Union{Test.Error, Test.Fail}}())) @test occursin("1 passed", tse_str) @test occursin("2 failed", tse_str) @test occursin("3 errored", tse_str) @test occursin("4 broken", tse_str) end @test Test.finish(Test.FallbackTestSet()) !== nothing OLD_STDOUT = stdout OLD_STDERR = stderr catch_out = IOStream("") catch_err = IOStream("") rde, wre = redirect_stderr() rdo, wro = redirect_stdout() # test that FallbackTestSet will throw immediately cmd = `$(Base.julia_cmd()) --startup-file=no --depwarn=error test_exec.jl` @test !success(pipeline(cmd)) @testset "no errors" begin @test true @test 1 == 1 end # Test entirely empty test set @testset "outer" begin @testset "inner" begin end end @testset "testset types" begin ts = @testset "@testset should return the testset" begin @test true end @test typeof(ts) == Test.DefaultTestSet @test ts.n_passed == 1 tss = @testset "@testset/for should return an array of testsets: $i" for i in 1:3 @test true end @test length(tss) == 3 @test typeof(tss[1]) == Test.DefaultTestSet @test tss[1].n_passed == 1 end @testset "accounting" begin local ts, fails try ts = @testset "outer" begin @testset "inner1" begin @test true @test false @test 1 == 1 @test 2 === :foo @test 3 == 3 @testset "d" begin @test 4 == 4 end @testset begin @test :blank !== :notblank end end @testset "inner1" begin @test 1 == 1 @test 2 == 2 @test 3 === :bar @test 4 == 4 @test_throws ErrorException 1+1 @test_throws ErrorException error() @test_throws RemoteException error() @testset "errrrr" begin @test "not bool" @test error() end error("exceptions in testsets should be caught") @test 1 == 1 # this test will not be run end @testset "loop with desc" begin @testset "loop1 $T" for T in (Float32, Float64) @test 1 == T(1) end end @testset "loops without desc" begin @testset for T in (Float32, Float64) @test 1 == T(1) end @testset for T in (Float32, Float64), S in (Int32,Int64) @test S(1) == T(1) end end @testset "some loops fail" begin @testset for i in 1:5 @test i <= 4 end # should add 3 errors and 3 passing tests @testset for i in 1:6 iseven(i) || error("error outside of test") @test true # only gets run if the above passed end end end # These lines shouldn't be called error("No exception was thrown!") catch ex redirect_stdout(OLD_STDOUT) redirect_stderr(OLD_STDERR) ex end @testset "ts results" begin @test isa(ts, Test.DefaultTestSet) tc = Test.get_test_counts(ts) total_pass = tc.passes + tc.cumulative_passes total_fail = tc.fails + tc.cumulative_fails total_error = tc.errors + tc.cumulative_errors total_broken = tc.broken + tc.cumulative_broken @test total_pass == 24 @test total_fail == 6 @test total_error == 6 @test total_broken == 0 end @atomic ts.anynonpass = 0x00 deleteat!(Test.get_testset().results, 1) end @test .1+.1+.1 ≈ .3 @test .1+.1+.1 ≉ .4 ts = @testset "@testset should return the testset" begin @test true end @test typeof(ts) == Test.DefaultTestSet @test ts.n_passed == 1 tss = @testset "@testset/for should return an array of testsets: $i" for i in 1:3 @test true end @test length(tss) == 3 @test typeof(tss[1]) == Test.DefaultTestSet @test tss[1].n_passed == 1 # Issue #17908 (return) testset_depth17908 = Test.get_testset_depth() @testset for i in 1:3 i > 1 && return @test i == 1 end # The return aborts the control flow so the expression above doesn't return a # value. The only thing we can test is whether the testset is properly popped. # Do not use `@test` since the issue this is testing will swallow the error. @assert testset_depth17908 == Test.get_testset_depth() # Issue #17462 and Issue #17908 (break, continue) testset_depth17462 = Test.get_testset_depth() counter_17462_pre = 0 counter_17462_post = 0 tss17462 = @testset for x in [1,2,3,4] global counter_17462_pre, counter_17462_post counter_17462_pre += 1 if x == 1 @test counter_17462_pre == x continue @test false elseif x == 3 @test counter_17462_pre == x break @test false elseif x == 4 @test false else @test counter_17462_pre == x @test x == 2 @test counter_17462_post == 0 end counter_17462_post += 1 end # Do not use `@test` since the issue this is testing will swallow the error. # Same for the `@assert` in the for loop below @assert testset_depth17462 == Test.get_testset_depth() @assert length(tss17462) == 3 for ts17462 in tss17462 @assert isa(ts17462, Test.DefaultTestSet) end @test counter_17462_pre == 3 @test counter_17462_post == 1 # Issue #21008 ts = try @testset "@test_broken and @test_skip should not give an exception" begin @test_broken false @test_skip true @test_skip false end catch nothing # Shouldn't get here end @test ts isa Test.DefaultTestSet # now we're done running tests with DefaultTestSet so we can go back to stdout redirect_stdout(OLD_STDOUT) redirect_stderr(OLD_STDERR) # import the methods needed for defining our own testset type import Test: record, finish using Test: get_testset_depth, get_testset using Test: AbstractTestSet, Result, Pass, Fail, Error struct CustomTestSet <: Test.AbstractTestSet description::String foo::Int results::Vector # constructor takes a description string and options keyword arguments CustomTestSet(desc; foo=1) = new(desc, foo, []) end record(ts::CustomTestSet, child::AbstractTestSet) = push!(ts.results, child) record(ts::CustomTestSet, res::Result) = push!(ts.results, res) function finish(ts::CustomTestSet) # just record if we're not the top-level parent if get_testset_depth() > 0 record(get_testset(), ts) end ts end ts = @testset CustomTestSet "Testing custom testsets" begin # this testset should inherit the parent testset type @testset "custom testset inner 1" begin @test true @test false @test error("this error will be reported as an error") @test_throws ErrorException nothing @test_throws ErrorException error("this error is a success") end # this testset has its own testset type @testset CustomTestSet foo=4 "custom testset inner 2" begin # this testset should inherit the type, but not the argument. If a particular # testset type wants inheritance behavior they should implement it themselves # using get_testset() in the constructor @testset "custom testset inner 2 inner 1" begin @test true end # make sure the RHS can use computed values, also tests options without # specifying the testset type @testset foo=(1+2) "custom testset inner 2 inner 2" begin @test true end end end @test typeof(ts) == CustomTestSet @test ts.foo == 1 @test ts.description == "Testing custom testsets" @test typeof(ts.results[1]) == CustomTestSet @test ts.results[1].description == "custom testset inner 1" @test ts.results[1].foo == 1 @test typeof(ts.results[1].results[1]) == Pass @test typeof(ts.results[1].results[2]) == Fail @test typeof(ts.results[1].results[3]) == Error @test typeof(ts.results[1].results[4]) == Fail @test typeof(ts.results[1].results[5]) == Pass @test typeof(ts.results[2]) == CustomTestSet @test ts.results[2].description == "custom testset inner 2" @test ts.results[2].foo == 4 @test typeof(ts.results[2].results[1]) == CustomTestSet @test ts.results[2].results[1].foo == 1 @test typeof(ts.results[2].results[1].results[1]) == Pass @test typeof(ts.results[2].results[2]) == CustomTestSet @test ts.results[2].results[2].foo == 3 # test custom testset types on testset/for tss = @testset CustomTestSet foo=3 "custom testset $i" for i in 1:6 @testset "inner testset $i-$j" for j in 1:3 @test iseven(i + j) end # make sure a testset within a testset/for works @testset "inner testset $i" begin @test iseven(i) end end for i in 1:6 @test typeof(tss[i]) == CustomTestSet @test tss[i].foo == 3 for j in 1:3 @test typeof(tss[i].results[j]) == CustomTestSet @test tss[i].results[j].foo == 1 @test typeof(tss[i].results[j].results[1]) == (iseven(i+j) ? Pass : Fail) end @test typeof(tss[i].results[4]) == CustomTestSet @test typeof(tss[i].results[4].results[1]) == (iseven(i) ? Pass : Fail) end # test that second argument is escaped correctly foo = 3 tss = @testset CustomTestSet foo=foo "custom testset - escaping" begin @test true end @test tss.foo == 3 # test @inferred uninferable_function(i) = (1, "1")[i] uninferable_small_union(i) = (1, nothing)[i] @test_throws ErrorException @inferred(uninferable_function(1)) @test @inferred(identity(1)) == 1 @test @inferred(Nothing, uninferable_small_union(1)) === 1 @test @inferred(Nothing, uninferable_small_union(2)) === nothing @test_throws ErrorException @inferred(Missing, uninferable_small_union(1)) @test_throws ErrorException @inferred(Missing, uninferable_small_union(2)) @test_throws ArgumentError @inferred(nothing, uninferable_small_union(1)) let T = Core.TypeVar(:T) f_free_typevar_result() = Rational{T} err = @test_throws ErrorException @inferred(f_free_typevar_result()) @test occursin("return type Type{Rational{T}} does not match inferred return type", err.value.msg) end # Ensure @inferred only evaluates the arguments once inferred_test_global = 0 function inferred_test_function() global inferred_test_global inferred_test_global += 1 true end @test @inferred inferred_test_function() @test inferred_test_global == 1 struct SillyArray <: AbstractArray{Float64,1} end Base.getindex(a::SillyArray, i) = rand() > 0.5 ? 0 : false @testset "@inferred works with A[i] expressions" begin @test (@inferred (1:3)[2]) == 2 test_result = @test_throws ErrorException (@inferred SillyArray()[2]) @test occursin("Bool", test_result.value.msg) end # Issue #14928 # Make sure abstract error type works. @test_throws Exception error("") # Issue #17105 # @inferred with kwargs inferable_kwtest(x; y=1) = 2x uninferable_kwtest(x; y=1) = 2x+y @test (@inferred inferable_kwtest(1)) == 2 @test (@inferred inferable_kwtest(1; y=1)) == 2 @test (@inferred uninferable_kwtest(1)) == 3 @test (@inferred uninferable_kwtest(1; y=2)) == 4 @test_throws ErrorException @testset "$(error())" for i in 1:10 end @test_throws ErrorException @testset "$(error())" begin end @testset "backtraces in test errors" begin local f = tempname() write(f, """ using Test @testset begin @test 1==2 @test_throws MethodError 1 end """) local msg = read(pipeline(ignorestatus(`$(Base.julia_cmd()) --startup-file=no --color=no $f`), stderr=devnull), String) @test !occursin("do_test(", msg) @test !occursin("include(", msg) @test occursin("at " * f * ":3", msg) @test occursin("at " * f * ":4", msg) rm(f; force=true) end @testset "backtraces in exceptions thrown outside of @test" begin # the backtrace is anchored at the enclosing @testset: frames below it # (include machinery, script/test-harness drivers) say nothing about the # failure local f = tempname() * ".jl" write(f, """ using Test @testset "outer" begin error("boom") end """) local msg = read(pipeline(ignorestatus(`$(Base.julia_cmd()) --startup-file=no --color=no $f`), stderr=devnull), String) @test occursin("Got exception outside of a @test", msg) # frame paths contract the home dir to `~` (e.g. the temp dir on Windows) @test occursin(Base.contractuser(f) * ":3", msg) @test !occursin("include(", msg) @test !occursin("exec_options", msg) @test !occursin("_start()", msg) rm(f; force=true) end @testset "provide informative location in backtrace for test failures" begin utils = tempname() write(utils, """ function test_properties2(value) @test isodd(value) end """) included = tempname() write(included, """ @testset "Other tests" begin @test 1 + 1 == 3 test_properties2(2) end test_properties2(8) # Test calls to `@test` and `@testset` with no file/lineno information (__source__ == nothing). eval(Expr(:macrocall, Symbol("@test"), nothing, :false)) eval(Expr(:macrocall, Symbol("@testset"), nothing, "Testset without source", quote @test false @test error("failed") end)) """) runtests = tempname() write(runtests, """ using Test include($(repr(utils))) function test_properties(value) @test isodd(value) end @testset "Tests" begin test_properties(8) @noinline test_properties(8) test_properties2(8) include($(repr(included))) end """) # Disable homedir contraction in stack traces so paths match tempname() output msg = withenv("JULIA_STACKTRACE_CONTRACT_HOMEDIR" => "0") do read(pipeline(ignorestatus(`$(Base.julia_cmd()) --startup-file=no --color=no $runtests`), stderr=devnull), String) end regex = r"((?:Tests|Other tests|Testset without source): Test Failed (?:.|\n)*?)\n Stacktrace:(?:.|\n)*?(?=\n(?:Tests|Other tests))" failures = map(eachmatch(regex, msg)) do m m = match(r"(Tests|Other tests|Testset without source): .*? at (.*?)\n Expression: (.*)(?:.|\n)*\n Stacktrace:\n((?:.|\n)*)", m.match) (; testset = m[1], source = m[2], ex = m[3], stacktrace = m[4]) end @test length(failures) == 8 # 8 failed tests @test count(contains("Error During Test"), split(msg, '\n')) == 1 # 1 error test_properties_macro_source = runtests * ":6" test_properties2_macro_source = utils * ":2" fail = failures[1]; lines = split(fail.stacktrace, '\n') @test length(lines)/2 ≤ 6 @test fail.testset == "Tests" && fail.source == test_properties_macro_source && fail.ex == "isodd(value)" @test count(contains(runtests * ":10"), lines) == 2 # @testset + test fail = failures[2]; lines = split(fail.stacktrace, '\n') @test length(lines)/2 ≤ 6 @test fail.testset == "Tests" && fail.source == test_properties_macro_source && fail.ex == "isodd(value)" @test count(contains(runtests * ":10"), lines) == 1 # @testset @test count(contains(runtests * ":11"), lines) == 1 # test fail = failures[3]; lines = split(fail.stacktrace, '\n') @test length(lines)/2 ≤ 6 @test fail.testset == "Tests" && fail.source == test_properties2_macro_source && fail.ex == "isodd(value)" @test count(contains(runtests * ":10"), lines) == 1 # @testset @test count(contains(runtests * ":12"), lines) == 1 # test fail = failures[4]; lines = split(fail.stacktrace, '\n') @test length(lines)/2 ≤ 5 @test fail.testset == "Other tests" && fail.source == included * ":2" && fail.ex == "1 + 1 == 3" @test count(contains(included * ":2"), lines) == 2 # @testset + test @test count(contains(runtests * ":10"), lines) == 0 # @testset (stop at the innermost testset) fail = failures[5]; lines = split(fail.stacktrace, '\n') @test length(lines)/2 ≤ 6 @test fail.testset == "Other tests" && fail.source == test_properties2_macro_source && fail.ex == "isodd(value)" @test count(contains(included * ":2"), lines) == 1 # @testset @test count(contains(included * ":3"), lines) == 1 # test @test count(contains(runtests * ":10"), lines) == 0 # @testset (stop at the innermost testset) fail = failures[6]; lines = split(fail.stacktrace, '\n') @test length(lines)/2 ≤ 8 @test fail.testset == "Tests" && fail.source == test_properties2_macro_source && fail.ex == "isodd(value)" @test count(contains(runtests * ":10"), lines) == 1 # @testset @test count(contains(runtests * ":14"), lines) == 1 # include @test count(contains(included * ":5"), lines) == 1 # test fail = failures[7]; lines = split(fail.stacktrace, '\n') @test length(lines)/2 ≤ 9 @test fail.testset == "Tests" && fail.source == "none:0" && fail.ex == "false" @test count(contains(runtests * ":10"), lines) == 1 # @testset @test count(contains(runtests * ":14"), lines) == 1 # include @test count(contains(included * ":8"), lines) == 1 # test fail = failures[8]; lines = split(fail.stacktrace, '\n') @test length(lines)/2 ≤ 5 @test fail.testset == "Testset without source" && fail.source == included * ":10" && fail.ex == "false" @test count(contains(included * ":10"), lines) == 2 # @testset + test @test count(contains(runtests * ":10"), lines) == 0 # @testset (stop at the innermost testset) end let io = IOBuffer() exc = Test.TestSetException(1,2,3,4,Vector{Union{Test.Error, Test.Fail}}()) Base.showerror(io, exc, backtrace()) @test !occursin("backtrace()", String(take!(io))) end @testset "#19750" begin io = IOBuffer() exc = Test.TestSetException(1,2,3,4,Vector{Union{Test.Error, Test.Fail}}()) Base.showerror(io, exc, backtrace()) @test !occursin("backtrace()", String(take!(io))) exc = Test.FallbackTestSetException("msg") Base.showerror(io, exc, backtrace()) str = String(take!(io)) @test occursin("msg", str) @test !occursin("backtrace()", str) end let msg = read(pipeline(ignorestatus(`$(Base.julia_cmd()) --startup-file=no --color=no -e ' using Test foo(x) = length(x)^2 @testset "Foo Tests" begin @testset "Animals" begin @testset "Felines" begin @test foo("cat") == 9 end @testset "Canines" begin @test foo("dog") == 11 end end @testset "Arrays" begin @test foo(zeros(2)) == 4 @test foo(fill(1., 4)) == 15 end end'`), stderr=devnull), String) @test occursin(r""" Test Summary: \| Pass Fail Total +Time Foo Tests \| 2 2 4 \s*(\d+m)?\d*\.\ds Animals \| 1 1 2 \s*(\d+m)?\d*\.\ds Felines \| 1 1 \s*(\d+m)?\d*\.\ds Canines \| 1 1 \s*(\d+m)?\d*\.\ds Arrays \| 1 1 2 \s*(\d+m)?\d*\.\ds """, msg) end # 20489 let msg = split(read(pipeline(ignorestatus(`$(Base.julia_cmd()) --startup-file=no --color=no -e ' Test.print_test_results(Test.DefaultTestSet(""))'`), stderr=devnull), String), "\n")[1] @test msg == rstrip(msg) end @testset "test guarded Random.seed!" begin seed = rand(UInt) orig = copy(Random.default_rng()) @test guardseed(()->rand(), seed) == guardseed(()->rand(), seed) @test guardseed(()->rand(Int), seed) == guardseed(()->rand(Int), seed) r1, r2 = MersenneTwister(0), MersenneTwister(0) a, b = guardseed(r1) do Random.seed!(r1, 0) rand(r1), rand(r1, Int) end::Tuple{Float64,Int} c, d = guardseed(r2) do Random.seed!(r2, 0) rand(r2), rand(r2, Int) end::Tuple{Float64,Int} @test a == c == rand(r1) == rand(r2) @test b == d == rand(r1, Int) == rand(r2, Int) @test orig == Random.default_rng() @test rand(orig) == rand() end @testset "file info in test errors" begin local f = tempname() write(f, """ using Test @testset begin @test 1==2 @test_throws UndefVarError 1 @test_broken 1 == 1 end """) local msg = read(pipeline(ignorestatus(`$(Base.julia_cmd()) --startup-file=no --color=no $f`), stderr=devnull), String) @test occursin("at " * f * ":" * "3", msg) @test occursin("at " * f * ":" * "4", msg) @test occursin("at " * f * ":" * "5", msg) rm(f; force=true) end # issue #24919 @testset "≈ with atol" begin local cmd = `$(Base.julia_cmd()) --startup-file=no --color=no` f(src) = read(pipeline(ignorestatus(`$cmd -e $src`), stderr=devnull), String) msg = f(""" using Test x, y = 0.9, 0.1 @test x ≈ y atol=0.01 """) @test occursin("Evaluated: 0.9 ≈ 0.1 (atol=0.01)", msg) msg = f(""" using Test x, y = 0.9, 0.1 @test x ≈ y nans=true atol=0.01 """) @test occursin("Evaluated: 0.9 ≈ 0.1 (nans=true, atol=0.01)", msg) end erronce() = @error "an error" maxlog=1 @testset "@test_logs" begin function foo(n) @info "Doing foo with n=$n" for i=1:n @debug "Iteration $i" end end @test_logs (Info,"Doing foo with n=2") foo(2) # Log pattern matching # Regex @test_logs (Info,r"^Doing foo with n=[0-9]+$") foo(10) @test_logs (Info,r"^Doing foo with n=[0-9]+$") foo(1) # Level symbols @test_logs (:debug,) min_level=Debug @debug "foo" @test_logs (:info,) @info "foo" @test_logs (:warn,) @warn "foo" @test_logs (:error,) @error "foo" # Pass through so the value of the expression can also be tested @test (@test_logs (Info,"blah") (@info "blah"; 42)) == 42 # Debug level log collection @test_logs (Info,"Doing foo with n=2") (Debug,"Iteration 1") (Debug,"Iteration 2") min_level=Debug foo(2) @test_logs (Debug,"Iteration 5") min_level=Debug match_mode=:any foo(10) # Respect `maxlog` (#41625). We check we only find one logging message. @test_logs (:error, "an error") (erronce(); erronce()) # Test `respect_maxlog=false`: test_logger = Test.TestLogger(; respect_maxlog=false) with_logger(test_logger) do erronce() erronce() end @test length(test_logger.logs) == 2 # Test failures fails = @testset NoThrowTestSet "check that @test_logs detects bad input" begin @test_logs (Warn,) foo(1) @test_logs (Warn,) match_mode=:any @info "foo" @test_logs (Debug,) @debug "foo" @test_logs (Warn,) error() end @test length(fails) == 4 @test fails[1] isa Test.LogTestFailure @test fails[2] isa Test.LogTestFailure @test fails[3] isa Test.LogTestFailure @test fails[4] isa Test.Error @test startswith(fails[4].value, "ErrorException") end @testset "@test_logs broken/skip keywords" begin # broken=true when logs don't match should be Broken let results = @testset NoThrowTestSet begin @test_logs (:warn,) broken=true @info "wrong level" end @test length(results) == 1 @test results[1] isa Test.Broken @test results[1].test_type === :test end # broken=true when logs match should be Error (unexpected pass) let results = @testset NoThrowTestSet begin @test_logs (:info,) broken=true @info "correct" end @test length(results) == 1 @test results[1] isa Test.Error @test results[1].test_type === :test_unbroken end # broken=true when expression errors should be Broken let results = @testset NoThrowTestSet begin @test_logs (:info,) broken=true error("test error") end @test length(results) == 1 @test results[1] isa Test.Broken end # skip=true should record Broken(:skipped) let results = @testset NoThrowTestSet begin @test_logs (:info,) skip=true error("should not run") end @test length(results) == 1 @test results[1] isa Test.Broken @test results[1].test_type === :skipped end # broken and skip work with other kwargs let results = @testset NoThrowTestSet begin @test_logs (:debug,) min_level=Debug broken=true @info "wrong" end @test length(results) == 1 @test results[1] isa Test.Broken end end let code = quote function newfunc() 42 end @deprecate oldfunc newfunc @testset "@test_deprecated" begin @test_deprecated oldfunc() @test Base.JLOptions().depwarn == 1 @test (@test_deprecated oldfunc()) == 42 fails = @testset NoThrowTestSet "check that @test_deprecated detects bad input" begin @test_deprecated newfunc() @test_deprecated r"Not found in message" oldfunc() end @test length(fails) == 2 @test fails[1] isa Test.LogTestFailure @test fails[2] isa Test.LogTestFailure end @testset "@test_deprecated broken/skip keywords" begin # broken=true when deprecation warning is missing should be Broken results = @testset NoThrowTestSet begin @test_deprecated newfunc() broken=true end @test length(results) == 1 @test results[1] isa Test.Broken # broken=true when deprecation warning is present should be Error results = @testset NoThrowTestSet begin @test_deprecated oldfunc() broken=true end @test length(results) == 1 @test results[1] isa Test.Error @test results[1].test_type === :test_unbroken # skip=true should record Broken(:skipped) results = @testset NoThrowTestSet begin @test_deprecated error("should not run") skip=true end @test length(results) == 1 @test results[1] isa Test.Broken @test results[1].test_type === :skipped end end incl = "include($(repr(joinpath(@__DIR__, "nothrow_testset.jl"))))" cmd = `$(Base.julia_cmd()) --startup-file=no --depwarn=yes -e 'using Test' -e $incl -e $code` @test success(pipeline(cmd)) end @testset "@testset preserves GLOBAL_RNG's state, and re-seeds it" begin # i.e. it behaves as if it was wrapped in a `guardseed(GLOBAL_SEED)` block seed = rand(UInt128) Random.seed!(seed) seeded_state = copy(Random.default_rng()) a = rand() @testset begin # global RNG must re-seeded at the beginning of @testset @test a == rand() end @testset for i=1:3 @test a == rand() end # the @testset's above must have no consequence for rand() below b = rand() Random.seed!(seed) @test Random.default_rng() == seeded_state @test a == rand() @test b == rand() # Even when seed!() is called within a testset A, subsequent testsets # should start with the same "global RNG state" as what A started with, # such that the test `refvalue == rand(Int)` below succeeds. # Currently, this means that `Random.get_tls_seed()` has to be restored, # in addition to the state of Random.default_rng(). tls_seed_orig = copy(Random.get_tls_seed()) local refvalue @testset "TLS seed is also preserved (setup)" begin @test tls_seed_orig == Random.get_tls_seed() refvalue = rand(Int) Random.seed!() @test tls_seed_orig != Random.get_tls_seed() end @test tls_seed_orig == Random.get_tls_seed() @testset "TLS seed is also preserved (forloop)" for _=1:3 @test refvalue == rand(Int) Random.seed!() end @test tls_seed_orig == Random.get_tls_seed() @testset "TLS seed is also preserved (beginend)" begin @test refvalue == rand(Int) end # @testset below is not compatible with e.g. v1.9, but it still fails there (at "main task") # when deleting lines using get_tls_seed() or GLOBAL_SEED @testset "TLS seed and concurrency" begin # Even with multi-tasking, the TLS seed must stay consistent: the default_rng() state # is reset to the "global seed" at the beginning, and the "global seed" is reset to what # it was at the end of the testset; make sure that distinct tasks don't see the mutation # of this "global seed" (iow, it's task-local) seed = rand(UInt128) Random.seed!(seed) seeded_state = copy(Random.default_rng()) a = rand() ch = Channel{Nothing}() @sync begin @async begin @testset "task 1" begin # tick 1 # this task didn't call seed! explicitly (yet), so its TaskLocalRNG() should have been # reset to `Random.GLOBAL_SEED` at the beginning of `@testset` @test Random.GLOBAL_SEED == Random.default_rng() Random.seed!() put!(ch, nothing) # tick 1 -> tick 2 take!(ch) # tick 3 end put!(ch, nothing) # tick 3 -> tick 4 end @async begin take!(ch) # tick 2 # @testset below will record the current TLS "seed" and reset default_rng() to # this value; # it must not be affected by the fact that "task 1" called `seed!()` first @test Random.get_tls_seed() == Random.GLOBAL_SEED @testset "task 2" begin @test Random.GLOBAL_SEED == Random.default_rng() Random.seed!() put!(ch, nothing) # tick 2 -> tick 3 take!(ch) # tick 4 end # when `@testset` of task 2 finishes, which is after `@testset` from task 1, # it resets `get_tls_seed()` to what it was before starting: @test Random.get_tls_seed() == Random.GLOBAL_SEED end end @testset "main task" begin @test Random.default_rng() == seeded_state @test a == rand() end end end @testset "InterruptExceptions #21043" begin @test_throws InterruptException (@test 1 == throw(InterruptException())) @testset begin @test_throws InterruptException throw(InterruptException()) end mktemp() do f, _ write(f, """ using Test @testset begin try @test_throws ErrorException throw(InterruptException()) catch e @test e isa InterruptException end end try @testset begin @test 1 == 1 throw(InterruptException()) end catch e @test e isa InterruptException end try @testset for i in 1:1 @test 1 == 1 throw(InterruptException()) end catch e @test e isa InterruptException end """) cmd = `$(Base.julia_cmd()) --startup-file=no --color=no $f` msg = success(pipeline(ignorestatus(cmd), stderr=devnull)) end end @testset "non AbstractTestSet as testset" begin local f, err = tempname(), tempname() write(f, """ using Test desc = "description" @testset desc begin @test 1==1 end """) run(pipeline(ignorestatus(`$(Base.julia_cmd()) --startup-file=no --color=no $f`), stderr=err)) msg = read(err, String) @test occursin("Expected `desc` to be an AbstractTestSet, it is a String", msg) rm(f; force=true) rm(err, force=true) end f25835(;x=nothing) = _f25835(x) _f25835(::Nothing) = () _f25835(x) = (x,) # A keyword function that is never type stable g25835(;x=1) = rand(Bool) ? 1.0 : 1 # A keyword function that is sometimes type stable h25835(;x=1,y=1) = x isa Int ? x*y : (rand(Bool) ? 1.0 : 1) @testset "keywords in @inferred" begin @test @inferred(f25835()) == () @test @inferred(f25835(x=nothing)) == () @test @inferred(f25835(x=1)) == (1,) # A global argument should make this uninferable global y25835 = 1 @test f25835(x=y25835) == (1,) @test_throws ErrorException @inferred((()->f25835(x=y25835))()) == (1,) @test_throws ErrorException @inferred(g25835()) == 1 @test_throws ErrorException @inferred(g25835(x=1)) == 1 @test @inferred(h25835()) == 1 @test @inferred(h25835(x=2,y=3)) == 6 @test_throws ErrorException @inferred(h25835(x=1.0,y=1.0)) == 1 end @testset "splatting in isapprox" begin a = [1, 2, 3] @test isapprox(identity.((a, a))...) end @testset "treat NaN and missing in exception fields" begin struct Exception31219{T} value::T end f31219(x) = throw(Exception31219(x)) @testset "exception field '$(repr(x))'" for x in ("ok", nothing, NaN, missing) @test_throws Exception31219(x) f31219(x) end end # Issue 20620 @test @inferred(.![true, false]) == [false, true] @test @inferred([3, 4] .- [1, 2] .+ [-2, -2]) == [0, 0] @testset "push/pop_testset invariance (Issue 32937)" begin io = IOBuffer() path = joinpath(@__DIR__(), "test_pop_testset_exec.jl") cmd = `$(Base.julia_cmd()) $path` ok = !success(pipeline(cmd; stdout = io, stderr = io)) if !ok @error "push/pop_testset invariance test failed" cmd Text(String(take!(io))) end @test ok end let ex = :(something_complex + [1, 2, 3]) b = PipeBuffer() let t = Test.Pass(:test, (ex, 1), (ex, 2), (ex, 3), LineNumberNode(@__LINE__, @__FILE__)) serialize(b, t) @test string(t) == string(deserialize(b)) @test eof(b) end let t = Test.Broken(:test, ex) serialize(b, t) @test string(t) == string(deserialize(b)) @test eof(b) end end @testset "verbose option" begin expected = r""" Test Summary: \| Pass Total +Time Parent \| 9 9 \s*(\d+m)?\d*\.\ds Child 1 \| 3 3 \s*(\d+m)?\d*\.\ds Child 1\.1 \(long name\) \| 1 1 \s*(\d+m)?\d*\.\ds Child 1\.2 \| 1 1 \s*(\d+m)?\d*\.\ds Child 1\.3 \| 1 1 \s*(\d+m)?\d*\.\ds Child 2 \| 3 3 \s*(\d+m)?\d*\.\ds Child 3 \| 3 3 \s*(\d+m)?\d*\.\ds Child 3\.1 \| 1 1 \s*(\d+m)?\d*\.\ds Child 3\.2 \| 1 1 \s*(\d+m)?\d*\.\ds Child 3\.3 \| 1 1 \s*(\d+m)?\d*\.\ds """ mktemp() do f, _ write(f, """ using Test @testset "Parent" verbose = true begin @testset "Child 1" verbose = true begin @testset "Child 1.1 (long name)" begin @test 1 == 1 end @testset "Child 1.2" begin @test 1 == 1 end @testset "Child 1.3" begin @test 1 == 1 end end @testset "Child 2" begin @testset "Child 2.1" begin @test 1 == 1 end @testset "Child 2.2" begin @test 1 == 1 end @testset "Child 2.3" begin @test 1 == 1 end end @testset "Child 3" verbose = true begin @testset "Child 3.1" begin @test 1 == 1 end @testset "Child 3.2" begin @test 1 == 1 end @testset "Child 3.3" begin @test 1 == 1 end end end """) cmd = `$(Base.julia_cmd()) --startup-file=no --color=no $f` result = read(pipeline(ignorestatus(cmd), stderr=devnull), String) @test occursin(expected, result) end end @testset "failfast option" begin @testset "non failfast (default)" begin expected = r""" Test Summary: \| Pass Fail Error Total +Time Foo \| 1 2 1 4 \s*(\d+m)?\d*\.\ds Bar \| 1 1 2 \s*(\d+m)?\d*\.\ds """ mktemp() do f, _ write(f, """ using Test @testset "Foo" begin @test false @test error() @testset "Bar" begin @test false @test true end end """) cmd = `$(Base.julia_cmd()) --startup-file=no --color=no $f` result = read(pipeline(ignorestatus(cmd), stderr=devnull), String) @test occursin(expected, result) end end @testset "failfast begin-end" begin expected = r""" Test Summary: \| Fail Total +Time Foo \| 1 1 \s*(\d+m)?\d*\.\ds """ mktemp() do f, _ write(f, """ using Test @testset "Foo" failfast=true begin @test false @test error() @testset "Bar" begin @test false @test true end end """) cmd = `$(Base.julia_cmd()) --startup-file=no --color=no $f` result = read(pipeline(ignorestatus(cmd), stderr=devnull), String) @test occursin(expected, result) end end @testset "failfast for-loop" begin expected = r""" Test Summary: \| Fail Total +Time Foo \| 1 1 \s*(\d+m)?\d*\.\ds 1 \| 1 1 \s*(\d+m)?\d*\.\ds """ mktemp() do f, _ write(f, """ using Test @testset "Foo" failfast=true begin @testset "\$x" for x in 1:2 @test false end @testset "Bar" begin @test false @test true end end """) cmd = `$(Base.julia_cmd()) --startup-file=no --color=no $f` result = read(pipeline(ignorestatus(cmd), stderr=devnull), String) @test occursin(expected, result) end end @testset "failfast passes to child testsets" begin expected = r""" Test Summary: \| Fail Total +Time Foo \| 1 1 \s*(\d+m)?\d*\.\ds 1 \| 1 1 \s*(\d+m)?\d*\.\ds """ mktemp() do f, _ write(f, """ using Test @testset "Foo" failfast=true begin @testset "1" begin @test false end @testset "2" begin @test true end end """) cmd = `$(Base.julia_cmd()) --startup-file=no --color=no $f` result = read(pipeline(ignorestatus(cmd), stderr=devnull), String) @test occursin(expected, result) end end @testset "failfast via env var" begin expected = r""" Test Summary: \| Fail Total +Time Foo \| 1 1 \s*(\d+m)?\d*\.\ds """ mktemp() do f, _ write(f, """ using Test @testset "Foo" begin @test false @test error() @testset "Bar" begin @test false @test true end end """) cmd = addenv(`$(Base.julia_cmd()) --startup-file=no --color=no $f`, "JULIA_TEST_FAILFAST"=>"true") result = read(pipeline(ignorestatus(cmd), stderr=devnull), String) @test occursin(expected, result) end end end # Non-booleans in @test (#35888) struct T35888 end Base.isequal(::T35888, ::T35888) = T35888() Base.:!(::T35888) = missing let errors = @testset NoThrowTestSet begin # 1 - evaluates to non-Boolean @test missing # 2 - evaluates to non-Boolean @test !missing # 3 - evaluates to non-Boolean @test isequal(5) # 4 - evaluates to non-Boolean @test !isequal(5) # 5 - evaluates to non-Boolean @test isequal(T35888(), T35888()) # 6 - evaluates to non-Boolean @test !isequal(T35888(), T35888()) # 7 - evaluates to non-Boolean @test 1 < 2 < missing # 8 - evaluates to non-Boolean @test !(1 < 2 < missing) # 9 - TypeError in chained comparison @test 1 < 2 < missing < 4 # 10 - TypeError in chained comparison @test !(1 < 2 < missing < 4) end for err in errors @test err isa Test.Error end let str = sprint(show, errors[1]) @test occursin("Expression evaluated to non-Boolean", str) @test occursin("Expression: missing", str) @test occursin("Value: missing", str) end let str = sprint(show, errors[2]) @test occursin("Expression evaluated to non-Boolean", str) @test occursin("Expression: !missing", str) @test occursin("Value: missing", str) end let str = sprint(show, errors[3]) @test occursin("Expression evaluated to non-Boolean", str) @test occursin("Expression: isequal(5)", str) end let str = sprint(show, errors[4]) @test occursin("Expression evaluated to non-Boolean", str) @test occursin("Expression: !(isequal(5))", str) end let str = sprint(show, errors[5]) @test occursin("Expression evaluated to non-Boolean", str) @test occursin("Expression: isequal(T35888(), T35888())", str) @test occursin("Value: $T35888()", str) end let str = sprint(show, errors[6]) @test occursin("Expression evaluated to non-Boolean", str) @test occursin("Expression: !(isequal(T35888(), T35888()))", str) @test occursin("Value: missing", str) end let str = sprint(show, errors[7]) @test occursin("Expression evaluated to non-Boolean", str) @test occursin("Expression: 1 < 2 < missing", str) @test occursin("Value: missing", str) end let str = sprint(show, errors[8]) @test occursin("Expression evaluated to non-Boolean", str) @test occursin("Expression: !(1 < 2 < missing)", str) @test occursin("Value: missing", str) end let str = sprint(show, errors[9]) @test occursin("TypeError: non-boolean (Missing) used in boolean context", str) @test occursin("Expression: 1 < 2 < missing < 4", str) end let str = sprint(show, errors[10]) @test occursin("TypeError: non-boolean (Missing) used in boolean context", str) @test occursin("Expression: !(1 < 2 < missing < 4)", str) end end macro test_macro_throw_1() throw(ErrorException("Real error")) end macro test_macro_throw_2() throw(LoadError("file", 111, ErrorException("Real error"))) end @testset "Soft deprecation of @test_throws LoadError [@]macroexpand[1]" begin # If a macroexpand was detected, undecorated LoadErrors can stand in for any error. # This will throw a deprecation warning. @test_deprecated (@test_throws LoadError macroexpand(@__MODULE__, :(@test_macro_throw_1))) @test_deprecated (@test_throws LoadError @macroexpand @test_macro_throw_1) # Decorated LoadErrors are unwrapped if the actual exception matches the inner, but not the outer, exception, regardless of whether or not a macroexpand is detected. # This will not throw a deprecation warning. @test_throws LoadError("file", 111, ErrorException("Real error")) macroexpand(@__MODULE__, :(@test_macro_throw_1)) @test_throws LoadError("file", 111, ErrorException("Real error")) @macroexpand @test_macro_throw_1 # Decorated LoadErrors are not unwrapped if a LoadError was thrown. @test_throws LoadError("file", 111, ErrorException("Real error")) @macroexpand @test_macro_throw_2 end # Issue 54807 struct FEexc a::Nothing b::Nothing end @testset "FieldError Shim tests and Softdeprecation of @test_throws ErrorException" begin feexc = FEexc(nothing, nothing) # This is redundant regular test for FieldError @test_throws FieldError feexc.c # This should raise ErrorException @test_throws ErrorException feexc.a = 1 # This is test for FieldError shim and deprecation @test_deprecated @test_throws ErrorException feexc.c end # Issue 25483 mutable struct PassInformationTestSet <: Test.AbstractTestSet results::Vector PassInformationTestSet(desc) = new([]) end Test.record(ts::PassInformationTestSet, t::Test.Result) = (push!(ts.results, t); t) Test.finish(ts::PassInformationTestSet) = ts @testset "Information in Pass result (Issue 25483)" begin ts = @testset PassInformationTestSet begin @test 1 == 1 @test_throws ErrorException throw(ErrorException("Msg")) end test_line_number = (@__LINE__) - 3 test_throws_line_number = (@__LINE__) - 3 @test ts.results[1].test_type === :test @test ts.results[1].orig_expr == :(1 == 1) @test ts.results[1].data == Expr(:comparison, 1, :(==), 1) @test ts.results[1].value == true @test ts.results[1].source == LineNumberNode(test_line_number, @__FILE__) @test ts.results[2].test_type === :test_throws @test ts.results[2].orig_expr == :(throw(ErrorException("Msg"))) @test ts.results[2].data == ErrorException @test ts.results[2].value == ErrorException("Msg") @test ts.results[2].source == LineNumberNode(test_throws_line_number, @__FILE__) end let f(x) = @test isone(x) function h(x) @testset f(x) @testset "success" begin @test true end @testset for i in 1:3 @test !iszero(i) end end tret = @testset h(1) tdesc = @testset "description" h(1) @testset "Function calls" begin @test tret.description == "h" @test tdesc.description == "description" @test length(tret.results) == 5 @test tret.results[1].description == "f" @test tret.results[2].description == "success" for i in 1:3 @test tret.results[2+i].description == "i = $i" end end end @testset "Docstrings" begin @test isempty(Docs.undocumented_names(Test)) end module CustomTestSetModule using Test struct CustomTestSet <: Test.AbstractTestSet description::String end Test.record(::CustomTestSet, result) = result Test.finish(cts::CustomTestSet) = cts end @testset "Unexported custom TestSet" begin using .CustomTestSetModule let res = @testset CustomTestSetModule.CustomTestSet begin @test true end @test res isa CustomTestSetModule.CustomTestSet end end struct CustomPrintingTestSet <: AbstractTestSet description::String passes::Int errors::Int fails::Int broken::Int end function Test.finish(cpts::CustomPrintingTestSet) if Test.get_testset_depth() != 0 push!(Test.get_current_testset(), cpts) # printing is handled by the parent return cpts end Test.print_testset_results(cpts) cpts end @testset "Custom testsets participate in printing" begin mktemp() do f, _ write(f, """ using Test mutable struct CustomPrintingTestSet <: Test.AbstractTestSet description::String passes::Int fails::Int errors::Int broken::Int end CustomPrintingTestSet(desc::String) = CustomPrintingTestSet(desc, 0,0,0,0) Test.record(cpts::CustomPrintingTestSet, ::Test.Pass) = cpts.passes += 1 Test.record(cpts::CustomPrintingTestSet, ::Test.Error) = cpts.errors += 1 Test.record(cpts::CustomPrintingTestSet, ::Test.Fail) = cpts.fails += 1 Test.record(cpts::CustomPrintingTestSet, ::Test.Broken) = cpts.broken += 1 Test.get_test_counts(ts::CustomPrintingTestSet) = Test.TestCounts( true, ts.passes, ts.fails, ts.errors, ts.broken, 0, 0, 0, 0, Test.format_duration(ts)) function Test.finish(cpts::CustomPrintingTestSet) if Test.get_testset_depth() != 0 Test.record(Test.get_testset(), cpts) # printing is handled by the parent return cpts end Test.print_test_results(cpts) cpts end struct NonRecordingTestSet <: Test.AbstractTestSet description::String end Test.record(nrts::NonRecordingTestSet, ::Test.Result) = nrts Test.finish(nrts::NonRecordingTestSet) = Test.record(Test.get_testset(), nrts) @testset "outer" begin @testset "a" begin @test true end @testset CustomPrintingTestSet "custom" begin @test false @test true @test_broken false @test error() end @testset NonRecordingTestSet "no-record" begin @test false @test true @test_broken false @test error() end @testset "b" begin @test true end end """) # this tests both the `TestCounts` parts as well as the fallback `x`s expected = r""" Test Summary: \| Pass Fail Error Broken Total +Time outer \| 3 1 1 1 6 \s*\d*.\ds a \| 1 1 \s*\d*.\ds custom \| 1 1 1 1 4 \s*\?s no-record \| x x x x \? \s*\?s b \| 1 1 \s*\d*.\ds RNG of the outermost testset: .* """ cmd = `$(Base.julia_cmd()) --startup-file=no --color=no $f` result = read(pipeline(ignorestatus(cmd), stderr=devnull), String) @test occursin(expected, result) end end @testset "Deprecated multiple arguments" begin msg1 = """Multiple descriptions provided to @testset. \ This is deprecated and may error in the future.""" @test_deprecated msg1 @macroexpand @testset "name1" "name2" begin end msg2 = """Multiple testset types provided to @testset. \ This is deprecated and may error in the future.""" @test_deprecated msg2 @macroexpand @testset DefaultTestSet DefaultTestSet begin end end # Issue #54082 module M54082 end @testset "@test_throws UndefVarError(:var)" begin # Single-arg `UndefVarError` should match all `UndefVarError` for the # same variable name, regardless of scope, to keep pre-v1.11 behaviour. f54082() = var @test_throws UndefVarError(:var) f54082() # But if scope is set, then it has to match. @test_throws UndefVarError(:var, M54082) M54082.var let result = @testset NoThrowTestSet begin # Wrong module scope @test_throws UndefVarError(:var, Main) M54082.var end @test only(result) isa Test.Fail end end @testset "Set RNG of testset" begin rng1 = Xoshiro(0x2e026445595ed28e, 0x07bb81ac4c54926d, 0x83d7d70843e8bad6, 0xdbef927d150af80b, 0xdbf91ddf2534f850) rng2 = Xoshiro(0xc380f460355639ee, 0xb39bc754b7d63bbf, 0x1551dbcfb5ed5668, 0x71ab5a18fec21a25, 0x649d0c1be1ca5436) rng3 = Xoshiro(0xee97f5b53f7cdc49, 0x480ac387b0527d3d, 0x614b416502a9e0f5, 0x5250cb36e4a4ceb1, 0xed6615c59e475fa0) @testset rng=rng1 begin @test rand() == rand(rng1) end @testset rng=rng2 "Outer" begin @test rand() == rand(rng2) @testset rng=rng3 "Inner: $(i)" for i in 1:10 @test rand() == rand(rng3) end end end @testset "_should_escape_call" begin @test !_should_escape_call(:(f())) @test _should_escape_call(:(f(x))) @test _should_escape_call(:(x == y)) @test _should_escape_call(:(f.(x))) @test !_should_escape_call(:f) @test !_should_escape_call(:(f = 1)) @test !_should_escape_call(:(f.x)) end @testset "_escape_call" begin @testset "invalid call" begin @test_throws ArgumentError _escape_call(:f) @test_throws ArgumentError _escape_call(:(f = 1)) @test_throws ArgumentError _escape_call(:(f.x)) end @testset "positional arguments" begin func = esc(:f) quoted_func = :(:f) @test _escape_call(:(f())) == (; func, args=[], kwargs=[], quoted_func) @test _escape_call(:(f(x))) == (; func, args=[esc(:x)], kwargs=[], quoted_func) @test _escape_call(:(f(x...))) == (; func, args=[:($(esc(:x))...)], kwargs=[], quoted_func) end @testset "keyword arguments" begin func = esc(:f) quoted_func = :(:f) @test _escape_call(:(f(y=1))) == (; func, args=[], kwargs=[:(:y => $(esc(1)))], quoted_func) @test _escape_call(:(f(; y))) == (; func, args=[], kwargs=[:(:y => $(esc(:y)))], quoted_func) @test _escape_call(:(f(; y=1))) == (; func, args=[], kwargs=[:(:y => $(esc(1)))], quoted_func) @test _escape_call(:(f(y=1; z))) == (; func, args=[], kwargs=[:(:y => $(esc(1))), :(:z => $(esc(:z)))], quoted_func) @test _escape_call(:(f(; y.z))) == (; func, args=[], kwargs=[:(:z => $(esc(:(y.z))))], quoted_func) @test _escape_call(:(f(; y...))) == (; func, args=[], kwargs=[:($(esc(:y))...)], quoted_func) end @testset "comparison" begin @test _escape_call(:(x == y)) == (; func=esc(:(==)), args=[esc(:x), esc(:y)], kwargs=[], quoted_func=:(:(==))) end @testset "broadcast" begin args = [esc(:x), esc(:y)] kwargs = [] @test _escape_call(:(f.(x, y))) == (; func=Expr(:., esc(:f)), args, kwargs, quoted_func=QuoteNode(Expr(:., :f))) @test _escape_call(:(Main.f.(x, y))) == (; func=:(Broadcast.BroadcastFunction($(esc(:(Main.f))))), args, kwargs, quoted_func=QuoteNode(Expr(:., :(Main.f)))) @test _escape_call(:(x .== y)) == (; func=esc(:(.==)), args, kwargs, quoted_func=:(:.==)) @test _escape_call(:((==).(x, y))) == (; func=Expr(:., esc(:(==))), args, kwargs, quoted_func=QuoteNode(Expr(:., :(==)))) # Test that .. operator is not treated as a broadcast operator @test _escape_call(:(x .. y)) == (; func=esc(:(..)), args, kwargs, quoted_func=:(:..)) end end @testset "Context display in @testset let blocks" begin # Mock parent testset that just captures results struct MockParentTestSet <: Test.AbstractTestSet results::Vector{Any} MockParentTestSet() = new([]) end Test.record(ts::MockParentTestSet, t) = (push!(ts.results, t); t) Test.finish(ts::MockParentTestSet) = ts @testset "context shown when a context testset fails" begin mock_parent1 = MockParentTestSet() ctx_ts1 = Test.ContextTestSet(mock_parent1, :x, 42) fail_result = Test.Fail(:test, "x == 99", "42 == 99", "42", nothing, LineNumberNode(1, :test), false) Test.record(ctx_ts1, fail_result) @test length(mock_parent1.results) == 1 recorded_fail = mock_parent1.results[1] @test recorded_fail isa Test.Fail @test recorded_fail.context !== nothing @test occursin("x = 42", recorded_fail.context) end @testset "context shown when a context testset errors" begin mock_parent2 = MockParentTestSet() ctx_ts2 = Test.ContextTestSet(mock_parent2, :x, 42) # Use internal constructor to create Error with pre-processed values error_result = Test.Error(:test_error, "error(\"test\")", "ErrorException(\"test\")", "test\nStacktrace:\n [1] error()", nothing, LineNumberNode(1, :test)) Test.record(ctx_ts2, error_result) @test length(mock_parent2.results) == 1 recorded_error = mock_parent2.results[1] @test recorded_error isa Test.Error @test recorded_error.context !== nothing @test occursin("x = 42", recorded_error.context) # Context shows up in string representation error_str = sprint(show, recorded_error) @test occursin("Context:", error_str) @test occursin("x = 42", error_str) # Multiple variables context mock_parent3 = MockParentTestSet() ctx_ts3 = Test.ContextTestSet(mock_parent3, :(x, y), (42, "hello")) error_result2 = Test.Error(:test_error, "error(\"test\")", "ErrorException(\"test\")", "test\nStacktrace:\n [1] error()", nothing, LineNumberNode(1, :test)) Test.record(ctx_ts3, error_result2) recorded_error2 = mock_parent3.results[1] @test recorded_error2 isa Test.Error @test recorded_error2.context !== nothing @test occursin("(x, y) = (42, \"hello\")", recorded_error2.context) end # Unexpected pass (broken=true) should show context before "Got correct result" message @testset "context shown for unexpected pass in context testset" begin mock_parent4 = MockParentTestSet() ctx_ts4 = Test.ContextTestSet(mock_parent4, :x, 42) unbroken_result = Test.Error(:test_unbroken, "x != 99", "true", "", nothing, LineNumberNode(1, :test)) Test.record(ctx_ts4, unbroken_result) @test length(mock_parent4.results) == 1 recorded = mock_parent4.results[1] @test recorded isa Test.Error @test recorded.context !== nothing @test occursin("x = 42", recorded.context) str = sprint(show, recorded) @test occursin("Unexpected Pass", str) @test occursin("Context:", str) @test occursin("x = 42", str) # Context should appear before "Got correct result" ctx_pos = findfirst("Context:", str) got_pos = findfirst("Got correct result", str) @test first(ctx_pos) < first(got_pos) end end @testset "io argument for Test output functions" begin # Test print_test_results and print_test_errors with io redirection io = IOBuffer() # Create a testset with passing and failing tests ts = Test.DefaultTestSet("IO Test"; time_start=1.36071654e9) Test.record(ts, Test.Pass(:test, nothing, nothing, nothing, LineNumberNode(1), false)) fail = Test.Fail(:test, "1 == 2", nothing, nothing, LineNumberNode(2, Symbol("test.jl"))) push!(ts.results, fail) # Test print_test_results with io Test.print_test_results(io, ts) output = String(take!(io)) @test occursin("Test Summary:", output) @test occursin("IO Test", output) @test occursin("Pass", output) @test occursin("Fail", output) # Test print_test_errors with io Test.print_test_errors(io, ts) output = String(take!(io)) @test occursin("Error in testset", output) @test occursin("1 == 2", output) end @testset "JULIA_TEST_VERBOSE" begin # Test the verbose testset entry/exit functionality Base.ScopedValues.@with Test.VERBOSE_TESTSETS => true begin # Capture output output = mktemp() do fname, f redirect_stdout(f) do @testset "Verbose Test" begin @test true @testset "Nested Verbose Test" begin sleep(0.01) # Add some duration @test 1 + 1 == 2 end end end seekstart(f) read(f, String) end # Check that verbose messages are present @test occursin("Starting testset: Verbose Test", output) @test occursin("Finished testset: Verbose Test", output) @test occursin("Starting testset: Nested Verbose Test", output) @test occursin("Finished testset: Nested Verbose Test", output) # Check that timing information is included in exit messages @test occursin(r"Finished testset: Nested Verbose Test \([0-9\.]+s\)", output) # Check indentation for nested testsets lines = split(output, '\n') entering_nested = findfirst(line -> occursin("Starting testset: Nested Verbose Test", line), lines) exiting_nested = findfirst(line -> occursin("Finished testset: Nested Verbose Test", line), lines) if entering_nested !== nothing && exiting_nested !== nothing # Both nested messages should have more indentation than outer messages @test startswith(lines[entering_nested], " ") @test startswith(lines[exiting_nested], " ") end end # Test that verbose output is disabled by default Base.ScopedValues.@with Test.VERBOSE_TESTSETS => false begin output = mktemp() do fname, f redirect_stdout(f) do @testset "Non-Verbose Test" begin @test true end end seekstart(f) read(f, String) end # Should not contain verbose messages @test !occursin("Starting testset:", output) @test !occursin("Finished testset:", output) end end # world age increments implicitly after each statement in the body of both # `@testset begin` and `@testset for`, as a special case let m = Module() Core.eval(m, :(f() = 0)) @testset "implicit world age increment in `@testset begin`" begin Core.eval(m, :(f() = 42)) @test m.f() == 42 end @testset "implicit world age increment in `@testset for` ($i)" for i in 1:2 Core.eval(m, :(f() = $i)) @test m.f() == i end end