Skip to content

Commit dc2c59f

Browse files
committed
record testsets passes and fails, and show "marks" in dry mode
1 parent 3edb481 commit dc2c59f

File tree

4 files changed

+123
-33
lines changed

4 files changed

+123
-33
lines changed

src/ReTest.jl

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ mutable struct TestsetExpr
6969
parent::Maybe{TestsetExpr}
7070
children::Vector{TestsetExpr}
7171
strings::Vector{Union{String,Missing}}
72+
results::Dict{String,Bool}
7273
# loopvalues & loopiters: when successful in evaluating loop values in resolve!,
7374
# we "flatten" the nested for loops into a single loop, with loopvalues
7475
# containing tuples of values, and loopiters the tuples of variables to which the
@@ -82,7 +83,8 @@ mutable struct TestsetExpr
8283
body::Expr
8384

8485
TestsetExpr(source, mod, desc, options, loops, parent, children=TestsetExpr[]) =
85-
new(0, source, mod, desc, options, loops, parent, children, String[])
86+
new(0, source, mod, desc, options, loops, parent, children, String[],
87+
Dict{String,Bool}())
8688
end
8789

8890
isfor(ts::TestsetExpr) = ts.loops !== nothing
@@ -255,8 +257,8 @@ function resolve!(mod::Module, ts::TestsetExpr, pat::Pattern;
255257

256258
function decide(subj)
257259
m = matches(pat, subj, ids)
258-
# For the curious, setting `s = something(static, missing)`, there are few
259-
# "formulas" to compute the result without `if`, but using only
260+
# For the curious reader, setting `s = something(static, missing)`, there
261+
# are few "formulas" to compute the result without `if`, but using only
260262
# `coalesce, |, &, ==, !=, ===, !==, (a,b) -> a, (a,b) -> b, (a,b) -> !a,
261263
# (a,b) -> !b`. The shortest formulas involve 5 such
262264
# functions `fi` and are of the form
@@ -372,6 +374,7 @@ function resolve!(mod::Module, ts::TestsetExpr, pat::Pattern;
372374
ts.hasbrokenrec |= tsc.hasbrokenrec
373375
end
374376
end
377+
375378
pop!(ids)
376379
if !run || !shown
377380
ts.descwidth = 0
@@ -407,7 +410,8 @@ function make_ts(ts::TestsetExpr, pat::Pattern, stats, chan)
407410

408411
if ts.loops === nothing
409412
quote
410-
@testset $(ts.mod) $(isfinal(ts)) $pat $(ts.id) $(ts.desc) $(ts.options) $stats $chan $body
413+
@testset $(ts.mod) $(isfinal(ts)) $pat $(ts.id) $(ts.desc) $(ts.options) #=
414+
=# $(ts.results) $stats $chan $body
411415
end
412416
else
413417
c = count(x -> x === nothing, (ts.loopvalues, ts.loopiters))
@@ -418,7 +422,8 @@ function make_ts(ts::TestsetExpr, pat::Pattern, stats, chan)
418422
loops = ts.loops
419423
end
420424
quote
421-
@testset $(ts.mod) $(isfinal(ts)) $pat $(ts.id) $(ts.desc) $(ts.options) $stats $chan $loops $body
425+
@testset $(ts.mod) $(isfinal(ts)) $pat $(ts.id) $(ts.desc) $(ts.options) #=
426+
=# $(ts.results) $stats $chan $loops $body
422427
end
423428
end
424429
end
@@ -471,7 +476,7 @@ const ArgType = Union{Module,PatternX,AbstractString,AbstractArray,Tuple,Symbol,
471476
dry::Bool=false, stats::Bool=false, verbose::Real=true,
472477
[id::Bool], shuffle::Bool=false, recursive::Bool=true,
473478
static::Union{Bool,Nothing}=nothing, dup::Bool=false,
474-
load::Bool=false, [seed::Integer])
479+
load::Bool=false, [seed::Integer], marks::Bool=true)
475480
476481
Run tests declared with [`@testset`](@ref) blocks, within modules `mod` if specified,
477482
or within all currently loaded modules otherwise.
@@ -512,7 +517,8 @@ Filtering `pattern`s can be specified to run only a subset of the tests.
512517
above), and are cached and used again on subsequent invocations.
513518
* If `seed` is provided, it is used to seed the global RNG before running
514519
the tests.
515-
520+
* When `marks` and `dry` are `true`, "check marks" are printed next to testsets
521+
which passed or failed in previous runs.
516522
517523
### Filtering
518524
@@ -599,10 +605,11 @@ function retest(@nospecialize(args::ArgType...);
599605
static::Maybe{Bool}=nothing,
600606
load::Bool=false,
601607
seed::Maybe{Integer}=nothing,
608+
marks::Bool=true,
602609
)
603610

604-
dry, stats, shuffle, group, verbose, recursive, id, strict, dup, static =
605-
update_keywords(args, dry, stats, shuffle, group, verbose, recursive, id, strict, dup, static)
611+
dry, stats, shuffle, group, verbose, recursive, id, strict, dup, static, marks =
612+
update_keywords(args, dry, stats, shuffle, group, verbose, recursive, id, strict, dup, static, marks)
606613

607614
implicitmodules, modules, verbose = process_args(args; verbose=verbose, shuffle=shuffle,
608615
recursive=recursive, load=load)
@@ -661,7 +668,7 @@ function retest(@nospecialize(args::ArgType...);
661668
end
662669
if verbose > 0
663670
foreach(ts -> dryrun(mod, ts, pat, id ? 0 : module_header*2,
664-
maxidw = id ? maxidw[] : 0),
671+
maxidw = id ? maxidw[] : 0, marks=marks),
665672
tests)
666673
end
667674
continue
@@ -1025,7 +1032,7 @@ end
10251032

10261033
# hidden feature, shortcuts for passing kwargs to retest
10271034
function update_keywords(@nospecialize(args), dry, stats, shuffle, group, verbose,
1028-
recursive, id, strict, dup, static)
1035+
recursive, id, strict, dup, static, marks)
10291036
for arg in args
10301037
if arg isa Symbol
10311038
for c in string(arg)
@@ -1052,13 +1059,15 @@ function update_keywords(@nospecialize(args), dry, stats, shuffle, group, verbos
10521059
dup = val
10531060
elseif c == 'c'
10541061
static = val
1062+
elseif c == 'm'
1063+
marks = val
10551064
else
10561065
error("bad keyword shortcut")
10571066
end
10581067
end
10591068
end
10601069
end
1061-
dry, stats, shuffle, group, verbose, recursive, id, strict, dup, static
1070+
dry, stats, shuffle, group, verbose, recursive, id, strict, dup, static, marks
10621071
end
10631072

10641073
function process_args(@nospecialize(args);
@@ -1307,7 +1316,7 @@ isindented(verbose, module_header, many) = (verbose > 0) & module_header
13071316
module_summary(verbose, many) = many | iszero(verbose)
13081317

13091318
function dryrun(mod::Module, ts::TestsetExpr, pat::Pattern, align::Int=0, parentsubj=""
1310-
; maxidw::Int, # external calls
1319+
; maxidw::Int, marks::Bool, # external calls
13111320
# only recursive calls:
13121321
evaldesc=true, repeated=nothing, ids::Vector{Int64}=Int64[])
13131322
@assert ts.run
@@ -1334,19 +1343,26 @@ function dryrun(mod::Module, ts::TestsetExpr, pat::Pattern, align::Int=0, parent
13341343
if maxidw > 0 # width (ndigits) of max id; <= 0 means ids not printed
13351344
printstyled(lpad(ts.id, maxidw), "| ", color = :light_black, bold=true)
13361345
end
1346+
13371347
printstyled(' '^align, desc, color = desc isa String ? :normal : Base.warn_color())
13381348

13391349
if repeated !== nothing
13401350
printstyled(" (repeated",
1341-
repeated == -1 ? ")" : " $repeated times)", '\n',
1351+
repeated == -1 ? ")" : " $repeated times)",
13421352
color=:light_black)
1343-
else
1344-
println()
13451353
end
1354+
1355+
res = get(ts.results, subject, nothing)
1356+
if marks && res !== nothing
1357+
printstyled(res ? "" : "", color = res ? :green : Base.error_color(),
1358+
bold=true)
1359+
end
1360+
println()
1361+
13461362
if ts.options.transient_verbose
13471363
for tsc in ts.children
13481364
tsc.run || continue
1349-
dryrun(mod, tsc, pat, align + 2, subject, maxidw=maxidw, ids=ids)
1365+
dryrun(mod, tsc, pat, align + 2, subject, maxidw=maxidw, marks=marks, ids=ids)
13501366
end
13511367
end
13521368
pop!(ids)
@@ -1372,8 +1388,9 @@ function dryrun(mod::Module, ts::TestsetExpr, pat::Pattern, align::Int=0, parent
13721388
ts.parent, ts.children)
13731389
beginend.run = true
13741390
beginend.id = ts.id
1391+
beginend.results = ts.results
13751392
dryrun(mod, beginend, pat, align, parentsubj; evaldesc=false,
1376-
repeated=repeated, maxidw=maxidw, ids=ids)
1393+
repeated=repeated, maxidw=maxidw, marks=marks, ids=ids)
13771394
end
13781395

13791396
loopvalues = ts.loopvalues

src/testset.jl

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,8 @@ function get_test_counts(ts::ReTestSet)
310310
return passes, fails, errors, broken, c_passes, c_fails, c_errors, c_broken
311311
end
312312

313+
anyfailed(ts::ReTestSet) = any(t -> t isa Union{Fail,Error}, ts.results)
314+
313315
# Recursive function that prints out the results at each level of
314316
# the tree of test sets
315317
function print_counts(ts::ReTestSet, fmt::Format, depth, align,
@@ -458,22 +460,23 @@ macro testset(mod::Module, isfinal::Bool, pat::Pattern, x...)
458460
end
459461

460462
# non-inline testset with regex filtering support
461-
macro testset(mod::Module, isfinal::Bool, pat::Pattern, id::Int64, desc::Union{String,Expr}, options,
462-
stats::Bool, chan, body)
463-
Testset.testset_beginend(mod, isfinal, pat, id, desc, options, stats, chan, body, __source__)
463+
macro testset(mod::Module, isfinal::Bool, pat::Pattern, id::Int64, desc::Union{String,Expr},
464+
options, results::Dict, stats::Bool, chan, body)
465+
Testset.testset_beginend(mod, isfinal, pat, id, desc,
466+
options, results, stats, chan, body, __source__)
464467
end
465468

466-
macro testset(mod::Module, isfinal::Bool, pat::Pattern, id::Int64, desc::Union{String,Expr}, options,
467-
stats::Bool, chan, loops, body)
468-
Testset.testset_forloop(mod, isfinal, pat, id, desc, options,
469-
stats, chan, loops, body, __source__)
469+
macro testset(mod::Module, isfinal::Bool, pat::Pattern, id::Int64, desc::Union{String,Expr},
470+
options, results::Dict, stats::Bool, chan, loops, body)
471+
Testset.testset_forloop(mod, isfinal, pat, id, desc,
472+
options, results, stats, chan, loops, body, __source__)
470473
end
471474

472475
"""
473476
Generate the code for a `@testset` with a `begin`/`end` argument
474477
"""
475478
function testset_beginend(mod::Module, isfinal::Bool, pat::Pattern, id::Int64, desc, options,
476-
stats::Bool, chan, tests, source)
479+
results::Dict, stats::Bool, chan, tests, source)
477480
# Generate a block of code that initializes a new testset, adds
478481
# it to the task local storage, evaluates the test(s), before
479482
# finally removing the testset and giving it a chance to take
@@ -514,6 +517,7 @@ function testset_beginend(mod::Module, isfinal::Bool, pat::Pattern, id::Int64, d
514517
Base.catch_stack(), $(QuoteNode(source))))
515518
finally
516519
copy!(RNG, oldrng)
520+
$results[current_str] = !anyfailed(ts)
517521
pop_testset()
518522
ret = finish(ts, $chan)
519523
end
@@ -533,7 +537,8 @@ end
533537
Generate the code for a `@testset` with a `for` loop argument
534538
"""
535539
function testset_forloop(mod::Module, isfinal::Bool, pat::Pattern, id::Int64,
536-
desc::Union{String,Expr}, options, stats, chan, loops, tests, source)
540+
desc::Union{String,Expr}, options, results::Dict, stats, chan,
541+
loops, tests, source)
537542

538543
desc = esc(desc)
539544
blk = quote
@@ -561,11 +566,13 @@ function testset_forloop(mod::Module, isfinal::Bool, pat::Pattern, id::Int64,
561566
let
562567
ts.timed = @stats $stats $(esc(tests))
563568
end
569+
$results[current_str] = !anyfailed(ts)
564570
catch err
565571
err isa InterruptException && rethrow()
566572
# Something in the test block threw an error. Count that as an
567573
# error in this test set
568574
record(ts, Error(:nontest_error, Expr(:tuple), err, Base.catch_stack(), $(QuoteNode(source))))
575+
$results[current_str] = false
569576
end
570577
end
571578
end

test/runtests.jl

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,9 @@ function check(rx, list)
6969
seekstart(io)
7070
expected = map(list) do t
7171
if t in innertestsets
72-
" " * t
72+
" " * t * ""
7373
else
74-
t
74+
t * ""
7575
end
7676
end
7777
expected = join(expected, '\n')
@@ -865,6 +865,71 @@ end
865865
end
866866

867867

868+
# * Marks ....................................................................
869+
870+
module Marks
871+
using ReTest, ..Trace
872+
873+
@testset "a" begin
874+
@test true
875+
@testset "b" begin end # no test, counts as pass
876+
@testset "c" begin
877+
@test true
878+
end
879+
x = 1
880+
@testset "d$x" begin
881+
@test true
882+
end
883+
@testset "e" begin
884+
@test false
885+
end
886+
end
887+
@testset "l$i" for i=1:2
888+
@testset "k$j" for j=1:2
889+
@test true
890+
end
891+
end
892+
end
893+
894+
@chapter Marks begin
895+
check(Marks, dry=true, marks=true, verbose=3, [], output=raw"""
896+
1| a
897+
2| b
898+
3| c
899+
4| "d$(x)"
900+
5| e
901+
6| l1
902+
7| k1
903+
7| k2
904+
6| l2
905+
7| k1
906+
7| k2
907+
""")
908+
retest(Marks, "k1")
909+
check(Marks, dry=true, marks=true, verbose=3, [], output=raw"""
910+
1| a ✅
911+
2| b
912+
3| c
913+
4| "d$(x)"
914+
5| e
915+
6| l1 ✅
916+
7| k1 ✅
917+
7| k2
918+
6| l2 ✅
919+
7| k1 ✅
920+
7| k2
921+
""")
922+
@test_throws Test.TestSetException retest(Marks, "e")
923+
check(Marks, "a", dry=true, marks=true, verbose=3, [], output=raw"""
924+
1| a ✅
925+
2| b
926+
3| c
927+
4| "d$(x)"
928+
5| e ✘
929+
""")
930+
end
931+
932+
868933
# * Failing ..................................................................
869934

870935
module Failing

test/setup.jl

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@ function trace(x)
1212
end
1313
end
1414

15+
# NOTE: all keywords have the same defaults as `retest`, except `marks`
1516
function check(x...; runtests=false, output::Union{Nothing,String}=nothing,
1617
verbose=true, stats=false, dry=false, strict::Bool=true,
17-
recursive=true, static=nothing, id=nothing, load=false)
18+
recursive=true, static=nothing, id=nothing, load=false, marks::Bool=false)
1819
@assert !(runtests & (output !== nothing)) "unimplemented"
1920
args = x[1:end-1]
2021
expected = x[end]
@@ -26,15 +27,15 @@ function check(x...; runtests=false, output::Union{Nothing,String}=nothing,
2627
if runtests
2728
getfield(args[1], :runtests)(args[2:end]...; verbose=verbose, stats=stats, dry=dry,
2829
strict=strict, recursive=recursive, static=static,
29-
id=id, load=load)
30+
id=id, load=load, marks=marks)
3031
elseif output === nothing
3132
retest(args...; verbose=verbose, stats=stats, dry=dry, strict=strict,
32-
recursive=recursive, static=static, id=id, load=load)
33+
recursive=recursive, static=static, id=id, load=load, marks=marks)
3334
else
3435
mktemp() do path, io
3536
redirect_stdout(io) do
3637
retest(args...; verbose=verbose, stats=stats, dry=dry, strict=strict,
37-
recursive=recursive, static=static, id=id, load=load)
38+
recursive=recursive, static=static, id=id, load=load, marks=marks)
3839
end
3940
seekstart(io)
4041
printed = join(map(rstrip, split(readchomp(io), '\n')), '\n')

0 commit comments

Comments
 (0)