Skip to content

Show evaluated test arguments for most functions #57825

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Mar 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion stdlib/Test/docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```
Expand Down
29 changes: 15 additions & 14 deletions stdlib/Test/src/Test.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)

#-----------------------------------------------------------------------
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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])

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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

Expand Down
133 changes: 83 additions & 50 deletions stdlib/Test/test/runtests.jl
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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