Skip to content
rpytest GitHub

Rust CLI · Python daemon · same conftest.py

Run your pytest suite faster. Change nothing.

rpytest is a Rust-powered, drop-in replacement for pytest. Same CLI, same flags, same plugins, same exit codes — but a persistent Python daemon keeps the interpreter warm so you stop paying startup and collection cost on every run.

install + run
# Install and run — that's it
$ pip install rpytest
$ rpytest
# Same flags you already know
$ rpytest tests/test_api.py::test_login -k auth -m "not slow"
$ rpytest -n auto              # built-in parallel
$ rpytest --watch              # TDD loop
$ rpytest --verify-dropin      # prove equivalence

Benchmark · from the repo

Honest numbers, not marketing math.

These figures come from BENCHMARK.md in the rpytest repository: a 500-test synthetic suite on an AMD Ryzen 7 5700U, Python 3.12.3, pytest 9.0.2, pytest-xdist 3.8.0. Your suite is not this suite. Run --verify-dropin and measure your own.

Wall clock, incl. startup

0.32 s

rpytest, 500 tests. pytest baseline: 0.63 s.

CLI memory

5.9 MB

rpytest CLI process. pytest baseline: 39.4 MB. Daemon adds ~80 MB shared.

Parallel vs xdist

0.25 s

rpytest -n 4. pytest -n 4 (xdist): 0.87 s. Worker startup is the gap.

Numbers from a single hardware profile and a synthetic suite. Real suites with heavy fixtures or I/O will show different ratios. The point isn't a single multiplier — it's that the daemon model removes a class of overhead that xdist re-pays per worker.

What you get

A pytest you don't have to migrate to.

Compatibility

Drop-in CLI

rpytest mirrors pytest's CLI exactly. Same flags, same selectors, same exit codes. `pytest.ini`, `pyproject.toml`, and `tox.ini` are read in place. If you know pytest, you know rpytest.

Architecture

Persistent Python daemon

First invocation spawns a background daemon that imports your suite once. Every subsequent run is a fast RPC into a warm interpreter — no startup, no re-import, no re-collection.

Parallel

Built-in parallelism

`rpytest -n auto` runs your tests across workers with duration-aware load balancing. No pytest-xdist plugin required, though the flag surface matches.

Developer loop

Watch mode for TDD

`rpytest --watch` re-runs affected tests on file changes, reusing the warm daemon. The inner TDD loop drops from seconds to subseconds for most suites.

Reliability

Flakiness controls

`--reruns N` retries failing tests, `--flaky-report` summarises which tests are quietly unreliable. Catch flake before it lands in your CI dashboards.

CI

CI sharding

`--shard 0 --total-shards 4` splits a suite deterministically across CI jobs. Combine with the daemon model and per-shard collection cost effectively disappears.

Safety

Drop-in verification

`rpytest --verify-dropin` runs both pytest and rpytest on your suite and compares collection counts, results, and exit codes. Ship the swap with evidence, not vibes.

How it works

A Rust front, a warm Python back.

01 · CLI

Rust binary

Parses flags, applies selectors, dispatches to the daemon over IPC. Memory footprint stays in single-digit megabytes.

02 · daemon

Persistent pytest

A background Python process imports your suite once, caches the test inventory, and keeps fixtures/plugins resident.

03 · run

RPC, then execute

Each invocation is a small RPC. The daemon filters the inventory, optionally fans out to workers, and runs only what changed.

04 · stream

Results back

Test results stream to the CLI in real time. Exit code and JUnit XML match pytest exactly.

Honest comparisons

Where rpytest fits in your test toolchain

Two comparisons we'll defend: the baseline you already run, and the parallel-pytest standard. Both are tools we respect.

FAQ

Questions teams ask before they swap.

+ Does rpytest re-implement pytest?

No. rpytest is a Rust CLI plus a Python daemon. The daemon hosts a real pytest process, so your fixtures, conftest.py, parametrization, and plugins all run in real pytest. The CLI handles dispatch, filtering, parallelism, and result streaming.

+ Do my plugins still work?

In general, yes — the daemon hosts pytest, so plugins load and run as they always have. Some plugins assume a fresh Python process per invocation; those are the most likely friction. The drop-in verifier flags differences for you.

+ How is it faster?

Two sources. (1) The daemon keeps Python warm, so you skip interpreter startup, plugin import, and test collection on every invocation. (2) The Rust CLI does filtering, parallel dispatch, and result aggregation without paying Python overhead. The published benchmark in the README shows 1.9x wall-clock on a 480-test suite; mileage varies with suite shape.

+ Does it replace pytest-xdist?

For parallel test execution, yes — `rpytest -n auto` ships built-in with duration-aware scheduling, no plugin install required. xdist remains a fine choice when you want xdist specifically; we are not trying to retire it.

+ How do I install it?

`pip install rpytest`. The pip package pulls in both the Rust CLI and the daemon binary. There are also Homebrew, npm, and cargo install paths in the README for non-pip environments.

+ Will it change my test output?

Exit codes and JUnit XML match pytest exactly, by design. Console output is similar but not byte-identical — if your tooling parses pytest stdout, run the drop-in verifier on a representative suite first.

+ Can I run it in CI?

Yes. The CI hot path is one of the design targets: the daemon model pays off most on small re-runs (last-failed, single-file, watch), but sharding (`--shard`) and built-in parallelism also remove the xdist installation step. The "Measuring CI minutes saved, honestly" blog post walks through the numbers.

+ Where does state live between runs?

The daemon is a separate long-lived process; manage it with `rpytest --daemon-status`, `rpytest --daemon-stop`, and `rpytest --cleanup`. The daemon is intentionally local — there is no remote control plane.

Stop reimporting pytest on every save.

Install, run, and have the same tests pass in a fraction of the time. If they don't, --verify-dropin will tell you why before you commit.