pytest test-smell linter (Rust, via maturin)
Project description
zorilla
A small, fast, opinionated Rust CLI that detects syntactic test smells in
pytest codebases. Built to compose with
biston (structural test duplication)
and with ruff's PT rules — zorilla owns only the gaps those two leave
behind.
Status: v0.1 ready. Ships seven rules (ZR001–ZR007), inline and file-level suppression comments,
text/json/sarifoutput formats, alist-rules/explainpair, and a pre-commit hook.
Installation
zorilla is distributed as a Python wheel built with maturin.
# once published
pip install zorilla
# or, for a local checkout (requires an activated venv)
maturin develop
This installs a zorilla binary on your PATH.
maturin developneeds an activated Python virtualenv — eithersource .venv/bin/activatefirst, or exportVIRTUAL_ENV=/path/to/venv. Having the venv'sbin/onPATHis not sufficient.
Usage
zorilla check path/to/tests
Exit codes: 0 clean, 1 findings reported, 2 error.
--files PATH (repeatable) and --files-from FILE (use - for stdin) accept explicit file lists, bypassing the configured include globs — useful for CI pipelines.
Rules
| Code | Name | Summary |
|---|---|---|
| ZR001 | conditional-test-logic | if / for / while / try in a test body |
| ZR002 | sleep-in-test | time.sleep / asyncio.sleep inside a test |
| ZR003 | no-assertion | Test function with no assertion or pytest.raises |
| ZR004 | assertion-roulette | Too many bare (message-less) asserts in one test |
| ZR005 | mystery-guest | Absolute path, URL, or ~-path literal inside a test |
| ZR006 | patch-stack | Too many stacked @patch / @mock.patch decorators |
| ZR007 | empty-test | Test body is empty (pass, ..., docstring-only) |
Long-form docs (motivation, positive/negative examples, config knobs,
suppression syntax) live under docs/rules/. You can
also print them inline with zorilla explain ZR### — see below.
Worked example
Save as tests/test_demo.py:
import time
def test_branch_and_wait():
if ready():
time.sleep(1)
assert done()
Run:
$ zorilla check .
tests/test_demo.py:4:5: ZR001 conditional-test-logic: test function has conditional logic (if/for/while/try)
tests/test_demo.py:5:9: ZR002 sleep-in-test: test calls sleep — wait on a condition instead
2 findings in 1 files discovered.
zorilla exits with status 1 because findings were reported. A clean
run (or an empty directory) exits 0; an internal error exits 2.
Output formats
zorilla check --format json .
emits a JSON array, one object per finding — suitable for piping into
jq or a CI aggregator:
[
{
"code": "ZR001",
"message": "test function has conditional logic (if/for/while/try)",
"file": "tests/test_demo.py",
"line": 4,
"column": 5,
"severity": "warning"
}
]
zorilla check --format sarif .
emits a SARIF 2.1.0 document that most code-scanning tools (GitHub code scanning, SonarQube, etc.) ingest directly:
{
"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
"version": "2.1.0",
"runs": [
{
"tool": { "driver": { "name": "zorilla", "version": "0.1.0" } },
"results": [
{
"ruleId": "ZR001",
"level": "warning",
"message": { "text": "test function has conditional logic (if/for/while/try)" },
"locations": [ /* ...physicalLocation... */ ]
}
]
}
]
}
JSON and SARIF output both omit the trailing human-readable summary line so stdout parses cleanly.
Scan statistics
zorilla stats post-processes a scan into aggregate counters and a
per-rule breakdown. Use it for CI dashboards, quick health checks, or a
high-level snapshot of how many tests are flagged and by which rules.
Unlike check, it always exits 0 — it is a report, not a gate.
$ zorilla stats path/to/tests
Scan statistics:
Files scanned: 12
Files with findings: 3
Clean files: 9
Total findings: 7
Breakdown by rule:
ZR001 conditional-test-logic: 2
ZR002 sleep-in-test: 1
ZR003 no-assertion: 4
ZR004 assertion-roulette: 0
ZR005 mystery-guest: 0
ZR006 patch-stack: 0
ZR007 empty-test: 0
Add --format json to emit a parseable summary (flat counters plus a
breakdown object keyed by rule code). --files and --files-from
work the same way they do for check.
Per-file overview
zorilla overview groups findings by file. Files are sorted by finding
count (most-flagged first), and files that produced no findings are
rolled up into a trailing count — useful when triaging a large repo.
Like stats, it always exits 0 and accepts --files / --files-from.
$ zorilla overview path/to/tests
Overview: 12 files, 7 findings in 3 files
tests/test_orders.py 3 findings
● 12:5 ZR003 no-assertion test has no assertion
● 25:5 ZR001 conditional-test-logic test function has conditional logic (if/for/while/try)
● 25:9 ZR002 sleep-in-test test calls sleep — wait on a condition instead
tests/test_returns.py 2 findings
● 7:5 ZR003 no-assertion test has no assertion
● 18:5 ZR003 no-assertion test has no assertion
9 clean files not shown.
The bullet (●) is coloured by severity — yellow for warnings, red for
errors — when stdout is a terminal. overview honours the NO_COLOR
convention, and emits plain bullets when output
is piped or redirected. Add --format json to get the same data as a
structured document (summary, files, clean_files) suitable for
dashboards or scripting.
List and explain
$ zorilla list-rules
CODE NAME DEFAULT
ZR001 conditional-test-logic on
ZR002 sleep-in-test on
ZR003 no-assertion on
ZR004 assertion-roulette on
ZR005 mystery-guest on
ZR006 patch-stack on
ZR007 empty-test on
$ zorilla explain ZR003
# ZR003 — no-assertion
…
explain accepts the rule code in either case (ZR003 or zr003) and
prints the bundled markdown. Unknown codes exit 2.
Pre-commit integration
Add zorilla to your project's .pre-commit-config.yaml:
repos:
- repo: https://github.com/mojzis/zorilla
rev: v0.1.0
hooks:
- id: zorilla
The hook entry point is zorilla check; pre-commit appends only the
staged Python files as positional arguments, so zorilla lints each one
directly (bypassing the include globs the way any explicit file path
does). v0.1.0 is the target release tag — replace it with whichever
tag is current when you wire the hook up.
Configuration
zorilla searches upward from the working directory for either
zorilla.toml or a pyproject.toml containing [tool.zorilla]. The
first match wins.
# pyproject.toml
[tool.zorilla]
include = ["tests/**/*.py", "**/test_*.py", "**/*_test.py", "**/conftest.py"]
exclude = ["**/fixtures/**"]
[tool.zorilla.rules.ZR004]
max_asserts = 4
[tool.zorilla.rules.ZR006]
max_patches = 3
Per-rule sections ([tool.zorilla.rules.ZRNNN]) accept enabled = false
to disable the rule and any rule-specific knobs (max_asserts for
ZR004, max_patches for ZR006, extra_helpers for ZR003,
allowed_prefixes for ZR005). See docs/rules/ for
the exhaustive list.
Suppression comments work per-line and per-file:
# zorilla: ignore-file <- silences the whole file
def test_x():
if cond: # zorilla: ignore[ZR001] <- silences just this line
...
Developing
# Pre-commit gate
cargo fmt --all --check
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test --workspace
# Maturin develop build
maturin develop
Workspace layout:
crates/
zorilla-core/ # library
zorilla-cli/ # binary (`zorilla`)
See CLAUDE.md for the development workflow and docs/plans/PLAN.md for
the design doc driving the rule set.
License
MIT. See LICENSE.
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 zorilla-0.1.3.tar.gz.
File metadata
- Download URL: zorilla-0.1.3.tar.gz
- Upload date:
- Size: 128.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 |
621f2765511f970bc38dc6dec82abb318ff9f468bd0fc9a9643f4923e5751e45
|
|
| MD5 |
70f70884d09da7e4257dea740c581177
|
|
| BLAKE2b-256 |
29e754b83582bc6c92dc9756ee2abcb2f300ef9d9c9d7c6e3d5d8ba226043e00
|
Provenance
The following attestation bundles were made for zorilla-0.1.3.tar.gz:
Publisher:
release.yml on mojzis/zorilla
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
zorilla-0.1.3.tar.gz -
Subject digest:
621f2765511f970bc38dc6dec82abb318ff9f468bd0fc9a9643f4923e5751e45 - Sigstore transparency entry: 2083034510
- Sigstore integration time:
-
Permalink:
mojzis/zorilla@92d64e978826163b911d8d5899563dda0770db63 -
Branch / Tag:
refs/tags/v0.1.3 - Owner: https://github.com/mojzis
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@92d64e978826163b911d8d5899563dda0770db63 -
Trigger Event:
push
-
Statement type:
File details
Details for the file zorilla-0.1.3-py3-none-win_amd64.whl.
File metadata
- Download URL: zorilla-0.1.3-py3-none-win_amd64.whl
- Upload date:
- Size: 1.2 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 |
547022e3bb27909d36b97b37b64c58c45058135e5f019737d2b16799118213b5
|
|
| MD5 |
ee22bc1919c3bf5ab035e4fdb351b329
|
|
| BLAKE2b-256 |
d2c2e91d8531f4662d897e3e6d425e1a30d4406636ef1485af3ece4e3d1ac2a7
|
Provenance
The following attestation bundles were made for zorilla-0.1.3-py3-none-win_amd64.whl:
Publisher:
release.yml on mojzis/zorilla
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
zorilla-0.1.3-py3-none-win_amd64.whl -
Subject digest:
547022e3bb27909d36b97b37b64c58c45058135e5f019737d2b16799118213b5 - Sigstore transparency entry: 2083034529
- Sigstore integration time:
-
Permalink:
mojzis/zorilla@92d64e978826163b911d8d5899563dda0770db63 -
Branch / Tag:
refs/tags/v0.1.3 - Owner: https://github.com/mojzis
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@92d64e978826163b911d8d5899563dda0770db63 -
Trigger Event:
push
-
Statement type:
File details
Details for the file zorilla-0.1.3-py3-none-manylinux_2_28_x86_64.whl.
File metadata
- Download URL: zorilla-0.1.3-py3-none-manylinux_2_28_x86_64.whl
- Upload date:
- Size: 1.4 MB
- Tags: Python 3, manylinux: glibc 2.28+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5ae8ea4b4b921879e1a180453990e7dfb09a949cd908e3848c837f154d9ec5b8
|
|
| MD5 |
d62c7f3f3175afd84bdfd3b71cf3a025
|
|
| BLAKE2b-256 |
8c2df6a086100791d76ff61b93c3b33991a690c8ef189bb03017fc3eaf527563
|
Provenance
The following attestation bundles were made for zorilla-0.1.3-py3-none-manylinux_2_28_x86_64.whl:
Publisher:
release.yml on mojzis/zorilla
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
zorilla-0.1.3-py3-none-manylinux_2_28_x86_64.whl -
Subject digest:
5ae8ea4b4b921879e1a180453990e7dfb09a949cd908e3848c837f154d9ec5b8 - Sigstore transparency entry: 2083034568
- Sigstore integration time:
-
Permalink:
mojzis/zorilla@92d64e978826163b911d8d5899563dda0770db63 -
Branch / Tag:
refs/tags/v0.1.3 - Owner: https://github.com/mojzis
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@92d64e978826163b911d8d5899563dda0770db63 -
Trigger Event:
push
-
Statement type:
File details
Details for the file zorilla-0.1.3-py3-none-macosx_11_0_arm64.whl.
File metadata
- Download URL: zorilla-0.1.3-py3-none-macosx_11_0_arm64.whl
- Upload date:
- Size: 1.2 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 |
5da89e76886954f16e85cf7b073605dc0f2e972f736cae94f4c965c9e220b609
|
|
| MD5 |
56253a07d2e2466f1db111b31df09cca
|
|
| BLAKE2b-256 |
5c37cadca69ebb3ade658b1650e85a4157e13b362b91e08772984c95c8c5271e
|
Provenance
The following attestation bundles were made for zorilla-0.1.3-py3-none-macosx_11_0_arm64.whl:
Publisher:
release.yml on mojzis/zorilla
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
zorilla-0.1.3-py3-none-macosx_11_0_arm64.whl -
Subject digest:
5da89e76886954f16e85cf7b073605dc0f2e972f736cae94f4c965c9e220b609 - Sigstore transparency entry: 2083034550
- Sigstore integration time:
-
Permalink:
mojzis/zorilla@92d64e978826163b911d8d5899563dda0770db63 -
Branch / Tag:
refs/tags/v0.1.3 - Owner: https://github.com/mojzis
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@92d64e978826163b911d8d5899563dda0770db63 -
Trigger Event:
push
-
Statement type: