litellm/tests/unit/test_test_quality_gate.py
devin-ai-integration[bot] d6e453f74b
ci(lint): gate every rule on its merge-base count and cap Anys at a fixed total (#40193)
* ci(lint): derive gate ceilings from merge-base counts and drop the budget files

The four lint gates (ruff strict, type discipline, basedpyright, test quality)
now fail a branch only when a rule's codebase count grows past its count at the
merge-base with litellm_internal_staging plus a fixed per-rule headroom, which
is zero everywhere except the LIT010/LIT011 and reportAny/reportExplicitAny
seeds. Base counts come from a disk cache, then the CI artifact the renamed
publish-lint-base-counts workflow uploads for every staging push (all four
checkers, one artifact per checker and sha), then a scan of the base worktree.

The four *-budget.json files, make lint-budget-update, budget_ratchet_check.py,
and the unratcheted check are gone, so no PR carries a budget edit again.

* test(lint-gates): assert checker identities by behavior and type the new gate tests

* test(lint-gates): resolve base points over an injected git so unit tests stay in-process

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-08 05:39:29 +00:00

179 lines
6 KiB
Python

"""Tests for scripts/test_quality_gate.py.
The blame rule itself lives in scripts/lint_base_counts.py and is tested there;
what is tested here is `parse_changed_lines`, the diff scan that turns a breach into
file:line. The base scan spawns a worktree and a checker subprocess, so its cleanup
on termination is driven end to end.
"""
import os
import signal
import subprocess
import sys
import time
from collections.abc import Callable
from contextlib import suppress
from pathlib import Path
from typing import Final, NamedTuple
import test_quality_gate as gate
_REPO_ROOT = Path(__file__).resolve().parents[2]
_MODULE_PATH = _REPO_ROOT / "scripts" / "test_quality_gate.py"
_SCAN_BASE = (
"import pathlib, sys\n"
"import test_quality_gate as gate\n"
"gate.base_counts('HEAD', repo_root=pathlib.Path(sys.argv[1]), checker=pathlib.Path(sys.argv[2]))\n"
)
_SCAN_BASE_WITH_SIGHUP_IGNORED = "import signal\nsignal.signal(signal.SIGHUP, signal.SIG_IGN)\n" + _SCAN_BASE
def test_editing_the_checker_rekeys_the_base_counts(tmp_path: Path) -> None:
checker: Final = tmp_path / "check.py"
checker.write_text("print('v1')\n")
before: Final = gate.checker_identity(checker).artifact_name("abc123")
assert gate.checker_identity(checker).artifact_name("abc123") == before
checker.write_text("print('v2')\n")
assert gate.checker_identity(checker).artifact_name("abc123") != before
def test_parse_changed_lines_groups_hunks_under_their_own_file():
diff = (
"diff --git a/tests/a.py b/tests/a.py\n"
"--- a/tests/a.py\n"
"+++ b/tests/a.py\n"
"@@ -0,0 +3,2 @@\n"
"+one\n"
"+two\n"
"diff --git a/tests/b.py b/tests/b.py\n"
"--- a/tests/b.py\n"
"+++ b/tests/b.py\n"
"@@ -0,0 +10 @@\n"
"+only\n"
)
changed = gate.parse_changed_lines(diff)
assert changed["tests/a.py"] == frozenset({3, 4})
assert changed["tests/b.py"] == frozenset({10})
def test_parse_changed_lines_handles_several_hunks_in_one_file():
diff = (
"+++ b/tests/a.py\n"
"@@ -0,0 +1,2 @@\n"
"+a\n"
"@@ -9,0 +20,1 @@\n"
"+b\n"
)
assert gate.parse_changed_lines(diff)["tests/a.py"] == frozenset({1, 2, 20})
def test_parse_changed_lines_on_an_empty_diff_is_empty():
assert dict(gate.parse_changed_lines("")) == {}
def test_introduced_keeps_only_violations_on_changed_lines():
violations = (
gate.Violation("tests/a.py", 3, "TQ001"),
gate.Violation("tests/a.py", 99, "TQ001"),
gate.Violation("tests/b.py", 3, "TQ003"),
)
kept = gate.introduced(violations, {"tests/a.py": frozenset({3})})
assert kept == (gate.Violation("tests/a.py", 3, "TQ001"),)
def _git(cwd: Path, *args: str) -> str:
proc = subprocess.run(["git", *args], cwd=cwd, capture_output=True, text=True)
assert proc.returncode == 0, proc.stderr
return proc.stdout.strip()
def _committed_repo(tmp_path: Path) -> Path:
repo = tmp_path / "repo"
(repo / "tests").mkdir(parents=True)
(repo / "tests" / "test_seed.py").write_text("def test_seed():\n assert True\n")
_git(repo, "init", "-q", "-b", "main")
_git(repo, "config", "user.email", "gate@example.com")
_git(repo, "config", "user.name", "gate")
_git(repo, "config", "commit.gpgsign", "false")
_git(repo, "add", "-A")
_git(repo, "commit", "-q", "-m", "seed")
return repo
def _wait_until(predicate: Callable[[], bool], timeout_seconds: float) -> bool:
deadline = time.monotonic() + timeout_seconds
while time.monotonic() < deadline:
if predicate():
return True
time.sleep(0.05)
return predicate()
def _reap(process: subprocess.Popen[bytes]) -> None:
with suppress(subprocess.TimeoutExpired):
process.wait(timeout=10)
if process.poll() is None:
process.kill()
process.wait(timeout=10)
def _registered_worktrees(repo: Path) -> int:
listing = _git(repo, "worktree", "list", "--porcelain")
return sum(line.startswith("worktree ") for line in listing.splitlines())
class _StalledScan(NamedTuple):
process: subprocess.Popen[bytes]
repo: Path
release: Path
temp_dir: Path
def _base_scan_stalled_in_its_checker(tmp_path: Path, driver: str) -> _StalledScan:
repo = _committed_repo(tmp_path)
scanning = tmp_path / "scanning"
release = tmp_path / "release"
slow_checker = tmp_path / "slow_checker.py"
slow_checker.write_text(
"import pathlib, time\n"
f"pathlib.Path({str(scanning)!r}).touch()\n"
f"while not pathlib.Path({str(release)!r}).exists():\n"
" time.sleep(0.05)\n"
)
temp_dir = tmp_path / "tmp"
temp_dir.mkdir()
scan = subprocess.Popen(
[sys.executable, "-c", driver, str(repo), str(slow_checker)],
cwd=_MODULE_PATH.parent,
env={**os.environ, "TMPDIR": str(temp_dir)},
)
if not _wait_until(scanning.exists, 30):
_reap(scan)
raise AssertionError("the base scan never reached the checker")
return _StalledScan(scan, repo, release, temp_dir)
def test_a_terminated_base_scan_still_removes_its_worktree(tmp_path: Path) -> None:
stalled = _base_scan_stalled_in_its_checker(tmp_path, _SCAN_BASE)
try:
stalled.process.send_signal(signal.SIGTERM)
assert stalled.process.wait(timeout=30) == 128 + signal.SIGTERM
finally:
_reap(stalled.process)
assert _registered_worktrees(stalled.repo) == 1
assert list(stalled.temp_dir.iterdir()) == []
def test_a_base_scan_keeps_ignoring_the_hangup_its_parent_ignored(tmp_path: Path) -> None:
stalled = _base_scan_stalled_in_its_checker(tmp_path, _SCAN_BASE_WITH_SIGHUP_IGNORED)
try:
stalled.process.send_signal(signal.SIGHUP)
time.sleep(1)
assert stalled.process.poll() is None, "a hangup the parent ignored killed the scan"
stalled.release.touch()
assert stalled.process.wait(timeout=30) == 0
finally:
_reap(stalled.process)
assert _registered_worktrees(stalled.repo) == 1
assert list(stalled.temp_dir.iterdir()) == []