litellm/tests/test_litellm/test_ruff_strict_gate.py
Mateo Wang a7b0b0ba09
feat: add lint-gate target and truncation-proof summary to the strict ruff gate (#30877)
* feat: add CI-parity mode and truncation-proof summary to strict ruff gate

* refactor: tolerant worktree cleanup and concrete GateInputs types

* fix: clean up temp dir when git worktree add fails

* fix: align lint-gate with CI by dropping unused --ci-parity path

The lint-gate Makefile target invoked ruff_strict_gate.py with --ci-parity,
which counted violations on a throwaway merge of base into HEAD against base
counts at the base tip. CI in test-linting.yml runs the same script without
--ci-parity on a PR-head checkout, taking the gather_fast path that counts on
the live tree against base counts at the merge-base. A local pass could
therefore disagree with CI.

Drop --ci-parity from the Makefile and remove the now-unused gather_ci_parity
branch and flag so there is one code path that both local and CI exercise.
The docstring claim that CI runs against the synthetic merge ref was also
wrong; the workflow checks out github.event.pull_request.head.sha.

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-06-20 11:46:01 -07:00

94 lines
3.3 KiB
Python

import importlib.util
from pathlib import Path
import pytest
_MODULE_PATH = Path(__file__).resolve().parents[2] / "scripts" / "ruff_strict_gate.py"
_spec = importlib.util.spec_from_file_location("ruff_strict_gate", _MODULE_PATH)
gate = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(gate)
Violation = gate.Violation
def rule(name, baseline, slack):
return {name: {"baseline": baseline, "slack": slack}}
def test_under_ceiling_passes():
assert gate.evaluate({"ANN001": 100}, {"ANN001": 100}, rule("ANN001", 90, 20)) == []
def test_ceiling_is_baseline_plus_slack_boundary():
budget = rule("ANN001", 90, 20) # cap 110
at = gate.evaluate({"ANN001": 110}, {"ANN001": 90}, budget)
over = gate.evaluate({"ANN001": 111}, {"ANN001": 90}, budget)
assert at == []
assert [b.rule for b in over] == ["ANN001"]
assert over[0].cap == 110
assert over[0].added == 21
def test_over_ceiling_and_change_added_fails():
breaches = gate.evaluate({"C901": 11}, {"C901": 9}, rule("C901", 10, 0))
assert [b.rule for b in breaches] == ["C901"]
assert breaches[0].added == 2
def test_base_already_over_ceiling_change_added_nothing_is_not_blamed():
# drift safety: base is over cap, this change leaves the count where it is
assert gate.evaluate({"C901": 15}, {"C901": 15}, rule("C901", 10, 0)) == []
def test_change_that_reduces_an_over_ceiling_rule_is_not_blamed():
# still over cap, but moving the right direction
assert gate.evaluate({"C901": 14}, {"C901": 16}, rule("C901", 10, 0)) == []
def test_rules_are_independent():
budget = {**rule("ANN001", 100, 50), **rule("C901", 10, 0)}
breaches = gate.evaluate(
{"ANN001": 130, "C901": 11}, {"ANN001": 100, "C901": 10}, budget
)
assert [b.rule for b in breaches] == ["C901"] # ANN001 130 <= 150, C901 11 > 10
def test_missing_rule_counts_as_zero():
assert gate.evaluate({}, {}, rule("C901", 0, 0)) == []
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 test_report_emits_breached_rules_as_final_line(capsys):
# CI surfaces only the tail of the log, so the breached-rule summary (rule,
# total/cap, added) must be the last line or it gets truncated away.
breaches = sorted([gate.Breach("UP045", 530, 529, 1), gate.Breach("ANN401", 12, 10, 2)])
new = [gate.Violation("litellm/types/llms/bedrock.py", 16, "UP045")]
gate.report(breaches, new, "origin/litellm_internal_staging")
last = capsys.readouterr().out.strip().splitlines()[-1]
assert last == "BREACHED RULES: ANN401 12/10 (+2); UP045 530/529 (+1)"