diff --git a/stdlib/Test/docs/src/index.md b/stdlib/Test/docs/src/index.md index 4989baa31d55e..7451f8434f4fa 100644 --- a/stdlib/Test/docs/src/index.md +++ b/stdlib/Test/docs/src/index.md @@ -230,7 +230,6 @@ Test Passed julia> @test 1 ≈ 0.999999 Test Failed at none:1 Expression: 1 ≈ 0.999999 - Evaluated: 1 ≈ 0.999999 ERROR: There was an error during testing ``` diff --git a/stdlib/Test/src/Test.jl b/stdlib/Test/src/Test.jl index e8d670b3d7d00..a1858ff8a213c 100644 --- a/stdlib/Test/src/Test.jl +++ b/stdlib/Test/src/Test.jl @@ -30,17 +30,6 @@ using InteractiveUtils: gen_call_with_extracted_types using Base: typesplit, remove_linenums! using Serialization: Serialization -const DISPLAY_FAILED = ( - :isequal, - :isapprox, - :≈, - :occursin, - :startswith, - :endswith, - :isempty, - :contains -) - const FAIL_FAST = Ref{Bool}(false) #----------------------------------------------------------------------- @@ -197,7 +186,7 @@ function Base.show(io::IO, t::Fail) print(io, "\n Expected: ", data) print(io, "\n No exception thrown") elseif t.test_type === :test - if data !== nothing + if data !== nothing && t.orig_expr != data # The test was an expression, so display the term-by-term # evaluated version as well print(io, "\n Evaluated: ", data) @@ -583,6 +572,16 @@ macro test_skip(ex, kws...) return :(record(get_testset(), $testres)) end +function _can_escape_call(@nospecialize ex) + ex.head === :call || return false + + # Broadcasted functions are not currently supported + first(string(ex.args[1])) != '.' || return false + + # At least one positional argument or keyword + return length(ex.args) > 1 +end + # An internal function, called by the code generated by the @test # macro to get results of the test expression. # In the special case of a comparison, e.g. x == 5, generate code to @@ -619,7 +618,7 @@ function get_test_result(ex, source) $(QuoteNode(source)), $negate, )) - elseif isa(ex, Expr) && ex.head === :call && ex.args[1] in DISPLAY_FAILED + elseif isa(ex, Expr) && _can_escape_call(ex) escaped_func = esc(ex.args[1]) quoted_func = QuoteNode(ex.args[1]) @@ -816,7 +815,7 @@ function do_test_throws(result::ExecutionResult, orig_expr, extype) Base.depwarn("macroexpand no longer throws a LoadError so `@test_throws LoadError ...` is deprecated and passed without checking the error type!", :do_test_throws) true elseif extype == ErrorException && isa(exc, FieldError) - Base.depwarn(lazy"ErrorException should no longer be used to test field access; FieldError should be used instead!", :do_test_throws) + Base.depwarn(lazy"Using ErrorException to test field access is deprecated; use FieldError instead.", :do_test_throws) true else isa(exc, extype) @@ -1623,6 +1622,7 @@ julia> @testset let logi = log(im) end Test Failed at none:3 Expression: !(iszero(real(logi))) + Evaluated: !(iszero(0.0)) Context: logi = 0.0 + 1.5707963267948966im ERROR: There was an error during testing @@ -1633,6 +1633,7 @@ julia> @testset let logi = log(im), op = !iszero end Test Failed at none:3 Expression: op(real(logi)) + Evaluated: op(0.0) Context: logi = 0.0 + 1.5707963267948966im op = !iszero diff --git a/stdlib/Test/test/runtests.jl b/stdlib/Test/test/runtests.jl index 02523dc6fd911..5b2963a0e1ea4 100644 --- a/stdlib/Test/test/runtests.jl +++ b/stdlib/Test/test/runtests.jl @@ -1,7 +1,7 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license using Test, Random -using Test: guardseed +using Test: guardseed, _can_escape_call using Serialization using Distributed: RemoteException @@ -115,54 +115,61 @@ let fails = @testset NoThrowTestSet begin @test_throws OverflowError error() # 2 - Fail - no exception @test_throws OverflowError 1 + 1 - # 3 - Fail - comparison + # 3 & 4 - Fail - comparison + @test 1 == 2 @test 1+1 == 2+2 - # 4 - Fail - approximate comparison + # 5 - Fail - approximate comparison @test 1/1 ≈ 2/1 - # 5 - Fail - chained comparison + # 6 - Fail - chained comparison @test 1+0 == 2+0 == 3+0 - # 6 - Fail - comparison call + # 7 - Fail - comparison call @test ==(1 - 2, 2 - 1) - # 7 - Fail - splatting + # 8 - Fail - splatting @test ==(1:2...) - # 8 - Fail - isequal + # 9 - Fail - isequal @test isequal(0 / 0, 1 / 0) - # 9 - Fail - function splatting + # 10 - Fail - function splatting @test isequal(1:2...) - # 10 - Fail - isapprox + # 11 - Fail - isapprox @test isapprox(0 / 1, -1 / 0) - # 11 & 12 - Fail - function with keyword + # 12 & 13 - Fail - function with keyword @test isapprox(1 / 2, 2 / 1, atol=1 / 1) @test isapprox(1 - 2, 2 - 1; atol=1 - 1) - # 13 - Fail - function keyword splatting + # 14 - Fail - function keyword splatting k = [(:atol, 0), (:nans, true)] @test isapprox(1, 2; k...) - # 14 - Fail - call negation + # 15 - Fail - call negation @test !isequal(1, 2 - 1) - # 15 - Fail - comparison negation + # 16 - Fail - comparison negation @test !(2 + 3 == 1 + 4) - # 16 - Fail - chained negation + # 17 - Fail - chained negation @test !(2 + 3 == 1 + 4 == 5) - # 17 - Fail - isempty + # 18 - Fail - isempty nonempty = [1, 2, 3] @test isempty(nonempty) str1 = "Hello" str2 = "World" - # 18 - Fail - occursin + # 19 - Fail - occursin @test occursin(str1, str2) - # 19 - Fail - startswith + # 20 - Fail - startswith @test startswith(str1, str2) - # 20 - Fail - endswith + # 21 - Fail - endswith @test endswith(str1, str2) - # 21 - Fail - contains - @test contains(str1, str2) - # 22 - Fail - Type Comparison + # 22 - Fail - contains + @test Base.contains(str1, str2) + # 23 - Fail - issetequal + a = [1, 2] + b = [1, 3] + @test issetequal(a, b) + # 24 - Fail - Type Comparison @test typeof(1) <: typeof("julia") - # 23 - 26 - Fail - wrong message + # 27 - 28 - 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] + # 29 - Fail - broadcast + @test 1 .== 2 end for fail in fails @test fail isa Test.Fail @@ -179,125 +186,140 @@ let fails = @testset NoThrowTestSet begin 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[4]) + 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[5]) + 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[6]) + 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[7]) + let str = sprint(show, fails[8]) @test occursin("Expression: (==)(1:2...)", str) - @test !occursin("Evaluated", str) + @test occursin("Evaluated: 1 == 2", str) end - let str = sprint(show, fails[8]) + let str = sprint(show, fails[9]) @test occursin("Expression: isequal(0 / 0, 1 / 0)", str) @test occursin("Evaluated: isequal(NaN, Inf)", str) end - let str = sprint(show, fails[9]) + let str = sprint(show, fails[10]) @test occursin("Expression: isequal(1:2...)", str) @test occursin("Evaluated: isequal(1, 2)", str) end - let str = sprint(show, fails[10]) + let str = sprint(show, fails[11]) @test occursin("Expression: isapprox(0 / 1, -1 / 0)", str) @test occursin("Evaluated: isapprox(0.0, -Inf)", str) end - let str = sprint(show, fails[11]) + let str = sprint(show, fails[12]) @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[12]) + let str = sprint(show, fails[13]) @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[13]) + let str = sprint(show, fails[14]) @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[14]) + let str = sprint(show, fails[15]) @test occursin("Expression: !(isequal(1, 2 - 1))", str) @test occursin("Evaluated: !(isequal(1, 1))", str) end - let str = sprint(show, fails[15]) + let str = sprint(show, fails[16]) @test occursin("Expression: !(2 + 3 == 1 + 4)", str) @test occursin("Evaluated: !(5 == 5)", str) end - let str = sprint(show, fails[16]) + let str = sprint(show, fails[17]) @test occursin("Expression: !(2 + 3 == 1 + 4 == 5)", str) @test occursin("Evaluated: !(5 == 5 == 5)", str) end - let str = sprint(show, fails[17]) + let str = sprint(show, fails[18]) @test occursin("Expression: isempty(nonempty)", str) @test occursin("Evaluated: isempty([1, 2, 3])", str) end - let str = sprint(show, fails[18]) + let str = sprint(show, fails[19]) @test occursin("Expression: occursin(str1, str2)", str) @test occursin("Evaluated: occursin(\"Hello\", \"World\")", str) end - let str = sprint(show, fails[19]) + let str = sprint(show, fails[20]) @test occursin("Expression: startswith(str1, str2)", str) @test occursin("Evaluated: startswith(\"Hello\", \"World\")", str) end - let str = sprint(show, fails[20]) + let str = sprint(show, fails[21]) @test occursin("Expression: endswith(str1, str2)", str) @test occursin("Evaluated: endswith(\"Hello\", \"World\")", str) end - let str = sprint(show, fails[21]) - @test occursin("Expression: contains(str1, str2)", str) - @test occursin("Evaluated: contains(\"Hello\", \"World\")", str) + let str = sprint(show, fails[22]) + @test occursin("Expression: Base.contains(str1, str2)", str) + @test occursin("Evaluated: Base.contains(\"Hello\", \"World\")", str) end - let str = sprint(show, fails[22]) + let str = sprint(show, fails[23]) + @test occursin("Expression: issetequal(a, b)", str) + @test occursin("Evaluated: issetequal([1, 2], [1, 3])", str) + end + + let str = sprint(show, fails[24]) @test occursin("Expression: typeof(1) <: typeof(\"julia\")", str) @test occursin("Evaluated: $(typeof(1)) <: $(typeof("julia"))", str) end - let str = sprint(show, fails[23]) + let str = sprint(show, fails[25]) @test occursin("Expected: \"A test\"", str) @test occursin("Message: \"a test\"", str) end - let str = sprint(show, fails[24]) + let str = sprint(show, fails[26]) @test occursin("Expected: r\"sqrt\\([Cc]omplx\"", str) @test occursin(r"Message: .*Try sqrt\(Complex", str) end - let str = sprint(show, fails[25]) + let str = sprint(show, fails[27]) @test occursin("Expected: < match function >", str) @test occursin("Message: \"a test\"", str) end - let str = sprint(show, fails[26]) + let str = sprint(show, fails[28]) @test occursin("Expected: [\"BoundsError\", \"acquire\", \"1-element\", \"at index [2]\"]", str) @test occursin(r"Message: \"BoundsError.* 1-element.*at index \[2\]", str) end + let str = sprint(show, fails[29]) + @test occursin("Expression: 1 .== 2", str) + @test !occursin("Evaluated", str) + end + end struct BadError <: Exception end @@ -345,9 +367,9 @@ let retval_tests = @testset NoThrowTestSet begin 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, 3, LineNumberNode(0, "An Error Mock")) - @test Test.record(ts, error_mock) isa Test.Error + @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) isa Test.Fail + @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 @@ -1770,3 +1792,14 @@ end end end end + +@testset "_can_escape_call" begin + @test !_can_escape_call(:(f())) + @test _can_escape_call(:(f(x))) + @test _can_escape_call(:(f(; x))) + @test _can_escape_call(:(f(x=1))) + + @test _can_escape_call(:(x == y)) + @test !_can_escape_call(:(x .== y)) + @test !_can_escape_call(:((==).(x, y))) +end