11module ReTestItems
22
3- using Base: @lock
3+ using Base: @lock , @kwdef
44using Dates: DateTime, ISODateTimeFormat, format, now, unix2datetime
55using Test: Test, DefaultTestSet, TestSetException
66using . Threads: @spawn , nthreads
@@ -242,6 +242,24 @@ function runtests(shouldrun, pkg::Module; kw...)
242242 return runtests (shouldrun, dir; kw... )
243243end
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+
245263function 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
294313end
295314
303322# By tracking and reusing test environments, we can avoid this issue.
304323const 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
345364end
346365
347366function _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)
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)
554573end
555574
556575function 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