Skip to content

Commit

Permalink
Test: add durations to testset reports (JuliaLang#43252)
Browse files Browse the repository at this point in the history
  • Loading branch information
IanButterworth authored Dec 5, 2021
1 parent 83d7eba commit ca11c2f
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 69 deletions.
6 changes: 6 additions & 0 deletions stdlib/Test/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,9 @@ InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b"

[extras]
Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b"

[targets]
test = ["Distributed"]
45 changes: 22 additions & 23 deletions stdlib/Test/docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,19 +113,19 @@ Test.TestSetException

We can put our tests for the `foo(x)` function in a test set:

```jldoctest testfoo
```jldoctest testfoo; filter = r"[0-9\.]+s"
julia> @testset "Foo Tests" begin
@test foo("a") == 1
@test foo("ab") == 4
@test foo("abc") == 9
end;
Test Summary: | Pass Total
Foo Tests | 3 3
Test Summary: | Pass Total Time
Foo Tests | 3 3 0.0s
```

Test sets can also be nested:

```jldoctest testfoo
```jldoctest testfoo; filter = r"[0-9\.]+s"
julia> @testset "Foo Tests" begin
@testset "Animals" begin
@test foo("cat") == 9
Expand All @@ -136,20 +136,19 @@ julia> @testset "Foo Tests" begin
@test foo(fill(1.0, i)) == i^2
end
end;
Test Summary: | Pass Total
Foo Tests | 8 8
Test Summary: | Pass Total Time
Foo Tests | 8 8 0.0s
```

As well as call functions:

```jldoctest testfoo
```jldoctest testfoo; filter = r"[0-9\.]+s"
julia> f(x) = @test isone(x)
f (generic function with 1 method)
julia> @testset f(1)
Test Summary: | Pass Total
f | 1 1
Test.DefaultTestSet("f", Any[], 1, false, false)
julia> @testset f(1);
Test Summary: | Pass Total Time
f | 1 1 0.0s
```

This can be used to allow for factorization of test sets, making it easier to run individual
Expand All @@ -158,7 +157,7 @@ Note that in the case of functions, the test set will be given the name of the c
In the event that a nested test set has no failures, as happened here, it will be hidden in the
summary, unless the `verbose=true` option is passed:

```jldoctest testfoo
```jldoctest testfoo; filter = r"[0-9\.]+s"
julia> @testset verbose = true "Foo Tests" begin
@testset "Animals" begin
@test foo("cat") == 9
Expand All @@ -169,17 +168,17 @@ julia> @testset verbose = true "Foo Tests" begin
@test foo(fill(1.0, i)) == i^2
end
end;
Test Summary: | Pass Total
Foo Tests | 8 8
Animals | 2 2
Arrays 1 | 2 2
Arrays 2 | 2 2
Arrays 3 | 2 2
Test Summary: | Pass Total Time
Foo Tests | 8 8 0.0s
Animals | 2 2 0.0s
Arrays 1 | 2 2 0.0s
Arrays 2 | 2 2 0.0s
Arrays 3 | 2 2 0.0s
```

If we do have a test failure, only the details for the failed test sets will be shown:

```julia-repl
```julia-repl; filter = r"[0-9\.]+s"
julia> @testset "Foo Tests" begin
@testset "Animals" begin
@testset "Felines" begin
Expand All @@ -199,10 +198,10 @@ Arrays: Test Failed
Expression: foo(fill(1.0, 4)) == 15
Evaluated: 16 == 15
[...]
Test Summary: | Pass Fail Total
Foo Tests | 3 1 4
Animals | 2 2
Arrays | 1 1 2
Test Summary: | Pass Fail Total Time
Foo Tests | 3 1 4 0.0s
Animals | 2 2 0.0s
Arrays | 1 1 2 0.0s
ERROR: Some tests did not pass: 3 passed, 1 failed, 0 errored, 0 broken.
```

Expand Down
61 changes: 44 additions & 17 deletions stdlib/Test/src/Test.jl
Original file line number Diff line number Diff line change
Expand Up @@ -978,8 +978,11 @@ mutable struct DefaultTestSet <: AbstractTestSet
n_passed::Int
anynonpass::Bool
verbose::Bool
showtiming::Bool
time_start::Float64
time_end::Union{Float64,Nothing}
end
DefaultTestSet(desc::AbstractString; verbose::Bool = false) = DefaultTestSet(String(desc)::String, [], 0, false, verbose)
DefaultTestSet(desc::AbstractString; verbose::Bool = false, showtiming::Bool = true) = DefaultTestSet(String(desc)::String, [], 0, false, verbose, showtiming, time(), nothing)

# For a broken result, simply store the result
record(ts::DefaultTestSet, t::Broken) = (push!(ts.results, t); t)
Expand Down Expand Up @@ -1026,7 +1029,7 @@ end
function print_test_results(ts::DefaultTestSet, depth_pad=0)
# Calculate the overall number for each type so each of
# the test result types are aligned
passes, fails, errors, broken, c_passes, c_fails, c_errors, c_broken = get_test_counts(ts)
passes, fails, errors, broken, c_passes, c_fails, c_errors, c_broken, duration = get_test_counts(ts)
total_pass = passes + c_passes
total_fail = fails + c_fails
total_error = errors + c_errors
Expand All @@ -1044,6 +1047,7 @@ function print_test_results(ts::DefaultTestSet, depth_pad=0)
error_width = dig_error > 0 ? max(length("Error"), dig_error) : 0
broken_width = dig_broken > 0 ? max(length("Broken"), dig_broken) : 0
total_width = dig_total > 0 ? max(length("Total"), dig_total) : 0
duration_width = max(length("Time"), length(duration))
# Calculate the alignment of the test result counts by
# recursively walking the tree of test sets
align = max(get_alignment(ts, 0), length("Test Summary:"))
Expand All @@ -1063,11 +1067,14 @@ function print_test_results(ts::DefaultTestSet, depth_pad=0)
printstyled(lpad("Broken", broken_width, " "), " "; bold=true, color=Base.warn_color())
end
if total_width > 0
printstyled(lpad("Total", total_width, " "); bold=true, color=Base.info_color())
printstyled(lpad("Total", total_width, " "), " "; bold=true, color=Base.info_color())
end
if ts.showtiming
printstyled(lpad("Time", duration_width, " "); bold=true)
end
println()
# Recursively print a summary at every level
print_counts(ts, depth_pad, align, pass_width, fail_width, error_width, broken_width, total_width)
print_counts(ts, depth_pad, align, pass_width, fail_width, error_width, broken_width, total_width, duration_width, ts.showtiming)
end


Expand All @@ -1076,6 +1083,7 @@ const TESTSET_PRINT_ENABLE = Ref(true)
# Called at the end of a @testset, behaviour depends on whether
# this is a child of another testset, or the "root" testset
function finish(ts::DefaultTestSet)
ts.time_end = time()
# If we are a nested test set, do not print a full summary
# now - let the parent test set do the printing
if get_testset_depth() != 0
Expand All @@ -1084,7 +1092,7 @@ function finish(ts::DefaultTestSet)
record(parent_ts, ts)
return ts
end
passes, fails, errors, broken, c_passes, c_fails, c_errors, c_broken = get_test_counts(ts)
passes, fails, errors, broken, c_passes, c_fails, c_errors, c_broken, duration = get_test_counts(ts)
total_pass = passes + c_passes
total_fail = fails + c_fails
total_error = errors + c_errors
Expand Down Expand Up @@ -1148,24 +1156,36 @@ function get_test_counts(ts::DefaultTestSet)
isa(t, Error) && (errors += 1)
isa(t, Broken) && (broken += 1)
if isa(t, DefaultTestSet)
np, nf, ne, nb, ncp, ncf, nce , ncb = get_test_counts(t)
np, nf, ne, nb, ncp, ncf, nce , ncb, duration = get_test_counts(t)
c_passes += np + ncp
c_fails += nf + ncf
c_errors += ne + nce
c_broken += nb + ncb
end
end
ts.anynonpass = (fails + errors + c_fails + c_errors > 0)
return passes, fails, errors, broken, c_passes, c_fails, c_errors, c_broken
duration = if isnothing(ts.time_end)
""
else
dur_s = ts.time_end - ts.time_start
if dur_s < 60
string(round(dur_s, digits = 1), "s")
else
m, s = divrem(dur_s, 60)
s = lpad(string(round(s, digits = 1)), 4, "0")
string(round(Int, m), "m", s, "s")
end
end
return passes, fails, errors, broken, c_passes, c_fails, c_errors, c_broken, duration
end

# Recursive function that prints out the results at each level of
# the tree of test sets
function print_counts(ts::DefaultTestSet, depth, align,
pass_width, fail_width, error_width, broken_width, total_width)
pass_width, fail_width, error_width, broken_width, total_width, duration_width, showtiming)
# Count results by each type at this level, and recursively
# through any child test sets
passes, fails, errors, broken, c_passes, c_fails, c_errors, c_broken = get_test_counts(ts)
passes, fails, errors, broken, c_passes, c_fails, c_errors, c_broken, duration = get_test_counts(ts)
subtotal = passes + fails + errors + broken + c_passes + c_fails + c_errors + c_broken
# Print test set header, with an alignment that ensures all
# the test results appear above each other
Expand Down Expand Up @@ -1204,9 +1224,13 @@ function print_counts(ts::DefaultTestSet, depth, align,
end

if np == 0 && nf == 0 && ne == 0 && nb == 0
printstyled("No tests", color=Base.info_color())
printstyled(lpad("None", total_width, " "), " ", color=Base.info_color())
else
printstyled(lpad(string(subtotal), total_width, " "), color=Base.info_color())
printstyled(lpad(string(subtotal), total_width, " "), " ", color=Base.info_color())
end

if showtiming
printstyled(lpad(string(duration), duration_width, " "))
end
println()

Expand All @@ -1216,7 +1240,7 @@ function print_counts(ts::DefaultTestSet, depth, align,
for t in ts.results
if isa(t, DefaultTestSet)
print_counts(t, depth + 1, align,
pass_width, fail_width, error_width, broken_width, total_width)
pass_width, fail_width, error_width, broken_width, total_width, duration_width, ts.showtiming)
end
end
end
Expand Down Expand Up @@ -1256,8 +1280,11 @@ along with a summary of the test results.
Any custom testset type (subtype of `AbstractTestSet`) can be given and it will
also be used for any nested `@testset` invocations. The given options are only
applied to the test set where they are given. The default test set type
accepts the `verbose` boolean option: if `true`, the result summary of the
nested testsets is shown even when they all pass (the default is `false`).
accepts two boolean options:
- `verbose`: if `true`, the result summary of the nested testsets is shown even
when they all pass (the default is `false`).
- `showtiming`: if `true`, the duration of each displayed testset is shown
(the default is `true`).
The description string accepts interpolation from the loop indices.
If no description is provided, one is constructed based on the variables.
Expand All @@ -1278,16 +1305,16 @@ re-arrangements of `@testset`s regardless of their side-effect on the
global RNG state.
# Examples
```jldoctest
```jldoctest; filter = r"trigonometric identities | 4 4 [0-9\\.]+s"
julia> @testset "trigonometric identities" begin
θ = 2/3*π
@test sin(-θ) ≈ -sin(θ)
@test cos(-θ) ≈ cos(θ)
@test sin(2θ) ≈ 2*sin(θ)*cos(θ)
@test cos(2θ) ≈ cos(θ)^2 - sin(θ)^2
end;
Test Summary: | Pass Total
trigonometric identities | 4 4
Test Summary: | Pass Total Time
trigonometric identities | 4 4 0.2s
```
"""
macro testset(args...)
Expand Down
38 changes: 19 additions & 19 deletions stdlib/Test/test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -760,13 +760,13 @@ let msg = read(pipeline(ignorestatus(`$(Base.julia_cmd()) --startup-file=no --co
@test foo(fill(1., 4)) == 15
end
end'`), stderr=devnull), String)
@test occursin("""
Test Summary: | Pass Fail Total
Foo Tests | 2 2 4
Animals | 1 1 2
Felines | 1 1
Canines | 1 1
Arrays | 1 1 2
@test occursin(r"""
Test Summary: | Pass Fail Total Time
Foo Tests | 2 2 4 \s*\d*.\ds
Animals | 1 1 2 \s*\d*.\ds
Felines | 1 1 \s*\d*.\ds
Canines | 1 1 \s*\d*.\ds
Arrays | 1 1 2 \s*\d*.\ds
""", msg)
end

Expand Down Expand Up @@ -1078,18 +1078,18 @@ let ex = :(something_complex + [1, 2, 3])
end

@testset "verbose option" begin
expected = """
Test Summary: | Pass Total
Parent | 9 9
Child 1 | 3 3
Child 1.1 (long name) | 1 1
Child 1.2 | 1 1
Child 1.3 | 1 1
Child 2 | 3 3
Child 3 | 3 3
Child 3.1 | 1 1
Child 3.2 | 1 1
Child 3.3 | 1 1
expected = r"""
Test Summary: | Pass Total Time
Parent | 9 9 \s*\d*.\ds
Child 1 | 3 3 \s*\d*.\ds
Child 1.1 (long name) | 1 1 \s*\d*.\ds
Child 1.2 | 1 1 \s*\d*.\ds
Child 1.3 | 1 1 \s*\d*.\ds
Child 2 | 3 3 \s*\d*.\ds
Child 3 | 3 3 \s*\d*.\ds
Child 3.1 | 1 1 \s*\d*.\ds
Child 3.2 | 1 1 \s*\d*.\ds
Child 3.3 | 1 1 \s*\d*.\ds
"""

mktemp() do f, _
Expand Down
Loading

0 comments on commit ca11c2f

Please sign in to comment.