1
1
module ReTestItems
2
2
3
- using Base: @lock
3
+ using Base: @lock , @kwdef
4
4
using Dates: DateTime, ISODateTimeFormat, format, now, unix2datetime
5
5
using Test: Test, DefaultTestSet, TestSetException
6
6
using . Threads: @spawn , nthreads
@@ -242,6 +242,24 @@ function runtests(shouldrun, pkg::Module; kw...)
242
242
return runtests (shouldrun, dir; kw... )
243
243
end
244
244
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
+
245
263
function runtests (
246
264
shouldrun,
247
265
paths:: AbstractString... ;
@@ -276,20 +294,21 @@ function runtests(
276
294
# If we were given paths but none were valid, then nothing to run.
277
295
! isempty (paths) && isempty (paths′) && return nothing
278
296
ti_filter = TestItemFilter (shouldrun, tags, name)
279
- mkpath (RETESTITEMS_TEMP_FOLDER[]) # ensure our folder wasn't removed
280
- save_current_stdio ()
281
297
nworkers = max (0 , nworkers)
282
298
retries = max (0 , retries)
283
- timeout = ceil (Int, testitem_timeout)
299
+ testitem_timeout = ceil (Int, testitem_timeout)
284
300
timeout_profile_wait = ceil (Int, timeout_profile_wait)
285
301
(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)
286
305
debuglvl = Int (debug)
287
306
if debuglvl > 0
288
307
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 )
290
309
end
291
310
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 )
293
312
end
294
313
end
295
314
303
322
# By tracking and reusing test environments, we can avoid this issue.
304
323
const TEST_ENVS = Dict {String, String} ()
305
324
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 )
307
326
# Don't recursively call `runtests` e.g. if we `include` a file which calls it.
308
327
# So we ignore the `runtests(...)` call in `test/runtests.jl` when `runtests(...)`
309
328
# was called from the command line.
@@ -323,7 +342,7 @@ function _runtests(ti_filter, paths, nworkers::Int, nworker_threads::String, wor
323
342
if is_running_test_runtests_jl (proj_file)
324
343
# Assume this is `Pkg.test`, so test env already active.
325
344
@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 )
327
346
else
328
347
@debugv 1 " Activating test environment for `$proj_file `"
329
348
orig_proj = Base. active_project ()
@@ -336,7 +355,7 @@ function _runtests(ti_filter, paths, nworkers::Int, nworker_threads::String, wor
336
355
testenv = TestEnv. activate ()
337
356
TEST_ENVS[proj_file] = testenv
338
357
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 )
340
359
finally
341
360
Base. set_active_project (orig_proj)
342
361
end
@@ -345,24 +364,24 @@ function _runtests(ti_filter, paths, nworkers::Int, nworker_threads::String, wor
345
364
end
346
365
347
366
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
351
368
)
352
369
start_time = time ()
353
370
proj_name = something (Pkg. Types. read_project (projectfile). name, " " )
354
371
@info " Scanning for test items in project `$proj_name ` at paths: $(join (paths, " , " )) "
355
372
inc_time = time ()
356
373
@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
358
377
ntestitems = length (testitems. testitems)
359
378
@debugv 1 " Done including tests in $paths "
360
379
@info " Finished scanning for test items in $(round (time () - inc_time, digits= 2 )) seconds." *
361
380
" Scheduling $ntestitems tests on pid $(Libc. getpid ()) " *
362
381
(nworkers == 0 ? " " : " with $nworkers worker processes and $nworker_threads threads per worker." )
363
382
try
364
383
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." )
366
385
# This is where we disable printing for the serial executor case.
367
386
Test. TESTSET_PRINT_ENABLE[] = false
368
387
ctx = TestContext (proj_name, ntestitems)
@@ -373,14 +392,14 @@ function _runtests_in_current_env(
373
392
testitem. eval_number[] = i
374
393
@atomic :monotonic testitems. count += 1
375
394
run_number = 1
376
- max_runs = 1 + max (retries, testitem. retries)
395
+ max_runs = 1 + max (cfg . retries, testitem. retries)
377
396
is_non_pass = false
378
397
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)
380
399
ts = res. testset
381
- print_errors_and_captured_logs (testitem, run_number; logs)
400
+ print_errors_and_captured_logs (testitem, run_number; cfg . logs)
382
401
report_empty_testsets (testitem, ts)
383
- if gc_between_testitems
402
+ if cfg . gc_between_testitems
384
403
@debugv 2 " Running GC"
385
404
GC. gc (true )
386
405
end
@@ -392,7 +411,7 @@ function _runtests_in_current_env(
392
411
break
393
412
end
394
413
end
395
- if failfast && is_non_pass
414
+ if cfg . failfast && is_non_pass
396
415
cancel! (testitems)
397
416
print_failfast_cancellation (testitem)
398
417
break
@@ -413,7 +432,7 @@ function _runtests_in_current_env(
413
432
@sync for i in 1 : nworkers
414
433
@spawn begin
415
434
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)
417
436
end
418
437
end
419
438
end
@@ -424,15 +443,15 @@ function _runtests_in_current_env(
424
443
ti = starting[i]
425
444
@spawn begin
426
445
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 )
428
447
end
429
448
end
430
449
end
431
450
end
432
451
Test. TESTSET_PRINT_ENABLE[] = true # reenable printing so our `finish` prints
433
452
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)
436
455
# Let users know if not all tests ran. Print this just above the final report as
437
456
# there might have been other logs printed since the cancellation was printed.
438
457
print_failfast_summary (testitems)
450
469
451
470
# Start a new `Worker` with `nworker_threads` threads and run `worker_init_expr` on it.
452
471
# 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)
455
474
i = worker_num == nothing ? " " : " $worker_num "
456
475
# remote_fetch here because we want to make sure the worker is all setup before starting to eval testitems
457
476
remote_fetch (w, quote
@@ -554,25 +573,23 @@ function record_test_error!(testitem, msg, elapsed_seconds::Real=0.0)
554
573
end
555
574
556
575
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 ,
560
577
)
561
578
ntestitems = length (testitems. testitems)
562
579
run_number = 1
563
- memory_threshold_percent = 100 * memory_threshold
580
+ memory_threshold_percent = 100 * cfg . memory_threshold
564
581
while testitem != = nothing
565
582
ch = Channel {TestItemResult} (1 )
566
583
if memory_percent () > memory_threshold_percent
567
584
@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."
568
585
terminate! (worker)
569
586
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)
571
588
end
572
589
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)
576
593
try
577
594
timer = Timer (timeout) do tm
578
595
close (tm)
@@ -598,9 +615,9 @@ function manage_worker(
598
615
ts = testitem_result. testset
599
616
push! (testitem. testsets, ts)
600
617
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)
602
619
report_empty_testsets (testitem, ts)
603
- if gc_between_testitems
620
+ if cfg . gc_between_testitems
604
621
@debugv 2 " Running GC on $worker "
605
622
remote_fetch (worker, :(GC. gc (true )))
606
623
end
@@ -609,7 +626,7 @@ function manage_worker(
609
626
run_number += 1
610
627
@info " Retrying $(repr (testitem. name)) on $worker . Run=$run_number ."
611
628
else
612
- if failfast && is_non_pass
629
+ if cfg . failfast && is_non_pass
613
630
already_cancelled = cancel! (testitems)
614
631
already_cancelled || print_failfast_cancellation (testitem)
615
632
end
@@ -623,10 +640,10 @@ function manage_worker(
623
640
@debugv 2 " Error" exception= e
624
641
# Handle the exception
625
642
if e isa TimeoutException
626
- if timeout_profile_wait > 0
643
+ if cfg . timeout_profile_wait > 0
627
644
@warn " $worker timed out running test item $(repr (testitem. name)) after $timeout seconds. \
628
645
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 )
630
647
end
631
648
terminate! (worker, :timeout )
632
649
wait (worker)
@@ -654,7 +671,7 @@ function manage_worker(
654
671
end
655
672
# Handle retries
656
673
if run_number == max_runs
657
- if failfast
674
+ if cfg . failfast
658
675
already_cancelled = cancel! (testitems)
659
676
already_cancelled || print_failfast_cancellation (testitem)
660
677
end
@@ -666,7 +683,7 @@ function manage_worker(
666
683
end
667
684
# The worker was terminated, so replace it unless there are no more testitems to run
668
685
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)
670
687
end
671
688
# Now loop back around to reschedule the testitem
672
689
continue
0 commit comments