mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-12 03:48:56 +00:00
* 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>
242 lines
7.7 KiB
Python
242 lines
7.7 KiB
Python
import json
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Final
|
|
|
|
import pytest
|
|
|
|
import ruff_strict_gate as gate
|
|
|
|
if sys.version_info >= (3, 11):
|
|
import tomllib
|
|
else:
|
|
import tomli as tomllib
|
|
|
|
_REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
|
|
Violation = gate.Violation
|
|
|
|
_ENABLED_BY_RUFF_DEFAULTS = frozenset({"F401"})
|
|
|
|
|
|
def test_parse_changed_lines_maps_added_lines_per_file():
|
|
diff = (
|
|
"+++ b/litellm/a.py\n"
|
|
"@@ -10 +10,3 @@\n+x\n+y\n+z\n"
|
|
"+++ b/litellm/b.py\n"
|
|
"@@ -5,2 +7 @@\n+q\n"
|
|
)
|
|
changed = gate.parse_changed_lines(diff)
|
|
assert changed["litellm/a.py"] == {10, 11, 12}
|
|
assert changed["litellm/b.py"] == {7}
|
|
|
|
|
|
def test_introduced_keeps_only_violations_on_changed_lines():
|
|
violations = [
|
|
Violation("litellm/a.py", 10, "ANN001"),
|
|
Violation("litellm/a.py", 99, "C901"),
|
|
]
|
|
assert gate.introduced(violations, {"litellm/a.py": {10}}) == [
|
|
Violation("litellm/a.py", 10, "ANN001")
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize("hunk", ["@@ -1 +1 @@", "@@ -1,0 +1,2 @@"])
|
|
def test_parse_changed_lines_handles_single_and_ranged_hunks(hunk):
|
|
assert gate.parse_changed_lines(f"+++ b/litellm/a.py\n{hunk}\n")["litellm/a.py"]
|
|
|
|
|
|
def _lint_section(config_name: str) -> dict:
|
|
return tomllib.loads((_REPO_ROOT / config_name).read_text())["lint"]
|
|
|
|
|
|
def _base_external() -> tuple[str, ...]:
|
|
return tuple(_lint_section("ruff.toml")["external"])
|
|
|
|
|
|
def _strict_external() -> tuple[str, ...]:
|
|
return tuple(_lint_section("ruff-strict.toml")["external"])
|
|
|
|
|
|
def _strict_selected() -> frozenset:
|
|
return frozenset(_lint_section("ruff-strict.toml")["select"])
|
|
|
|
|
|
def _prefix_covered(code: str, prefixes: tuple[str, ...]) -> bool:
|
|
return any(code.startswith(prefix) for prefix in prefixes)
|
|
|
|
|
|
def _selected_by_the_normal_config() -> frozenset:
|
|
return frozenset(_lint_section("ruff.toml")["extend-select"]) | _ENABLED_BY_RUFF_DEFAULTS
|
|
|
|
|
|
def _ruff_binary() -> str | None:
|
|
beside_interpreter = Path(sys.executable).with_name("ruff")
|
|
return str(beside_interpreter) if beside_interpreter.exists() else shutil.which("ruff")
|
|
|
|
|
|
_RUFF = _ruff_binary()
|
|
_needs_ruff = pytest.mark.skipif(_RUFF is None, reason="ruff is not installed in this environment")
|
|
|
|
|
|
def _ruff_output_for_noqa(code: str, *extra_args: str) -> str:
|
|
proc = subprocess.run(
|
|
[
|
|
_RUFF,
|
|
"check",
|
|
"--no-cache",
|
|
"--stdin-filename",
|
|
"litellm/types/_external_probe.py",
|
|
*extra_args,
|
|
"-",
|
|
],
|
|
cwd=_REPO_ROOT,
|
|
input=f"def _probe(x: int): # noqa: {code}\n return x\n",
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
return proc.stdout
|
|
|
|
|
|
def test_every_strict_gate_rule_is_protected_from_base_ruf100():
|
|
unprotected = frozenset(
|
|
selector
|
|
for selector in _strict_selected()
|
|
if not _prefix_covered(selector, _base_external())
|
|
and selector not in _selected_by_the_normal_config()
|
|
)
|
|
assert unprotected == frozenset(), (
|
|
f"`ruff check` deletes any `# noqa` naming {sorted(unprotected)} as unused, so suppressing "
|
|
"one of those strict-gate rules breaks lint. Cover them in ruff.toml's lint.external or "
|
|
"enable them in its lint.extend-select."
|
|
)
|
|
|
|
|
|
def test_every_selected_rule_keeps_stale_noqa_detection_somewhere():
|
|
policed_by_strict = frozenset(
|
|
selector
|
|
for selector in _strict_selected()
|
|
if not _prefix_covered(selector, _strict_external())
|
|
)
|
|
policed_by_base = frozenset(
|
|
selector
|
|
for selector in _selected_by_the_normal_config()
|
|
if not _prefix_covered(selector, _base_external())
|
|
)
|
|
shadowed = (
|
|
_strict_selected() | _selected_by_the_normal_config()
|
|
) - policed_by_strict - policed_by_base
|
|
assert shadowed == frozenset(), (
|
|
f"no config's RUF100 can ever report a stale `# noqa` for {sorted(shadowed)}: every config "
|
|
"that selects each of them also shadows it with an external entry. Narrow the external "
|
|
"entry in ruff.toml or ruff-strict.toml."
|
|
)
|
|
|
|
|
|
_BASE_OWNED_FAMILY = re.compile(r"E[479]\d+|F\d+|T20\d+")
|
|
_BASE_OWNED_SINGLES = frozenset({"PGH004", "RUF008", "RUF009", "RUF100"})
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def all_ruff_rule_codes() -> frozenset:
|
|
listing = subprocess.run(
|
|
[_RUFF, "rule", "--all", "--output-format", "json"],
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
assert listing.returncode == 0, listing.stderr
|
|
return frozenset(
|
|
entry["code"] for entry in json.loads(listing.stdout) if "Removed" not in entry["status"]
|
|
)
|
|
|
|
|
|
@_needs_ruff
|
|
def test_every_base_owned_rule_is_external_or_selected_in_the_strict_config(all_ruff_rule_codes):
|
|
base_owned = frozenset(
|
|
code
|
|
for code in all_ruff_rule_codes
|
|
if _BASE_OWNED_FAMILY.fullmatch(code) or code in _BASE_OWNED_SINGLES
|
|
)
|
|
stranded = frozenset(
|
|
code
|
|
for code in base_owned
|
|
if code not in _strict_selected() and not _prefix_covered(code, _strict_external())
|
|
)
|
|
assert stranded == frozenset(), (
|
|
f"the strict gate's RUF100 reads a valid `# noqa` for {sorted(stranded)} as unused, the "
|
|
"spurious-breach trap ruff-strict.toml's external override exists to prevent. Cover them "
|
|
"there."
|
|
)
|
|
double_booked = frozenset(
|
|
code
|
|
for code in base_owned
|
|
if code in _strict_selected() and _prefix_covered(code, _strict_external())
|
|
)
|
|
assert double_booked == frozenset(), (
|
|
f"{sorted(double_booked)} are selected by the strict config yet shadowed by its external "
|
|
"list, so their stale suppressions can never be reported. Narrow the external entry in "
|
|
"ruff-strict.toml."
|
|
)
|
|
|
|
|
|
@_needs_ruff
|
|
def test_a_noqa_for_a_strict_gate_rule_survives_the_normal_ruff_run():
|
|
assert "RUF100" not in _ruff_output_for_noqa("ANN202")
|
|
|
|
|
|
@_needs_ruff
|
|
def test_the_external_list_is_what_saves_that_noqa():
|
|
assert "RUF100" in _ruff_output_for_noqa("ANN202", "--config", "lint.external=[]")
|
|
|
|
|
|
@_needs_ruff
|
|
def test_a_stale_noqa_for_a_locally_enabled_rule_is_still_reported():
|
|
assert "RUF100" in _ruff_output_for_noqa("F401")
|
|
|
|
|
|
def _ruff_output_for_source(source: str) -> str:
|
|
proc = subprocess.run(
|
|
[_RUFF, "check", "--no-cache", "--stdin-filename", "litellm/types/_graduate_probe.py", "-"],
|
|
cwd=_REPO_ROOT,
|
|
input=source,
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
return proc.stdout
|
|
|
|
|
|
_DEPRECATED_TYPING_ALIAS = "from typing import List # noqa: UP035\n\n\ndef _probe(x: List[int]) -> None: ...\n"
|
|
|
|
|
|
@_needs_ruff
|
|
def test_a_graduated_rule_now_fails_the_normal_ruff_run_instead_of_waiting_for_the_gate():
|
|
assert "UP006" in _ruff_output_for_source(_DEPRECATED_TYPING_ALIAS)
|
|
|
|
|
|
@_needs_ruff
|
|
def test_a_graduated_rule_can_still_be_suppressed_without_tripping_unused_noqa():
|
|
suppressed = _DEPRECATED_TYPING_ALIAS.replace("...\n", "... # noqa: UP006\n")
|
|
output = _ruff_output_for_source(suppressed)
|
|
assert "UP006" not in output
|
|
assert "RUF100" not in output
|
|
|
|
|
|
def test_editing_either_ruff_config_or_upgrading_ruff_rekeys_the_base_counts(tmp_path: Path) -> None:
|
|
strict: Final = tmp_path / "ruff-strict.toml"
|
|
base: Final = tmp_path / "ruff.toml"
|
|
strict.write_text("strict v1\n")
|
|
base.write_text("base v1\n")
|
|
|
|
def artifact(version: str) -> str:
|
|
return gate.checker_identity(strict, base, lambda: version).artifact_name("abc123")
|
|
|
|
before: Final = artifact("ruff 0.1.0")
|
|
assert artifact("ruff 0.1.0") == before
|
|
assert artifact("ruff 0.2.0") != before
|
|
strict.write_text("strict v2\n")
|
|
after_strict_edit: Final = artifact("ruff 0.1.0")
|
|
base.write_text("base v2\n")
|
|
assert len({before, after_strict_edit, artifact("ruff 0.1.0")}) == 3
|