Skip to content

Commit b90c053

Browse files
Print a completion message when all tests are done (#192)
* Print completion message when all tests are done * More debug logs * Log a message when each worker has completed tests * Fix import for `at-debugv` * Debug log after `close(worker)` * Use at-debug in workers.jl * Add worker num to all worker restart messages * Fix typo in debug message * Make worker debug messages consistent * Replace LoggingExtras.jl dep with our own debug macro (#195) * Replace LoggingExtras.jl dep with our own debug macro * Update debug log syntax in tests * fixup! Replace LoggingExtras.jl dep with our own debug macro * fixup! Replace LoggingExtras.jl dep with our own debug macro
1 parent 87048fe commit b90c053

File tree

8 files changed

+108
-40
lines changed

8 files changed

+108
-40
lines changed

Project.toml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ version = "1.30.0"
55
[deps]
66
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
77
Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
8-
LoggingExtras = "e6f89c97-d47a-5376-807f-9c37f3926c36"
98
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
109
Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
1110
Sockets = "6462fe0b-24de-5631-8697-dd941f90decc"
@@ -15,7 +14,6 @@ TestEnv = "1e6cf692-eddd-4d53-88a5-2d735e33781b"
1514
[compat]
1615
Dates = "1"
1716
Logging = "1"
18-
LoggingExtras = "1"
1917
Pkg = "1"
2018
Profile = "1"
2119
Random = "1"

src/ReTestItems.jl

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@ using Test: Test, DefaultTestSet, TestSetException
66
using .Threads: @spawn, nthreads
77
using Pkg: Pkg
88
using TestEnv
9-
using Logging
10-
using LoggingExtras
9+
using Logging: current_logger, with_logger
1110

1211
export runtests, runtestitem
1312
export @testsetup, @testitem
@@ -66,6 +65,7 @@ function softscope_all!(@nospecialize ex)
6665
end
6766
end
6867

68+
include("debug.jl")
6969
include("workers.jl")
7070
using .Workers
7171
include("macros.jl")
@@ -304,7 +304,7 @@ function runtests(
304304
cfg = _Config(; nworkers, nworker_threads, worker_init_expr, test_end_expr, testitem_timeout, testitem_failfast, failfast, retries, logs, report, verbose_results, timeout_profile_wait, memory_threshold, gc_between_testitems)
305305
debuglvl = Int(debug)
306306
if debuglvl > 0
307-
LoggingExtras.withlevel(LoggingExtras.Debug; verbosity=debuglvl) do
307+
withdebug(debuglvl) do
308308
_runtests(ti_filter, paths′, cfg)
309309
end
310310
else
@@ -443,23 +443,24 @@ function _runtests_in_current_env(
443443
ti = starting[i]
444444
@spawn begin
445445
with_logger(original_logger) do
446-
manage_worker($w, $proj_name, $testitems, $ti, $cfg)
446+
manage_worker($w, $proj_name, $testitems, $ti, $cfg; worker_num=$i)
447447
end
448448
end
449449
end
450450
end
451451
Test.TESTSET_PRINT_ENABLE[] = true # reenable printing so our `finish` prints
452+
# Let users know if tests are done, and if all of them ran (or if we failed fast).
453+
# Print this above the final report as there might have been other logs printed
454+
# since a failfast-cancellation was printed, but print it ASAP after tests finish
455+
# in case any of the recording/reporting steps have an issue.
456+
print_completion_summary(testitems; failedfast=(cfg.failfast && is_cancelled(testitems)))
452457
record_results!(testitems)
453458
cfg.report && write_junit_file(proj_name, dirname(projectfile), testitems.graph.junit)
454-
if cfg.failfast && is_cancelled(testitems)
455-
# Let users know if not all tests ran. Print this just above the final report as
456-
# there might have been other logs printed since the cancellation was printed.
457-
print_failfast_summary(testitems)
458-
end
459+
@debugv 1 "Calling Test.finish(testitems)"
459460
Test.finish(testitems) # print summary of total passes/failures/errors
460461
finally
461462
Test.TESTSET_PRINT_ENABLE[] = true
462-
# Cleanup test setup logs
463+
@debugv 1 "Cleaning up test setup logs"
463464
foreach(Iterators.filter(endswith(".log"), readdir(RETESTITEMS_TEMP_FOLDER[], join=true))) do logfile
464465
try
465466
# See https://github.com/JuliaTesting/ReTestItems.jl/issues/124
@@ -468,7 +469,9 @@ function _runtests_in_current_env(
468469
@debug "Error while attempting to remove $(logfile)" err
469470
end
470471
end
472+
@debugv 1 "Done cleaning up test setup logs"
471473
end
474+
@debugv 1 "DONE"
472475
return nothing
473476
end
474477

@@ -577,16 +580,18 @@ function record_test_error!(testitem, msg, elapsed_seconds::Real=0.0)
577580
return testitem
578581
end
579582

583+
# The provided `worker_num` is only for logging purposes, and not persisted as part of the worker.
580584
function manage_worker(
581-
worker::Worker, proj_name::AbstractString, testitems::TestItems, testitem::Union{TestItem,Nothing}, cfg::_Config,
585+
worker::Worker, proj_name::AbstractString, testitems::TestItems, testitem::Union{TestItem,Nothing}, cfg::_Config;
586+
worker_num::Int
582587
)
583588
ntestitems = length(testitems.testitems)
584589
run_number = 1
585590
memory_threshold_percent = 100 * cfg.memory_threshold
586591
while testitem !== nothing
587592
ch = Channel{TestItemResult}(1)
588593
if memory_percent() > memory_threshold_percent
589-
@warn "Memory usage ($(Base.Ryu.writefixed(memory_percent(), 1))%) is higher than threshold ($(Base.Ryu.writefixed(memory_threshold_percent, 1))%). Restarting worker process to try to free memory."
594+
@warn "Memory usage ($(Base.Ryu.writefixed(memory_percent(), 1))%) is higher than threshold ($(Base.Ryu.writefixed(memory_threshold_percent, 1))%). Restarting process for worker $worker_num to try to free memory."
590595
terminate!(worker)
591596
wait(worker)
592597
worker = robust_start_worker(proj_name, cfg.nworker_threads, cfg.worker_init_expr, ntestitems)
@@ -642,7 +647,7 @@ function manage_worker(
642647
close(timer)
643648
end
644649
catch e
645-
@debugv 2 "Error" exception=e
650+
@debugv 2 "Error: $e"
646651
# Handle the exception
647652
if e isa TimeoutException
648653
if cfg.timeout_profile_wait > 0
@@ -684,7 +689,7 @@ function manage_worker(
684689
run_number = 1
685690
else
686691
run_number += 1
687-
@info "Retrying $(repr(testitem.name)) on a new worker process. Run=$run_number."
692+
@info "Retrying $(repr(testitem.name)) on a new worker $worker_num process. Run=$run_number."
688693
end
689694
# The worker was terminated, so replace it unless there are no more testitems to run
690695
if testitem !== nothing
@@ -694,7 +699,9 @@ function manage_worker(
694699
continue
695700
end
696701
end
702+
@info "All tests on worker $worker_num completed. Closing $worker."
697703
close(worker)
704+
@debugv 1 "Worker $worker_num closed: $(worker)"
698705
return nothing
699706
end
700707

src/debug.jl

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
DEBUG_LEVEL::Int = 0
2+
3+
function setdebug!(level::Int)
4+
global DEBUG_LEVEL = level
5+
return nothing
6+
end
7+
8+
"""
9+
withdebug(level::Int) do
10+
func()
11+
end
12+
"""
13+
function withdebug(f, level::Int)
14+
old = DEBUG_LEVEL
15+
try
16+
setdebug!(level)
17+
f()
18+
finally
19+
setdebug!(old)
20+
end
21+
end
22+
23+
"""
24+
@debugv 1 "msg"
25+
"""
26+
macro debugv(level::Int, messsage)
27+
quote
28+
if DEBUG_LEVEL >= $level
29+
_full_file = $String($(QuoteNode(__source__.file)))
30+
_file = $last($splitdir(_full_file))
31+
_line = $(QuoteNode(__source__.line))
32+
msg = $(esc(messsage))
33+
$print("DEBUG @ $(_file):$(_line) | $msg\n")
34+
end
35+
end
36+
end

src/junit_xml.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ function write_junit_file(path::AbstractString, junit::Union{JUnitTestSuites,JUn
191191
open(path, "w") do io
192192
write_junit_file(io, junit)
193193
end
194+
@debugv 1 "Done writing JUnit XML file to $(repr(path))"
194195
return nothing
195196
end
196197

@@ -201,6 +202,7 @@ function write_junit_file(io::IO, junit::Union{JUnitTestSuites,JUnitTestSuite})
201202
end
202203

203204
function write_junit_xml(io, junit::JUnitTestSuites)
205+
@debugv 2 "Writing JUnit XML for testsuites $(junit.name)"
204206
write(io, "\n<testsuites")
205207
write_counts(io, junit.counts)
206208
write(io, ">")
@@ -212,6 +214,7 @@ function write_junit_xml(io, junit::JUnitTestSuites)
212214
end
213215

214216
function write_junit_xml(io, ts::JUnitTestSuite)
217+
@debugv 2 "Writing JUnit XML for testsuite $(ts.name)"
215218
write(io, "\n<testsuite name=", xml_markup(ts.name))
216219
write_counts(io, ts.counts)
217220
write(io, ">")
@@ -258,6 +261,7 @@ function write_dd_tags(io, tc::JUnitTestCase)
258261
end
259262

260263
function write_junit_xml(io, tc::JUnitTestCase)
264+
@debugv 2 "Writing JUnit XML for testcase $(tc.name)"
261265
write(io, "\n\t<testcase name=", xml_markup(tc.name))
262266
write_counts(io, tc.counts)
263267
write(io, ">")

src/log_capture.jl

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -310,9 +310,13 @@ end
310310
# So that the user is warned that not all tests were run.
311311
# We don't use loglock here, because this is only called once on the coordinator after all
312312
# tasks running tests have stopped and we're printing the final test report.
313-
function print_failfast_summary(t::TestItems)
313+
function print_completion_summary(t::TestItems; failedfast::Bool)
314314
io = DEFAULT_STDOUT[]
315-
printstyled(io, "[ Fail Fast: "; bold=true, color=Base.warn_color())
315+
if failedfast
316+
printstyled(io, "[ Fail Fast: "; bold=true, color=Base.warn_color())
317+
else
318+
printstyled(io, "[ Tests Completed: "; bold=true, color=Base.info_color())
319+
end
316320
println(io, "$(t.count)/$(length(t.testitems)) test items were run.")
317321
return nothing
318322
end

src/testcontext.jl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,12 @@ is_cancelled(t::TestItems) = @atomic t.cancelled
108108
###
109109

110110
function record_results!(ti::TestItems)
111+
@debugv 1 "Recording testitem results"
111112
foreach(ti.graph.children) do child
112113
record_results!(ti.graph, child)
113114
end
115+
@debugv 1 "Done recording testitem results"
116+
return ti
114117
end
115118

116119
function record_results!(dir::DirNode, child_dir::DirNode)
@@ -153,7 +156,7 @@ function get_starting_testitems(ti::TestItems, n)
153156
len = length(ti.testitems)
154157
step = max(1, len / n)
155158
testitems = [ti.testitems[round(Int, i)] for i in 1:step:len]
156-
@debugv 2 "get_starting_testitems" len n allunique(testitems)
159+
@debugv 2 "get_starting_testitems len=$len n=$n allunique=$(allunique(testitems))"
157160
@assert length(testitems) == min(n, len) && allunique(testitems)
158161
for (i, t) in enumerate(testitems)
159162
@atomic t.scheduled_for_evaluation.value = true

src/workers.jl

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ end
7373
function terminate!(w::Worker, from::Symbol=:manual)
7474
already_terminated = @atomicswap :monotonic w.terminated = true
7575
if !already_terminated
76-
@debug "terminating worker $(w.pid) from $from"
76+
@debug "terminating $(w) from $(from)"
7777
end
7878
wte = WorkerTerminatedException(w)
7979
@lock w.lock begin
@@ -114,7 +114,7 @@ end
114114
# Called when timeout_profile_wait is non-zero.
115115
function trigger_profile(w::Worker, timeout_profile_wait, from::Symbol=:manual)
116116
if !Sys.iswindows()
117-
@debug "sending profile request to worker $(w.pid) from $from"
117+
@debug "sending profile request to $(w) from $(from)"
118118
if Sys.islinux()
119119
kill(w.process, 10) # SIGUSR1
120120
elseif Sys.isbsd()
@@ -128,13 +128,15 @@ end
128128
# gracefully terminate a worker by sending a shutdown message
129129
# and waiting for the other tasks to perform worker shutdown
130130
function Base.close(w::Worker)
131+
@debug "closing $(w)"
131132
if !w.terminated && isopen(w.socket)
132133
req = Request(Symbol(), :(), rand(UInt64), true)
133134
@lock w.lock begin
134135
serialize(w.socket, req)
135136
flush(w.socket)
136137
end
137138
end
139+
@debug "waiting for $(w) to terminate"
138140
wait(w)
139141
return
140142
end
@@ -231,7 +233,7 @@ function redirect_worker_output(io::IO, w::Worker, fn, proc, ev::Threads.Event)
231233
end
232234
end
233235
catch e
234-
# @error "Error redirecting worker output $(w.pid)" exception=(e, catch_backtrace())
236+
# @error "Error redirecting $(w) output" exception=(e, catch_backtrace())
235237
terminate!(w, :redirect_worker_output)
236238
e isa EOFError || e isa Base.IOError || rethrow()
237239
finally
@@ -250,13 +252,13 @@ function process_responses(w::Worker, ev::Threads.Event)
250252
while isopen(w.socket) && !w.terminated
251253
# get the next Response from the worker
252254
r = deserialize(w.socket)
253-
@assert r isa Response "Received invalid response from worker $(w.pid): $(r)"
254-
# println("Received response $(r) from worker $(w.pid)")
255+
@assert r isa Response "Received invalid response from $(w): $(r)"
256+
# println("Received response $(r) from $(w)")
255257
@lock lock begin
256-
@assert haskey(reqs, r.id) "Received response for unknown request $(r.id) from worker $(w.pid)"
258+
@assert haskey(reqs, r.id) "Received response for unknown request $(r.id) from $(w)"
257259
# look up the Future for this request
258260
fut = pop!(reqs, r.id)
259-
@assert !isready(fut.value) "Received duplicate response for request $(r.id) from worker $(w.pid)"
261+
@assert !isready(fut.value) "Received duplicate response for request $(r.id) from $(w)"
260262
if r.error !== nothing
261263
# this allows rethrowing the exception from the worker to the caller
262264
close(fut.value, r.error)
@@ -266,7 +268,7 @@ function process_responses(w::Worker, ev::Threads.Event)
266268
end
267269
end
268270
catch e
269-
# @error "Error processing responses from worker $(w.pid)" exception=(e, catch_backtrace())
271+
# @error "Error processing responses from $(w)" exception=(e, catch_backtrace())
270272
terminate!(w, :process_responses)
271273
e isa EOFError || e isa Base.IOError || rethrow()
272274
end

test/integrationtests.jl

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -45,18 +45,24 @@ end
4545

4646
# test we can call runtests manually w/ directory
4747
@testset "manual `runtests(dir)`" begin
48-
results = encased_testset() do
49-
runtests(joinpath(TEST_PKG_DIR, "NoDeps.jl"))
48+
using IOCapture
49+
c = IOCapture.capture() do
50+
encased_testset(() -> runtests(joinpath(TEST_PKG_DIR, "NoDeps.jl")))
5051
end
52+
results = c.value
5153
@test n_passed(results) == 2 # NoDeps has two test files with a test each
54+
@test contains(c.output, "[ Tests Completed: 2/2 test items were run.")
5255
end
5356

5457
@testset "manual `runtests(file)`" begin
5558
# test we can point to a file at the base of the package (not just in `src` or `test`)
56-
results = encased_testset() do
57-
runtests(joinpath(TEST_PKG_DIR, "NoDeps.jl", "toplevel_tests.jl"))
59+
using IOCapture
60+
c = IOCapture.capture() do
61+
encased_testset(() -> runtests(joinpath(TEST_PKG_DIR, "NoDeps.jl", "toplevel_tests.jl")))
5862
end
63+
results = c.value
5964
@test n_passed(results) == 1
65+
@test contains(c.output, "[ Tests Completed: 1/1 test items were run.")
6066
end
6167

6268
@testset "`runtests(path)` auto finds testsetups" begin
@@ -273,20 +279,28 @@ end
273279
nworkers = 2
274280
@testset "runtests with nworkers = $nworkers" verbose=true begin
275281
@testset "Pkg.test() $pkg" for pkg in TEST_PKGS
276-
results = with_test_package(pkg) do
277-
withenv("RETESTITEMS_NWORKERS" => nworkers) do
278-
Pkg.test()
282+
c = IOCapture.capture() do
283+
with_test_package(pkg) do
284+
withenv("RETESTITEMS_NWORKERS" => nworkers) do
285+
Pkg.test()
286+
end
279287
end
280288
end
289+
results = c.value
281290
@test all_passed(results)
291+
@test contains(c.output, "[ Tests Completed")
282292
end
283293
@testset "Pkg.test() DontPass.jl" begin
284-
results = with_test_package("DontPass.jl") do
285-
withenv("RETESTITEMS_NWORKERS" => 2) do
286-
Pkg.test()
294+
c = IOCapture.capture() do
295+
with_test_package("DontPass.jl") do
296+
withenv("RETESTITEMS_NWORKERS" => 2) do
297+
Pkg.test()
298+
end
287299
end
288300
end
301+
results = c.value
289302
@test length(non_passes(results)) > 0
303+
@test contains(c.output, "[ Tests Completed")
290304
end
291305
end
292306

@@ -447,9 +461,9 @@ end
447461
@test !contains(c.output, "tests done")
448462
end
449463
if debug
450-
@test contains(c.output, "Debug:")
464+
@test contains(c.output, "DEBUG @")
451465
else
452-
@test !contains(c.output, "Debug:")
466+
@test !contains(c.output, "DEBUG @")
453467
end
454468
# Test we have the expected summary table
455469
testset = c.value
@@ -1198,7 +1212,7 @@ end
11981212
# monkey-patch the internal `memory_percent` function to return a fixed value, so we
11991213
# can control if we hit the `memory_threshold`.
12001214
@eval ReTestItems.memory_percent() = 83.1
1201-
expected_warning = "Warning: Memory usage (83.1%) is higher than threshold (7.0%). Restarting worker process to try to free memory."
1215+
expected_warning = "Warning: Memory usage (83.1%) is higher than threshold (7.0%). Restarting process for worker 1 to try to free memory."
12021216

12031217
# Pass `memory_threshold` keyword, and hit the memory threshold.
12041218
c1 = IOCapture.capture() do

0 commit comments

Comments
 (0)