Metadata-Version: 2.4
Name: rstest
Version: 0.9.0
Classifier: Development Status :: 3 - Alpha
Classifier: Framework :: Pytest
Classifier: Intended Audience :: Developers
Classifier: Operating System :: MacOS
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Rust
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development :: Testing
Requires-Dist: msgpack>=1.0
Requires-Dist: pluggy>=1.5,<2
Requires-Dist: iniconfig
Requires-Dist: packaging
Requires-Dist: pygments>=2.7.2
Requires-Dist: exceptiongroup>=1 ; python_full_version < '3.11'
Requires-Dist: tomli>=1 ; python_full_version < '3.11'
Requires-Dist: colorama>=0.4 ; sys_platform == 'win32'
License-File: LICENSE-MIT
License-File: LICENSE-APACHE
Summary: A fast, pytest-compatible test runner: parallel by design, with built-in suite diagnostics
Maintainer: Kovant AB
License-Expression: MIT OR Apache-2.0
Requires-Python: >=3.10
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Changelog, https://github.com/KovantAI/rstest/blob/main/CHANGELOG.md
Project-URL: Documentation, https://python-rstest.readthedocs.io/en/stable/
Project-URL: Homepage, https://python-rstest.readthedocs.io/en/stable/
Project-URL: Issues, https://github.com/KovantAI/rstest/issues
Project-URL: Repository, https://github.com/KovantAI/rstest

# rstest

[![CI](https://github.com/KovantAI/rstest/actions/workflows/ci.yml/badge.svg)](https://github.com/KovantAI/rstest/actions/workflows/ci.yml)
[![codecov](https://codecov.io/gh/KovantAI/rstest/graph/badge.svg)](https://codecov.io/gh/KovantAI/rstest)
[![PyPI](https://img.shields.io/pypi/v/rstest)](https://pypi.org/project/rstest/)
[![Python versions](https://img.shields.io/pypi/pyversions/rstest)](https://pypi.org/project/rstest/)
[![Wheel](https://img.shields.io/pypi/wheel/rstest)](https://pypi.org/project/rstest/)
[![License](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue)](https://github.com/KovantAI/rstest#license)
[![Downloads](https://static.pepy.tech/badge/rstest/month)](https://pepy.tech/project/rstest)
[![Docs](https://readthedocs.org/projects/python-rstest/badge/?version=stable)](https://python-rstest.readthedocs.io/en/stable/)
[![GitHub stars](https://img.shields.io/github/stars/KovantAI/rstest)](https://github.com/KovantAI/rstest/stargazers)

**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](#will-rstest-speed-up-your-suite)).

> **Note:** This is the Python test runner on PyPI (`pip install rstest`). It is
> not related to the Rust fixture crate [`rstest`](https://crates.io/crates/rstest)
> on crates.io.

<p align="center">
  <img src="https://raw.githubusercontent.com/KovantAI/rstest/main/docs/assets/rstest-demo.gif" alt="Terminal recording: the aiohttp suite under pytest (193s), then rstest --doctor (67s, 14 parallel workers) pinpointing the wait-bound file that gates the suite" width="820">
</p>

<p align="center"><sub>aiohttp's suite: <b>pytest 193s → rstest 67s</b> on a warm cache, then <code>--doctor</code> shows <i>where the time goes</i>. Recorded at <code>-n auto</code> (14 workers); the suite gains nothing past 8, so the <a href="#benchmarks">tables below</a> use <code>-n 8</code>.</sub></p>

📚 **[Full documentation → python-rstest.readthedocs.io](https://python-rstest.readthedocs.io/en/stable/)**

## Quick start

In any pytest project:

```bash
pip install rstest      # or: uv pip install rstest
rstest
```

Also works with your tool of choice:

```bash
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](https://python-rstest.readthedocs.io/en/stable/getting-started/installation/#tool-install)).

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](https://python-rstest.readthedocs.io/en/stable/guides/parallel-safety/#session-scoped-fixtures-duplicate),
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:

<!-- --8<-- [start:speed-table] -->
| 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](https://python-rstest.readthedocs.io/en/stable/concepts/glossary/#performance-cores) 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](https://python-rstest.readthedocs.io/en/stable/concepts/glossary/#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. |

<!-- --8<-- [end:speed-table] -->

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_cache` or expect cold-run timing.
- **Adopting rstest adopts pytest 9**, whatever pytest you have installed
  ([details](https://python-rstest.readthedocs.io/en/stable/getting-started/installation/#your-suite-runs-on-pytest-9)).

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](https://python-rstest.readthedocs.io/en/stable/getting-started/evaluating/#try-it-first)).

## 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).

<!-- SOURCE OF TRUTH: docs/reference/benchmarks.md, keep numbers in sync -->
| 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):

<!-- SOURCE OF TRUTH: docs/reference/benchmarks.md, keep numbers in sync -->
| | 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](https://python-rstest.readthedocs.io/en/stable/reference/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 |

<!-- SOURCE OF TRUTH: docs/index.md "Highlights" (a persona test keeps them identical) -->
- **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`, `--reruns` and `--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](https://python-rstest.readthedocs.io/en/stable/guides/parallel-safety/#session-scoped-fixtures-duplicate),
  as under pytest-xdist, plus a
  [short list of differences](https://python-rstest.readthedocs.io/en/stable/guides/migrate-from-pytest/#what-changes).
- **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](https://python-rstest.readthedocs.io/en/stable/concepts/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](https://python-rstest.readthedocs.io/en/stable/concepts/glossary/#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](https://python-rstest.readthedocs.io/en/stable/reference/cli/#shadowed-flags),
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](https://python-rstest.readthedocs.io/en/stable/concepts/compatibility/)
and [Known gaps](https://python-rstest.readthedocs.io/en/stable/concepts/compatibility/#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):

<!-- SOURCE OF TRUTH: docs/guides/doctor.md, keep sample in sync -->
```text
================== 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 →](https://python-rstest.readthedocs.io/en/stable/guides/doctor/)

## Docs

- [Getting started](https://python-rstest.readthedocs.io/en/stable/getting-started/)
- [Evaluating rstest](https://python-rstest.readthedocs.io/en/stable/getting-started/evaluating/): what it speeds up, when not to adopt, and what switching costs
- [Migrating from pytest](https://python-rstest.readthedocs.io/en/stable/guides/migrate-from-pytest/)
- [Migrating from pytest-xdist](https://python-rstest.readthedocs.io/en/stable/guides/migrate-from-xdist/)
- [Parallel safety](https://python-rstest.readthedocs.io/en/stable/guides/parallel-safety/)
- [Suite diagnostics (`--doctor`)](https://python-rstest.readthedocs.io/en/stable/guides/doctor/)
- [Watch mode](https://python-rstest.readthedocs.io/en/stable/guides/watch-mode/)
- [CI quickstart](https://python-rstest.readthedocs.io/en/stable/guides/ci-quickstart/)
- [CLI reference](https://python-rstest.readthedocs.io/en/stable/reference/cli/)
- [Troubleshooting](https://python-rstest.readthedocs.io/en/stable/reference/troubleshooting/)
- [Agent skills](https://python-rstest.readthedocs.io/en/stable/guides/agent-skills/): `rstest install-skills` or the Claude Code plugin

## License

Licensed under either of

- Apache License, Version 2.0 ([LICENSE-APACHE](https://github.com/KovantAI/rstest/blob/main/LICENSE-APACHE) or <http://www.apache.org/licenses/LICENSE-2.0>)
- MIT license ([LICENSE-MIT](https://github.com/KovantAI/rstest/blob/main/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.

