diff --git a/scripts/pre_commit_lint.sh b/scripts/pre_commit_lint.sh index afe55603466..82498ec10cd 100755 --- a/scripts/pre_commit_lint.sh +++ b/scripts/pre_commit_lint.sh @@ -55,11 +55,13 @@ else merge_base=$(git merge-base origin/litellm_internal_staging HEAD 2>/dev/null) || { echo "check: cannot resolve the merge base with origin/litellm_internal_staging." >&2 echo " Fix: git fetch origin litellm_internal_staging" >&2 + echo "check: FAIL" exit 1 } scope=$(printf '%s\n' "$(git diff --name-only --diff-filter=ACMRD "$merge_base")" "$untracked" | sed '/^$/d' | sort -u) if [ -z "$scope" ]; then echo "check: nothing to check (no staged files, no working-tree changes, no branch changes vs origin/litellm_internal_staging)" + echo "check: PASS" exit 0 fi echo "check: nothing staged; scoping to the working tree's diff against the merge base with origin/litellm_internal_staging:" @@ -281,4 +283,30 @@ if [ -n "${gen_pid:-}" ]; then cat "$gen_log"; rm -f "$gen_log" fi +summary_item() { + local check_name=$1 triggered=$2 skip_reason=$3 + if [ -n "$triggered" ]; then + echo " ran: $check_name" + else + echo " skipped: $check_name ($skip_reason)" + fi +} + +echo "check: summary" +summary_item "Python lint (make lint)" "$litellm_py_files" "no litellm/ Python files in scope" +summary_item "tests/e2e checks (basedpyright + raw HTTP client ban)" "$e2e_py_files" "no tests/e2e Python files in scope" +summary_item "dashboard lint (prettier + eslint + lint budgets)" "$ui_prettier_changed$ui_eslint_changed" "no dashboard files in scope" +summary_item "dashboard API-type sync (npm run gen:api)" "$spec_files" "no litellm/proxy, litellm/types, or generator files in scope" + +if [ -z "$litellm_py_files$e2e_py_files$ui_prettier_changed$ui_eslint_changed$spec_files" ]; then + echo "check: NOTE - no gating lint check matches the files in scope, so nothing ran:" >&2 + printf '%s\n' "$scope" | sed 's/^/ /' >&2 + echo " A pass here is a no-op, not a lint verdict." >&2 +fi + +if [ "$status" -eq 0 ]; then + echo "check: PASS" +else + echo "check: FAIL" +fi exit $status diff --git a/tests/test_litellm/test_pre_commit_lint.py b/tests/test_litellm/test_pre_commit_lint.py index 35d98903226..33baf7474ce 100644 --- a/tests/test_litellm/test_pre_commit_lint.py +++ b/tests/test_litellm/test_pre_commit_lint.py @@ -224,6 +224,7 @@ def test_nothing_staged_and_no_changes_is_an_explicit_no_op(tmp_path: Path) -> N proc = _run(repo, bin_dir, {}) assert proc.returncode == 0, proc.stdout + proc.stderr assert "nothing to check" in proc.stdout + assert "check: PASS" in proc.stdout assert "linting Python" not in proc.stdout @@ -234,6 +235,7 @@ def test_nothing_staged_without_a_base_ref_fails_with_a_fetch_hint(tmp_path: Pat assert proc.returncode == 1 assert "cannot resolve the merge base" in proc.stdout assert "git fetch origin litellm_internal_staging" in proc.stdout + assert "check: FAIL" in proc.stdout def test_partial_staging_warns_which_checks_were_skipped(tmp_path: Path) -> None: @@ -384,3 +386,43 @@ def test_a_failing_block_fails_the_whole_run(tmp_path: Path, fail: str, message: proc = _run(repo, bin_dir, {"STUB_FAIL": fail}) assert proc.returncode == 1 assert message in proc.stdout + proc.stderr + + +def test_run_ends_with_a_summary_of_ran_and_skipped_blocks(tmp_path: Path) -> None: + repo, bin_dir = _sandbox(tmp_path) + proc = _run(repo, bin_dir, {}) + assert proc.returncode == 0, proc.stdout + proc.stderr + assert "check: summary" in proc.stdout + assert "ran: Python lint (make lint)" in proc.stdout + assert "ran: dashboard lint (prettier + eslint + lint budgets)" in proc.stdout + assert "ran: dashboard API-type sync (npm run gen:api)" in proc.stdout + assert "skipped: tests/e2e checks (basedpyright + raw HTTP client ban) (no tests/e2e Python files in scope)" in proc.stdout + assert "check: PASS" in proc.stdout + assert "check: FAIL" not in proc.stdout + + +def test_staged_files_matching_no_check_print_an_explicit_noop_note_and_nonempty_log(tmp_path: Path) -> None: + repo, bin_dir = _sandbox(tmp_path) + _commit_all(repo, "base") + tests_dir = repo / "tests" / "test_litellm" + tests_dir.mkdir(parents=True) + (tests_dir / "test_x.py").write_text("def test_x() -> None: ...\n") + subprocess.run(["git", "add", "tests"], cwd=repo, check=True) + proc = _run(repo, bin_dir, {}) + assert proc.returncode == 0, proc.stdout + proc.stderr + assert "no gating lint check matches the files in scope, so nothing ran" in proc.stdout + assert "tests/test_litellm/test_x.py" in proc.stdout + assert "a no-op, not a lint verdict" in proc.stdout + assert "check: PASS" in proc.stdout + assert "linting Python" not in proc.stdout + log = (repo / ".git" / "pre_commit_lint.log").read_text() + assert "check: summary" in log + assert "skipped: Python lint (make lint) (no litellm/ Python files in scope)" in log + + +def test_failing_run_ends_with_a_fail_verdict(tmp_path: Path) -> None: + repo, bin_dir = _sandbox(tmp_path) + proc = _run(repo, bin_dir, {"STUB_FAIL": "make-lint"}) + assert proc.returncode == 1 + assert "check: FAIL" in proc.stdout + assert "check: PASS" not in proc.stdout