utils/repo_contract/stacks.py
Krzysztof kuhy Rudnicki 6c71d531bb repo contract + coverage_gaps gates, test_changed, dependency bumps
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-04 16:27:28 +02:00

119 lines
3.3 KiB
Python

"""Stack detection and a bounded "does this repo have tests" probe."""
from __future__ import annotations
import os
from pathlib import Path
PRUNE = frozenset(
{
".git",
"node_modules",
"build",
".dart_tool",
".venv",
"venv",
"target",
"__pycache__",
".gradle",
".idea",
"dist",
".mypy_cache",
".ruff_cache",
".pytest_cache",
"Pods",
".godot",
"vendor",
"third_party",
}
)
MAX_DEPTH = 4
MAX_FILES = 5000
# stack -> marker files at the repo root (checked in order of this table).
MARKERS: tuple[tuple[str, tuple[str, ...]], ...] = (
("flutter", ("pubspec.yaml",)),
("godot", ("project.godot",)),
(
"gradle",
("build.gradle", "build.gradle.kts", "settings.gradle", "settings.gradle.kts"),
),
("python", ("pyproject.toml", "setup.py", "setup.cfg", "requirements.txt")),
("node", ("package.json",)),
("go", ("go.mod",)),
("rust", ("Cargo.toml",)),
)
def _walk(repo: Path) -> list[Path]:
"""Files under `repo` (pruned, depth- and count-bounded; never follows links)."""
found: list[Path] = []
base = len(repo.parts)
for root, dirs, files in os.walk(repo, followlinks=False):
dirs[:] = [d for d in dirs if d not in PRUNE]
if len(Path(root).parts) - base >= MAX_DEPTH:
dirs[:] = []
found.extend(Path(root, f) for f in files)
if len(found) >= MAX_FILES:
break
return found
def detect_stacks(repo: Path) -> list[str]:
"""Stacks present in `repo`, most specific first; `shell` if only scripts."""
stacks = [s for s, names in MARKERS if any((repo / n).is_file() for n in names)]
if not stacks and any(p.suffix == ".sh" for p in _walk(repo)):
stacks.append("shell")
return stacks
def _has_pattern(
files: list[Path], suffixes: tuple[str, ...], names: tuple[str, ...]
) -> bool:
"""True when some file ends with a test suffix or sits under a test dir."""
for path in files:
if path.name.endswith(suffixes) or path.name.startswith(names):
return True
return False
def has_tests(repo: Path, stacks: list[str]) -> bool:
"""Whether the repo ships any automated tests (best-effort, bounded)."""
files = _walk(repo)
rels = [p.relative_to(repo) for p in files]
in_test_dir = any(
part in {"tests", "test", "spec", "__tests__"}
for r in rels
for part in r.parts[:-1]
)
named = _has_pattern(
files,
(
"_test.py",
"_test.dart",
".test.ts",
".test.js",
".test.tsx",
".spec.ts",
".spec.js",
"_test.go",
"Test.kt",
"Test.java",
".bats",
"_test.gd",
),
("test_",),
)
return in_test_dir or named or _node_test_script(repo, stacks)
def _node_test_script(repo: Path, stacks: list[str]) -> bool:
"""A real `scripts.test` in package.json (not npm's placeholder)."""
if "node" not in stacks:
return False
try:
text = (repo / "package.json").read_text(encoding="utf-8")
except (OSError, UnicodeDecodeError):
return False
return '"test"' in text and "no test specified" not in text