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.
Rust CLI · Python daemon · same conftest.py
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 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
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
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.
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.
`rpytest -n auto` runs your tests across workers with duration-aware load balancing. No pytest-xdist plugin required, though the flag surface matches.
`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.
`--reruns N` retries failing tests, `--flaky-report` summarises which tests are quietly unreliable. Catch flake before it lands in your CI dashboards.
`--shard 0 --total-shards 4` splits a suite deterministically across CI jobs. Combine with the daemon model and per-shard collection cost effectively disappears.
`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
01 · CLI
Parses flags, applies selectors, dispatches to the daemon over IPC. Memory footprint stays in single-digit megabytes.
02 · daemon
A background Python process imports your suite once, caches the test inventory, and keeps fixtures/plugins resident.
03 · run
Each invocation is a small RPC. The daemon filters the inventory, optionally fans out to workers, and runs only what changed.
04 · stream
Test results stream to the CLI in real time. Exit code and JUnit XML match pytest exactly.
Honest comparisons
Two comparisons we'll defend: the baseline you already run, and the parallel-pytest standard. Both are tools we respect.
The baseline. We are wire-compatible on flags, config, and exit codes. The pitch is that you keep pytest semantics and remove the startup tax.
The standard way to parallel-run pytest. rpytest ships built-in `-n` with duration-aware scheduling and skips xdist's per-worker startup cost.
FAQ
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.
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.
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.
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.
`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.
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.
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.
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.
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.