rstest
Run your existing pytest suite in parallel, unchanged. Same fixtures,
same plugins, same outcomes — just faster. Rust-orchestrated, parallel by
default, with built-in suite diagnostics (--doctor) that tell you where
your test time actually goes.
aiohttp, 4,469 tests: pytest 197s → rstest 68s warm (151s cold)
📚 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 rstest # uv projects
poetry add rstest # Poetry
pdm add rstest # PDM
pipx install rstest # isolated CLI install
No config. No test changes. If pytest runs it, rstest runs it — in
parallel (-n auto), out of the box.
- Tests that can't run in parallel →
@pytest.mark.serial(run exclusively, after the parallel phase). - Order-dependent suites →
--dist loadfile. - Want byte-exact pytest semantics →
rstest -n 0(single pytest session).
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 — test-granular dispatch splits the slow files xdist pins to one worker. |
| CPU-bound, already splits well under xdist | Parity, not a win — gain up to core count, same as xdist (pandas: 63s vs 61s). |
| 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 truths worth knowing 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. rstest runs a vendored pytest 9.1.1
core. A suite that's warning-clean on recent pytest 8.x is almost always
already pytest-9-clean; if not, clear deprecations first (the same upgrade
you'd owe pytest anyway).
rstest -n 0surfaces them.
Fastest way to find out for real: rstest try runs your suite under plain
pytest and under rstest -n auto, then reports whether outcomes match and
how much faster rstest was — no migration, no config.
Benchmarks
Real open-source suites, end-to-end, with per-test outcome diffing against the pytest baseline — 100% parity on the measured run (a few tests flake under plain pytest itself; those are catalogued in the docs).
| Suite | Tests | pytest | xdist (-n 8) |
rstest |
|---|---|---|---|---|
| aiohttp | 4,469 | 197s | 160s | 68s warm · 151s cold |
| pandas | 193,627 | 182s | 61s | 63s (parity, not a win) |
| django-allauth | 2,050 | 22s | 8s | 8s (-n 4) |
| rich | 981 | 3.4s | 2.8s | 2.5s (-n 4) |
Apple Silicon, CPython 3.13, pytest-xdist 3.8.
Monorepo (langchain-ai/langgraph, 6 libs/* packages, 4,284 tests, each
with its own pytest config — a single pytest can't run from the root at all):
| wall | parity | |
|---|---|---|
| pytest — 6 serial invocations | 880.4s | baseline |
| rstest at the root, cold | 245.7s (3.6×) | 100% (measured) |
| rstest at the root, warm | 121–133s (6.6–7.3×) | projected |
The cold run is measured. The warm figure is a projection, not a recorded run — the cold run's per-package duration caches predict where the planner lands once warm, so it carries no parity number. Discount it until you measure your own.
Full methodology: benchmarks.
Why rstest
| pytest | pytest-xdist | rstest | |
|---|---|---|---|
| Runs your suite unchanged | ✅ | mostly | ✅ |
| Parallel by default | ❌ | ⚙️ opt-in | ✅ |
| Duration-aware scheduling | ❌ | ❌ | ✅ |
| Crashed workers respawn mid-run | ❌ | ❌ | ✅ |
| Suite diagnostics | ❌ | ❌ | ✅ --doctor |
| Watch mode | plugin | ❌ | ✅ built-in |
- Drop-in. Forwards the pytest flag surface; runs conftest, fixtures, parametrize, marks, and pytest plugins (pytest-django, pytest-asyncio, hypothesis, …) through a vendored pytest core.
- Parallel by design. Test-granular work distribution with duration-aware
scheduling;
@pytest.mark.serialand--dist loadfilesafety rails; crashed workers respawn without losing your run. rstest --doctor. Wait-bound tests, parallel-floor analysis, fixture hotspots, slowest files.rstest --watch. Instant 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, outcomes are byte-exact — one vendored-pytest session; any
difference at -n 0 is a bug. In parallel modes, outcomes are preserved for
parallel-safe tests; tests with hidden time/ordering/shared-state
assumptions can flake under high concurrency — exactly as under
pytest-xdist. rstest --doctor and lower -n values help find and contain
them; @pytest.mark.serial is the escape hatch.
Vendored pytest 9. rstest runs a vendored pytest 9.1.1 core, so adopting rstest adopts pytest 9's behavior regardless of the pytest version installed. The 8→9 gap is a cleanup major (removes already-deprecated APIs); a suite warning-clean on recent pytest 8.x is almost always pytest-9-clean. If it isn't, clear the deprecations first — the same upgrade you'd owe pytest anyway. There is one vendored core, tracked forward; no older-core build.
Silent-at--n ≥ 2 plugins. A few plugins that need a single master
process are no-ops in parallel: --html (pytest-html) writes nothing (no
crash) — generate reports at -n 0/-n 1. Terminal-UI plugins (pytest-sugar
and friends) don't paint; data-level behavior is unaffected. Full list:
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
... and 33 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.
FIXTURE HOTSPOTS (setup time across all workers):
0.79s 4442x scope=function blockbuster
SLOWEST FILES:
150.46s (81.0%) tests/test_proxy_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. More →
Docs
- Getting started
- Migrating from pytest
- Migrating from pytest-xdist
- Parallel safety
- Suite diagnostics (
--doctor) - Watch mode
- CI quickstart
- CLI reference
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.6.1
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.6.1-py3-none-win_arm64.whl | Python 3 | none | Windows ARM64 | Details |
| rstest-0.6.1-py3-none-win_amd64.whl | Python 3 | none | Windows x86-64 | Details |
| rstest-0.6.1-py3-none-musllinux_1_2_x86_64.whl | Python 3 | none | Linux musl 1.2+ x86-64 | Details |
| rstest-0.6.1-py3-none-musllinux_1_2_aarch64.whl | Python 3 | none | Linux musl 1.2+ ARM64 | Details |
| rstest-0.6.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | Python 3 | none | Linux glibc 2.17+ x86-64 | Details |
| rstest-0.6.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl | Python 3 | none | Linux glibc 2.17+ ARM64 | Details |
| rstest-0.6.1-py3-none-macosx_11_0_arm64.whl | Python 3 | none | macOS 11.0+ ARM64 | Details |
Total release size: 20.3 MB
Release files / rstest-0.6.1-py3-none-win_arm64.whl
| Download URL | rstest-0.6.1-py3-none-win_arm64.whl |
|---|---|
| Size | 2.5 MB |
| Tags | Python 3 Windows ARM64 |
|
SHA-256 checksum How to use checksums |
2da4eaa6b41d3a1c06d380c44f6bd1217bac6c98a2dbbc0a022eb2dc8ec5ce2a
|
|
BLAKE2b-256 checksum How to use checksums |
cccd611a72064cd7bfcf7487f3f59ec1325f5dfd131f76d3d4e5994ad51fb530
|
| 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 Sep 8, 2026.
Transparency logRelease files / rstest-0.6.1-py3-none-win_amd64.whl
| Download URL | rstest-0.6.1-py3-none-win_amd64.whl |
|---|---|
| Size | 2.7 MB |
| Tags | Python 3 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
d6114ca9b36dd6c2a4c89038cc45b6ef325acadbb70a2c858f920058c832e35e
|
|
BLAKE2b-256 checksum How to use checksums |
e02c239b12d93605db6d4ad6b08cd11fdc23cacee2ce9ae2d027d3322e428476
|
| 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 Sep 8, 2026.
Transparency logRelease files / rstest-0.6.1-py3-none-musllinux_1_2_x86_64.whl
| Download URL | rstest-0.6.1-py3-none-musllinux_1_2_x86_64.whl |
|---|---|
| Size | 3.2 MB |
| Tags | Linux musl 1.2+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
e64bc03f95838f6c6b37f03fdf3c58b00b13a96563bed89f45fa204dbe33afb3
|
|
BLAKE2b-256 checksum How to use checksums |
5259376c3fed0a372244c876a3a4046bb40fa72d2db0af295e0d49178d396289
|
| 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 Sep 8, 2026.
Transparency logRelease files / rstest-0.6.1-py3-none-musllinux_1_2_aarch64.whl
| Download URL | rstest-0.6.1-py3-none-musllinux_1_2_aarch64.whl |
|---|---|
| Size | 3.0 MB |
| Tags | Linux musl 1.2+ ARM64 Python 3 |
|
SHA-256 checksum How to use checksums |
9c31a4e76227999e4e50abe4fcca71f73751aeb80378c70e22401084ea7cf598
|
|
BLAKE2b-256 checksum How to use checksums |
6f867499fcaf117fa6ab0aa8b8ec6bc38b8959bbef7de4a1b71a81914b96c06f
|
| 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 Sep 8, 2026.
Transparency logRelease files / rstest-0.6.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | rstest-0.6.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 3.1 MB |
| Tags | Linux glibc 2.17+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
5555d106c88db26d25b99e664f65ba62fe1a5b740a0210ca2c68656dc3d39634
|
|
BLAKE2b-256 checksum How to use checksums |
fdfa372b16e23384684f0aed9c62f71ed742744cdf4cc519a92e489f54444b0c
|
| 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 Sep 8, 2026.
Transparency logRelease files / rstest-0.6.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | rstest-0.6.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 3.0 MB |
| Tags | Linux glibc 2.17+ ARM64 Python 3 |
|
SHA-256 checksum How to use checksums |
f9c4eb93b9cd7122f39f56a5bc1668b8a7d3bd9ee96a85d2765d086fd7faae3c
|
|
BLAKE2b-256 checksum How to use checksums |
d4a538dea11bf142b7efd27927a76dbfbb5928f8d5f46d9c460c5ffd2206b045
|
| 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 Sep 8, 2026.
Transparency logRelease files / rstest-0.6.1-py3-none-macosx_11_0_arm64.whl
| Download URL | rstest-0.6.1-py3-none-macosx_11_0_arm64.whl |
|---|---|
| Size | 2.7 MB |
| Tags | Python 3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
a41b87959fdf130ebc40fbc4df01d8c76aac0f72484941a5da231e6469b8584c
|
|
BLAKE2b-256 checksum How to use checksums |
e8d8782b9db9f87bde53e3955a44b238304cb67245337b67b606398f34ee33e6
|
| 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 Sep 8, 2026.
Transparency log