mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-09 03:18:44 +00:00
* chore(lint): raise basedpyright per-rule slack to 50% of baseline The per-rule ceilings in basedpyright-code-budget.json sat at roughly 10% slack over baseline, which several in-flight PRs are already bumping into. Raise the slack on every rule to at least 50% of its baseline so there is ample headroom for a long while, while never lowering any rule that already had more generous slack (e.g. reportReturnType stays at 100). Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com> * refactor(lint): collapse type/lint budgets to a single per-rule limit The three non-frontend budget files (ruff-strict, type-discipline, basedpyright-code) tracked a per-rule baseline and slack whose sum was the ceiling. Nothing consumed the split beyond that sum, so this replaces both keys with a single limit equal to the old baseline + slack; the original baselines live in git history if anyone needs them. The gate scripts and the ratchet guard now read limit directly. lint-budget-update no longer re-captures raw counts; it ratchets each rule's limit down by the number of violations this branch cleared since its branch point (the merge-base), so the granted headroom shrinks by exactly what was fixed and a limit never rises. The ratchet guard reads either schema so it still compares correctly across the migration boundary. Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com> * chore(lint): surface staged-vs-working parity for pre-commit and budget-update make pre-commit selects which checks to run from the staged index but runs the linters over the working tree, so unstaged edits to tracked files and untracked files skew a green/red away from what a commit of only the staged changes would produce. There is no safe in-place way to lint the index, so the script now warns when unstaged or untracked changes are present, and CLAUDE.md documents that you must stage everything first for both make pre-commit and make lint-budget-update to predict CI correctly. Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com> * docs(lint): list type-discipline budget in lint-budget-update instruction --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
96 lines
3.2 KiB
Python
96 lines
3.2 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, limit):
|
|
return {name: {"limit": limit}}
|
|
|
|
|
|
def test_under_ceiling_passes():
|
|
assert gate.evaluate({"ANN001": 100}, {"ANN001": 100}, rule("ANN001", 110)) == []
|
|
|
|
|
|
def test_ceiling_is_the_limit_boundary():
|
|
budget = rule("ANN001", 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))
|
|
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 limit, this change leaves the count where it is
|
|
assert gate.evaluate({"C901": 15}, {"C901": 15}, rule("C901", 10)) == []
|
|
|
|
|
|
def test_change_that_reduces_an_over_ceiling_rule_is_not_blamed():
|
|
# still over limit, but moving the right direction
|
|
assert gate.evaluate({"C901": 14}, {"C901": 16}, rule("C901", 10)) == []
|
|
|
|
|
|
def test_rules_are_independent():
|
|
budget = {**rule("ANN001", 150), **rule("C901", 10)}
|
|
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)) == []
|
|
|
|
|
|
def test_update_ratchets_limit_down_by_what_the_branch_fixed_never_up():
|
|
budget = {**rule("ANN001", 150), **rule("C901", 10)}
|
|
# ANN001 fixed 20 (100 -> 80) so its limit falls 150 -> 130; C901 grew, so its
|
|
# limit holds flat at 10 (a fix must never loosen a ceiling).
|
|
current = {"ANN001": 80, "C901": 12}
|
|
base = {"ANN001": 100, "C901": 9}
|
|
assert gate.ratcheted_budget(budget, current, base) == {
|
|
"ANN001": {"limit": 130},
|
|
"C901": {"limit": 10},
|
|
}
|
|
|
|
|
|
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"]
|