Skip to main content

Tells you which functions in your diff were executed by nothing — including by the test you just wrote.

Project description

ranit

Which functions in your diff were executed by nothing — including by the test you just wrote for them.

pip install ranit
coverage run -m pytest

That's it. ranit registers itself as a pytest plugin, so there is no new command to remember. When your suite finishes it either says nothing, or says this:

ranit: changed code that nothing executed
  src/billing/refunds.py:88  function _apply_partial_credit

Three lines you just wrote, a green suite, and not one of them ran.


Why this exists

Linters prove your code parses. Type checkers prove it types. Coverage prints a percentage for a file. None of them tell you whether the function you just changed actually ran.

The gap shows up most sharply in a test that passes while executing nothing — mock out the subject, reimplement the logic in the test body, assert the mock you just created was called by yourself, print a green checkmark. It passes in 0.08s. Every tool above is satisfied.

ranit reads the coverage database your test run already produced, intersects it with your git diff and the AST, and reports per symbol.

What it says

verdict meaning exit
PROVEN every changed symbol executed 0
REFUTED at least one changed symbol executed by nothing 1
INSUFFICIENT not measurable — stale database, unmeasured files, no per-test contexts 2

INSUFFICIENT is the point. It is a distinct third state, never folded into a pass. If the tool cannot see, it says so:

ranit: no coverage active — nothing was measured, so nothing was checked.
Run `coverage run -m pytest` (or `pytest --cov`) to get a verdict.

It prints that only when your diff actually contained something it would have judged. A clean tree, a comment-only change, or a trivial edit stays silent.

Usage

coverage run -m pytest          # plugin reports automatically
pytest --cov=.                  # also works

ranit                           # check the current diff by hand
ranit --tests                   # which TESTS executed zero product lines
ranit --quiet                   # print only real findings
ranit --allow-stale             # proceed on an older coverage database
ranit --install-hook            # install a pre-commit hook

--tests needs per-test attribution, which needs a coverage rcfile:

[run]
dynamic_context = test_function

Note that coverage run --context=NAME sets one static label for the whole run and cannot attribute anything to an individual test.

It never changes your suite's exit status. A reporting plugin that can fail someone else's build is worse than no plugin.

Why not diff-cover, or Codecov patch coverage?

They answer a genuinely different question, and if line-level patch coverage is what you want, use them — they are good at it.

  • diff-cover / Codecov patch coverage report what percentage of your changed lines are covered. The unit is the line, the output is a ratio, and the usual response to "87%" is nothing.
  • ranit reports which named functions in your diff executed nothing. The unit is the symbol, the output is a list of names, and the response to _apply_partial_credit ran zero times is obvious and immediate.

A patch at 87% and a patch containing one entirely dead new function can be the same patch. The percentage is where that fact goes to hide.

Also: Codecov is a service with an account, a token and an upload. ranit is one dependency, runs locally, sends nothing anywhere, and needs no API key.

Why not vulture, or a dead-code finder?

vulture and friends are static — they infer from the AST whether a symbol looks used. ranit reads what actually executed. Static analysis cannot see a function reached only through a plugin registry, and it cannot see one that is referenced everywhere but never called.

Why diff-scoped

A repo-wide list of never-executed symbols runs to thousands of rows. Nobody acts on it, the same way nobody has acted on coverage's "Missing" column for thirty years. Scoped to the diff it becomes a fact with one obvious response, delivered while the code is still cheap to change.

Design

  • One dependency (coverage). No LLM, no vendor, no API key, no network, no telemetry.
  • Quiet by default. A plugin that speaks on every run gets disabled, and then it protects nothing.
  • Never raises into the host. Every failure path is a silent return.
  • Hunk-scoped. Editing one function in a 600-line module does not report every other dead symbol in that file as yours.

Receipts are written to .ranit/ only when you opt in via RANIT_HOME.

Honest limits

  • It needs a coverage run. No coverage, no verdict — and it tells you rather than passing.
  • Symbols under 3 lines are skipped; flagging one-line accessors trains people to ignore the report.
  • --tests needs the rcfile above; without it you get INSUFFICIENT, not a guess.
  • It proves execution, not correctness. A function that ran is not a function that works.

Verification

ranit is checked against third-party repositories it has never seen, with positive and negative controls. Latest run: 20/20 conditions across tenacity, click, markupsafe and attrs, zero configuration — including the case where a symbol is referenced by a test that never runs.

The check itself is also tested, by running deliberately broken builds through it and requiring them to be rejected. Two exist so far and both are rejected, but by disjoint controls — so the honest claim is that the check catches those two, not broken builds in general.

License

MIT © Nucleus Team

Project details


Download files

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

Source Distribution

ranit-0.1.0.tar.gz (27.6 kB view details)

Uploaded Source

Built Distribution

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

ranit-0.1.0-py3-none-any.whl (23.6 kB view details)

Uploaded Python 3

File details

Details for the file ranit-0.1.0.tar.gz.

File metadata

  • Download URL: ranit-0.1.0.tar.gz
  • Upload date:
  • Size: 27.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.4

File hashes

Hashes for ranit-0.1.0.tar.gz
Algorithm Hash digest
SHA256 5ceecd829fdc745abb314d2b0e9593dc12a48cf33d786018e2a1a804520486a3
MD5 f46046a599c313787bca3907ce106c00
BLAKE2b-256 41caddc1925816ebe4754b3b0c02f873c50a6ffb1731b8a6cda9b653e2cd0bb7

See more details on using hashes here.

File details

Details for the file ranit-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: ranit-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 23.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.4

File hashes

Hashes for ranit-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 528d542dfb842af8240ff23f7487c56687ec382c3a2535d218d11007eeecec68
MD5 25cb100c10de41a9decf4317c79bdfaa
BLAKE2b-256 00904038ae5103e2d00dea0bdbd93460b679efd0a104a83fa79a5a1ac5723051

See more details on using hashes here.

Supported by

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