chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
"""Unit tests for get_haproxy_binary() resolution logic.
|
||||
|
||||
get_haproxy_binary() resolves an HAProxy binary path with this priority:
|
||||
1. Explicit RAY_SERVE_HAPROXY_BINARY_PATH override, validated and returned.
|
||||
2. Bundled binary from the ``ray-haproxy`` PyPI package.
|
||||
3. System ``haproxy`` on PATH.
|
||||
4. FileNotFoundError with an actionable message.
|
||||
"""
|
||||
|
||||
import stat
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from ray.serve._private.haproxy import get_haproxy_binary
|
||||
|
||||
# Patch targets: module-level constants imported at the top of haproxy.py.
|
||||
HAPROXY_MODULE = "ray.serve._private.haproxy"
|
||||
BINARY_PATH_PATCH = f"{HAPROXY_MODULE}.RAY_SERVE_HAPROXY_BINARY_PATH"
|
||||
WHICH_PATCH = f"{HAPROXY_MODULE}.shutil.which"
|
||||
|
||||
|
||||
def _make_executable_binary(tmp_path):
|
||||
"""Create an empty executable file under tmp_path and return its path."""
|
||||
binary = tmp_path / "haproxy"
|
||||
binary.write_bytes(b"")
|
||||
binary.chmod(binary.stat().st_mode | stat.S_IXUSR)
|
||||
return binary
|
||||
|
||||
|
||||
def test_explicit_path_takes_precedence_over_bundled(tmp_path):
|
||||
"""An explicit RAY_SERVE_HAPROXY_BINARY_PATH wins over the bundled
|
||||
ray-haproxy package, so an operator's custom binary is not silently
|
||||
overridden."""
|
||||
binary = _make_executable_binary(tmp_path)
|
||||
|
||||
mock_module = MagicMock()
|
||||
mock_module.get_haproxy_binary.return_value = "/bundled/haproxy"
|
||||
with patch.dict("sys.modules", {"ray_haproxy": mock_module}), patch(
|
||||
BINARY_PATH_PATCH, str(binary)
|
||||
):
|
||||
assert get_haproxy_binary() == str(binary)
|
||||
mock_module.get_haproxy_binary.assert_not_called()
|
||||
|
||||
|
||||
def test_explicit_path_validates_executable(tmp_path):
|
||||
"""An explicit RAY_SERVE_HAPROXY_BINARY_PATH is validated before use: it must
|
||||
exist and be executable, otherwise resolution raises."""
|
||||
binary = _make_executable_binary(tmp_path)
|
||||
|
||||
with patch(BINARY_PATH_PATCH, str(binary)):
|
||||
assert get_haproxy_binary() == str(binary)
|
||||
|
||||
# Same file without the execute bit is rejected.
|
||||
binary.chmod(0o644)
|
||||
with patch(BINARY_PATH_PATCH, str(binary)):
|
||||
with pytest.raises(FileNotFoundError, match="HAPROXY_BINARY_PATH"):
|
||||
get_haproxy_binary()
|
||||
|
||||
# A nonexistent path is rejected.
|
||||
with patch(BINARY_PATH_PATCH, str(tmp_path / "no_such_file")):
|
||||
with pytest.raises(FileNotFoundError, match="HAPROXY_BINARY_PATH"):
|
||||
get_haproxy_binary()
|
||||
|
||||
|
||||
@patch(BINARY_PATH_PATCH, "")
|
||||
def test_bundled_package_used_when_no_explicit_path():
|
||||
"""With no explicit path set, the bundled ray-haproxy binary is used."""
|
||||
mock_module = MagicMock()
|
||||
mock_module.get_haproxy_binary.return_value = "/bundled/haproxy"
|
||||
with patch.dict("sys.modules", {"ray_haproxy": mock_module}):
|
||||
assert get_haproxy_binary() == "/bundled/haproxy"
|
||||
|
||||
|
||||
@patch(WHICH_PATCH, return_value="/usr/sbin/haproxy")
|
||||
@patch(BINARY_PATH_PATCH, "")
|
||||
def test_pip_package_oserror_falls_through(_mock_which):
|
||||
"""If ray-haproxy is installed but its binary is broken (e.g. missing file,
|
||||
bad permissions), the function should fall through to the system haproxy
|
||||
rather than crashing the proxy actor. We catch OSError (not just
|
||||
FileNotFoundError) so PermissionError is also handled."""
|
||||
mock_module = MagicMock()
|
||||
mock_module.get_haproxy_binary.side_effect = OSError("bad permissions")
|
||||
|
||||
with patch.dict("sys.modules", {"ray_haproxy": mock_module}):
|
||||
assert get_haproxy_binary() == "/usr/sbin/haproxy"
|
||||
|
||||
|
||||
@patch(WHICH_PATCH, return_value=None)
|
||||
@patch(BINARY_PATH_PATCH, "")
|
||||
def test_nothing_available_raises_with_instructions(_mock_which):
|
||||
"""When no binary is available from any source, the error message must
|
||||
tell the user how to fix it (install ray[serve], set the env var, or
|
||||
put haproxy on PATH)."""
|
||||
with patch.dict("sys.modules", {"ray_haproxy": None}):
|
||||
with pytest.raises(FileNotFoundError, match=r"ray\[serve\]"):
|
||||
get_haproxy_binary()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
sys.exit(pytest.main(["-v", "-s", __file__]))
|
||||
Reference in New Issue
Block a user