Skip to content

Parallel execution

Karva runs tests in parallel Python worker processes. Tests in the same worker share an interpreter; tests in different workers do not.

Worker count

The default is one worker per CPU core. Override with -n / --num-workers:

Bash
1
uv run karva test -n 4

Use --no-parallel (equivalent to -n 1) when debugging or testing shared resources that cannot be partitioned:

Bash
1
uv run karva test --no-parallel

Karva caps the worker count at the number of independently schedulable tests to avoid idle workers. It honours smaller requests without assuming how long each test takes. Worker-count reductions are logged at info level; the normal startup line shows the applied count.

Parametrized tests

Karva can distribute cases from one parametrized test across workers when the parameter values are written directly as a list or tuple literal:

Python
1
2
3
@karva.tags.parametrize("value", [1, 2, 3])
def test_value(value):
    pass

Values provided through a variable, comprehension, generator, or function call cannot be counted before a worker imports the module. Karva keeps those cases together on one worker:

Python
1
2
3
4
5
values = [1, 2, 3]

@karva.tags.parametrize("value", values)
def test_value(value):
    pass

All cases still run; only their distribution across workers changes.

Partitioning shared resources

Give each worker its own database, port, or directory using KARVA_WORKER_ID:

Python
1
2
3
4
5
6
import os

@karva.fixture(scope="session")
def database_url():
    worker = os.environ["KARVA_WORKER_ID"]
    return f"postgresql:///test_{worker}"

KARVA_WORKER_ID is 0-indexed and stable for the lifetime of the worker. See Environment Variables for the full list of variables the worker exposes to tests.

Output capture

By default, stdout/stderr from a test is captured and emitted only when the test fails or when --show-output / -s is set. This keeps parallel output legible — without capture, output from concurrent tests would interleave on the terminal.

While output is captured, stdin reads fail immediately with a diagnostic instead of waiting for terminal input. Pass input explicitly to code under test, or use --no-capture for an intentional interactive debugging session. Child processes receive an immediate end-of-file on stdin unless the test passes a pipe, file, or DEVNULL explicitly.

--no-capture disables capture entirely and forces a single worker, since uncaptured output from concurrent workers cannot safely interleave:

Bash
1
uv run karva test --no-capture

Use --no-capture for pdb or live output. Use -s / --show-output to print every test's captured output while keeping parallel execution.

Splitting a run across CI jobs

--partition slice:M/N runs only slice M of N total slices. Tests are sorted by qualified name and distributed round-robin: test 1 to slice 1, test 2 to slice 2, ..., test N+1 to slice 1, and so on. Running every slice:1/N through slice:N/N together covers every collected test exactly once.

Bash
1
2
3
uv run karva test --partition slice:1/3
uv run karva test --partition slice:2/3
uv run karva test --partition slice:3/3

The same test set produces the same slices. Adding or removing tests can move other tests between slices.

Use hash:M/N when stable assignment matters more than even slices. Each qualified test name maps to one stable bucket, so adding or removing a test does not move any other test:

Bash
1
2
3
uv run karva test --partition hash:1/3
uv run karva test --partition hash:2/3
uv run karva test --partition hash:3/3

Hash buckets can be uneven. Run every bucket together to cover the complete suite.