11module ParallelTestRunner
22
3- export runtests
3+ export runtests, addworkers, addworker
44
55using Distributed
66using Dates
@@ -213,6 +213,35 @@ function default_njobs(; cpu_threads = Sys.CPU_THREADS, free_memory = Sys.free_m
213213 return max (1 , min (jobs, memory_jobs))
214214end
215215
216+ """
217+ addworkers(X; kwargs...)
218+
219+ Add `X` worker processes, with additional keyword arguments passed to `addprocs`.
220+ """
221+ test_exeflags = Base. julia_cmd ()
222+ filter! (test_exeflags. exec) do c
223+ return ! (startswith (c, " --depwarn" ) || startswith (c, " --check-bounds" ))
224+ end
225+ push! (test_exeflags. exec, " --check-bounds=yes" )
226+ push! (test_exeflags. exec, " --startup-file=no" )
227+ push! (test_exeflags. exec, " --depwarn=yes" )
228+ push! (test_exeflags. exec, " --project=$(Base. active_project ()) " )
229+ test_exename = popfirst! (test_exeflags. exec)
230+ function addworkers (X; kwargs... )
231+ exename = test_exename
232+
233+ return withenv (" JULIA_NUM_THREADS" => 1 , " OPENBLAS_NUM_THREADS" => 1 ) do
234+ procs = addprocs (X; exename = exename, exeflags = test_exeflags, kwargs... )
235+ Distributed. remotecall_eval (
236+ Main, procs, quote
237+ import ParallelTestRunner
238+ end
239+ )
240+ procs
241+ end
242+ end
243+ addworker (; kwargs... ) = addworkers (1 ; kwargs... )[1 ]
244+
216245"""
217246 runtests(ARGS; testfilter = Returns(true), RecordType = TestRecord, custom_tests = Dict())
218247
@@ -230,6 +259,8 @@ Several keyword arguments are also supported:
230259- `custom_tests`: Optional dictionary of custom tests, mapping test names to expressions.
231260- `init_code`: Code use to initialize each test's sandbox module (e.g., import auxiliary
232261 packages, define constants, etc).
262+ - `test_worker`: Optional function that takes a test name and returns a specific worker.
263+ When returning `nothing`, the test will be assigned to any available default worker.
233264
234265## Command Line Options
235266
@@ -272,7 +303,8 @@ Workers are automatically recycled when they exceed memory limits to prevent out
272303issues during long test runs. The memory limit is set based on system architecture.
273304"""
274305function runtests (ARGS ; testfilter = Returns (true ), RecordType = TestRecord,
275- custom_tests:: Dict{String, Expr} = Dict {String, Expr} (), init_code = :())
306+ custom_tests:: Dict{String, Expr} = Dict {String, Expr} (), init_code = :(),
307+ test_worker = Returns (nothing ))
276308 do_help, _ = extract_flag! (ARGS , " --help" )
277309 if do_help
278310 println (
@@ -372,29 +404,7 @@ function runtests(ARGS; testfilter = Returns(true), RecordType = TestRecord,
372404 @info " Running $jobs tests in parallel. If this is too many, specify the `--jobs=N` argument to the tests, or set the `JULIA_CPU_THREADS` environment variable."
373405
374406 # add workers
375- test_exeflags = Base. julia_cmd ()
376- filter! (test_exeflags. exec) do c
377- return ! (startswith (c, " --depwarn" ) || startswith (c, " --check-bounds" ))
378- end
379- push! (test_exeflags. exec, " --check-bounds=yes" )
380- push! (test_exeflags. exec, " --startup-file=no" )
381- push! (test_exeflags. exec, " --depwarn=yes" )
382- push! (test_exeflags. exec, " --project=$(Base. active_project ()) " )
383- test_exename = popfirst! (test_exeflags. exec)
384- function addworker (X; kwargs... )
385- exename = test_exename
386-
387- return withenv (" JULIA_NUM_THREADS" => 1 , " OPENBLAS_NUM_THREADS" => 1 ) do
388- procs = addprocs (X; exename = exename, exeflags = test_exeflags, kwargs... )
389- Distributed. remotecall_eval (
390- Main, procs, quote
391- import ParallelTestRunner
392- end
393- )
394- procs
395- end
396- end
397- addworker (min (jobs, length (tests)))
407+ addworkers (min (jobs, length (tests)))
398408
399409 # pretty print information about gc and mem usage
400410 testgroupheader = " Test"
@@ -492,21 +502,21 @@ function runtests(ARGS; testfilter = Returns(true), RecordType = TestRecord,
492502 while length (tests) > 0
493503 test = popfirst! (tests)
494504
495- # sometimes a worker failed, and we need to spawn a new one
505+ # if a worker failed, spawn a new one
496506 if p === nothing
497- p = addworker (1 )[1 ]
507+ p = addworkers (1 )[1 ]
498508 end
499- wrkr = p
500509
501- local resp
510+ # some tests may need a special worker
511+ wrkr = something (test_worker (test), p)
502512
503513 # run the test
504514 running_tests[test] = now ()
505- try
506- resp = remotecall_fetch (runtest, wrkr, RecordType, test_runners[test], test, init_code)
515+ resp = try
516+ remotecall_fetch (runtest, wrkr, RecordType, test_runners[test], test, init_code)
507517 catch e
508518 isa (e, InterruptException) && return
509- resp = Any[e]
519+ Any[e]
510520 end
511521 delete! (running_tests, test)
512522 push! (results, (test, resp))
@@ -529,6 +539,11 @@ function runtests(ARGS; testfilter = Returns(true), RecordType = TestRecord,
529539 # so future tests get a fresh environment
530540 p = recycle_worker (p)
531541 end
542+
543+ # get rid of the custom worker
544+ if wrkr != p
545+ recycle_worker (wrkr)
546+ end
532547 end
533548
534549 if p != = nothing
0 commit comments