rstest
Runs most pytest suites unchanged, in parallel, usually faster. Same
fixtures, same plugins: byte-exact per-test outcomes at -n 0, and in
parallel the same caveats as pytest-xdist (see
Known gaps).
Rust-orchestrated, parallel by default, with built-in suite diagnostics
(--doctor) that tell you where your test time actually goes.
Note: This is the Python test runner on PyPI (
pip install rstest). It is not related to the Rust fixture cratersteston crates.io.
aiohttp, 4,469 tests: pytest 193s → rstest 67s warm (150s cold), -n 8
Same suite: pytest 193s → rstest 67s (warm, -n auto = 14 workers, as recorded); --doctor shows where the time goes. Current measured numbers: benchmarks.
📚 Full documentation → python-rstest.readthedocs.io
Quick start
Evaluating rstest? Run rstest try first: it runs your suite under plain
pytest and under rstest, then tells you whether outcomes match and how much
faster rstest was (details).
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 also needs rstest in the
project environment; see
Installation.)
Requires Python 3.10+ on macOS, Linux, or Windows. Windows runs the full test gate in CI, but the 33-suite public corpus runs only on macOS/Linux, so Windows is validated at a smaller scale. 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, each worker is a
separate process, so session- and module-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 byte-exact pytest semantics →
rstest -n 0(single 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. 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. Plugins run on that core too, so a plugin'spytest<9pin is inert (details).
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. (It runs the baseline
as python -m pytest, so pytest must be installed in the project's
environment for this one command.)
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, 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 | 67s warm · 150s cold |
| pandas | 193,843 | 190s | 89s | 43s (xdist's controller is the bottleneck) |
| 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, 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% |
Only the cold run is measured. From its per-project duration caches, a warm run is projected (not measured) at 121–133s (6.6–7.3×); discount that until you measure your own.
Full methodology: benchmarks.
Why rstest
| pytest | pytest-xdist | rstest | |
|---|---|---|---|
| Runs your suite unchanged | ✅ | parallel-safe tests | ✅ at -n 0; parallel-safe tests at -n ≥ 2 |
| 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 |
- pytest underneath. 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. Duration-aware work distribution that starts the
slowest tests first (per test, or per file when a large suite gets
lazy collection);
@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 (byte-exact mode), per-test outcomes match pytest exactly: one
vendored-pytest session; any difference at -n 0 is a bug. The guarantee is
per-test outcomes (every phase, skips, xfails). With no --output set,
the terminal output at -n 0 is pytest's own as well, with
rstest's extras (doctor, coverage, gate messages) appended after it, and
--junitxml is pytest's own document at every worker count. 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 controller
process are no-ops in parallel: report plugins such as pytest-reportlog and
pytest-json-report write nothing (no crash), and terminal-UI plugins
(pytest-sugar and friends) don't paint; data-level behavior is unaffected.
--html is handled by rstest itself and works at any worker count. 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
10.97s waiting of 10.97s tests/test_proxy_functional.py::test_proxy_https_connect
... 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. 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
- 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.8.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.8.0-py3-none-win_arm64.whl | Python 3 | none | Windows ARM64 | Details |
| rstest-0.8.0-py3-none-win_amd64.whl | Python 3 | none | Windows x86-64 | Details |
| rstest-0.8.0-py3-none-musllinux_1_2_x86_64.whl | Python 3 | none | Linux musl 1.2+ x86-64 | Details |
| rstest-0.8.0-py3-none-musllinux_1_2_aarch64.whl | Python 3 | none | Linux musl 1.2+ ARM64 | Details |
| rstest-0.8.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | Python 3 | none | Linux glibc 2.17+ x86-64 | Details |
| rstest-0.8.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl | Python 3 | none | Linux glibc 2.17+ ARM64 | Details |
| rstest-0.8.0-py3-none-macosx_11_0_arm64.whl | Python 3 | none | macOS 11.0+ ARM64 | Details |
Total release size: 31.4 MB
Release files / rstest-0.8.0-py3-none-win_arm64.whl
| Download URL | rstest-0.8.0-py3-none-win_arm64.whl |
|---|---|
| Size | 3.9 MB |
| Tags | Python 3 Windows ARM64 |
|
SHA-256 checksum How to use checksums |
73f95c6f78207db06978013757956f63c51d5d89462b0692a3ba48b79811c815
|
|
BLAKE2b-256 checksum How to use checksums |
3e38195503fe64f55e9011bbb9f4e7562de27eadf61206e52e9025c76aa09771
|
| 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 30, 2026.
Transparency logRelease files / rstest-0.8.0-py3-none-win_amd64.whl
| Download URL | rstest-0.8.0-py3-none-win_amd64.whl |
|---|---|
| Size | 4.2 MB |
| Tags | Python 3 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
36d4dc12e412ea13c0b69d15f8b991493fb8caeb5da70b28e6fa9884ac3e9e4a
|
|
BLAKE2b-256 checksum How to use checksums |
921a0e7b9c7aebda3326a7ea9c1571a975faee3499039c7e108d5ea2a993eaec
|
| 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 30, 2026.
Transparency logRelease files / rstest-0.8.0-py3-none-musllinux_1_2_x86_64.whl
| Download URL | rstest-0.8.0-py3-none-musllinux_1_2_x86_64.whl |
|---|---|
| Size | 4.9 MB |
| Tags | Linux musl 1.2+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
e9562f571cf798226e908ea1ca3f8287a404a232b04428b08a9011925b15f5cc
|
|
BLAKE2b-256 checksum How to use checksums |
c40478ae5213e500be85488bbaddf994f1da87332c77621b828851146a65e453
|
| 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 30, 2026.
Transparency logRelease files / rstest-0.8.0-py3-none-musllinux_1_2_aarch64.whl
| Download URL | rstest-0.8.0-py3-none-musllinux_1_2_aarch64.whl |
|---|---|
| Size | 4.7 MB |
| Tags | Linux musl 1.2+ ARM64 Python 3 |
|
SHA-256 checksum How to use checksums |
39f677d47d5785878a485d664a831b2dbd9df05909d6d02296cc6fffd305d4b0
|
|
BLAKE2b-256 checksum How to use checksums |
049ab7209d3c3a00fe92081354e67474b5fdbc255f353933294cef7012a36fe5
|
| 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 30, 2026.
Transparency logRelease files / rstest-0.8.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | rstest-0.8.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 4.7 MB |
| Tags | Linux glibc 2.17+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
23a2ececd4578d4ff7bcec1a888ccbd89b0cffb749c3a8b29b9251d65d713e25
|
|
BLAKE2b-256 checksum How to use checksums |
1851edfd2f3f5d6476abbbcbe64ce7b5d27df643d73999cc16a386c8f2f21752
|
| 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 30, 2026.
Transparency logRelease files / rstest-0.8.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | rstest-0.8.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 4.7 MB |
| Tags | Linux glibc 2.17+ ARM64 Python 3 |
|
SHA-256 checksum How to use checksums |
eec823571dc970e9201afc22820613623d8eb204a2d292d0a7c1b9d7db246a37
|
|
BLAKE2b-256 checksum How to use checksums |
23ef73a6591e6a709f67fcb8cd29e62ed98c9b62851081fc83f92a71ffeac0ac
|
| 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 30, 2026.
Transparency logRelease files / rstest-0.8.0-py3-none-macosx_11_0_arm64.whl
| Download URL | rstest-0.8.0-py3-none-macosx_11_0_arm64.whl |
|---|---|
| Size | 4.3 MB |
| Tags | Python 3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
cf3666418d9d8e63590379f3ebcffde7d9f0d2b53d75406d33aad5c1d90808f8
|
|
BLAKE2b-256 checksum How to use checksums |
d25790950bad030f924ae17ac108f5171802c97c3491ba9676c3421d09136268
|
| 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 30, 2026.
Transparency log