From 06f400e4e9061b8067060cf3cf4f4ec1c9461567 Mon Sep 17 00:00:00 2001 From: agustin18 Date: Sun, 27 Sep 2026 23:06:30 +0000 Subject: [PATCH] test(timeout): use clock-free unit tests per AGENTS.md Signed-off-by: agustin18 --- tests/unit/test_timeout.py | 101 ++++++++++++++++++++++--------------- 1 file changed, 61 insertions(+), 40 deletions(-) diff --git a/tests/unit/test_timeout.py b/tests/unit/test_timeout.py index 6656c20e9b7..e6f1d915ce2 100644 --- a/tests/unit/test_timeout.py +++ b/tests/unit/test_timeout.py @@ -1,7 +1,6 @@ """Unit tests for litellm.timeout decorator.""" import asyncio -import time from typing import Final import pytest @@ -12,65 +11,87 @@ from litellm.timeout import timeout @pytest.mark.asyncio @pytest.mark.parametrize("timeout_arg", ["request_timeout", "force_timeout"]) -async def test_async_timeout_decorator_respects_per_call_timeout(timeout_arg: str): - """An async function decorated with a large timeout must respect a shorter per-call timeout.""" +async def test_async_timeout_decorator_passes_per_call_timeout( + monkeypatch: pytest.MonkeyPatch, timeout_arg: str +) -> None: + captured_timeout: Final[list[float | None]] = [] + + async def fake_wait_for(fut, timeout): + captured_timeout.append(timeout) + return await fut + + monkeypatch.setattr("litellm.timeout.asyncio.wait_for", fake_wait_for) @timeout(timeout_duration=5.0) - async def slow_async_func(**kwargs): - await asyncio.sleep(0.5) + async def sample_func(**kwargs): return "ok" - start_time: Final = time.monotonic() - kwargs = {timeout_arg: 0.05, "model": "test-model"} + result: Final = await sample_func(**{timeout_arg: 0.05, "model": "test-model"}) + assert result == "ok" + assert captured_timeout == [0.05] + + +@pytest.mark.asyncio +@pytest.mark.parametrize("timeout_arg", ["request_timeout", "force_timeout"]) +async def test_async_timeout_decorator_raises_with_per_call_timeout_message( + monkeypatch: pytest.MonkeyPatch, timeout_arg: str +) -> None: + async def fake_wait_for_timeout(fut, timeout): + fut.close() + raise asyncio.TimeoutError() + + monkeypatch.setattr("litellm.timeout.asyncio.wait_for", fake_wait_for_timeout) + + @timeout(timeout_duration=5.0) + async def sample_func(**kwargs): + return "ok" with pytest.raises(Timeout) as exc_info: - await slow_async_func(**kwargs) + await sample_func(**{timeout_arg: 0.05, "model": "test-model"}) - elapsed: Final = time.monotonic() - start_time - assert elapsed < 0.3, f"Call waited {elapsed}s instead of respecting {timeout_arg}=0.05" assert "0.05 second(s)" in str(exc_info.value) @pytest.mark.asyncio -async def test_async_timeout_decorator_extends_duration_with_per_call_timeout(): - """An async function with a small default timeout must not time out if request_timeout gives more time.""" +async def test_async_timeout_decorator_handles_none_force_timeout(monkeypatch: pytest.MonkeyPatch) -> None: + captured_timeout: Final[list[float | None]] = [] - @timeout(timeout_duration=0.05) - async def moderately_slow_async_func(**kwargs): - await asyncio.sleep(0.1) - return "completed" + async def fake_wait_for(fut, timeout): + captured_timeout.append(timeout) + return await fut - result = await moderately_slow_async_func(request_timeout=1.0, model="test-model") - assert result == "completed" + monkeypatch.setattr("litellm.timeout.asyncio.wait_for", fake_wait_for) + + @timeout(timeout_duration=5.0) + async def sample_func(**kwargs): + return "ok" + + await sample_func(force_timeout=None, model="test-model") + assert captured_timeout == [5.0] @pytest.mark.asyncio -async def test_async_timeout_decorator_handles_none_force_timeout(): - """Passing force_timeout=None should not break timeout_duration fallback.""" +async def test_async_timeout_decorator_uses_default_timeout(monkeypatch: pytest.MonkeyPatch) -> None: + captured_timeout: Final[list[float | None]] = [] - @timeout(timeout_duration=0.05) - async def slow_async_func(**kwargs): - await asyncio.sleep(0.2) + async def fake_wait_for(fut, timeout): + captured_timeout.append(timeout) + return await fut + + monkeypatch.setattr("litellm.timeout.asyncio.wait_for", fake_wait_for) + + @timeout(timeout_duration=42.0) + async def sample_func(**kwargs): return "ok" - with pytest.raises(Timeout) as exc_info: - await slow_async_func(force_timeout=None, model="test-model") - - assert "0.05 second(s)" in str(exc_info.value) + await sample_func(model="test-model") + assert captured_timeout == [42.0] -def test_sync_timeout_decorator_respects_per_call_timeout(): - """A sync function decorated with a large timeout must respect a shorter per-call timeout.""" - +def test_sync_timeout_decorator_runs_without_clock() -> None: @timeout(timeout_duration=5.0) - def slow_sync_func(**kwargs): - time.sleep(0.5) - return "ok" + def sample_sync_func(**kwargs): + return "sync_ok" - start_time: Final = time.monotonic() - with pytest.raises(Timeout) as exc_info: - slow_sync_func(request_timeout=0.05, model="test-model") - - elapsed: Final = time.monotonic() - start_time - assert elapsed < 0.3, f"Call waited {elapsed}s instead of respecting request_timeout=0.05" - assert "0.05 second(s)" in str(exc_info.value) + result: Final = sample_sync_func(request_timeout=0.05, model="test-model") + assert result == "sync_ok"