rstest
Runs most pytest suites unchanged, in parallel. Same fixtures, same
plugins, parallel by default, with built-in suite diagnostics (--doctor)
that tell you where your test time actually goes. How much faster depends
on your suite's shape (self-check below).
Note: This is the Python test runner on PyPI (
pip install rstest). It is not related to the Rust fixture cratersteston crates.io.
aiohttp's suite: pytest 193s → rstest 67s on a warm cache, then --doctor shows where the time goes. Recorded at -n auto (14 workers); the suite gains nothing past 8, so the tables below use -n 8.
📚 Full documentation → python-rstest.readthedocs.io
Quick start
In any pytest project:
pip install rstest # or: uv pip install rstest
rstest
Also works with your tool of choice:
uv add --dev rstest # uv projects
poetry add --group dev rstest # Poetry
pdm add -dG test rstest # PDM
Install rstest into the same environment as your tests: workers run in
that interpreter. A pipx / uv tool install alone can't run your tests;
it only puts rstest on your PATH
(details).
Requires Python 3.10+ on macOS (Apple silicon), Linux, or Windows (Windows is validated at a smaller scale than macOS/Linux). rstest is alpha (0.x): expect breaking changes between minor versions until 1.0.
No config and no test changes needed: rstest runs your pytest suite in
parallel (-n auto) out of the box. As with pytest-xdist, session-scoped
fixtures run
once per worker,
not once per run.
- Tests that can't run in parallel (shared files, ports, databases) →
@pytest.mark.serial(run exclusively, after the parallel phase). - Tests that depend on order within a file →
--dist loadfile(keeps each file's tests on one worker); across files →rstest -n 0. - Want pytest's exact behavior →
rstest -n 0(single-worker mode: one pytest session, per-test outcomes match pytest exactly).
Will rstest speed up your suite?
The wins come from suite shape, not magic. Quick self-check:
| Your suite | What to expect |
|---|---|
| Wait-bound (IO, sleeps, network, timeouts) | Biggest win: xdist's default --dist load hands out consecutive batches in collection order with no timing data, so a file of slow tests clusters on a few workers and starts late; rstest's duration cache starts the slowest tests first, spread across workers. |
| CPU-bound, already splits well under xdist | Parity, not a win: gain up to the performance-core count, same as xdist (sympy -n 8: 16.1s vs 14.8s, overlapping spreads). |
| Very many tests (100k+) | Win over xdist: xdist's single Python controller becomes the bottleneck; rstest's orchestrator is Rust (pandas -n 8: 43s vs 89s). |
| Gated by one long test | No win beyond that test: no worker count beats the long pole. --doctor names it. |
| Small (< ~10s serial) | Little wall-time change: value is --watch, --changed, --doctor, not raw speed. |
| Many tiny per-service suites | Speedup is per-suite; the aggregate CI win depends on your largest suites. |
Two more things to know before you benchmark:
- Warm cache matters. Duration-aware scheduling needs one run of timing
data. First run is cold; the win arrives on run two. In ephemeral CI,
persist
.rstest_cacheor expect cold-run timing. - Adopting rstest adopts pytest 9, whatever pytest you have installed (details).
Fastest way to find out for real: rstest try compares a plain pytest run
with rstest -n auto and reports parity and speed, with no config
(how it works).
Benchmarks
Real open-source suites, end-to-end, with per-test outcome diffing against the pytest baseline: 100% parity on pandas, django-allauth and rich in the measured run (allauth and rich have tests that flake under plain pytest too, so some runs land at ~99.8-99.9%), and 99.91-99.98% on aiohttp, whose socket-leak warning flake hits xdist too (every known flake is catalogued in the docs).
| Suite | Tests | pytest | xdist -n 8 |
rstest -n 8 |
|---|---|---|---|---|
| aiohttp | 4,469 | 193s | 160s (73s with --dist worksteal) |
67s warm · 150s cold |
| pandas | 193,843 | 190s | 89s | 43s (xdist's controller pins a core) |
| django-allauth | 2,050 | 26s | 8.9s | 5.8s |
| rich | 981 | 3.7s | 2.7s | 2.5s |
Apple M4 Max, CPython 3.13, pytest-xdist 3.8, median of 5 runs at the same
-n for both runners. CPU-bound suites (sympy, scikit-learn) land at parity
with xdist; see the benchmarks page.
Monorepo (langchain-ai/langgraph, the 5 libs/* packages that need no
live services, 838 tests, each with its own pytest config; a single pytest
can't run from the root at all):
| wall | parity | |
|---|---|---|
| pytest: 5 serial invocations | 190.0s | baseline |
| rstest at the root | 26.1s (7.3×) | 100% |
Same method as above (median of 5, rstest -n auto). One of the five
packages, libs/sdk-py, collects no tests under either runner in the
benchmark venv (a missing test dependency), so the 838 tests come from the
other four.
Full methodology: benchmarks.
Why rstest
| pytest | pytest-xdist | rstest | |
|---|---|---|---|
| Runs your suite unchanged | ✅ | parallel-safe tests | parallel-safe tests; any suite at -n 0 |
| Parallel by default | ❌ | ⚙️ opt-in | ✅ |
| Duration-aware scheduling | plugin (pytest-split, across CI jobs) | ❌ | ✅ |
| Crashed workers replaced mid-run | ❌ | ✅ | ✅ |
| Crash pinned to the exact test | ❌ | inferred from queue | ✅ explicit per-test signal |
| Suite diagnostics | ❌ | ❌ | ✅ --doctor |
| Watch mode | plugin (pytest-watch) | --looponfail (deprecated) |
✅ built-in |
- Your tests, unchanged. A vendored pytest 9.1.1 core runs your conftest
hierarchies, fixtures, parametrize, marks, and installed plugins
(pytest-django, pytest-asyncio, hypothesis, pytest-mock, ...) as pytest
does. Most pytest flags (
-k,-m,-x,--lf, plugin flags) forward unchanged; a few, such as--timeout,--rerunsand--html, are rstest's own with the same basic syntax as the plugins they replace. - xdist semantics in parallel. Session fixtures run once per worker, as under pytest-xdist, plus a short list of differences.
- Parallel by design. Work distribution across worker processes,
duration-aware scheduling that starts your slowest tests first (per test,
or per file when a large suite gets
lazy collection), and safety rails for
tests that can't parallelize
(
@pytest.mark.serial,--dist loadfile). - Crash-safe. A segfaulting test costs you one FAILED line: the worker is replaced, its remaining tests redistribute, and the run completes.
rstest --doctor. Tells you why the suite is slow: tests that wait instead of compute, the long-pole tests that cap any parallelism, fixture hotspots, slowest files.rstest --watch. Reruns on save; changed test files rerun alone, source changes rerun only the tests the import graph says are affected.
Compatibility contract. At -n 0 (single-worker mode) per-test outcomes
match pytest exactly, and any difference is a bug (except for the
few flags rstest shares with pytest or a plugin,
which rstest handles itself at every worker count). In parallel, outcomes are
preserved for parallel-safe tests, with the same caveats as pytest-xdist and
a few report plugins that go quiet. Details:
Compatibility
and Known gaps.
rstest --doctor
Runs your suite, then answers where does the time actually go?, from data the runner already owns (per-test wall/CPU time, per-fixture setup):
================== rstest doctor ==================
4442 tests, 185.8s test time (wall 67.7s, 8 workers)
WAIT-BOUND: 95% of test time (176.5s) is waiting, not computing (sleeps / IO / timeouts).
54.20s waiting of 54.25s tests/test_proxy_functional.py::test_proxy_https_multi_conn_limit
10.97s waiting of 10.97s tests/test_proxy_functional.py::test_proxy_https_connect
... and 27 more
PARALLEL FLOOR: the longest test (54.2s) exceeds the ideal per-worker share (23.2s at -n 8);
no worker count can finish faster than its longest test. Gate tests:
54.25s tests/test_proxy_functional.py::test_proxy_https_multi_conn_limit
FIXTURE HOTSPOTS (setup time across all workers):
0.79s 4442x scope=function blockbuster
0.54s 157x scope=function transport
SLOWEST FILES:
150.46s (81.0%) tests/test_proxy_functional.py
8.60s ( 4.6%) tests/test_client_functional.py
===================================================
That's aiohttp's real suite: one file is 81% of total test time, almost all of it waiting on 10-second proxy timeouts. (Doctor counts tests with a recorded call duration, so skipped tests drop out: 4442 here against the 4,469 collected.) More →
Docs
- Getting started
- Evaluating rstest: what it speeds up, when not to adopt, and what switching costs
- Migrating from pytest
- Migrating from pytest-xdist
- Parallel safety
- Suite diagnostics (
--doctor) - Watch mode
- CI quickstart
- CLI reference
- Troubleshooting
- Agent skills:
rstest install-skillsor the Claude Code plugin
License
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
Metadata
Release files for rstest 0.9.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| rstest-0.9.0-py3-none-win_arm64.whl | Python 3 | none | Windows ARM64 | Details |
| rstest-0.9.0-py3-none-win_amd64.whl | Python 3 | none | Windows x86-64 | Details |
| rstest-0.9.0-py3-none-musllinux_1_2_x86_64.whl | Python 3 | none | Linux musl 1.2+ x86-64 | Details |
| rstest-0.9.0-py3-none-musllinux_1_2_aarch64.whl | Python 3 | none | Linux musl 1.2+ ARM64 | Details |
| rstest-0.9.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | Python 3 | none | Linux glibc 2.17+ x86-64 | Details |
| rstest-0.9.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl | Python 3 | none | Linux glibc 2.17+ ARM64 | Details |
| rstest-0.9.0-py3-none-macosx_11_0_arm64.whl | Python 3 | none | macOS 11.0+ ARM64 | Details |
Total release size: 32.7 MB
Release files / rstest-0.9.0-py3-none-win_arm64.whl
| Download URL | rstest-0.9.0-py3-none-win_arm64.whl |
|---|---|
| Size | 4.1 MB |
| Tags | Python 3 Windows ARM64 |
|
SHA-256 checksum How to use checksums |
51eefba6b4e15a0cde486ee1a0936c5743a0bb01bf864e1d2e076eda0a7eb18a
|
|
BLAKE2b-256 checksum How to use checksums |
f9a2e7e38ee2e97e4c662b94ea0effa65518b75cd7c48b235636868388b587f8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.
Transparency logRelease files / rstest-0.9.0-py3-none-win_amd64.whl
| Download URL | rstest-0.9.0-py3-none-win_amd64.whl |
|---|---|
| Size | 4.4 MB |
| Tags | Python 3 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
788c99bf6689340ed9c9440610b8368879be4fd19249c59ada7538601a633aa0
|
|
BLAKE2b-256 checksum How to use checksums |
66d00a937169178206fe507d5fb9e73e164fc968accf7d5f43e1c946f43a92f3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.
Transparency logRelease files / rstest-0.9.0-py3-none-musllinux_1_2_x86_64.whl
| Download URL | rstest-0.9.0-py3-none-musllinux_1_2_x86_64.whl |
|---|---|
| Size | 5.1 MB |
| Tags | Linux musl 1.2+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
0bcf7d449f6a70199e001e0c8169dbb68281fda8dd9d7035e4cbe84e4e5e8ec0
|
|
BLAKE2b-256 checksum How to use checksums |
98dd8aca5eb3eea5730c64a42189ab83cd0655b050538695fa620ed6ad197c04
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.
Transparency logRelease files / rstest-0.9.0-py3-none-musllinux_1_2_aarch64.whl
| Download URL | rstest-0.9.0-py3-none-musllinux_1_2_aarch64.whl |
|---|---|
| Size | 4.9 MB |
| Tags | Linux musl 1.2+ ARM64 Python 3 |
|
SHA-256 checksum How to use checksums |
306a2b9753fb27cd486b9466139f01139d92c4d281c437d0b4f088ba3d34aeff
|
|
BLAKE2b-256 checksum How to use checksums |
25766876ee56dc22a42c3a93c700b25919728b60861e4139ff1c37b41e0e9a63
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.
Transparency logRelease files / rstest-0.9.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | rstest-0.9.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 4.9 MB |
| Tags | Linux glibc 2.17+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
fbcbe6d1ea5726267b56fb835501ed276e509954874777808ddc078d30470f63
|
|
BLAKE2b-256 checksum How to use checksums |
cfa4b1d42a910a553c16fb13f37a2282fb5ef1160f58ca49ee65af3686ecca94
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.
Transparency logRelease files / rstest-0.9.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | rstest-0.9.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 4.9 MB |
| Tags | Linux glibc 2.17+ ARM64 Python 3 |
|
SHA-256 checksum How to use checksums |
da9d9a4ea243c6e0cb14a13edc7d7d38ca7fdd2a6b96a4ce2fe0a966d6321266
|
|
BLAKE2b-256 checksum How to use checksums |
88c1fd42de9ea3df972051f20bd7f9efef45d8cbc33d85309f9b222c489571ae
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.
Transparency logRelease files / rstest-0.9.0-py3-none-macosx_11_0_arm64.whl
| Download URL | rstest-0.9.0-py3-none-macosx_11_0_arm64.whl |
|---|---|
| Size | 4.5 MB |
| Tags | Python 3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
b21ecec9a17039d2fea493ee4d5086fa1bc7ef1cf4f64e73dfdb2e865d66d158
|
|
BLAKE2b-256 checksum How to use checksums |
bdcc6f185159cad9c3b8eba88468c5b17dc88a57d8bc8f305a423f984d0f7894
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.
Transparency log