Skip to content

Commit b09d96a

Browse files
authored
Make it possible to customize the worker a test is run on. (#9)
In the case of Metal.jl, some tests require specific env vars to be set.
1 parent b992175 commit b09d96a

File tree

2 files changed

+64
-32
lines changed

2 files changed

+64
-32
lines changed

src/ParallelTestRunner.jl

Lines changed: 47 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module ParallelTestRunner
22

3-
export runtests
3+
export runtests, addworkers, addworker
44

55
using Distributed
66
using 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))
214214
end
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
272303
issues during long test runs. The memory limit is set based on system architecture.
273304
"""
274305
function 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

test/runtests.jl

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,20 @@ custom_tests = Dict(
2020
end
2121
)
2222
runtests(ARGS; init_code, custom_tests)
23+
24+
# custom worker
25+
function test_worker(name)
26+
if name == "needs env var"
27+
return addworker(env=["SPECIAL_ENV_VAR"=>"42"])
28+
end
29+
return nothing
30+
end
31+
custom_tests = Dict(
32+
"needs env var" => quote
33+
@test ENV["SPECIAL_ENV_VAR"] == "42"
34+
end,
35+
"doesn't need env var" => quote
36+
@test !haskey(ENV, "SPECIAL_ENV_VAR")
37+
end
38+
)
39+
runtests(ARGS; test_worker, custom_tests)

0 commit comments

Comments
 (0)