fix(detect): gate coverage/ pruning on report artefacts (#2339)

"coverage" was an unconditional _SKIP_DIRS entry and _is_noise_dir matches
directory names at any depth, so a repo where coverage is a legitimate
package name lost the whole package from the graph — no warning, no
skipped_sensitive entry, nothing in the report. The failure is quiet in
the worst way: the package's dependents survive, so queries keep
returning plausible neighbours while the package itself has no nodes.

The entry's comment cites Vitest/Istanbul/nyc HTML reports (#870), but
the pruning is language-agnostic, so it also removes Python/Go/Rust
packages that happen to be called coverage.

detect.py has been fixed twice for this exact shape — #1666 gated a bare
snapshots/ on real .snap evidence, #2058 gated env/.env/*_env on real
virtualenv markers — so this applies the established pattern rather than
a new mechanism. _has_coverage_artifacts() mirrors _has_venv_markers():
same OSError guard, same "cannot verify, keep a possibly-real code dir"
contract when no parent is available. Evidence is a file a coverage tool
actually writes (lcov.info, coverage-final.json, clover.xml,
coverage.xml, cobertura-coverage.xml, jacoco.xml, .coverage, index.html)
or an lcov-report/ / html-report/ subtree, covering lcov, nyc/Istanbul,
coverage.py, JaCoCo and Cobertura.

lcov-report stays unconditional: it has no false-positive class, so
gating it would add filesystem probes for no benefit — the same split
#1666 made between the unambiguous __snapshots__ and the bare snapshots.
This commit is contained in:
Manoj21k
2026-08-01 11:36:30 +01:00
committed by safishamsi
parent 1a7be303f5
commit 1cdbf48c57
2 changed files with 121 additions and 1 deletions
+42 -1
View File
@@ -795,7 +795,9 @@ _SKIP_DIRS = {
".tox", ".nox", ".eggs", "*.egg-info", # nox is tox's successor, same .nox/ venv shape (#1804)
"graphify-out", GRAPHIFY_OUT_NAME, # never treat own output as source input (#524); honour GRAPHIFY_OUT (#1423)
# Coverage/test-artefact dirs — generated, never architecturally meaningful
"coverage", "lcov-report", # Vitest/Istanbul/nyc HTML reports (#870)
"lcov-report", # Vitest/Istanbul/nyc HTML reports (#870);
# bare "coverage" is gated on report
# artefacts below (#2339)
"visual-tests", "visual-test", # Playwright/visual-regression bundles (#869)
"__snapshots__", # Jest/Vitest snapshot dir (unambiguous)
"storybook-static", # Storybook production build output
@@ -824,6 +826,39 @@ _SKIP_FILES = {
# unconditionally pruned above; only the ambiguous bare name is gated here.
_JS_SNAPSHOT_TEST_ROOTS = frozenset({"__tests__", "__test__"})
# Files a coverage tool writes into its own output dir. Any one of them is proof
# the directory is generated: lcov (lcov.info), nyc/Istanbul (coverage-final.json,
# clover.xml, the lcov-report/ subtree), coverage.py (coverage.xml, .coverage),
# JaCoCo/Cobertura (jacoco.xml, cobertura-coverage.xml).
_COVERAGE_ARTIFACT_FILES = frozenset({
"lcov.info", "coverage-final.json", "coverage-summary.json",
"clover.xml", "coverage.xml", "cobertura-coverage.xml", "jacoco.xml",
".coverage", "index.html",
})
_COVERAGE_ARTIFACT_DIRS = frozenset({"lcov-report", "html-report"})
def _has_coverage_artifacts(d: "Path") -> bool:
"""True only when *d* holds files a coverage tool actually generated.
``coverage`` is a legitimate package name (a Python package, a Go/Rust module,
a domain namespace), so pruning it by name alone silently drops real source —
an entire 5-module package in #2339, with its dependents left in the graph so
queries still returned plausible neighbours. Prune it only on real evidence,
mirroring the ``snapshots``/``env`` gating (#1666/#2058): a coverage report
file, or an Istanbul/lcov HTML report subtree.
"""
try:
for name in _COVERAGE_ARTIFACT_FILES:
if (d / name).is_file():
return True
for name in _COVERAGE_ARTIFACT_DIRS:
if (d / name).is_dir():
return True
except OSError:
pass
return False
def _has_venv_markers(d: "Path") -> bool:
"""True only when *d* has actual virtualenv/conda structure on disk.
@@ -858,6 +893,12 @@ def _is_noise_dir(part: str, parent: "Path | None" = None) -> bool:
if parent is None:
return False # cannot verify; keep a possibly-real code dir
return _has_venv_markers(parent / part)
if part == "coverage":
# Ambiguous: a generated report dir OR a real package named coverage.
# Prune only on actual coverage-artefact evidence (#2339).
if parent is None:
return False # cannot verify; keep a possibly-real code dir
return _has_coverage_artifacts(parent / part)
if part == "snapshots":
# Prune only when it looks like an actual JS/Vitest snapshot dir.
if parent is None:
+79
View File
@@ -686,6 +686,85 @@ def test_detect_skips_coverage_dir(tmp_path):
assert any("main.py" in f for f in all_files)
def test_detect_skips_coverage_dir_by_lcov_info(tmp_path):
"""A coverage/ dir is still pruned on any single artefact file — an lcov.info
with no lcov-report/ subtree is enough evidence (#870, #2339)."""
cov = tmp_path / "coverage"
cov.mkdir()
(cov / "lcov.info").write_text("TN:\nSF:src/app.ts\nend_of_record\n")
(cov / "prettify.js").write_text("var PR_SHOULD_USE_CONTINUATION=true;")
(tmp_path / "main.py").write_text("def hello(): pass")
result = detect(tmp_path)
all_files = [f for files in result["files"].values() for f in files]
assert not any(f.startswith(str(cov)) for f in all_files)
assert any("main.py" in f for f in all_files)
def test_detect_keeps_coverage_code_namespace(tmp_path):
"""#2339: a coverage/ dir holding real modules and no coverage artefacts is a
legitimate package name, not a generated report, and must NOT be pruned.
Pruning it by name dropped an entire production package while leaving its
dependents in the graph, so queries kept returning plausible neighbours and
nothing in the report or skipped lists showed the loss."""
pkg = tmp_path / "auditor_toolkit" / "assurance" / "coverage"
pkg.mkdir(parents=True)
(pkg / "__init__.py").write_text("from .impact import Impact\n")
(pkg / "impact.py").write_text("class Impact:\n def score(self): return 1\n")
(pkg / "inventory.py").write_text("def inventory():\n return []\n")
(tmp_path / "app.py").write_text("from auditor_toolkit.assurance import coverage\n")
result = detect(tmp_path)
all_files = [f for files in result["files"].values() for f in files]
assert any("impact.py" in f for f in all_files)
assert any("inventory.py" in f for f in all_files)
assert any(f.endswith("coverage" + os.sep + "__init__.py") for f in all_files)
def test_collect_files_keeps_coverage_code_namespace(tmp_path):
"""#2339 as reported: collect_files returned [] for a real coverage package,
both when it is the walk target and when it is reached through the repo root.
A genuine report dir alongside it must still be skipped."""
from graphify.extract import collect_files
pkg = tmp_path / "auditor_toolkit" / "assurance" / "coverage"
pkg.mkdir(parents=True)
for name in ("__init__.py", "impact.py", "mapping.py"):
(pkg / name).write_text("def f(): pass\n")
report = tmp_path / "webapp" / "coverage"
report.mkdir(parents=True)
(report / "index.html").write_text("<html>coverage</html>")
(report / "base.css").write_text("body{}")
(report / "prettify.js").write_text("var PR=1;")
assert sorted(p.name for p in collect_files(pkg)) == [
"__init__.py", "impact.py", "mapping.py",
]
walked = {str(p.relative_to(tmp_path)) for p in collect_files(tmp_path)}
assert any(p.endswith("impact.py") for p in walked)
assert not any("webapp" in p for p in walked), (
"a generated Istanbul report dir must still be pruned (#870)"
)
def test_is_noise_dir_coverage_is_evidence_gated(tmp_path):
"""The gate itself: name alone is not enough, and an unverifiable call
(no parent) keeps a possibly-real code dir same contract as env/snapshots."""
src = tmp_path / "coverage"
src.mkdir()
(src / "__init__.py").write_text("")
assert detect_mod._is_noise_dir("coverage", tmp_path) is False
generated = tmp_path / "report" / "coverage"
generated.mkdir(parents=True)
(generated / "coverage-final.json").write_text("{}")
assert detect_mod._is_noise_dir("coverage", tmp_path / "report") is True
assert detect_mod._is_noise_dir("coverage") is False
# lcov-report stays unconditional — no package is ever named that.
assert detect_mod._is_noise_dir("lcov-report") is True
def test_detect_skips_visual_tests_dir(tmp_path):
"""visual-tests/ bundles and snapshots are noise — must be excluded (#869)."""
vt = tmp_path / "visual-tests"