1 Parallel
dxtr edited this page 2026-10-18 12:07:40 +02:00

Parallel runs

Parallelism is per test, opt-in per run, opt-out per test: (run :parallel t) uses one worker thread per processor, tests marked :serial run afterwards on the calling thread, and the final report is identical to a serial run's.

Turning it on

*parallel* (default NIL) is the default for run's :parallel:

Value Workers
NIL one thread (serial)
t one worker per processor (falls back to 4 when the system will not say)
a positive integer that many

:debug forces NIL. :shuffle t runs the selection in random order and records the seed; :seed n reproduces a shuffled order; both apply to serial and parallel runs.

(run :my-project/test :parallel t)
(run :my-project/test :parallel 8 :shuffle t)

From the command line: --parallel, --parallel=8, --serial.

Phases

With more than one worker, the non-serial tests go onto one queue. workers threads named assay worker N take tests from it under a mutex until it is empty and return their results. The calling thread joins them, then runs the :serial tests in definition order itself. With fail-fast, the first failing result stops the queue, running tests finish, and the serial phase is skipped.

The final report is printed from the summary in definition order, so it does not depend on scheduling. In verbose mode the live per-test lines arrive in completion order. With --no-capture, worker output interleaves on the shared stream, as cargo test -- --nocapture does.

The summary line gains the worker count (shown only when more than one) and the seed (only when shuffled):

ok: 128 tests   (310 ms, 8 workers, seed 1711243)

The :serial rule

A test is parallel-safe when it touches only its own data or rebinds specials with let. A test that:

  • setfs a global variable,
  • writes a shared file or other external resource, or
  • uses with-mocks (which replaces global function definitions),

is not parallel-safe and must carry :serial t:

(deftest (updates-the-cache :serial t)
  ...)

(deftest (welcome-email :serial t)
  (with-mocks (send-email) ...))

with-mocks on a worker thread is an error that names the test and the fix, so you find out the first time you run the suite in parallel rather than chasing a flaky failure:

WITH-MOCKS replaces global definitions, so the test using it must run alone:
(deftest (welcome-email :serial t) ...).

Everything else a test needs is already thread-local by construction: the runner binds the current result and the capture stream per test, timeouts arm a timer for the current thread, and the handlers and restarts are established inside each thread.

Abort and worker death

If the calling thread unwinds out of a parallel run (a C-c, an abort restart), every live worker is terminated and the run ends with the results collected so far. A worker that dies for any other reason is reported with a warning and its remaining tests count as not run. Timeouts work per test on workers exactly as they do serially.

Next: Mocks.