diff --git a/.circleci/config.yml b/.circleci/config.yml index 23ef423039..f5a8fe77a2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -182,7 +182,14 @@ jobs: - run: name: Run Windows-specific test command: | - uv run --no-sync python -m pytest tests/windows_tests/test_litellm_on_windows.py -v + uv run --no-sync python -m pytest tests/windows_tests/ -v + - run: + name: Guard against MAX_PATH-busting packaged wheel paths + environment: + UV_HTTP_TIMEOUT: "300" + command: | + uv build --wheel --out-dir dist + uv run --no-sync python tests/windows_tests/check_windows_wheel_install.py local_testing_part1: docker: diff --git a/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/guardrail_benchmarks/results/block_age_discrimination_-_contentfilter_(age_discrimination.yaml).json b/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/guardrail_benchmarks/results/age_discrimination_cf.json similarity index 100% rename from litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/guardrail_benchmarks/results/block_age_discrimination_-_contentfilter_(age_discrimination.yaml).json rename to litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/guardrail_benchmarks/results/age_discrimination_cf.json diff --git a/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/guardrail_benchmarks/results/block_claims_fraud_coaching_-_contentfilter_(claims_fraud_coaching.yaml).json b/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/guardrail_benchmarks/results/claims_fraud_coaching_cf.json similarity index 100% rename from litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/guardrail_benchmarks/results/block_claims_fraud_coaching_-_contentfilter_(claims_fraud_coaching.yaml).json rename to litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/guardrail_benchmarks/results/claims_fraud_coaching_cf.json diff --git a/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/guardrail_benchmarks/results/block_claims_medical_advice_-_contentfilter_(claims_medical_advice.yaml).json b/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/guardrail_benchmarks/results/claims_medical_advice_cf.json similarity index 100% rename from litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/guardrail_benchmarks/results/block_claims_medical_advice_-_contentfilter_(claims_medical_advice.yaml).json rename to litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/guardrail_benchmarks/results/claims_medical_advice_cf.json diff --git a/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/guardrail_benchmarks/results/block_claims_phi_disclosure_-_contentfilter_(claims_phi_disclosure.yaml).json b/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/guardrail_benchmarks/results/claims_phi_disclosure_cf.json similarity index 100% rename from litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/guardrail_benchmarks/results/block_claims_phi_disclosure_-_contentfilter_(claims_phi_disclosure.yaml).json rename to litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/guardrail_benchmarks/results/claims_phi_disclosure_cf.json diff --git a/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/guardrail_benchmarks/results/block_claims_prior_auth_gaming_-_contentfilter_(claims_prior_auth_gaming.yaml).json b/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/guardrail_benchmarks/results/claims_prior_auth_gaming_cf.json similarity index 100% rename from litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/guardrail_benchmarks/results/block_claims_prior_auth_gaming_-_contentfilter_(claims_prior_auth_gaming.yaml).json rename to litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/guardrail_benchmarks/results/claims_prior_auth_gaming_cf.json diff --git a/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/guardrail_benchmarks/results/block_claims_system_override_-_contentfilter_(claims_system_override.yaml).json b/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/guardrail_benchmarks/results/claims_system_override_cf.json similarity index 100% rename from litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/guardrail_benchmarks/results/block_claims_system_override_-_contentfilter_(claims_system_override.yaml).json rename to litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/guardrail_benchmarks/results/claims_system_override_cf.json diff --git a/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/guardrail_benchmarks/results/block_disability_discrimination_-_contentfilter_(disability.yaml).json b/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/guardrail_benchmarks/results/disability_discrimination_cf.json similarity index 100% rename from litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/guardrail_benchmarks/results/block_disability_discrimination_-_contentfilter_(disability.yaml).json rename to litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/guardrail_benchmarks/results/disability_discrimination_cf.json diff --git a/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/guardrail_benchmarks/results/block_gender_discrimination_-_contentfilter_(gender_sexual_orientation.yaml).json b/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/guardrail_benchmarks/results/gender_discrimination_cf.json similarity index 100% rename from litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/guardrail_benchmarks/results/block_gender_discrimination_-_contentfilter_(gender_sexual_orientation.yaml).json rename to litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/guardrail_benchmarks/results/gender_discrimination_cf.json diff --git a/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/guardrail_benchmarks/results/block_military_discrimination_-_contentfilter_(military_status.yaml).json b/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/guardrail_benchmarks/results/military_discrimination_cf.json similarity index 100% rename from litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/guardrail_benchmarks/results/block_military_discrimination_-_contentfilter_(military_status.yaml).json rename to litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/guardrail_benchmarks/results/military_discrimination_cf.json diff --git a/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/guardrail_benchmarks/results/block_religion_discrimination_-_contentfilter_(religion.yaml).json b/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/guardrail_benchmarks/results/religion_discrimination_cf.json similarity index 100% rename from litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/guardrail_benchmarks/results/block_religion_discrimination_-_contentfilter_(religion.yaml).json rename to litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/guardrail_benchmarks/results/religion_discrimination_cf.json diff --git a/tests/windows_tests/check_windows_wheel_install.py b/tests/windows_tests/check_windows_wheel_install.py new file mode 100644 index 0000000000..6dbb9da628 --- /dev/null +++ b/tests/windows_tests/check_windows_wheel_install.py @@ -0,0 +1,76 @@ +"""Reproduce a default-Windows ``pip install litellm`` to catch the 260-char +MAX_PATH regression that content-filter benchmark fixtures keep reintroducing +(#21941, #22039, #29536). Run after ``uv build --wheel --out-dir dist``. +""" + +import glob +import os +import subprocess +import sys +import zipfile + +MAX_PATH = 260 +# Worst-case Windows site-packages prefix: long profile name + roaming AppData venv. +WORST_CASE_PREFIX = 100 + + +def overlong_install_paths(wheel, prefix_len=WORST_CASE_PREFIX, max_path=MAX_PATH): + with zipfile.ZipFile(wheel) as zf: + names = zf.namelist() + return sorted( + (n for n in names if prefix_len + len(n) > max_path), key=len, reverse=True + ) + + +def _deep_venv_dir(target_prefix=WORST_CASE_PREFIX): + drive = os.path.splitdrive(os.getcwd())[0] or "C:" + root = drive + os.sep + "lmwin" + os.sep + # +2: the sep joining the venv root to "Lib", plus the trailing sep before the entry + suffix = len(os.path.join("Lib", "site-packages")) + 2 + return root + "x" * (target_prefix - suffix - len(root)) + + +def _run(cmd): + print("+ " + subprocess.list2cmdline(cmd), flush=True) + return subprocess.call(cmd) + + +def main(): + wheels = glob.glob(os.path.join("dist", "*.whl")) + if not wheels: + print("::error::no wheel in dist/; run `uv build --wheel --out-dir dist` first") + return 1 + wheel = max(wheels, key=os.path.getmtime) + + offenders = overlong_install_paths(wheel) + if offenders: + print( + f"::error::{len(offenders)} packaged path(s) bust the Windows MAX_PATH limit " + f"at a {WORST_CASE_PREFIX}-char install prefix:" + ) + for n in offenders[:15]: + print(f" on-disk {WORST_CASE_PREFIX + len(n):4} {n}") + return 1 + + venv = _deep_venv_dir() + os.makedirs(os.path.dirname(venv), exist_ok=True) + if _run(["uv", "venv", venv]) != 0: + return 1 + python = os.path.join(venv, "Scripts", "python.exe") + if _run(["uv", "pip", "install", "--python", python, wheel]) != 0: + print( + f"::error::installing {os.path.basename(wheel)} into a deep prefix failed" + ) + return 1 + if _run([python, "-c", "import litellm; import litellm.types.utils"]) != 0: + print("::error::litellm did not import after install (half-unpacked package)") + return 1 + + print( + f"ok: {os.path.basename(wheel)} installs into a worst-case prefix and imports" + ) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tests/windows_tests/test_check_windows_wheel_install.py b/tests/windows_tests/test_check_windows_wheel_install.py new file mode 100644 index 0000000000..22a197604e --- /dev/null +++ b/tests/windows_tests/test_check_windows_wheel_install.py @@ -0,0 +1,36 @@ +import zipfile + +from check_windows_wheel_install import ( + MAX_PATH, + WORST_CASE_PREFIX, + overlong_install_paths, +) + + +def _wheel(tmp_path, *entry_names): + path = tmp_path / "pkg.whl" + with zipfile.ZipFile(path, "w") as zf: + for name in entry_names: + zf.writestr(name, "{}") + return str(path) + + +def test_flags_entry_one_char_over_budget(tmp_path): + busts = "a" * (MAX_PATH - WORST_CASE_PREFIX + 1) + assert overlong_install_paths(_wheel(tmp_path, busts)) == [busts] + + +def test_allows_entry_exactly_at_budget(tmp_path): + at_limit = "a" * (MAX_PATH - WORST_CASE_PREFIX) + assert ( + overlong_install_paths(_wheel(tmp_path, at_limit, "litellm/__init__.py")) == [] + ) + + +def test_orders_offenders_longest_first(tmp_path): + longer = "a" * (MAX_PATH - WORST_CASE_PREFIX + 5) + shorter = "b" * (MAX_PATH - WORST_CASE_PREFIX + 1) + assert overlong_install_paths(_wheel(tmp_path, shorter, longer)) == [ + longer, + shorter, + ]