From 16f6131cfba15e4b88583c89e39a6b14cb723e6c Mon Sep 17 00:00:00 2001 From: serenity4 Date: Mon, 17 Apr 2023 22:31:26 +0200 Subject: [PATCH 01/12] Extend backtraces for tests not directly under testset --- stdlib/Test/src/Test.jl | 64 ++++++++++++++++++++++++++++++++--------- 1 file changed, 50 insertions(+), 14 deletions(-) diff --git a/stdlib/Test/src/Test.jl b/stdlib/Test/src/Test.jl index 0253b5a42520c..6ddf5ad12dc8b 100644 --- a/stdlib/Test/src/Test.jl +++ b/stdlib/Test/src/Test.jl @@ -47,23 +47,47 @@ const FAIL_FAST = Ref{Bool}(false) # Backtrace utility functions function ip_has_file_and_func(ip, file, funcs) - return any(fr -> (string(fr.file) == file && fr.func in funcs), StackTraces.lookup(ip)) + return any(fr -> (in_file(fr, file) && fr.func in funcs), StackTraces.lookup(ip)) end +in_file(frame, file) = string(frame.file) == file -function scrub_backtrace(bt) +function test_location(bt, file_ts, file_t) + (isnothing(file_ts) || isnothing(file_t)) && return macrocall_location(bt, something(file_ts, @__FILE__)) + index = test_callsite(bt, file_ts, file_t) + # An IP index pointing to a relevant callsite has been found. + !isnothing(index) && return index + # The macrocall `__source__` will be printed in the test message upon failure. + # Always include at least the internal macrocall location in the stacktrace. + return macrocall_location(bt, @__FILE__) +end + +function test_callsite(bt, file_ts, file_t) + macrocall = findfirst(ip -> any(frame -> in_file(frame, file_t), StackTraces.lookup(ip)), bt)::Int + testset = macrocall_location(bt, file_ts)::Int + macrocall != testset && return testset + frames = StackTraces.lookup(bt[testset]) + outer_frame = findfirst(frame -> in_file(frame, file_ts) && frame.func == Symbol("macro expansion"), frames)::Int + # The `@test` call has been directly made in a `@testset`. + # No need to include the testset in the stacktrace. + in_file(frames[outer_frame], file_t) && return nothing + return testset +end + +macrocall_location(bt, file) = findfirst(ip -> ip_has_file_and_func(ip, file, (Symbol("macro expansion"),)), bt) +toplevel_location(bt, file) = findfirst(ip -> ip_has_file_and_func(ip, file, (Symbol("top-level scope"),)), bt) + +function scrub_backtrace(bt, file_ts, file_t) do_test_ind = findfirst(ip -> ip_has_file_and_func(ip, @__FILE__, (:do_test, :do_test_throws)), bt) if do_test_ind !== nothing && length(bt) > do_test_ind bt = bt[do_test_ind + 1:end] end - name_ind = findfirst(ip -> ip_has_file_and_func(ip, @__FILE__, (Symbol("macro expansion"),)), bt) - if name_ind !== nothing && length(bt) != 0 - bt = bt[1:name_ind] - end + stop_at = test_location(bt, file_ts, file_t) + !isnothing(stop_at) && !isempty(bt) && return bt[1:stop_at] return bt end -function scrub_exc_stack(stack) - return Any[ (x[1], scrub_backtrace(x[2]::Vector{Union{Ptr{Nothing},Base.InterpreterIP}})) for x in stack ] +function scrub_exc_stack(stack, file_ts, file_t) + return Any[ (x[1], scrub_backtrace(x[2]::Vector{Union{Ptr{Nothing},Base.InterpreterIP}}, file_ts, file_t)) for x in stack ] end # define most of the test infrastructure without type specialization @@ -185,7 +209,7 @@ struct Error <: Result function Error(test_type::Symbol, orig_expr, value, bt, source::LineNumberNode) if test_type === :test_error - bt = scrub_exc_stack(bt) + bt = scrub_exc_stack(bt, nothing, extract_file(source)) end if test_type === :test_error || test_type === :nontest_error bt_str = try # try the latest world for this, since we might have eval'd new code for show @@ -1013,8 +1037,9 @@ mutable struct DefaultTestSet <: AbstractTestSet time_start::Float64 time_end::Union{Float64,Nothing} failfast::Bool + file::Union{String,Nothing} end -function DefaultTestSet(desc::AbstractString; verbose::Bool = false, showtiming::Bool = true, failfast::Union{Nothing,Bool} = nothing) +function DefaultTestSet(desc::AbstractString; verbose::Bool = false, showtiming::Bool = true, failfast::Union{Nothing,Bool} = nothing, source = nothing) if isnothing(failfast) # pass failfast state into child testsets parent_ts = get_testset() @@ -1024,8 +1049,11 @@ function DefaultTestSet(desc::AbstractString; verbose::Bool = false, showtiming: failfast = false end end - return DefaultTestSet(String(desc)::String, [], 0, false, verbose, showtiming, time(), nothing, failfast) + return DefaultTestSet(String(desc)::String, [], 0, false, verbose, showtiming, time(), nothing, failfast, extract_file(source)) end +extract_file(source::LineNumberNode) = extract_file(source.file) +extract_file(file::Symbol) = string(file) +extract_file(::Nothing) = nothing struct FailFastError <: Exception end @@ -1043,7 +1071,7 @@ function record(ts::DefaultTestSet, t::Union{Fail, Error}; print_result::Bool=TE if !(t isa Error) || t.test_type !== :test_interrupted print(t) if !isa(t, Error) # if not gets printed in the show method - Base.show_backtrace(stdout, scrub_backtrace(backtrace())) + Base.show_backtrace(stdout, scrub_backtrace(backtrace(), ts.file, extract_file(t.source))) end println() end @@ -1489,7 +1517,11 @@ function testset_beginend_call(args, tests, source) ex = quote _check_testset($testsettype, $(QuoteNode(testsettype.args[1]))) local ret - local ts = $(testsettype)($desc; $options...) + local ts = if ($testsettype === $DefaultTestSet) && $(isa(source, LineNumberNode)) + $(testsettype)($desc; source=$(QuoteNode(source.file)), $options...) + else + $(testsettype)($desc; $options...) + end push_testset(ts) # we reproduce the logic of guardseed, but this function # cannot be used as it changes slightly the semantic of @testset, @@ -1585,7 +1617,11 @@ function testset_forloop(args, testloop, source) copy!(RNG, tmprng) end - ts = $(testsettype)($desc; $options...) + ts = if ($testsettype === $DefaultTestSet) && $(isa(source, LineNumberNode)) + $(testsettype)($desc; source=$(QuoteNode(source.file)), $options...) + else + $(testsettype)($desc; $options...) + end push_testset(ts) first_iteration = false try From 90f6a701194640d5fc686d27c04f27ffd810700c Mon Sep 17 00:00:00 2001 From: serenity4 Date: Wed, 19 Apr 2023 11:37:06 +0200 Subject: [PATCH 02/12] Add tests --- stdlib/Test/test/runtests.jl | 103 +++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/stdlib/Test/test/runtests.jl b/stdlib/Test/test/runtests.jl index ac643e0ccfca2..ba5a8ff0affc5 100644 --- a/stdlib/Test/test/runtests.jl +++ b/stdlib/Test/test/runtests.jl @@ -722,6 +722,109 @@ end 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) + eval(Expr(:macrocall, Symbol("@test"), nothing, :false)) + eval(Expr(:macrocall, Symbol("@testset"), nothing, "Testset without source", quote + @test false + end)) + """) + + runtests = tempname() + write(runtests, + """ + using Test + + include("$utils") + + function test_properties(value) + @test isodd(value) + end + + @testset "Tests" begin + test_properties(8) + @noinline test_properties(8) + test_properties2(8) + + include("$included") + end + """) + msg = read(pipeline(ignorestatus(`$(Base.julia_cmd()) --startup-file=no --color=no $runtests`), stderr=devnull), String) + regex = r"((?:Tests|Other tests|Testset without source): Test Failed (?:.|\n)*?)\n\nStacktrace:(?:.|\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 tests in total, 8 failures + 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 * ":6"), 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 * ":8" && fail.ex == "false" + @test count(contains(included * ":8"), 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()) From f8f33433e44ea861b67849f9e4fad4a641cd4aa3 Mon Sep 17 00:00:00 2001 From: serenity4 Date: Fri, 21 Apr 2023 11:04:36 +0200 Subject: [PATCH 03/12] Add basic test when test errors --- stdlib/Test/test/runtests.jl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/stdlib/Test/test/runtests.jl b/stdlib/Test/test/runtests.jl index ba5a8ff0affc5..3466690338adf 100644 --- a/stdlib/Test/test/runtests.jl +++ b/stdlib/Test/test/runtests.jl @@ -742,6 +742,7 @@ end eval(Expr(:macrocall, Symbol("@test"), nothing, :false)) eval(Expr(:macrocall, Symbol("@testset"), nothing, "Testset without source", quote @test false + @test error("failed") end)) """) @@ -770,7 +771,8 @@ end 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 tests in total, 8 failures + @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" From 8b080e78435808af3b2c7537f705e3f6f4d12a59 Mon Sep 17 00:00:00 2001 From: serenity4 Date: Fri, 21 Apr 2023 11:28:51 +0200 Subject: [PATCH 04/12] Remove unused function --- stdlib/Test/src/Test.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/stdlib/Test/src/Test.jl b/stdlib/Test/src/Test.jl index 6ddf5ad12dc8b..9cc0c5c676277 100644 --- a/stdlib/Test/src/Test.jl +++ b/stdlib/Test/src/Test.jl @@ -74,7 +74,6 @@ function test_callsite(bt, file_ts, file_t) end macrocall_location(bt, file) = findfirst(ip -> ip_has_file_and_func(ip, file, (Symbol("macro expansion"),)), bt) -toplevel_location(bt, file) = findfirst(ip -> ip_has_file_and_func(ip, file, (Symbol("top-level scope"),)), bt) function scrub_backtrace(bt, file_ts, file_t) do_test_ind = findfirst(ip -> ip_has_file_and_func(ip, @__FILE__, (:do_test, :do_test_throws)), bt) From dc8a0217a8980d76695182c2ad6fa463a8b0ddeb Mon Sep 17 00:00:00 2001 From: serenity4 Date: Fri, 21 Apr 2023 11:59:17 +0200 Subject: [PATCH 05/12] Add comments --- stdlib/Test/src/Test.jl | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/stdlib/Test/src/Test.jl b/stdlib/Test/src/Test.jl index 9cc0c5c676277..0434b42c78807 100644 --- a/stdlib/Test/src/Test.jl +++ b/stdlib/Test/src/Test.jl @@ -54,7 +54,7 @@ in_file(frame, file) = string(frame.file) == file function test_location(bt, file_ts, file_t) (isnothing(file_ts) || isnothing(file_t)) && return macrocall_location(bt, something(file_ts, @__FILE__)) index = test_callsite(bt, file_ts, file_t) - # An IP index pointing to a relevant callsite has been found. + # A stacktrace index pointing to a relevant callsite has been found. !isnothing(index) && return index # The macrocall `__source__` will be printed in the test message upon failure. # Always include at least the internal macrocall location in the stacktrace. @@ -64,12 +64,17 @@ end function test_callsite(bt, file_ts, file_t) macrocall = findfirst(ip -> any(frame -> in_file(frame, file_t), StackTraces.lookup(ip)), bt)::Int testset = macrocall_location(bt, file_ts)::Int + # If stacktrace locations differ, use the stacktrace in which `@testset` appears. macrocall != testset && return testset + # `@test` and `@testset` occurred at the same stacktrace location. + # This may happen if `@test` occurred directly in scope of the testset, + # or if `@test` occurred in a function that has been inlined in the testset. frames = StackTraces.lookup(bt[testset]) outer_frame = findfirst(frame -> in_file(frame, file_ts) && frame.func == Symbol("macro expansion"), frames)::Int - # The `@test` call has been directly made in a `@testset`. - # No need to include the testset in the stacktrace. + # The `@test` call occurred directly in scope of a `@testset`. + # The __source__ from `@test` is enough, no need to include more frames. in_file(frames[outer_frame], file_t) && return nothing + # The `@test` call was simply inlined, so we still need to include the callsite. return testset end From d6366008a6518e663baccc5a17d0ba9eedc1e97e Mon Sep 17 00:00:00 2001 From: serenity4 Date: Fri, 21 Apr 2023 12:13:21 +0200 Subject: [PATCH 06/12] Add NEWS.md entry --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index 931db0ad1081f..62007e0e04bc9 100644 --- a/NEWS.md +++ b/NEWS.md @@ -102,6 +102,7 @@ Standard library changes * The `@test_broken` macro (or `@test` with `broken=true`) now complains if the test expression returns a non-boolean value in the same way as a non-broken test. ([#47804]) +* When a call to `@test` fails or errors inside a function, a larger stacktrace is now printed such that the location of the test within a `@testset` can be retrieved ([#49451]) #### Dates From 5c679a9599bbec54b5d8131d6fa6b091167359b4 Mon Sep 17 00:00:00 2001 From: serenity4 Date: Sun, 23 Apr 2023 14:10:08 +0200 Subject: [PATCH 07/12] Add comment for `eval` tests --- stdlib/Test/test/runtests.jl | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/stdlib/Test/test/runtests.jl b/stdlib/Test/test/runtests.jl index 3466690338adf..f4d92d5585947 100644 --- a/stdlib/Test/test/runtests.jl +++ b/stdlib/Test/test/runtests.jl @@ -739,6 +739,8 @@ end 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 @@ -818,12 +820,12 @@ end @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 * ":6"), lines) == 1 # test + @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 * ":8" && fail.ex == "false" - @test count(contains(included * ":8"), lines) == 2 # @testset + test + @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 From 626ad2a6fdfd0a2c65fbc83362cdf4541f2188d9 Mon Sep 17 00:00:00 2001 From: serenity4 Date: Sun, 23 Apr 2023 16:57:20 +0200 Subject: [PATCH 08/12] Avoid duplicating calls to lookup --- stdlib/Test/src/Test.jl | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/stdlib/Test/src/Test.jl b/stdlib/Test/src/Test.jl index 0434b42c78807..7b74f33c3b7a7 100644 --- a/stdlib/Test/src/Test.jl +++ b/stdlib/Test/src/Test.jl @@ -52,19 +52,23 @@ end in_file(frame, file) = string(frame.file) == file function test_location(bt, file_ts, file_t) - (isnothing(file_ts) || isnothing(file_t)) && return macrocall_location(bt, something(file_ts, @__FILE__)) - index = test_callsite(bt, file_ts, file_t) - # A stacktrace index pointing to a relevant callsite has been found. - !isnothing(index) && return index - # The macrocall `__source__` will be printed in the test message upon failure. - # Always include at least the internal macrocall location in the stacktrace. - return macrocall_location(bt, @__FILE__) + if (isnothing(file_ts) || isnothing(file_t)) + return macrocall_location(bt, something(file_ts, @__FILE__)) + else + return test_callsite(bt, file_ts, file_t) + end end function test_callsite(bt, file_ts, file_t) - macrocall = findfirst(ip -> any(frame -> in_file(frame, file_t), StackTraces.lookup(ip)), bt)::Int - testset = macrocall_location(bt, file_ts)::Int - # If stacktrace locations differ, use the stacktrace in which `@testset` appears. + # We avoid duplicate calls to `StackTraces.lookup`, as it is an expensive call. + # For that, we retrieve locations from lower to higher stack elements + # and only traverse parts of the backtrace which we haven't traversed before. + # The order will always be -> `@test` -> `@testset`. + internal = macrocall_location(bt, @__FILE__)::Int + macrocall = internal - 1 + findfirst(ip -> any(frame -> in_file(frame, file_t), StackTraces.lookup(ip)), @view bt[internal:end])::Int + testset = macrocall - 1 + macrocall_location(@view(bt[macrocall:end]), file_ts)::Int + + # If stacktrace locations differ, include frames until the `@testset` appears. macrocall != testset && return testset # `@test` and `@testset` occurred at the same stacktrace location. # This may happen if `@test` occurred directly in scope of the testset, @@ -72,9 +76,10 @@ function test_callsite(bt, file_ts, file_t) frames = StackTraces.lookup(bt[testset]) outer_frame = findfirst(frame -> in_file(frame, file_ts) && frame.func == Symbol("macro expansion"), frames)::Int # The `@test` call occurred directly in scope of a `@testset`. - # The __source__ from `@test` is enough, no need to include more frames. - in_file(frames[outer_frame], file_t) && return nothing - # The `@test` call was simply inlined, so we still need to include the callsite. + # The __source__ from `@test` will be printed in the test message upon failure. + # There is no need to include more frames, but always include at least the internal macrocall location in the stacktrace. + in_file(frames[outer_frame], file_t) && return internal + # The `@test` call was inlined, so we still need to include the callsite. return testset end @@ -86,7 +91,7 @@ function scrub_backtrace(bt, file_ts, file_t) bt = bt[do_test_ind + 1:end] end stop_at = test_location(bt, file_ts, file_t) - !isnothing(stop_at) && !isempty(bt) && return bt[1:stop_at] + !isempty(bt) && return bt[1:stop_at] return bt end From 6bb23d7d2e2afa2d3e14fe969582e453fc599542 Mon Sep 17 00:00:00 2001 From: serenity4 Date: Sun, 23 Apr 2023 19:00:42 +0200 Subject: [PATCH 09/12] Rename variable --- stdlib/Test/src/Test.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/stdlib/Test/src/Test.jl b/stdlib/Test/src/Test.jl index 7b74f33c3b7a7..a11b79c2b5a74 100644 --- a/stdlib/Test/src/Test.jl +++ b/stdlib/Test/src/Test.jl @@ -65,11 +65,11 @@ function test_callsite(bt, file_ts, file_t) # and only traverse parts of the backtrace which we haven't traversed before. # The order will always be -> `@test` -> `@testset`. internal = macrocall_location(bt, @__FILE__)::Int - macrocall = internal - 1 + findfirst(ip -> any(frame -> in_file(frame, file_t), StackTraces.lookup(ip)), @view bt[internal:end])::Int - testset = macrocall - 1 + macrocall_location(@view(bt[macrocall:end]), file_ts)::Int + test = internal - 1 + findfirst(ip -> any(frame -> in_file(frame, file_t), StackTraces.lookup(ip)), @view bt[internal:end])::Int + testset = test - 1 + macrocall_location(@view(bt[test:end]), file_ts)::Int # If stacktrace locations differ, include frames until the `@testset` appears. - macrocall != testset && return testset + test != testset && return testset # `@test` and `@testset` occurred at the same stacktrace location. # This may happen if `@test` occurred directly in scope of the testset, # or if `@test` occurred in a function that has been inlined in the testset. From 70fa83c5fe5223433bf1aec653ee670e8f771cfb Mon Sep 17 00:00:00 2001 From: serenity4 Date: Mon, 24 Apr 2023 19:26:01 +0200 Subject: [PATCH 10/12] Remove any carriage returns for tests on Windows --- stdlib/Test/test/runtests.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/stdlib/Test/test/runtests.jl b/stdlib/Test/test/runtests.jl index f4d92d5585947..6f8185c288604 100644 --- a/stdlib/Test/test/runtests.jl +++ b/stdlib/Test/test/runtests.jl @@ -768,6 +768,7 @@ end end """) msg = read(pipeline(ignorestatus(`$(Base.julia_cmd()) --startup-file=no --color=no $runtests`), stderr=devnull), String) + msg = filter(!=('\r'), msg) regex = r"((?:Tests|Other tests|Testset without source): Test Failed (?:.|\n)*?)\n\nStacktrace:(?:.|\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) From bdb02408712847b2de2f77e15dd3e2d1c40fdf18 Mon Sep 17 00:00:00 2001 From: belmant Date: Tue, 25 Apr 2023 21:30:25 +0200 Subject: [PATCH 11/12] Correctly handle Windows filenames --- stdlib/Test/test/runtests.jl | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/stdlib/Test/test/runtests.jl b/stdlib/Test/test/runtests.jl index 6f8185c288604..0388e2107e098 100644 --- a/stdlib/Test/test/runtests.jl +++ b/stdlib/Test/test/runtests.jl @@ -723,7 +723,8 @@ end end @testset "provide informative location in backtrace for test failures" begin - utils = tempname() + win2unix(filename) = replace(filename, "\\" => '/') + utils = win2unix(tempname()) write(utils, """ function test_properties2(value) @@ -731,7 +732,7 @@ end end """) - included = tempname() + included = win2unix(tempname()) write(included, """ @testset "Other tests" begin @@ -748,7 +749,7 @@ end end)) """) - runtests = tempname() + runtests = win2unix(tempname()) write(runtests, """ using Test @@ -768,7 +769,7 @@ end end """) msg = read(pipeline(ignorestatus(`$(Base.julia_cmd()) --startup-file=no --color=no $runtests`), stderr=devnull), String) - msg = filter(!=('\r'), msg) + msg = win2unix(msg) regex = r"((?:Tests|Other tests|Testset without source): Test Failed (?:.|\n)*?)\n\nStacktrace:(?:.|\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) From 32d90034227918b6ddcbb918bb5285271622aabb Mon Sep 17 00:00:00 2001 From: serenity4 Date: Fri, 28 Apr 2023 14:50:00 +0200 Subject: [PATCH 12/12] Allow `stop_at` to be `nothing` --- stdlib/Test/src/Test.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdlib/Test/src/Test.jl b/stdlib/Test/src/Test.jl index a11b79c2b5a74..48b37ef8047fe 100644 --- a/stdlib/Test/src/Test.jl +++ b/stdlib/Test/src/Test.jl @@ -91,7 +91,7 @@ function scrub_backtrace(bt, file_ts, file_t) bt = bt[do_test_ind + 1:end] end stop_at = test_location(bt, file_ts, file_t) - !isempty(bt) && return bt[1:stop_at] + !isnothing(stop_at) && !isempty(bt) && return bt[1:stop_at] return bt end