Test coverage

See which tests reach your code, without a coverage run.

Repowise reads the call and import graph to show which tests reach each file and which tests a change needs. Ingest a coverage report and the measured numbers appear beside the inferred ones, each labelled.

Inferred answers name the tests. Percentages come from the reports you ingest.

At a glance

97.5%
precision of the inferred tests to run
47 targets on the repowise repository, checked against per-test coverage.
95.7%
precision of inferred test-to-file links
44 of 46 links confirmed by per-test coverage on the same repository.
8
coverage report formats
LCOV, Cobertura, Clover, JaCoCo, Go coverprofile, coverage.py, Apex and a normalized JSON.
40%
untested-hotspot line
A hotspot with at least four dependents and line coverage under it.

Sound familiar?

Coverage gives you a number. You needed a list.

  • 01

    The coverage job takes forty minutes, so nobody runs it locally.

  • 02

    Coverage is 78 percent and nobody knows which 22 percent matters.

  • 03

    A one-line change runs the whole suite in CI.

  • 04

    The agent says it ran the tests. Which ones?

What makes it different

What the index tells you about your tests.

  1. Inferred reach

    Every file answered at index time.

    Reached, unreached, mocked only or type only, recomputed on every index update.

  2. Measured coverage

    Eight report formats, one command.

    LCOV, Cobertura, Clover, JaCoCo, Go coverprofile, coverage.py, Salesforce Apex and a normalized JSON format.

  3. Untested hotspots

    Risky files no test reaches.

    Top-quartile churn, at least four dependents and under 40 percent line coverage, or, with no report ingested, no test reaching the file.

  4. Impacted tests

    The tests a change needs.

    From the per-test map when you have one and from the graph when you do not, each row labelled with how it was found.

  5. Patch coverage gate

    Judge a pull request on its new lines.

    repowise coverage check needs only git and a report, and returns exit codes CI understands.

  6. Test quality

    Tests that check nothing.

    Advisory markers flag assertion-free and mock-saturated tests in Python, JavaScript and TypeScript; oversized assertion blocks count against health.

In depth

Test answers from the graph, and from your reports.

Tests to run

A diff in, a run list out.

On httpx, a one-line change to httpx/_urlparse.py maps to one test through the per-test coverage map, test_url_invalid_port, and to 32 candidate test files through the call and import graph. The two lists are printed apart, and every candidate says how it was found, so a guess never passes for evidence. With no per-test map, the graph list becomes the run list.

via coverage
A test whose recorded lines touch the change.
call-graph
A test whose calls reach the changed file.
import-graph
A test that imports it, directly or through a chain.
Runner arguments
--format args prints what to pass to pytest, or :all.
repowise impacted-tests HEAD~1
$ repowise impacted-tests HEAD~1Tests covering the changed lines  tests/models/test_url.py::test_url_invalid_port|run    covers httpx/_urlparse.py1 test(s) directly cover the change (via recorded per-test coverage). Inferred tests (NOT coverage-backed)  tests/models/test_whatwg.py          call-graph  tests/client/test_async_client.py    import-graph  tests/client/test_auth.py            import-graph  ... and 29 more32 candidate(s) for 1 file(s) with no coverage.
Real output, repowise 0.59 on httpx, list trimmed

Patch coverage in CI

Gate the new lines, not the whole project.

repowise coverage check reads the diff and one coverage report and answers a single question: of the changed lines the report marks executable, how many ran. It needs no index, so it drops into any CI job. Files missing from the report are listed, never counted as zero, and with an index each uncovered range names the test file to extend.

Exit codes
0 pass, 1 below a gate, 2 could not run.
Path-scoped gates
A stricter bar for the API than for scripts.
max_drop
How far project coverage may fall from the base.
suggest-gates
Proposes gate YAML from your current coverage, writes nothing.
patch coverage in CI
$ repowise coverage check origin/main...HEAD --report coverage/lcov.info --fail-under 80# exit 0: passed or nothing to judge# exit 1: below a gate# exit 2: could not run (no report, unknown revision) # .repowise/config.yamlcoverage:  fail_under: 80  max_drop: 0.5  gates:    - {name: api, paths: ["/services/api/"], fail_under: 85}
Command and config keys from the repowise reference

With a coverage report

Health against coverage, file by file.

Once a report is ingested, every instrumented file is placed by its health score and its line coverage. The low-health, low-coverage corner is where a bug is most likely to land with no test to catch it, and each file opens to a line-level heatmap.

Repowise health against coverage chart: every instrumented file plotted by defect-health score and line coverage, sized by lines of code, with the low-health, low-coverage quadrant marked as critical hotspots.
Health against measured coverage, 1,640 files

In plain words

Two kinds of answer, kept apart.

A coverage report says which lines ran. The graph says which tests can reach a file. Repowise keeps both. Measured answers come from a report you ingest and carry percentages. Inferred answers come from the graph, are file-level, and say whether tests reach the file and how. That makes the graph the right source for "is anything testing this" and a coverage report the right source for percentages.

How to read it

What each file can say

Reached
Tests call into the file or import it. The answer names the tests and how they reach it.
Unreached
No walk from any test reaches the file. On a hotspot, write the test before the refactor.
Mocked only
Every test that reaches the file replaces it with a double, so its own code may never run.
Type only
The file declares only types. There is nothing for a test to run, so it is never counted as a gap.

How it works

How an answer is produced.

The inferred map is a walk over a graph the index has already built. A coverage report, when you add one, is a parse and a database write.
  1. 01 / Index

    Resolve imports and calls

    The same dependency graph every other answer uses.
  2. 02 / Walk

    Start from every test file

    Record which production files each test reaches, and whether by call, import or wiring such as a fixture.
  3. 03 / Ingest

    Add a report when you have one

    File, line and branch coverage, plus a per-test map from LCOV, coverage.py or Apex.
  4. 04 / Answer

    Label every row

    Measured or inferred, side by side and never averaged.

Questions

What people ask before they try it.

Can I see test coverage without running my test suite?

Yes, file by file. During indexing repowise resolves imports and calls across the repository, then walks that graph from every test file. Each production file comes out reached, unreached, mocked only or type only (with two more states for files the index has not seen and repositories with no tests), and a reached file names the tests and whether they call into it or import it. These answers are labelled inferred and are never turned into a percentage, because a call edge shows a test can reach a file, not which lines ran. For line and branch percentages, ingest a coverage report.

How accurate is the inferred test map?

Measured on the repowise repository against per-test coverage from coverage run --contexts=test: 95.7% of inferred "which tests reach this file" links were confirmed (44 of 46), and the inferred list of tests to run for a change was 97.5% precise across 47 targets. The method and raw counts are on the benchmarks page.

Which coverage formats can I ingest?

LCOV, Cobertura, Clover and JaCoCo XML, Go coverprofile, Salesforce Apex JSON, coverage.py's .coverage file and a normalized JSON format, repowise-coverage-v1, for any other runner. LCOV with test names, .coverage recorded with per-test contexts and the Apex run result also carry a per-test map: which test ran which lines. Run repowise coverage add with no path and it finds reports in the usual locations.

What is an untested hotspot?

A file that is a hotspot (top-quartile churn in its repository plus minimum activity), has at least four dependents and has line coverage under 40 percent. Without an ingested report, it fires only when nothing says a test touches the file: no test file paired by name and no test reaching it in the graph. It is the place to add a characterization test before you refactor.

How do I run only the tests a pull request needs?

repowise impacted-tests main...HEAD diffs the branch from its merge base and returns the tests whose recorded coverage touches the changed lines, then lists graph-inferred candidates in a separate table marked not coverage-backed. With --format args --runner pytest it prints runner arguments, or :all when any part of the answer is unknown, so CI falls back to the full suite instead of skipping tests.

Can I gate pull requests on coverage?

Yes. repowise coverage check gates a change on patch coverage: of the changed lines the report marks executable, what share did the tests run. It needs only git and a report, no index. It exits 0 when it passes, 1 when a gate fails and 2 when it cannot run. Gates can be repository-wide or scoped to paths, and max_drop limits how far project coverage may fall from the base branch.

Does an empty test list mean my change is untested?

No. An empty list means no test is needed only when the tool says so, as for a docs change. In every other case the answer carries run_all: true, the CLI tells you to run the full suite, and a file with no coverage data is reported as having no data, never as uncovered.

Last reviewed: October 2026

Get started

See which files no test reaches.

Index a public repository free and open its Tests page. Add a coverage report when you have one and the measured numbers appear beside the inferred ones.