diff --git a/tests/integration/_support/process.py b/tests/integration/_support/process.py index 8e9f689ec2d..12780540380 100644 --- a/tests/integration/_support/process.py +++ b/tests/integration/_support/process.py @@ -119,7 +119,7 @@ def _stop(process: subprocess.Popen[bytes]) -> None: _PORT_ATTEMPTS: Final = 3 -_BIND_COLLISION: Final = os.strerror(errno.EADDRINUSE) +_BIND_COLLISION: Final = os.strerror(errno.EADDRINUSE).lower() def _free_port() -> int: @@ -151,7 +151,7 @@ def _launch(command: tuple[str, ...], root: Path, environment: Mapping[str, str] def _lost_port_race(exit_code: int | None, log: Path) -> bool: - return exit_code is not None and _BIND_COLLISION in log.read_text() + return exit_code is not None and _BIND_COLLISION in log.read_text().lower() def _wait_until_ready(launch: _Launch) -> None: diff --git a/tests/unit/integration_support/test_process.py b/tests/unit/integration_support/test_process.py index 042a2447dff..7896786fce8 100644 --- a/tests/unit/integration_support/test_process.py +++ b/tests/unit/integration_support/test_process.py @@ -1,8 +1,10 @@ from __future__ import annotations +import asyncio import errno import importlib -import os +import socket +from collections.abc import Iterator from pathlib import Path from types import ModuleType from typing import Final @@ -10,7 +12,6 @@ from typing import Final import pytest TESTS_DIR: Final = Path(__file__).resolve().parents[2] -BIND_ERROR_LINE: Final = f"ERROR: {OSError(errno.EADDRINUSE, os.strerror(errno.EADDRINUSE))}\n" UNRELATED_CRASH: Final = "Traceback (most recent call last):\nModuleNotFoundError: No module named 'litellm'\n" @@ -20,19 +21,41 @@ def process_module(monkeypatch: pytest.MonkeyPatch) -> ModuleType: return importlib.import_module("integration._support.process") +async def _refused_bind(port: int) -> OSError | None: + try: + await asyncio.get_running_loop().create_server(asyncio.Protocol, "127.0.0.1", port) + except OSError as refused: + return refused + return None + + +@pytest.fixture +def bind_error_line() -> Iterator[str]: + with socket.socket() as held: + held.bind(("127.0.0.1", 0)) + held.listen() + refused: Final = asyncio.run(_refused_bind(held.getsockname()[1])) + assert refused is not None and refused.errno == errno.EADDRINUSE + yield f"ERROR: {refused}\n" + + def _written_log(directory: Path, text: str) -> Path: log: Final = directory / "owned-proxy.log" log.write_text(text) return log -def test_lost_port_race_matches_the_bind_error_the_server_logs(process_module: ModuleType, tmp_path: Path) -> None: - assert process_module._lost_port_race(1, _written_log(tmp_path, BIND_ERROR_LINE)) +def test_lost_port_race_matches_the_bind_error_the_server_logs( + process_module: ModuleType, bind_error_line: str, tmp_path: Path +) -> None: + assert process_module._lost_port_race(1, _written_log(tmp_path, bind_error_line)) def test_lost_port_race_ignores_an_exit_for_another_reason(process_module: ModuleType, tmp_path: Path) -> None: assert not process_module._lost_port_race(1, _written_log(tmp_path, UNRELATED_CRASH)) -def test_lost_port_race_needs_the_process_to_have_exited(process_module: ModuleType, tmp_path: Path) -> None: - assert not process_module._lost_port_race(None, _written_log(tmp_path, BIND_ERROR_LINE)) +def test_lost_port_race_needs_the_process_to_have_exited( + process_module: ModuleType, bind_error_line: str, tmp_path: Path +) -> None: + assert not process_module._lost_port_race(None, _written_log(tmp_path, bind_error_line))