Skip to content

Commit 0b39226

Browse files
Add test verbose env var and show when starting/finishing testsets (JuliaLang#59295)
1 parent b2f8ee8 commit 0b39226

File tree

4 files changed

+147
-2
lines changed

4 files changed

+147
-2
lines changed

NEWS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ New library functions
4848
---------------------
4949

5050
* `ispositive(::Real)` and `isnegative(::Real)` are provided for performance and convenience ([#53677]).
51+
* The `Test` module now supports the `JULIA_TEST_VERBOSE` environment variable. When set to `true`,
52+
it enables verbose testset entry/exit messages with timing information and sets the default `verbose=true`
53+
for `DefaultTestSet` to show detailed hierarchical test summaries ([#59295]).
5154
* Exporting function `fieldindex` to get the index of a struct's field ([#58119]).
5255
* `Base.donotdelete` is now public. It prevents deadcode elemination of its arguments ([#55774]).
5356
* `Sys.sysimage_target()` returns the CPU target string used to build the current system image ([#58970]).

stdlib/Test/docs/src/index.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,39 @@ Foo Tests | 8 8 0.0s
177177
Arrays 3 | 2 2 0.0s
178178
```
179179

180+
### Environment Variable Support
181+
182+
The `Test` module supports the `JULIA_TEST_VERBOSE` environment variable for controlling
183+
verbose behavior globally:
184+
185+
- When `JULIA_TEST_VERBOSE=true`, testsets will automatically use `verbose=true` by default,
186+
and additionally print "Starting testset" and "Finished testset" messages with timing
187+
information as testsets are entered and exited.
188+
- When `JULIA_TEST_VERBOSE=false` or unset, testsets use `verbose=false` by default and
189+
no entry/exit messages are printed.
190+
191+
This environment variable provides a convenient way to enable comprehensive verbose output
192+
for debugging test suites without modifying the test code itself.
193+
194+
```julia
195+
$ JULIA_TEST_VERBOSE=true julia -e '
196+
using Test
197+
@testset "Example" begin
198+
@test 1 + 1 == 2
199+
@testset "Nested" begin
200+
@test 2 * 2 == 4
201+
end
202+
end'
203+
204+
Starting testset: Example
205+
Starting testset: Nested
206+
Finished testset: Nested (0.0s)
207+
Finished testset: Example (0.0s)
208+
Test Summary: | Pass Total Time
209+
Example | 2 2 0.0s
210+
Nested | 1 1 0.0s
211+
```
212+
180213
If we do have a test failure, only the details for the failed test sets will be shown:
181214

182215
```julia-repl; filter = r"[0-9\.]+s"

stdlib/Test/src/Test.jl

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@ All tests belong to a *test set*. There is a default, task-level
1010
test set that throws on the first failure. Users can choose to wrap
1111
their tests in (possibly nested) test sets that will store results
1212
and summarize them at the end of the test set with `@testset`.
13+
14+
Environment variables:
15+
16+
* `JULIA_TEST_VERBOSE`: Set to `true` to enable verbose test output, including
17+
testset entry/exit messages and detailed hierarchical test summaries.
18+
* `JULIA_TEST_FAILFAST`: Set to `true` to stop testing on the first failure.
19+
* `JULIA_TEST_RECORD_PASSES`: Set to `true` to record passed tests (for debugging).
1320
"""
1421
module Test
1522

@@ -1226,7 +1233,7 @@ mutable struct DefaultTestSet <: AbstractTestSet
12261233
results_lock::ReentrantLock
12271234
results::Vector{Any}
12281235
end
1229-
function DefaultTestSet(desc::AbstractString; verbose::Bool = false, showtiming::Bool = true, failfast::Union{Nothing,Bool} = nothing, source = nothing, rng = nothing)
1236+
function DefaultTestSet(desc::AbstractString; verbose::Bool = something(Base.ScopedValues.get(VERBOSE_TESTSETS)), showtiming::Bool = true, failfast::Union{Nothing,Bool} = nothing, source = nothing, rng = nothing)
12301237
if isnothing(failfast)
12311238
# pass failfast state into child testsets
12321239
parent_ts = get_testset()
@@ -2103,9 +2110,19 @@ const TESTSET_PRINT_ENABLE = ScopedValue{Bool}(true)
21032110
const TEST_RECORD_PASSES = LazyScopedValue{Bool}(OncePerProcess{Bool}() do
21042111
return Base.get_bool_env("JULIA_TEST_RECORD_PASSES", false)
21052112
end)
2113+
const VERBOSE_TESTSETS = LazyScopedValue{Bool}(OncePerProcess{Bool}() do
2114+
return Base.get_bool_env("JULIA_TEST_VERBOSE", false)
2115+
end)
21062116

21072117
macro with_testset(ts, expr)
2108-
:(@with(CURRENT_TESTSET => $(esc(ts)), TESTSET_DEPTH => get_testset_depth() + 1, $(esc(expr))))
2118+
quote
2119+
print_testset_verbose(:enter, $(esc(ts)))
2120+
try
2121+
@with(CURRENT_TESTSET => $(esc(ts)), TESTSET_DEPTH => get_testset_depth() + 1, $(esc(expr)))
2122+
finally
2123+
print_testset_verbose(:exit, $(esc(ts)))
2124+
end
2125+
end
21092126
end
21102127

21112128
"""
@@ -2127,6 +2144,41 @@ function get_testset_depth()
21272144
something(Base.ScopedValues.get(TESTSET_DEPTH))
21282145
end
21292146

2147+
"""
2148+
Print testset entry/exit messages when JULIA_TEST_VERBOSE is set
2149+
"""
2150+
function print_testset_verbose(action::Symbol, ts::AbstractTestSet)
2151+
something(Base.ScopedValues.get(VERBOSE_TESTSETS)) || return
2152+
indent = " " ^ get_testset_depth()
2153+
desc = if hasfield(typeof(ts), :description)
2154+
ts.description
2155+
elseif isa(ts, ContextTestSet)
2156+
string(ts.context_name, " = ", ts.context)
2157+
else
2158+
string(typeof(ts))
2159+
end
2160+
if action === :enter
2161+
println("$(indent)Starting testset: $desc")
2162+
elseif action === :exit
2163+
duration_str = ""
2164+
# Calculate duration for testsets that have timing information
2165+
if hasfield(typeof(ts), :time_start) && hasfield(typeof(ts), :showtiming)
2166+
if ts.showtiming
2167+
current_time = time()
2168+
dur_s = current_time - ts.time_start
2169+
if dur_s < 60
2170+
duration_str = " ($(round(dur_s, digits = 1))s)"
2171+
else
2172+
m, s = divrem(dur_s, 60)
2173+
s = lpad(string(round(s, digits = 1)), 4, "0")
2174+
duration_str = " ($(round(Int, m))m$(s)s)"
2175+
end
2176+
end
2177+
end
2178+
println("$(indent)Finished testset: $desc$duration_str")
2179+
end
2180+
end
2181+
21302182
_args_and_call((args..., f)...; kwargs...) = (args, kwargs, f(args...; kwargs...))
21312183
_materialize_broadcasted(f, args...) = Broadcast.materialize(Broadcast.broadcasted(f, args...))
21322184

stdlib/Test/test/runtests.jl

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2033,3 +2033,60 @@ end
20332033
@test occursin("Error in testset", output)
20342034
@test occursin("1 == 2", output)
20352035
end
2036+
2037+
@testset "JULIA_TEST_VERBOSE" begin
2038+
# Test the verbose testset entry/exit functionality
2039+
Base.ScopedValues.@with Test.VERBOSE_TESTSETS => true begin
2040+
# Capture output
2041+
output = mktemp() do fname, f
2042+
redirect_stdout(f) do
2043+
@testset "Verbose Test" begin
2044+
@test true
2045+
@testset "Nested Verbose Test" begin
2046+
sleep(0.01) # Add some duration
2047+
@test 1 + 1 == 2
2048+
end
2049+
end
2050+
end
2051+
seekstart(f)
2052+
read(f, String)
2053+
end
2054+
2055+
# Check that verbose messages are present
2056+
@test occursin("Starting testset: Verbose Test", output)
2057+
@test occursin("Finished testset: Verbose Test", output)
2058+
@test occursin("Starting testset: Nested Verbose Test", output)
2059+
@test occursin("Finished testset: Nested Verbose Test", output)
2060+
2061+
# Check that timing information is included in exit messages
2062+
@test occursin(r"Finished testset: Nested Verbose Test \([0-9\.]+s\)", output)
2063+
2064+
# Check indentation for nested testsets
2065+
lines = split(output, '\n')
2066+
entering_nested = findfirst(line -> occursin("Starting testset: Nested Verbose Test", line), lines)
2067+
exiting_nested = findfirst(line -> occursin("Finished testset: Nested Verbose Test", line), lines)
2068+
2069+
if entering_nested !== nothing && exiting_nested !== nothing
2070+
# Both nested messages should have more indentation than outer messages
2071+
@test startswith(lines[entering_nested], " ")
2072+
@test startswith(lines[exiting_nested], " ")
2073+
end
2074+
end
2075+
2076+
# Test that verbose output is disabled by default
2077+
Base.ScopedValues.@with Test.VERBOSE_TESTSETS => false begin
2078+
output = mktemp() do fname, f
2079+
redirect_stdout(f) do
2080+
@testset "Non-Verbose Test" begin
2081+
@test true
2082+
end
2083+
end
2084+
seekstart(f)
2085+
read(f, String)
2086+
end
2087+
2088+
# Should not contain verbose messages
2089+
@test !occursin("Starting testset:", output)
2090+
@test !occursin("Finished testset:", output)
2091+
end
2092+
end

0 commit comments

Comments
 (0)