An extremely fast Python test runner, written in Rust.
Project description
tezt
An extremely fast Python test runner, written in Rust.
"tez" means "fast" in Uzbek, and tezt sounds like "test". That's the whole pitch.
tezt runs Python tests. It is built around two ideas that make it quick: it discovers your tests by parsing them in Rust — no imports, no plugin loading — and it runs them on a pool of Python workers that start once and stay warm, so you pay interpreter startup a handful of times per run instead of once per test.
The difference shows up on anything non-trivial. On a generated suite of 10,000 tests across 500 files, listing the tests takes tezt about 20 ms where pytest takes about 10 s, and the full run takes ~0.2 s against pytest's ~13 s. Numbers and how to reproduce them are in Performance.
It aims to run ordinary pytest-style suites without changes — fixtures, parametrize,
marks, conftest.py, async tests, the xunit setup_*/teardown_* hooks — and it reads
both the attributes pytest's decorators produce and a small built-in import tezt
API, so a project that doesn't have pytest installed can still run its tests under tezt.
Status
Alpha. The core runner is stable and the test suite runs on Linux, macOS, and Windows against CPython 3.9 through 3.14 (every push is checked across that matrix in CI). What isn't done yet — plugins, assertion rewriting, a few fixture scopes — is listed honestly in Compatibility and the Roadmap. If your suite leans on the pytest plugin ecosystem, keep using pytest; see the FAQ.
Quickstart
# test_math.py
import tezt
def test_addition():
assert 1 + 1 == 2
@tezt.parametrize("n,squared", [(2, 4), (3, 9), (4, 16)])
def test_squares(n, squared):
assert n * n == squared
def test_wrong_addition():
total = 2 + 2
assert total == 5
Point tezt at the directory (or just run it with no arguments in your project):
$ tezt
collected 5 tests in 6ms
python: .venv/bin/python3
FAILED test_math.py::test_wrong_addition
assert failed: assert total == 5 | locals: total=4
Traceback (most recent call last):
File "test_math.py", line 15, in test_wrong_addition
assert total == 5
AssertionError
4 passed, 1 failed in 0.07s
tezt -v prints a line per test instead of a progress counter; tezt -q prints only the
summary. The exit code follows pytest's convention — 0 all passed, 1 failures or
errors, 5 nothing collected — so it drops into CI unchanged.
Using an existing suite
There are two ways to write tests, and tezt understands both.
The built-in API, which needs nothing installed:
import tezt
@tezt.fixture
def client():
c = make_client()
yield c
c.close()
@tezt.parametrize("value", [1, 2, 3])
def test_positive(value, client):
assert client.check(value) > 0
tezt provides fixture, parametrize, mark.skip/skipif/xfail, raises, skip,
fail, and xfail, plus the builtin fixtures tmp_path, tmp_path_factory, and
monkeypatch.
Plain pytest-style tests also work — tezt recognizes the markers @pytest.fixture
and @pytest.mark.* leave on your functions. The one caveat is the obvious one: a file
that says import pytest needs pytest installed to import, because tezt does not fake the
pytest module. If you want a zero-dependency suite, use import tezt.
Compatibility
tezt is not a pytest reimplementation; it's a fast runner for the common subset. Here's where the line currently sits.
| Works today | Not yet |
|---|---|
test_*.py / *_test.py, test_* functions, Test* classes |
The pytest plugin ecosystem (pytest-mock, -django, -cov, …) |
Fixtures: function / module / session scope, yield teardown, conftest.py chains |
Assertion rewriting (pytest's per-operator introspection) |
parametrize, including stacked decorators and ids= |
Class-scoped fixtures, async fixtures |
skip / skipif / xfail / xpass |
Mark expressions (-m) |
async def tests (run on a fresh event loop each) |
--lf / --ff, pdb, coverage |
xunit hooks: setup_module, setup_class, setup_method, and teardowns |
Custom plugin hooks / pytest_* |
Builtins: tmp_path, tmp_path_factory, monkeypatch |
|
Reads @pytest.fixture / @pytest.mark.*, or the zero-dep import tezt API |
Failures show the assertion's source line and the locals in scope, which covers most of what assertion rewriting gives you without the rewriting. Rich per-operator diffs are on the roadmap.
Performance
Median of 5 end-to-end runs (wall time around subprocess.run) on an 11-core Apple Silicon
machine, against a generated suite of 10,000 tests in 500 files. The collection cache is
turned off here so this is an honest parse-versus-import comparison; the harness lives in
bench/ and the full table is regenerated into bench/RESULTS.md.
| Phase | tezt | pytest | pytest -n 8 (xdist) |
|---|---|---|---|
Collect (--collect-only) |
0.019 s | 10.11 s | — |
| Full run | 0.19 s | 13.27 s | 19.53 s |
| Cold start (1 test) | 0.061 s | 0.58 s | — |
A couple of things worth saying plainly. The collection gap is large because pytest imports
every module to discover tests while tezt only parses them. The full-run gap is mostly the
per-test overhead: tezt spends about 0.02 ms of its own time per test, so on trivial
tests the wall time is dominated by interpreter and dispatch costs that the warm worker pool
amortizes away. And pytest -n 8 is slower than plain pytest on this suite — xdist's
per-process distribution costs more than the tests themselves when each test is cheap, which
is exactly the workload persistent workers are built for. On tests that actually do work, the
gap narrows to whatever your test bodies cost, as it should.
The collection cache (on by default) takes this further on repeat runs: unchanged files skip parsing entirely and are reconstructed from a per-file entry keyed on size and mtime. That's another ~1.5× on this trivial suite and considerably more on real files where parsing isn't free.
CLI reference
| Flag | Description |
|---|---|
-k EXPRESSION |
Run only tests whose id matches the expression (and / or / not, parentheses) |
-x / --maxfail N |
Stop after the first failure, or after N failures/errors |
-j N / --jobs N |
Number of worker processes (default: CPU count) |
-v / -q |
One line per test / summary only |
-s / --no-capture |
Don't capture test stdout/stderr |
--collect-only |
List the tests that would run, then exit |
--durations N |
Print the N slowest tests |
--timeout SECONDS |
Kill and report any test that runs longer than this (off by default) |
--json PATH |
Write a machine-readable JSON report |
--python EXE |
Interpreter for the workers (also $TEZT_PYTHON); accepts a path or an X.Y version |
--no-cache / --clear-cache |
Skip / delete the collection cache for this run |
--color WHEN |
auto (default), always, or never |
Configuration
tezt is configured by flags and a few environment variables; there is no config file yet.
TEZT_PYTHON— the interpreter to run workers with. When unset, tezt looks for an active virtualenv ($VIRTUAL_ENV), then$CONDA_PREFIX, then a project-local.venv(walking up to the project root), thenpython3/pythononPATH. This is why tests run against the dependencies you actually installed rather than the system interpreter.NO_COLOR— disables color, same as--color never(respects the standard).TEZT_DEBUG=1— logs internal detail to stderr, including any file whose static parse fell back to import-time discovery..tezt_cache/— the per-project collection cache. It's safe to delete and is marked so backup tools skip it; add it to.gitignore(tezt writes one inside the directory too).
Installation
The one-line and
pipinstalls need a published release. Until the first one lands, build from source (last option) — the release pipeline below is wired up and ready.
One line — downloads the right prebuilt binary for your platform and puts it on PATH:
# Linux / macOS
curl -fsSL https://raw.githubusercontent.com/BilagoNet/tezt/main/install.sh | sh
# Windows (PowerShell)
irm https://raw.githubusercontent.com/BilagoNet/tezt/main/install.ps1 | iex
Set TEZT_INSTALL_DIR to pick the location or TEZT_VERSION to pin a tag.
With Python tooling — the binary ships inside a wheel, so no Rust toolchain is needed:
pip install tezt # or: uv tool install tezt
Direct download — grab a tezt-<target>.tar.gz / .zip from the
Releases page and put the binary on your PATH.
From source — any platform with a recent stable Rust toolchain:
git clone https://github.com/BilagoNet/tezt
cd tezt
cargo build --release # binary at ./target/release/tezt
How it works
Collection and execution are split between the two languages, each doing what it's good at.
Collection stays entirely in Rust: a parser walks each test file's syntax tree and builds the list of tests directly, so discovering a suite never starts a Python interpreter. Execution runs on a pool of worker processes that import each module once and then sit waiting for work, talking to the scheduler over a line-delimited JSON protocol on stdin/stdout. Because the workers are persistent, the cost of dispatching one more test is a JSON write and read — well under a millisecond — instead of a process launch.
flowchart LR
A["Rust collector<br/>(parse, no imports)"] --> B[Scheduler]
B -->|JSON lines| W1[worker 1]
B -->|JSON lines| W2[worker 2]
B -->|JSON lines| WN[worker N]
W1 --> R[reporter]
W2 --> R
WN --> R
Interrupting a run is handled carefully, because a half-killed run that leaves Python processes behind is worse than no speedup at all. Each worker runs in its own process group on Unix and inside a kill-on-close Job Object on Windows, and a Ctrl-C handler tears the whole tree down — so an interrupted run never orphans a worker or a subprocess one of your tests spawned.
Roadmap
Roughly in the order it's likely to happen:
- Prebuilt binaries +
pip/uv tooldistribution - Rich assertion diffs (operator-aware), beyond source line + locals
- Mark expressions (
-m) - Class-scoped and async fixtures
-
--lf/--ff(last-failed / failed-first) - A plugin API, and coverage integration
FAQ
Should I switch off pytest? For most projects, no — pytest is mature, extensible, and has an enormous plugin ecosystem that tezt doesn't try to replace. tezt is for the cases where the test loop itself is the bottleneck: large suites, tight edit-run cycles, CI minutes. If you live in those, the collection and startup savings are hard to give up; if you don't, pytest is the safer choice.
Will my existing suite run?
If it sticks to fixtures, parametrize, marks, and conftest.py, there's a good chance it
runs unchanged. If it depends on plugins or on pytest's assertion rewriting, it won't yet —
the compatibility table is the honest boundary.
How is this different from karva, rtest, or maelstrom? They're all worth a look — this is a small wave of people asking what Rust can do for Python testing, the way uv and ruff did for packaging and linting. tezt's particular bet is the combination of static (no-import) collection, a warm worker pool with sub-millisecond dispatch, and running ordinary suites with no configuration.
Contributing
See CONTRIBUTING.md. The short version: cargo test runs the Rust unit
and integration tests, python python/test_worker_protocol.py exercises the worker protocol
directly, and bench/ has the benchmark harness. CI runs all of it across the OS and Python
matrix.
Acknowledgements
tezt borrows pytest's design and ergonomics — its test layout, fixtures, and marks set the bar this tries to meet. The architecture takes after Astral's uv and ruff: a fast Rust core doing the heavy lifting for a Python workflow.
License
MIT.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tezt-0.1.0.tar.gz.
File metadata
- Download URL: tezt-0.1.0.tar.gz
- Upload date:
- Size: 106.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e33ee0634b01055713ae92074747a73b4d857f3b27b133cf992c66bf36f2897d
|
|
| MD5 |
1d491c4eb46fe1556b9bd1a1c6edfa3b
|
|
| BLAKE2b-256 |
8495c51dc308b53d8d0708a1a0e2a59566fafd32cca86c19982685f16e373458
|
Provenance
The following attestation bundles were made for tezt-0.1.0.tar.gz:
Publisher:
release.yml on BilagoNet/tezt
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tezt-0.1.0.tar.gz -
Subject digest:
e33ee0634b01055713ae92074747a73b4d857f3b27b133cf992c66bf36f2897d - Sigstore transparency entry: 1717994850
- Sigstore integration time:
-
Permalink:
BilagoNet/tezt@fe9f1b64ef3a89ba335d90e5d1d3b4212aeed410 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/BilagoNet
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@fe9f1b64ef3a89ba335d90e5d1d3b4212aeed410 -
Trigger Event:
push
-
Statement type:
File details
Details for the file tezt-0.1.0-py3-none-win_amd64.whl.
File metadata
- Download URL: tezt-0.1.0-py3-none-win_amd64.whl
- Upload date:
- Size: 2.3 MB
- Tags: Python 3, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
32e5e8aa4c6d31e5b20f06e83e979318173d88abaca163396897e2b7ced79cd3
|
|
| MD5 |
119baf90235b74720401a191d285360a
|
|
| BLAKE2b-256 |
125a7e6266dcad0fc5570c8be59b1396059ff9ba0fbafe517211690cfdc428f9
|
Provenance
The following attestation bundles were made for tezt-0.1.0-py3-none-win_amd64.whl:
Publisher:
release.yml on BilagoNet/tezt
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tezt-0.1.0-py3-none-win_amd64.whl -
Subject digest:
32e5e8aa4c6d31e5b20f06e83e979318173d88abaca163396897e2b7ced79cd3 - Sigstore transparency entry: 1717995088
- Sigstore integration time:
-
Permalink:
BilagoNet/tezt@fe9f1b64ef3a89ba335d90e5d1d3b4212aeed410 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/BilagoNet
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@fe9f1b64ef3a89ba335d90e5d1d3b4212aeed410 -
Trigger Event:
push
-
Statement type:
File details
Details for the file tezt-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: tezt-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 2.3 MB
- Tags: Python 3, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a0c938645f947d93743a39ff3e79108438018388124ced9c23021da6bba3ca29
|
|
| MD5 |
9c4f8bd7af8a76702534c8a88612834a
|
|
| BLAKE2b-256 |
96d6786bc9feabac8b97c0a28149fe6a1ae296fd037b43e00d5242bf12fd9a37
|
Provenance
The following attestation bundles were made for tezt-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:
Publisher:
release.yml on BilagoNet/tezt
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tezt-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl -
Subject digest:
a0c938645f947d93743a39ff3e79108438018388124ced9c23021da6bba3ca29 - Sigstore transparency entry: 1717995673
- Sigstore integration time:
-
Permalink:
BilagoNet/tezt@fe9f1b64ef3a89ba335d90e5d1d3b4212aeed410 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/BilagoNet
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@fe9f1b64ef3a89ba335d90e5d1d3b4212aeed410 -
Trigger Event:
push
-
Statement type:
File details
Details for the file tezt-0.1.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.
File metadata
- Download URL: tezt-0.1.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- Upload date:
- Size: 2.2 MB
- Tags: Python 3, manylinux: glibc 2.17+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6ba5201f8691601eed1b33b84ae373a9d8b00fd2174fe72c434f48bc88f241c2
|
|
| MD5 |
cd900f5440eaaa947f022d25e1c891de
|
|
| BLAKE2b-256 |
91ebbde9f80cfe94761809beb585880c69d0d2e9915c4e8eb5dc093f1ccb27bb
|
Provenance
The following attestation bundles were made for tezt-0.1.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:
Publisher:
release.yml on BilagoNet/tezt
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tezt-0.1.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl -
Subject digest:
6ba5201f8691601eed1b33b84ae373a9d8b00fd2174fe72c434f48bc88f241c2 - Sigstore transparency entry: 1717995569
- Sigstore integration time:
-
Permalink:
BilagoNet/tezt@fe9f1b64ef3a89ba335d90e5d1d3b4212aeed410 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/BilagoNet
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@fe9f1b64ef3a89ba335d90e5d1d3b4212aeed410 -
Trigger Event:
push
-
Statement type:
File details
Details for the file tezt-0.1.0-py3-none-macosx_11_0_arm64.whl.
File metadata
- Download URL: tezt-0.1.0-py3-none-macosx_11_0_arm64.whl
- Upload date:
- Size: 2.1 MB
- Tags: Python 3, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
08ed79ebdb481f1e94736067f6b6b604065d73b8eb7acb6d7bc31d80dfa849b3
|
|
| MD5 |
68f9df56178dc8fc95fd9a972910d9fc
|
|
| BLAKE2b-256 |
b81283add96ed3ceabf73837698a33120bbf4bfe6b17129d63f181d461e368b1
|
Provenance
The following attestation bundles were made for tezt-0.1.0-py3-none-macosx_11_0_arm64.whl:
Publisher:
release.yml on BilagoNet/tezt
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tezt-0.1.0-py3-none-macosx_11_0_arm64.whl -
Subject digest:
08ed79ebdb481f1e94736067f6b6b604065d73b8eb7acb6d7bc31d80dfa849b3 - Sigstore transparency entry: 1717995912
- Sigstore integration time:
-
Permalink:
BilagoNet/tezt@fe9f1b64ef3a89ba335d90e5d1d3b4212aeed410 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/BilagoNet
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@fe9f1b64ef3a89ba335d90e5d1d3b4212aeed410 -
Trigger Event:
push
-
Statement type:
File details
Details for the file tezt-0.1.0-py3-none-macosx_10_12_x86_64.whl.
File metadata
- Download URL: tezt-0.1.0-py3-none-macosx_10_12_x86_64.whl
- Upload date:
- Size: 2.2 MB
- Tags: Python 3, macOS 10.12+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ae0d26773973daedaa7bbcefd4dc99b401b34af1e44182b9233c846f9802a60e
|
|
| MD5 |
fc829728cff927670670f0f6e3262520
|
|
| BLAKE2b-256 |
8634673fc36fb15ebe032e8813d609d161043e471ffcc047a50180a39648d0d1
|
Provenance
The following attestation bundles were made for tezt-0.1.0-py3-none-macosx_10_12_x86_64.whl:
Publisher:
release.yml on BilagoNet/tezt
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tezt-0.1.0-py3-none-macosx_10_12_x86_64.whl -
Subject digest:
ae0d26773973daedaa7bbcefd4dc99b401b34af1e44182b9233c846f9802a60e - Sigstore transparency entry: 1717995391
- Sigstore integration time:
-
Permalink:
BilagoNet/tezt@fe9f1b64ef3a89ba335d90e5d1d3b4212aeed410 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/BilagoNet
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@fe9f1b64ef3a89ba335d90e5d1d3b4212aeed410 -
Trigger Event:
push
-
Statement type: