Skip to content

Commit 79cb313

Browse files
Refactor options into an internal Config struct (#190)
* Refactor options into an internal Config struct * fixup! Refactor options into an internal Config struct * timeout is Int
1 parent f9e5e54 commit 79cb313

File tree

1 file changed

+57
-40
lines changed

1 file changed

+57
-40
lines changed

src/ReTestItems.jl

Lines changed: 57 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module ReTestItems
22

3-
using Base: @lock
3+
using Base: @lock, @kwdef
44
using Dates: DateTime, ISODateTimeFormat, format, now, unix2datetime
55
using Test: Test, DefaultTestSet, TestSetException
66
using .Threads: @spawn, nthreads
@@ -242,6 +242,24 @@ function runtests(shouldrun, pkg::Module; kw...)
242242
return runtests(shouldrun, dir; kw...)
243243
end
244244

245+
@kwdef struct _Config
246+
nworkers::Int
247+
nworker_threads::String
248+
worker_init_expr::Expr
249+
test_end_expr::Expr
250+
testitem_timeout::Int
251+
testitem_failfast::Bool
252+
failfast::Bool
253+
retries::Int
254+
logs::Symbol
255+
report::Bool
256+
verbose_results::Bool
257+
timeout_profile_wait::Int
258+
memory_threshold::Float64
259+
gc_between_testitems::Bool
260+
end
261+
262+
245263
function runtests(
246264
shouldrun,
247265
paths::AbstractString...;
@@ -276,20 +294,21 @@ function runtests(
276294
# If we were given paths but none were valid, then nothing to run.
277295
!isempty(paths) && isempty(paths′) && return nothing
278296
ti_filter = TestItemFilter(shouldrun, tags, name)
279-
mkpath(RETESTITEMS_TEMP_FOLDER[]) # ensure our folder wasn't removed
280-
save_current_stdio()
281297
nworkers = max(0, nworkers)
282298
retries = max(0, retries)
283-
timeout = ceil(Int, testitem_timeout)
299+
testitem_timeout = ceil(Int, testitem_timeout)
284300
timeout_profile_wait = ceil(Int, timeout_profile_wait)
285301
(timeout_profile_wait > 0 && Sys.iswindows()) && @warn "CPU profiles on timeout is not supported on Windows, ignoring `timeout_profile_wait`"
302+
mkpath(RETESTITEMS_TEMP_FOLDER[]) # ensure our folder wasn't removed
303+
save_current_stdio()
304+
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)
286305
debuglvl = Int(debug)
287306
if debuglvl > 0
288307
LoggingExtras.withlevel(LoggingExtras.Debug; verbosity=debuglvl) do
289-
_runtests(ti_filter, paths′, nworkers, nworker_threads, worker_init_expr, test_end_expr, timeout, retries, memory_threshold, verbose_results, debuglvl, report, logs, timeout_profile_wait, gc_between_testitems, failfast, testitem_failfast)
308+
_runtests(ti_filter, paths′, cfg)
290309
end
291310
else
292-
return _runtests(ti_filter, paths′, nworkers, nworker_threads, worker_init_expr, test_end_expr, timeout, retries, memory_threshold, verbose_results, debuglvl, report, logs, timeout_profile_wait, gc_between_testitems, failfast, testitem_failfast)
311+
return _runtests(ti_filter, paths′, cfg)
293312
end
294313
end
295314

@@ -303,7 +322,7 @@ end
303322
# By tracking and reusing test environments, we can avoid this issue.
304323
const TEST_ENVS = Dict{String, String}()
305324

306-
function _runtests(ti_filter, paths, nworkers::Int, nworker_threads::String, worker_init_expr::Expr, test_end_expr::Expr, testitem_timeout::Int, retries::Int, memory_threshold::Real, verbose_results::Bool, debug::Int, report::Bool, logs::Symbol, timeout_profile_wait::Int, gc_between_testitems::Bool, failfast::Bool, testitem_failfast::Bool)
325+
function _runtests(ti_filter, paths, cfg::_Config)
307326
# Don't recursively call `runtests` e.g. if we `include` a file which calls it.
308327
# So we ignore the `runtests(...)` call in `test/runtests.jl` when `runtests(...)`
309328
# was called from the command line.
@@ -323,7 +342,7 @@ function _runtests(ti_filter, paths, nworkers::Int, nworker_threads::String, wor
323342
if is_running_test_runtests_jl(proj_file)
324343
# Assume this is `Pkg.test`, so test env already active.
325344
@debugv 2 "Running in current environment `$(Base.active_project())`"
326-
return _runtests_in_current_env(ti_filter, paths, proj_file, nworkers, nworker_threads, worker_init_expr, test_end_expr, testitem_timeout, retries, memory_threshold, verbose_results, debug, report, logs, timeout_profile_wait, gc_between_testitems, failfast, testitem_failfast)
345+
return _runtests_in_current_env(ti_filter, paths, proj_file, cfg)
327346
else
328347
@debugv 1 "Activating test environment for `$proj_file`"
329348
orig_proj = Base.active_project()
@@ -336,7 +355,7 @@ function _runtests(ti_filter, paths, nworkers::Int, nworker_threads::String, wor
336355
testenv = TestEnv.activate()
337356
TEST_ENVS[proj_file] = testenv
338357
end
339-
_runtests_in_current_env(ti_filter, paths, proj_file, nworkers, nworker_threads, worker_init_expr, test_end_expr, testitem_timeout, retries, memory_threshold, verbose_results, debug, report, logs, timeout_profile_wait, gc_between_testitems, failfast, testitem_failfast)
358+
_runtests_in_current_env(ti_filter, paths, proj_file, cfg)
340359
finally
341360
Base.set_active_project(orig_proj)
342361
end
@@ -345,24 +364,24 @@ function _runtests(ti_filter, paths, nworkers::Int, nworker_threads::String, wor
345364
end
346365

347366
function _runtests_in_current_env(
348-
ti_filter, paths, projectfile::String, nworkers::Int, nworker_threads, worker_init_expr::Expr, test_end_expr::Expr,
349-
testitem_timeout::Int, retries::Int, memory_threshold::Real, verbose_results::Bool, debug::Int, report::Bool, logs::Symbol,
350-
timeout_profile_wait::Int, gc_between_testitems::Bool, failfast::Bool, testitem_failfast::Bool,
367+
ti_filter, paths, projectfile::String, cfg::_Config
351368
)
352369
start_time = time()
353370
proj_name = something(Pkg.Types.read_project(projectfile).name, "")
354371
@info "Scanning for test items in project `$proj_name` at paths: $(join(paths, ", "))"
355372
inc_time = time()
356373
@debugv 1 "Including tests in $paths"
357-
testitems, _ = include_testfiles!(proj_name, projectfile, paths, ti_filter, verbose_results, report)
374+
testitems, _ = include_testfiles!(proj_name, projectfile, paths, ti_filter, cfg.verbose_results, cfg.report)
375+
nworkers = cfg.nworkers
376+
nworker_threads = cfg.nworker_threads
358377
ntestitems = length(testitems.testitems)
359378
@debugv 1 "Done including tests in $paths"
360379
@info "Finished scanning for test items in $(round(time() - inc_time, digits=2)) seconds." *
361380
" Scheduling $ntestitems tests on pid $(Libc.getpid())" *
362381
(nworkers == 0 ? "" : " with $nworkers worker processes and $nworker_threads threads per worker.")
363382
try
364383
if nworkers == 0
365-
length(worker_init_expr.args) > 0 && error("worker_init_expr is set, but will not run because number of workers is 0.")
384+
length(cfg.worker_init_expr.args) > 0 && error("worker_init_expr is set, but will not run because number of workers is 0.")
366385
# This is where we disable printing for the serial executor case.
367386
Test.TESTSET_PRINT_ENABLE[] = false
368387
ctx = TestContext(proj_name, ntestitems)
@@ -373,14 +392,14 @@ function _runtests_in_current_env(
373392
testitem.eval_number[] = i
374393
@atomic :monotonic testitems.count += 1
375394
run_number = 1
376-
max_runs = 1 + max(retries, testitem.retries)
395+
max_runs = 1 + max(cfg.retries, testitem.retries)
377396
is_non_pass = false
378397
while run_number max_runs
379-
res = runtestitem(testitem, ctx; test_end_expr, verbose_results, logs, failfast=testitem_failfast)
398+
res = runtestitem(testitem, ctx; cfg.test_end_expr, cfg.verbose_results, cfg.logs, failfast=cfg.testitem_failfast)
380399
ts = res.testset
381-
print_errors_and_captured_logs(testitem, run_number; logs)
400+
print_errors_and_captured_logs(testitem, run_number; cfg.logs)
382401
report_empty_testsets(testitem, ts)
383-
if gc_between_testitems
402+
if cfg.gc_between_testitems
384403
@debugv 2 "Running GC"
385404
GC.gc(true)
386405
end
@@ -392,7 +411,7 @@ function _runtests_in_current_env(
392411
break
393412
end
394413
end
395-
if failfast && is_non_pass
414+
if cfg.failfast && is_non_pass
396415
cancel!(testitems)
397416
print_failfast_cancellation(testitem)
398417
break
@@ -413,7 +432,7 @@ function _runtests_in_current_env(
413432
@sync for i in 1:nworkers
414433
@spawn begin
415434
with_logger(original_logger) do
416-
$workers[$i] = robust_start_worker($proj_name, $nworker_threads, $worker_init_expr, $ntestitems; worker_num=$i)
435+
$workers[$i] = robust_start_worker($proj_name, $(cfg.nworker_threads), $(cfg.worker_init_expr), $ntestitems; worker_num=$i)
417436
end
418437
end
419438
end
@@ -424,15 +443,15 @@ function _runtests_in_current_env(
424443
ti = starting[i]
425444
@spawn begin
426445
with_logger(original_logger) do
427-
manage_worker($w, $proj_name, $testitems, $ti, $nworker_threads, $worker_init_expr, $test_end_expr, $testitem_timeout, $retries, $memory_threshold, $verbose_results, $debug, $report, $logs, $timeout_profile_wait, $gc_between_testitems, $failfast, $testitem_failfast)
446+
manage_worker($w, $proj_name, $testitems, $ti, $cfg)
428447
end
429448
end
430449
end
431450
end
432451
Test.TESTSET_PRINT_ENABLE[] = true # reenable printing so our `finish` prints
433452
record_results!(testitems)
434-
report && write_junit_file(proj_name, dirname(projectfile), testitems.graph.junit)
435-
if failfast && is_cancelled(testitems)
453+
cfg.report && write_junit_file(proj_name, dirname(projectfile), testitems.graph.junit)
454+
if cfg.failfast && is_cancelled(testitems)
436455
# Let users know if not all tests ran. Print this just above the final report as
437456
# there might have been other logs printed since the cancellation was printed.
438457
print_failfast_summary(testitems)
@@ -450,8 +469,8 @@ end
450469

451470
# Start a new `Worker` with `nworker_threads` threads and run `worker_init_expr` on it.
452471
# The provided `worker_num` is only for logging purposes, and not persisted as part of the worker.
453-
function start_worker(proj_name, nworker_threads, worker_init_expr, ntestitems; worker_num=nothing)
454-
w = Worker(; threads="$nworker_threads")
472+
function start_worker(proj_name, nworker_threads::String, worker_init_expr::Expr, ntestitems::Int; worker_num=nothing)
473+
w = Worker(; threads=nworker_threads)
455474
i = worker_num == nothing ? "" : " $worker_num"
456475
# remote_fetch here because we want to make sure the worker is all setup before starting to eval testitems
457476
remote_fetch(w, quote
@@ -554,25 +573,23 @@ function record_test_error!(testitem, msg, elapsed_seconds::Real=0.0)
554573
end
555574

556575
function manage_worker(
557-
worker::Worker, proj_name::AbstractString, testitems::TestItems, testitem::Union{TestItem,Nothing}, nworker_threads, worker_init_expr::Expr, test_end_expr::Expr,
558-
default_timeout::Int, retries::Int, memory_threshold::Real, verbose_results::Bool, debug::Int, report::Bool, logs::Symbol, timeout_profile_wait::Int,
559-
gc_between_testitems::Bool, failfast::Bool, testitem_failfast::Bool,
576+
worker::Worker, proj_name::AbstractString, testitems::TestItems, testitem::Union{TestItem,Nothing}, cfg::_Config,
560577
)
561578
ntestitems = length(testitems.testitems)
562579
run_number = 1
563-
memory_threshold_percent = 100*memory_threshold
580+
memory_threshold_percent = 100 * cfg.memory_threshold
564581
while testitem !== nothing
565582
ch = Channel{TestItemResult}(1)
566583
if memory_percent() > memory_threshold_percent
567584
@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."
568585
terminate!(worker)
569586
wait(worker)
570-
worker = robust_start_worker(proj_name, nworker_threads, worker_init_expr, ntestitems)
587+
worker = robust_start_worker(proj_name, cfg.nworker_threads, cfg.worker_init_expr, ntestitems)
571588
end
572589
testitem.workerid[] = worker.pid
573-
timeout = something(testitem.timeout, default_timeout)
574-
fut = remote_eval(worker, :(ReTestItems.runtestitem($testitem, GLOBAL_TEST_CONTEXT; test_end_expr=$(QuoteNode(test_end_expr)), verbose_results=$verbose_results, logs=$(QuoteNode(logs)), failfast=$testitem_failfast)))
575-
max_runs = 1 + max(retries, testitem.retries)
590+
timeout = something(testitem.timeout, cfg.testitem_timeout)
591+
fut = remote_eval(worker, :(ReTestItems.runtestitem($testitem, GLOBAL_TEST_CONTEXT; test_end_expr=$(QuoteNode(cfg.test_end_expr)), verbose_results=$(cfg.verbose_results), logs=$(QuoteNode(cfg.logs)), failfast=$(cfg.testitem_failfast))))
592+
max_runs = 1 + max(cfg.retries, testitem.retries)
576593
try
577594
timer = Timer(timeout) do tm
578595
close(tm)
@@ -598,9 +615,9 @@ function manage_worker(
598615
ts = testitem_result.testset
599616
push!(testitem.testsets, ts)
600617
push!(testitem.stats, testitem_result.stats)
601-
print_errors_and_captured_logs(testitem, run_number; logs)
618+
print_errors_and_captured_logs(testitem, run_number; cfg.logs)
602619
report_empty_testsets(testitem, ts)
603-
if gc_between_testitems
620+
if cfg.gc_between_testitems
604621
@debugv 2 "Running GC on $worker"
605622
remote_fetch(worker, :(GC.gc(true)))
606623
end
@@ -609,7 +626,7 @@ function manage_worker(
609626
run_number += 1
610627
@info "Retrying $(repr(testitem.name)) on $worker. Run=$run_number."
611628
else
612-
if failfast && is_non_pass
629+
if cfg.failfast && is_non_pass
613630
already_cancelled = cancel!(testitems)
614631
already_cancelled || print_failfast_cancellation(testitem)
615632
end
@@ -623,10 +640,10 @@ function manage_worker(
623640
@debugv 2 "Error" exception=e
624641
# Handle the exception
625642
if e isa TimeoutException
626-
if timeout_profile_wait > 0
643+
if cfg.timeout_profile_wait > 0
627644
@warn "$worker timed out running test item $(repr(testitem.name)) after $timeout seconds. \
628645
A CPU profile will be triggered on the worker and then it will be terminated."
629-
trigger_profile(worker, timeout_profile_wait, :timeout)
646+
trigger_profile(worker, cfg.timeout_profile_wait, :timeout)
630647
end
631648
terminate!(worker, :timeout)
632649
wait(worker)
@@ -654,7 +671,7 @@ function manage_worker(
654671
end
655672
# Handle retries
656673
if run_number == max_runs
657-
if failfast
674+
if cfg.failfast
658675
already_cancelled = cancel!(testitems)
659676
already_cancelled || print_failfast_cancellation(testitem)
660677
end
@@ -666,7 +683,7 @@ function manage_worker(
666683
end
667684
# The worker was terminated, so replace it unless there are no more testitems to run
668685
if testitem !== nothing
669-
worker = robust_start_worker(proj_name, nworker_threads, worker_init_expr, ntestitems)
686+
worker = robust_start_worker(proj_name, cfg.nworker_threads, cfg.worker_init_expr, ntestitems)
670687
end
671688
# Now loop back around to reschedule the testitem
672689
continue

0 commit comments

Comments
 (0)