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: 32/32 conditions — eight controls across tenacity, click, markupsafe and attrs, zero configuration. The controls include a symbol referenced only by a test that never runs, a symbol dispatched dynamically so its name appears nowhere at the call site, and a symbol called from a branch the test never takes.

The check itself is tested, by running deliberately broken builds through it and requiring them to be rejected. Seven exist and all seven are rejected — five of them written specifically to defeat the controls that existed at the time, and three of those succeeded before a new control was added. The most recent passes seven of the eight controls and is caught only by the branch-coverage one.

That is the honest claim: the check catches those seven, not broken builds in general. Every fake is kept and re-run rather than retired, because a gate that no longer faces the thing it once failed has no evidence it still holds.

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.1.tar.gz (27.9 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.1-py3-none-any.whl (23.8 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: ranit-0.1.1.tar.gz
  • Upload date:
  • Size: 27.9 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.1.tar.gz
Algorithm Hash digest
SHA256 5d4d50f856190cf3f440b840c78585befc32232284d926f3be61fe45957bc971
MD5 29a9042140c93c5b5afbda06ba2bb10b
BLAKE2b-256 5e84daf5688a66fa1bb5c9cdc69d5fb91207bd9f8f42e1f0e204672c8c76a50c

See more details on using hashes here.

File details

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

File metadata

  • Download URL: ranit-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 23.8 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.1-py3-none-any.whl
Algorithm Hash digest
SHA256 74e2a3caf42278f89de66d82bc54c3bf063571d508b4a1c9a8f467f4aa17a688
MD5 a49145ee4880ec6ac14a833cc0e3642a
BLAKE2b-256 6b602ecc39991f6806db4a0964c0429b0f42a8fecd7e68a6bf4f2c1bce12fad6

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