pytest-run-witness
Fail closed when pytest stops before every collected test reaches a terminal outcome.
pytest-run-witness wraps one pytest invocation, records the collected item set before execution, and writes a durable receipt as tests finish. A separate process can later verify that every collected item reached teardown.
A normal completed run looks like this:
$ pytest-run-witness -- tests -q -n auto
RUN_ID=...
RECEIPT=.../.pytest-run-witness/receipt-....jsonl
...
VERIFIED 842/842
If execution stops early, the wrapper refuses to certify the run:
INCOMPLETE 817/842; 25 collected tests have no terminal result (MISSING_TERMINAL_RESULTS)
Install
python -m pip install pytest-run-witness
Python 3.11–3.14 and pytest 7.1.3–9.x are supported by the 0.1.x release line. pytest-xdist is optional and is used only when your own pytest command uses it.
For development and the bundled xdist tests:
python -m pip install -e ".[test]"
Use it
Put your ordinary pytest arguments after --:
pytest-run-witness -- tests -q
pytest-run-witness -- tests -q -n auto
pytest-run-witness -- -k smoke -m "not slow"
The wrapper launches python -m pytest with the same interpreter, working directory, environment, arguments, and configured plugins.
For a receipt that can be checked by a later CI step or job, choose a stable path and run ID:
pytest-run-witness \
--receipt .pytest-run-witness/receipt.jsonl \
--run-id 123456-1 \
-- tests -q -n auto
pytest-run-witness verify \
.pytest-run-witness/receipt.jsonl \
--run-id 123456-1
On GitHub Actions, a convenient run identity is ${{ github.run_id }}-${{ github.run_attempt }}. See examples/github-actions.yml for a two-job producer/verifier example.
Exit behavior
pytest-run-witness does not turn ordinary test failures into success.
| Situation | Wrapper exit |
|---|---|
| Pytest completed and every collected item reached a terminal outcome | pytest's own exit code |
| Completion cannot be proven | 10 |
| Wrapper usage error | 2 |
The independent verifier exits:
| Situation | Verifier exit |
|---|---|
| Receipt proves complete execution for the expected run ID | 0 |
| Receipt is missing, corrupt, stale, wrong-run, or incomplete | 11 |
This distinction matters: a failing test suite can still be VERIFIED n/n because all selected tests finished. The wrapper then preserves pytest's nonzero exit code.
What counts as terminal
An item is terminal after pytest reports its teardown phase. That includes:
- passed and failed calls;
- skips;
- xfail/xpass;
- setup failures and setup skips;
- teardown failures.
Selection flags still define the scope of the proof. A run using -k, -m, --ignore, a node ID, or another pytest selector can be fully verified for the subset pytest actually collected.
Options such as -x and --maxfail intentionally leave later collected tests unstarted. Those runs therefore produce an incomplete receipt.
Why a separate verifier?
The journal starts before pytest runs and records collection before test execution. At normal session end it file-syncs the final session record and then writes a post-sync confirmation marker; verification fails closed if that marker is missing. The wrapper checks the receipt when the child process exits, but a later process can verify the same receipt independently:
pytest-run-witness verify RECEIPT --run-id ID
That lets CI separate the test-producing job from the proof-checking job. The verifier still has to be scheduled by the outer CI system; this project cannot prove that the CI platform itself executed every intended job.
Trust boundary
The guarantee is deliberately narrow:
- The denominator is the item set pytest collected for one invocation.
- It does not prove that pytest selected every test your repository intended.
- It does not prove that every CI matrix job or shard ran.
- The test process and pytest plugins share the same user context as the receipt and can modify it. This is not an anti-malware boundary.
- The receipt stores keyed item digests rather than node IDs or paths.
- A torn, malformed, stale, wrong-run, or incomplete journal fails closed.
- The wrapper does not manage external timeouts or guarantee process-tree termination.
See Architecture, CLI contract, Limitations, and the failure-mode matrix.
Development
python -m pip install -e ".[test]"
python -m pytest
The hosted proof workflow covers Windows and Ubuntu, Python 3.11–3.14, a pytest 7.1.3 floor, current pytest 9.x, normal execution, abrupt process exit, missing/corrupt/stale receipts, and xdist worker loss.
License
MIT.
Release files for pytest-run-witness 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pytest_run_witness-0.1.0.tar.gz | 45.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pytest_run_witness-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 59.4 kB
Release files / pytest_run_witness-0.1.0.tar.gz
| Download URL | pytest_run_witness-0.1.0.tar.gz |
|---|---|
| Size | 45.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5f8ec82b441f8c3484181c0754f8bb9dc4a5445a1f625e7835b5c8cd1e27c6c9
|
|
BLAKE2b-256 checksum How to use checksums |
5a778848251fa175b03b8baae046106cf04d7ffcc067e287365a28396671137e
|
| 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 28, 2026.
Transparency logRelease files / pytest_run_witness-0.1.0-py3-none-any.whl
| Download URL | pytest_run_witness-0.1.0-py3-none-any.whl |
|---|---|
| Size | 14.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
86a04df966f6e6bb1152ffaa2c9828026bd7cc829d1a6397f18c35d415d5de41
|
|
BLAKE2b-256 checksum How to use checksums |
ae626cd5c2b28d563f9a09a9266d5a88bca6e941f54ca6c1f900351c746bd468
|
| 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 28, 2026.
Transparency log