Advanced Usage
This page covers advanced features of ParallelTestRunner for customizing test execution.
Customizing the test suite
By default, runtests automatically discovers all .jl files in your test/ directory (excluding runtests.jl itself) using the find_tests function. You can customize which tests to run by providing a custom testsuite dictionary:
# Manually define your test suite
testsuite = Dict(
"basic" => quote
include("basic.jl")
end,
"advanced" => quote
include("advanced.jl")
end
)
runtests(MyModule, ARGS; testsuite)Filtering Test Files
You can also use find_tests to automatically discover test files and then filter or modify them. This requires manually parsing arguments so that filtering is only applied when the user did not request specific tests to run:
# Start with autodiscovered tests
testsuite = find_tests(pwd())
# Parse arguments
args = parse_args(ARGS)
if filter_tests!(testsuite, args)
# Remove tests that shouldn't run on Windows
if Sys.iswindows()
delete!(testsuite, "ext/specialfunctions")
end
end
runtests(MyModule, args; testsuite)The filter_tests! function returns true if no positional arguments were provided (allowing additional filtering) and false if the user specified specific tests (preventing further filtering).
Initialization Code
Use the init_code keyword argument to runtests to provide code that runs before each test file. This is useful for:
- Importing packages
- Defining constants, defaults or helper functions
- Setting up test infrastructure
using ParallelTestRunner
const init_code = quote
using Test
using MyPackage
# Define a helper function available to all tests
function test_helper(x)
return x * 2
end
end
runtests(MyPackage, ARGS; init_code)The init_code is evaluated in each test's sandbox module, so all definitions are available to your test files.
Custom Workers
For tests that require specific environment variables or Julia flags, you can use the test_worker keyword argument to runtests to assign tests to custom workers:
using ParallelTestRunner
function test_worker(name)
if name == "needs_env_var"
# Create a worker with a specific environment variable
return addworker(; env = ["SPECIAL_ENV_VAR" => "42"])
elseif name == "needs_threads"
# Create a worker with multiple threads
return addworker(; exeflags = ["--threads=4"])
end
# Return nothing to use the default worker
return nothing
end
testsuite = Dict(
"needs_env_var" => quote
@test ENV["SPECIAL_ENV_VAR"] == "42"
end,
"needs_threads" => quote
@test Base.Threads.nthreads() == 4
end,
"normal_test" => quote
@test 1 + 1 == 2
end
)
runtests(MyPackage, ARGS; test_worker, testsuite)The test_worker function receives the test name and should return either:
- A worker object (from
addworker) for tests that need special configuration nothingto use the default worker pool
Custom Arguments
If your package needs to accept its own command-line arguments in addition to ParallelTestRunner's options, use parse_args with custom flags:
using ParallelTestRunner
# Parse arguments with custom flags
args = parse_args(ARGS; custom=["myflag", "another-flag"])
# Access custom flags
if args.custom["myflag"] !== nothing
println("Custom flag was set!")
end
# Pass parsed args to runtests
runtests(MyPackage, args)Custom flags are stored in the custom field of the ParsedArgs object, with values of nothing (not set) or Some(value) (set, with optional value).
Interactive use
Arguments can also be passed via the standard Pkg.test interface for interactive control. For example, here is how we could run the subset of test files that start with the name test_cool_feature in i) verbose mode, and ii) with a specific number of Julia threads enabled:
# Start julia in an environment where `MyPackage.jl` is available
julia --projectjulia> using Pkg
# No need to start a fresh session to change threading
julia> Pkg.test("MyModule"; test_args=`--verbose test_cool_feature`, julia_args=`--threads=auto`);Alternatively, arguments can be passed directly from the command line with a shell alias like the one below:
jltest --threads=auto -- --verbose test_cool_featureShell alias:
function jltest {
julia=(julia)
# certain arguments (like those beginnning with a +) need to come first
if [[ $# -gt 0 && "$1" = +* ]]; then
julia+=("$1")
shift
fi
"${julia[@]}" --startup-file=no --project -e "using Pkg; Pkg.API.test(; test_args=ARGS)" "$@"
}Best Practices
Keep tests isolated: Each test file runs in its own module, so avoid relying on global state between tests.
Use
init_codefor common setup: Instead of duplicating setup code in each test file, useinit_codeto share common initialization.Filter tests appropriately: Use
filter_tests!to respect user-specified test filters while allowing additional programmatic filtering.Handle platform differences: Use conditional logic in your test suite setup to handle platform-specific tests:
testsuite = find_tests(pwd()) if Sys.iswindows() delete!(testsuite, "unix_specific_test") endLoad balance the test files:
ParallelTestRunnerruns the tests files in parallel, ideally all test files should run for roughly the same time for better performance. Having few long-running test files and other short-running ones hinders scalability.Use custom workers sparingly: Custom workers add overhead. Only use them when tests genuinely require different configurations.