diff --git a/tests/test_litellm/proxy/db/conftest.py b/tests/test_litellm/proxy/db/conftest.py index 03d7ea81257..d3226b0ec50 100644 --- a/tests/test_litellm/proxy/db/conftest.py +++ b/tests/test_litellm/proxy/db/conftest.py @@ -6,7 +6,7 @@ import time from collections.abc import Generator from dataclasses import dataclass from pathlib import Path -from typing import Optional +from typing import Final, Optional import pytest @@ -122,10 +122,18 @@ class FakePrismaCli: return [json.loads(line) for line in self.calls_file.read_text().splitlines()] def grandchild_is_gone(self, within_seconds: float) -> bool: - deadline = time.monotonic() + within_seconds + pid: Final = int(self.grandchild_pidfile.read_text()) + deadline: Final = time.monotonic() + within_seconds while time.monotonic() < deadline: + if os.name != "nt": + try: + reaped_pid, _ = os.waitpid(pid, os.WNOHANG) + if reaped_pid == pid: + return True + except ChildProcessError: + pass try: - os.kill(int(self.grandchild_pidfile.read_text()), 0) + os.kill(pid, 0) except ProcessLookupError: return True time.sleep(0.05) @@ -154,3 +162,4 @@ def fake_prisma_cli(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Generato os.kill(int(cli.grandchild_pidfile.read_text()), signal.SIGKILL) except ProcessLookupError: pass + assert cli.grandchild_is_gone(within_seconds=5) diff --git a/tests/test_litellm/proxy/db/test_query_engine_reaper.py b/tests/test_litellm/proxy/db/test_query_engine_reaper.py index efcecb4bc08..5018176854e 100644 --- a/tests/test_litellm/proxy/db/test_query_engine_reaper.py +++ b/tests/test_litellm/proxy/db/test_query_engine_reaper.py @@ -3,6 +3,7 @@ import signal import subprocess import sys import time +from typing import Final from unittest.mock import MagicMock, patch import pytest @@ -15,7 +16,6 @@ from litellm.proxy.db.query_engine_reaper import ( _try_reap, list_orphaned_engine_pids, reap_orphaned_engines, - set_child_subreaper, start_query_engine_reaper, terminate_and_reap, terminate_and_reap_all, @@ -79,11 +79,19 @@ class TestListOrphanedEnginePids: class TestSetChildSubreaper: def test_matches_platform_capability(self): - result = set_child_subreaper() - if sys.platform.startswith("linux"): - assert result is True - else: - assert result is False + result: Final = subprocess.run( + [ + sys.executable, + "-c", + "import sys; " + "from litellm.proxy.db.query_engine_reaper import set_child_subreaper; " + "assert set_child_subreaper() is sys.platform.startswith('linux')", + ], + capture_output=True, + text=True, + timeout=30, + ) + assert result.returncode == 0, result.stderr @pytest.mark.skipif(sys.platform == "win32", reason="POSIX signals and waitpid") diff --git a/tests/test_litellm/proxy/test_proxy_cli.py b/tests/test_litellm/proxy/test_proxy_cli.py index 0c20d5e0ff0..c76ff189a8a 100644 --- a/tests/test_litellm/proxy/test_proxy_cli.py +++ b/tests/test_litellm/proxy/test_proxy_cli.py @@ -1525,7 +1525,12 @@ class TestProxyInitializationHelpers: def capture_run(self): captured["options"] = dict(self.options) - with patch("gunicorn.app.base.BaseApplication.run", capture_run): + with ( + patch("gunicorn.app.base.BaseApplication.run", capture_run), + patch( # test-quality-ok: option tests must not start a thread or change the pytest worker's child ownership + "litellm.proxy.proxy_cli.start_query_engine_reaper" + ), + ): ProxyInitializationHelpers._run_gunicorn_server( host="127.0.0.1", port=4010, @@ -1553,6 +1558,9 @@ class TestProxyInitializationHelpers: with ( patch("gunicorn.app.base.BaseApplication.run", capture_run), patch("builtins.print") as mock_print, + patch( # test-quality-ok: option tests must not start a thread or change the pytest worker's child ownership + "litellm.proxy.proxy_cli.start_query_engine_reaper" + ), ): ProxyInitializationHelpers._run_gunicorn_server( host="127.0.0.1",