Skip to content
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

Add the ability to use function calls in @testset directly. #42518

Merged
merged 6 commits into from
Oct 24, 2021
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
3 changes: 3 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ New library features
* `@test_throws "some message" triggers_error()` can now be used to check whether the displayed error text
contains "some message" regardless of the specific exception type.
Regular expressions, lists of strings, and matching functions are also supported. ([#41888])
* `@testset foo()` can now be used to create a test set from a given function. The name of the test set
is the name of the called function. The called function can contain `@test` and other `@testset`
definitions, including to other function calls, while recording all intermediate test results. ([#42518])

Standard library changes
------------------------
Expand Down
15 changes: 15 additions & 0 deletions stdlib/Test/docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,21 @@ Test Summary: | Pass Total
Foo Tests | 8 8
```

As well as call functions:

```jldoctest testfoo
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)
```

This can be used to allow for factorization of test sets, making it easier to run individual
test sets by running the associated functions instead.
Note that in the case of functions, the test set will be given the name of the called function.
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:

Expand Down
19 changes: 13 additions & 6 deletions stdlib/Test/src/Test.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1225,6 +1225,7 @@ end
@testset [CustomTestSet] [option=val ...] ["description"] begin ... end
@testset [CustomTestSet] [option=val ...] ["description \$v"] for v in (...) ... end
@testset [CustomTestSet] [option=val ...] ["description \$v, \$w"] for v in (...), w in (...) ... end
@testset [CustomTestSet] [option=val ...] ["description \$v, \$w"] foo()

Starts a new test set, or multiple test sets if a `for` loop is provided.

Expand All @@ -1241,6 +1242,7 @@ nested testsets is shown even when they all pass (the default is `false`).

The description string accepts interpolation from the loop indices.
If no description is provided, one is constructed based on the variables.
If a function call is provided, its name will be used. Explicit description strings override this behavior.

By default the `@testset` macro will return the testset object itself, though
this behavior can be customized in other testset types. If a `for` loop is used
Expand Down Expand Up @@ -1275,24 +1277,29 @@ macro testset(args...)
tests = args[end]

# Determine if a single block or for-loop style
if !isa(tests,Expr) || (tests.head !== :for && tests.head !== :block)
error("Expected begin/end block or for loop as argument to @testset")
if !isa(tests,Expr) || (tests.head !== :for && tests.head !== :block && tests.head != :call)

error("Expected function call, begin/end block or for loop as argument to @testset")
end

if tests.head === :for
return testset_forloop(args, tests, __source__)
else
return testset_beginend(args, tests, __source__)
return testset_beginend_call(args, tests, __source__)
end
end

"""
Generate the code for a `@testset` with a `begin`/`end` argument
Generate the code for a `@testset` with a function call or `begin`/`end` argument
"""
function testset_beginend(args, tests, source)
function testset_beginend_call(args, tests, source)
desc, testsettype, options = parse_testset_args(args[1:end-1])
if desc === nothing
desc = "test set"
if tests.head === :call
desc = string(tests.args[1]) # use the function name as test name
else
desc = "test set"
end
end
# If we're at the top level we'll default to DefaultTestSet. Otherwise
# default to the type of the parent testset
Expand Down
24 changes: 24 additions & 0 deletions stdlib/Test/test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1239,3 +1239,27 @@ Test.finish(ts::PassInformationTestSet) = ts
@test ts.results[2].value == ErrorException("Msg")
@test ts.results[2].source == LineNumberNode(test_throws_line_number, @__FILE__)
end

let
f(x) = @test isone(x)
function h(x)
@testset f(x)
@testset "success" begin @test true end
@testset for i in 1:3
@test !iszero(i)
end
end
tret = @testset h(1)
tdesc = @testset "description" h(1)
@testset "Function calls" begin
@test tret.description == "h"
@test tdesc.description == "description"
@test length(tret.results) == 5
@test tret.results[1].description == "f"
@test tret.results[2].description == "success"
for i in 1:3
@test tret.results[2+i].description == "i = $i"
end
end
end