Skip to main content

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 / sarif output formats, a list-rules / explain pair, 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 develop needs an activated Python virtualenv — either source .venv/bin/activate first, or export VIRTUAL_ENV=/path/to/venv. Having the venv's bin/ on PATH is 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.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

zorilla-0.1.4.tar.gz (128.9 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

zorilla-0.1.4-py3-none-win_amd64.whl (1.2 MB view details)

Uploaded Python 3Windows x86-64

zorilla-0.1.4-py3-none-manylinux_2_28_x86_64.whl (1.4 MB view details)

Uploaded Python 3manylinux: glibc 2.28+ x86-64

zorilla-0.1.4-py3-none-macosx_11_0_arm64.whl (1.2 MB view details)

Uploaded Python 3macOS 11.0+ ARM64

File details

Details for the file zorilla-0.1.4.tar.gz.

File metadata

  • Download URL: zorilla-0.1.4.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

Hashes for zorilla-0.1.4.tar.gz
Algorithm Hash digest
SHA256 ae207d1d23aedfaf57485acb51bedca8d15689a1c181f07df00ffb1e2e1734ea
MD5 4c85066975f85132cb27121a9797c5e2
BLAKE2b-256 f84d68697f2f0859c537e820ff2b15e61a83f506da65d3073b363acb625af727

See more details on using hashes here.

Provenance

The following attestation bundles were made for zorilla-0.1.4.tar.gz:

Publisher: release.yml on mojzis/zorilla

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file zorilla-0.1.4-py3-none-win_amd64.whl.

File metadata

  • Download URL: zorilla-0.1.4-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

Hashes for zorilla-0.1.4-py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 b5330dfda32b501cf4663e1dc9ad1757dd9bb7730ab1024eee45145b78687061
MD5 f33ad085b946dfe0392386b725e7f769
BLAKE2b-256 1c0ff17be72f0d0a148a46c4cc3e7da3680228a3ee1ff30b6da0be0d7bf19438

See more details on using hashes here.

Provenance

The following attestation bundles were made for zorilla-0.1.4-py3-none-win_amd64.whl:

Publisher: release.yml on mojzis/zorilla

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file zorilla-0.1.4-py3-none-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for zorilla-0.1.4-py3-none-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 66c77fa85f3cda4e52fbfd02c2331ca18e72b39db2f9c970d71dc41450c82997
MD5 3ddc281e69359aab7e270e09b14886ec
BLAKE2b-256 7f5585c347643f480dd6ce9a3dd82b8bb2ad382e87da6331a05eb522a491bab6

See more details on using hashes here.

Provenance

The following attestation bundles were made for zorilla-0.1.4-py3-none-manylinux_2_28_x86_64.whl:

Publisher: release.yml on mojzis/zorilla

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file zorilla-0.1.4-py3-none-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for zorilla-0.1.4-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 5714e12edb80ba64247d44beca35dc3d167db141e9f95fdc757788d99477ff6a
MD5 75d857b6f6716f96e772d6b6ef8d84cc
BLAKE2b-256 82780c201b6bdd292be92c9b05c48ba288f5d64a745e4a94f2571ac42c54a27e

See more details on using hashes here.

Provenance

The following attestation bundles were made for zorilla-0.1.4-py3-none-macosx_11_0_arm64.whl:

Publisher: release.yml on mojzis/zorilla

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.1.4 This release

4 files

0.1.3

4 files

0.1.2

4 files

0.1.0

3 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page