Skip to content

Commit f962609

Browse files
committed
Move previewer task and channel into functions
1 parent 4831b0f commit f962609

File tree

1 file changed

+175
-108
lines changed

1 file changed

+175
-108
lines changed

src/ReTest.jl

+175-108
Original file line numberDiff line numberDiff line change
@@ -602,6 +602,174 @@ def(kw::Symbol) =
602602
retest_defaults[kw]
603603
end
604604

605+
"""
606+
get_previewer_channel(
607+
spin, stdout, version, nthreads, nprocs
608+
)::Maybe{RemoteChannel}
609+
610+
Optionally returns a channel for previewing testsets yet to be completed.
611+
612+
# Arguments:
613+
- `spin::Bool`: Whether to show an active 'spinner' along with the description
614+
of the currently executing testset.
615+
- `stdout::DataType`: Output stream to print to.
616+
- `version::VersionNumber`: Current version of Julia being used.
617+
- `nthreads::Integer`: Number of threads available to ReTest.
618+
- `nprocs::Integer`: Number of processes available to ReTest.
619+
"""
620+
function get_previewer_channel(spin, stdout, version, nthreads, nprocs)
621+
can_call_thread_pin = nthreads > 1 && version >= v"1.3"
622+
if spin && stdout isa Base.TTY && (can_call_thread_pin || nprocs > 1)
623+
RemoteChannel(() -> Channel{Maybe{Tuple{Int64,String}}}(Inf))
624+
# Needs to be "remote" in the case nprocs() == 2, as then nworkers() ==
625+
# 1, which means the one remote worker will put descriptions on
626+
# previewchan (if nworkers() > 1, descriptions are not put because we
627+
# can't predict the order in which they complete, and then the previewer
628+
# will not show the descriptions, just the spinning wheel)
629+
630+
# On VERSION < v"1.3" : we can't call `thread_pin` (see below), and in
631+
# this case previewing doesn't work well, as the worker and previewer
632+
# tasks can end up in the same thread, and the previewer is not
633+
# responsive.
634+
635+
# channel size: if nworkers() == 1, then 2 would suffice (one for the
636+
# "compilation step", one for @testset execution step, and then the
637+
# printer would empty the channel; but for two workers and more, this
638+
# second step is not done, so the buffer needs a size of at least
639+
# `nworkers()`
640+
else
641+
# Otherwise, the previewing doesn't work well, because the worker task
642+
# keeps the thread busy and doesn't yield enough for previewing to be
643+
# useful.
644+
nothing
645+
end
646+
end
647+
648+
"""
649+
take_latest!(previewchan)::Tuple{Int64, Union{String, Nothing}}
650+
651+
Gets the ID and description of the latest testset to preview.
652+
653+
# Arguments:
654+
- `previewchan::Maybe{RemoteChannel}`: Object to get ID/description pairs from.
655+
"""
656+
function take_latest!(previewchan)
657+
local id_desc
658+
while isready(previewchan)
659+
# printer/previewer can't take! it, as we locked
660+
id_desc = take!(previewchan)
661+
end
662+
if @isdefined(id_desc)
663+
something(id_desc, (Int64(0), nothing))
664+
else
665+
(Int64(0), "")
666+
end
667+
end
668+
669+
# TODO: Clarify purpose of `align_overflow`.
670+
# TODO: Clarify purpose of `maxidw` - maximum indentation width?
671+
"""
672+
get_previewer(
673+
previewchan, interrupted, printlock, gotprinted, align_overflow,
674+
verbose, format, module_header, many, maxidw
675+
)::Maybe{Task}
676+
677+
Optionally schedules and returns a task for previewing the completion of
678+
testsets for the current module, if previewing is enabled.
679+
680+
# Arguments:
681+
- `previewchan::Maybe{RemoteChannel}`: Object for acquiring the latest testset
682+
to preview.
683+
- `interrupted::Threads.Atomic{Bool}`: Whether or not the previewer was
684+
interrupted during execution.
685+
- `printlock::ReentrantLock`: Lock for printing to the output stream.
686+
- `gotprinted::Bool`: Whether the latest testset was printed.
687+
- `align_overflow::Integer`: How many characters overflow.
688+
- `verbose::Bool`: Whether to preview nested testsets.
689+
- `format::.Testset.Format`: Container for formatting information.
690+
- `module_header::Bool`: Whether to print module header before testsets
691+
belonging to that module.
692+
- `many::Bool`: Whether there are multiple testsets, individually or in loops.
693+
- `maxidw::Ref{Int}`: Visual width for showing testset IDs.
694+
"""
695+
function get_previewer(
696+
previewchan, interrupted, printlock, gotprinted, align_overflow, verbose,
697+
format, module_header, many, maxidw
698+
)
699+
previewchan !== nothing || return nothing
700+
previewer = @async try
701+
timer = ['|', '/', '-', '\\']
702+
cursor = 0
703+
desc = ""
704+
id = Int64(0)
705+
finito = false
706+
707+
while !finito && !interrupted[]
708+
lock(printlock) do
709+
newid, newdesc = take_latest!(previewchan)
710+
if newdesc === nothing
711+
finito = true
712+
return # no need to sleep before looping
713+
elseif newdesc != ""
714+
desc = newdesc
715+
id = newid
716+
cursor = 0
717+
gotprinted = false
718+
elseif gotprinted
719+
desc = ""
720+
gotprinted = false
721+
align_overflow = 0
722+
elseif desc != ""
723+
align = format.desc_align
724+
if nworkers() > 1
725+
description = align >= 3 ? "..." : ""
726+
style = NamedTuple()
727+
elseif startswith(desc, '\0')
728+
description = chop(desc, head=1, tail=0)
729+
style = (color = :light_black, bold=true)
730+
else
731+
description = desc
732+
style = NamedTuple()
733+
end
734+
if isindented(verbose, module_header, many)
735+
description = " " * description
736+
end
737+
cursor += 1
738+
739+
# when verbose == 0, we still can print the currently run
740+
# testset, but then its description might be larger than
741+
# `align`, because it was not taken into account for
742+
# computing `align`;
743+
# `align_overflow` computes how many characters do overflow,
744+
# so that the printer can "erase" them later on;
745+
# once we overflow, we don't go back (leftwards) until the
746+
# printer prints
747+
align_overflow =
748+
max(align_overflow, textwidth(description) - align)
749+
print('\r')
750+
print_id(id, maxidw[])
751+
printstyled(rpad("$description", align+align_overflow, " "),
752+
' ',
753+
timer[mod1(cursor, end)];
754+
style...)
755+
end
756+
end
757+
previewer_refresh_duration_seconds = 0.13
758+
sleep(previewer_refresh_duration_seconds)
759+
end
760+
catch ex
761+
# TODO: clarify what is the correct thing to do here
762+
if ex isa InterruptException
763+
interrupted[] = true
764+
rethrow()
765+
else
766+
# then there is probably a bug in the previewer code, but it might
767+
# be fine for the worker/printer to continue?
768+
rethrow()
769+
end
770+
end # previewer task
771+
previewer
772+
end
605773

606774
"""
607775
retest(mod..., pattern...;
@@ -883,118 +1051,17 @@ function retest(@nospecialize(args::ArgType...);
8831051
many = hasmany(tests)
8841052

8851053
printlock = ReentrantLock()
886-
previewchan =
887-
if spin && stdout isa Base.TTY && (nthreads() > 1 && VERSION >= v"1.3" ||
888-
nprocs() > 1)
889-
RemoteChannel(() -> Channel{Maybe{Tuple{Int64,String}}}(Inf))
890-
# needs to be "remote" in the case nprocs() == 2, as then nworkers() == 1,
891-
# which means the one remote worker will put descriptions on previewchan
892-
# (if nworkers() > 1, descriptions are not put because we can't predict
893-
# the order in which they complete, and then the previewer will
894-
# not show the descriptions, just the spinning wheel)
895-
896-
# on VERSION < v"1.3" : we can't call `thread_pin` (see below), and in this
897-
# case previewing doesn't work well, as the worker and previewer tasks
898-
# can end up in the same thread, and the previewer is not responsive
899-
900-
# channel size: if nworkers() == 1, then 2 would suffice (one for
901-
# the "compilation step", one for @testset execution step, and then
902-
# the printer would empty the channel; but for two workers and more,
903-
# this second step is not done, so the buffer needs a size of at least
904-
# `nworkers()`
905-
else
906-
# otherwise, the previewing doesn't work well, because the worker task
907-
# keeps the thread busy and doesn't yield enough for previewing to be useful
908-
nothing
909-
end
1054+
previewchan = get_previewer_channel(
1055+
spin, stdout, VERSION, nthreads(), nprocs()
1056+
)
9101057

9111058
gotprinted = false
9121059
align_overflow = 0
9131060

914-
function take_latest!(previewchan)
915-
local id_desc
916-
while isready(previewchan)
917-
# printer/previewer can't take! it, as we locked
918-
id_desc = take!(previewchan)
919-
end
920-
if @isdefined(id_desc)
921-
something(id_desc, (Int64(0), nothing))
922-
else
923-
(Int64(0), "")
924-
end
925-
end
926-
927-
previewer = previewchan === nothing ? nothing :
928-
@async try
929-
timer = ['|', '/', '-', '\\']
930-
cursor = 0
931-
desc = ""
932-
id = Int64(0)
933-
finito = false
934-
935-
while !finito && !interrupted[]
936-
lock(printlock) do
937-
newid, newdesc = take_latest!(previewchan)
938-
if newdesc === nothing
939-
finito = true
940-
return # no need to sleep before looping
941-
elseif newdesc != ""
942-
desc = newdesc
943-
id = newid
944-
cursor = 0
945-
gotprinted = false
946-
elseif gotprinted
947-
desc = ""
948-
gotprinted = false
949-
align_overflow = 0
950-
elseif desc != ""
951-
align = format.desc_align
952-
if nworkers() > 1
953-
description = align >= 3 ? "..." : ""
954-
style = NamedTuple()
955-
elseif startswith(desc, '\0')
956-
description = chop(desc, head=1, tail=0)
957-
style = (color = :light_black, bold=true)
958-
else
959-
description = desc
960-
style = NamedTuple()
961-
end
962-
if isindented(verbose, module_header, many)
963-
description = " " * description
964-
end
965-
cursor += 1
966-
967-
# when verbose == 0, we still can print the currently run
968-
# testset, but then its description might be larger than
969-
# `align`, because it was not taken into account for computing
970-
# `align`;
971-
# `align_overflow` computes how many characters do overflow,
972-
# so that the printer can "erase" them later on;
973-
# once we overflow, we don't go back (leftwards) until the
974-
# printer prints
975-
align_overflow =
976-
max(align_overflow, textwidth(description) - align)
977-
print('\r')
978-
print_id(id, maxidw[])
979-
printstyled(rpad("$description", align+align_overflow, " "),
980-
' ',
981-
timer[mod1(cursor, end)];
982-
style...)
983-
end
984-
end
985-
sleep(0.13)
986-
end
987-
catch ex
988-
# TODO: clarify what is the correct thing to do here
989-
if ex isa InterruptException
990-
interrupted[] = true
991-
rethrow()
992-
else
993-
# then there is probably a bug in the previewer code, but it might be fine
994-
# for the worker/printer to continue?
995-
rethrow()
996-
end
997-
end # previewer task
1061+
previewer = get_previewer(
1062+
previewchan, interrupted, printlock, gotprinted, align_overflow,
1063+
verbose, format, module_header, many, maxidw
1064+
)
9981065

9991066
# TODO: move printer task out of worker?
10001067
worker = @task begin

0 commit comments

Comments
 (0)