Skip to content

Commit 34e6eed

Browse files
committed
allow adding labels (symbols) in @testset expressions
1 parent b07ceb2 commit 34e6eed

File tree

4 files changed

+148
-107
lines changed

4 files changed

+148
-107
lines changed

InlineTest/src/InlineTest.jl

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,18 @@ function retest end
5151
Similar to `Test.@testset args...`, but the contained tests are not run
5252
immediately, and are instead stored for later execution, triggered by
5353
[`retest()`](@ref) or `runtests()`.
54+
55+
Besides the `@testset` body (last argument) and a description string,
56+
arguments of `@testset` can be:
57+
* the `verbose` option, with a *literal* boolean value (e.g.
58+
`verbose=true`)
59+
* a literal symbol, serving as a label which can be used for
60+
testset filtering (see [`retest`](@ref)'s docstring for details).
61+
All nested testsets inherit such labels.
62+
5463
Invocations of `@testset` can be nested, but qualified invocations of
5564
`ReTest.@testset` can't.
65+
5666
Internally, `@testset` expressions are converted to an equivalent of
5767
`Test.@testset` at execution time.
5868
"""

src/ReTest.jl

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,13 @@ mutable struct TestsetExpr
6565
mod::Module # enclosing module
6666
desc::Union{String,Expr}
6767
options::Options
68+
marks::Marks
6869
# loops: the original loop expression, if any, but where each `x=...` is
6970
# pulled out into a vector
7071
loops::Maybe{Vector{Expr}}
7172
parent::Maybe{TestsetExpr}
7273
children::Vector{TestsetExpr}
7374
strings::Vector{Union{String,Missing}}
74-
marks::Marks
7575
# loopvalues & loopiters: when successful in evaluating loop values in resolve!,
7676
# we "flatten" the nested for loops into a single loop, with loopvalues
7777
# containing tuples of values, and loopiters the tuples of variables to which the
@@ -85,9 +85,8 @@ mutable struct TestsetExpr
8585
descwidth::Int # max width of self and children shown descriptions
8686
body::Expr
8787

88-
TestsetExpr(source, mod, desc, options, loops, parent, children=TestsetExpr[]) =
89-
new(0, source, mod, desc, options, loops, parent, children, String[],
90-
Marks())
88+
TestsetExpr(source, mod, desc, options, marks, loops, parent, children=TestsetExpr[]) =
89+
new(0, source, mod, desc, options, marks, loops, parent, children, String[])
9190
end
9291

9392
isfor(ts::TestsetExpr) = ts.loops !== nothing
@@ -156,9 +155,16 @@ function parse_ts(source::LineNumberNode, mod::Module, args::Tuple, parent=nothi
156155

157156
local desc
158157
options = Options()
158+
marks = Marks()
159+
if parent !== nothing
160+
append!(marks.hard, parent.marks.hard) # copy! not available in Julia 1.0
161+
end
159162
for arg in args[1:end-1]
160163
if arg isa String || Meta.isexpr(arg, :string)
161164
desc = arg
165+
elseif arg isa QuoteNode && arg.value isa Symbol
166+
# TODO: support non-literal symbols?
167+
push!(marks.hard, arg.value)
162168
elseif Meta.isexpr(arg, :(=))
163169
arg.args[1] in fieldnames(Options) ||
164170
return tserror("unsupported @testset option")
@@ -200,7 +206,7 @@ function parse_ts(source::LineNumberNode, mod::Module, args::Tuple, parent=nothi
200206
return tserror("expected begin/end block or for loop as argument to @testset")
201207
end
202208

203-
ts = TestsetExpr(source, mod, desc, options, loops, parent)
209+
ts = TestsetExpr(source, mod, desc, options, marks, loops, parent)
204210
ts.body, ts.hasbroken = replace_ts(source, mod, tsbody, ts)
205211
ts, false # hasbroken counts only "proper" @test_broken, not recursive ones
206212
end
@@ -723,14 +729,15 @@ function retest(@nospecialize(args::ArgType...);
723729
maxidw[] = id ? maxidw[] : 0
724730

725731
if tag isa Symbol || tag isa Not && tag.x isa Symbol
726-
tag = [tag]
727-
elseif !(tag isa Vector{<:Union{Symbol,Not}})
728-
tag = vec(collect(Union{Symbol,Not}, tag))
732+
tag = Pair[tag => false]
733+
else
734+
tag = vec(Pair[t => false for t in tag])
735+
# values indicate whether a warning was already issued for this tag
729736
end
730737
if !dry && !isempty(tag)
731738
@warn "tag keyword: labels can be added only in dry mode"
732739
end
733-
for t in tag
740+
for (t, _) in tag
734741
label::Symbol = t isa Symbol ? t : t.x
735742
startswith(String(label), '_') &&
736743
throw(ArgumentError("tag keyword: labels can't start with an underscore"))
@@ -1461,16 +1468,19 @@ function dryrun(mod::Module, ts::TestsetExpr, pat::Pattern, align::Int=0,
14611468
res = pastresult(ts.marks, subject)
14621469
if clear && res !== nothing
14631470
delmark!(ts.marks, subject, res ? _pass : _fail)
1464-
# should we set res=nothing here ? not doing it might be confusing,
1465-
# but it gives an idea about which marks are being deleted
1471+
# TODO: should we set res=nothing here ? not doing it might be confusing,
1472+
# but it gives an idea about which marks are being deleted;
14661473
end
14671474
if subject !== missing
1468-
for mark=tag
1475+
for (ith, (mark, warned)) in enumerate(tag)
14691476
if ismatch
14701477
if mark isa Symbol
14711478
addmark!(ts.marks, subject, mark)
14721479
else
1473-
delmark!(ts.marks, subject, mark.x)
1480+
justwarned = delmark!(ts.marks, subject, mark.x, warned)
1481+
if justwarned > warned
1482+
tag[ith] = mark => justwarned
1483+
end
14741484
end
14751485
end
14761486
end
@@ -1562,13 +1572,12 @@ function dryrun(mod::Module, ts::TestsetExpr, pat::Pattern, align::Int=0,
15621572
end
15631573
end
15641574
end
1565-
beginend = TestsetExpr(ts.source, ts.mod, descx, ts.options, nothing,
1575+
beginend = TestsetExpr(ts.source, ts.mod, descx, ts.options, ts.marks, nothing,
15661576
ts.parent, ts.children)
15671577
beginend.run = true
15681578
beginend.id = ts.id
15691579
beginend.iter = iter
15701580
ts.iter = iter # necessary when reachable is used
1571-
beginend.marks = ts.marks
15721581
dryrun(mod, beginend, pat, align, parentsubj; evaldesc=false,
15731582
repeated=repeated, maxidw=maxidw, marks=marks, tag=tag,
15741583
clear=clear, show=show)

src/marks.jl

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
struct Marks
22
# Union to avoid creating a vector in most cases
33
soft::Dict{String, Union{Symbol, Vector{Symbol}}} # TODO: should be a MultiDict
4-
5-
Marks() = new(Dict{String, Union{Symbol, Vector{Symbol}}}())
4+
hard::Vector{Symbol} # attached to all instances of a @testset
65
end
76

7+
Marks() = Marks(Dict{String, Union{Symbol, Vector{Symbol}}}(), Symbol[])
8+
89
const _pass = :__pass__
910
const _fail = :__fail__
1011

@@ -42,20 +43,22 @@ function markiter(marks::Marks, subject, skipres::Bool)
4243
ms = get(marks.soft, subject, Symbol())
4344
if ms isa Symbol
4445
if ms === Symbol() || skipres && ms (_pass, _fail)
45-
()
46+
marks.hard
4647
else
47-
(ms,)
48+
Iterators.flatten((marks.hard, (ms,)))
4849
end
4950
else
50-
if skipres
51-
Iterators.filter(m -> m (_pass, _fail), ms)
52-
else
53-
ms
54-
end
51+
Iterators.flatten((marks.hard,
52+
if skipres
53+
Iterators.filter(m -> m (_pass, _fail), ms)
54+
else
55+
ms
56+
end))
5557
end
5658
end
5759

5860
function addmark!(marks::Marks, subject, m::Symbol)
61+
m marks.hard && return false
5962
soft = marks.soft
6063
ms = get(soft, subject, Symbol())
6164
if ms isa Symbol
@@ -68,15 +71,20 @@ function addmark!(marks::Marks, subject, m::Symbol)
6871
soft[subject] = [ms, m]
6972
true
7073
end
71-
elseif findfirst(==(m), ms) === nothing
74+
elseif m ms
7275
push!(ms, m)
7376
true
7477
else
7578
false
7679
end
7780
end
7881

79-
function delmark!(marks::Marks, subject, m::Symbol)
82+
# return whether warning is issued
83+
function delmark!(marks::Marks, subject, m::Symbol, warned::Bool=false)
84+
if m marks.hard && !warned
85+
@warn "cannot remove statically attached label (@testset :$m ...)"
86+
return true
87+
end
8088
soft = marks.soft
8189
ms = get(soft, subject, Symbol())
8290
if ms isa Symbol
@@ -89,10 +97,11 @@ function delmark!(marks::Marks, subject, m::Symbol)
8997
deleteat!(ms, p)
9098
end
9199
end
92-
nothing
100+
false
93101
end
94102

95103
function hasmark(marks::Marks, subject, m::Symbol)
104+
m marks.hard && return true
96105
ms = get(marks.soft, subject, Symbol())
97106
if ms isa Symbol
98107
ms === m

0 commit comments

Comments
 (0)