fix(gateway): control-socket fallback survives deep TMPDIR; tests bind-location-aware

CI runners put pytest tmp roots past sun_path, which routed every test
home through the fallback: two tests assumed in-home binding. Tests now
assert against the resolved bind location, and the fallback itself
prefers /tmp when tempfile.gettempdir() is too deep to fit sun_path.
This commit is contained in:
Teknium
2026-08-22 11:55:19 -07:00
parent d1c84fc5d3
commit 67aac4d863
2 changed files with 38 additions and 7 deletions
+15 -2
View File
@@ -96,8 +96,21 @@ def _default_socket_path(home: Path) -> Path:
def _fallback_socket_path(home: Path) -> Path:
"""Short temp-dir path for homes whose direct socket path exceeds sun_path."""
return Path(tempfile.gettempdir()) / f"hermes-gw-{_home_hash(home)}.sock"
"""Short temp-dir path for homes whose direct socket path exceeds sun_path.
Prefers ``tempfile.gettempdir()``; when even that yields a too-long path
(deep $TMPDIR), falls back to ``/tmp`` on POSIX. If nothing fits, the
tempdir candidate is returned anyway — bind will fail non-fatally and
consumers use the scan layer.
"""
name = f"hermes-gw-{_home_hash(home)}.sock"
candidates = [Path(tempfile.gettempdir()) / name]
if not _IS_WINDOWS:
candidates.append(Path("/tmp") / name)
for candidate in candidates:
if len(str(candidate).encode("utf-8")) <= _MAX_UNIX_PATH:
return candidate
return candidates[0]
def resolve_server_socket_path(home: Path) -> tuple[Path, Optional[Path]]:
+23 -5
View File
@@ -44,10 +44,24 @@ def _serve(home: Path, handlers=None):
# Path resolution
# ---------------------------------------------------------------------------
def test_short_home_binds_in_home(home: Path):
bind, pointer = resolve_server_socket_path(home)
assert bind == home / "gateway.sock"
assert pointer is None
def test_short_home_binds_in_home(tmp_path: Path):
# A home short enough for sun_path binds in-home with no pointer.
# tmp_path can exceed the limit on CI runners, so build one in the
# system temp root directly.
import tempfile
short_root = Path(tempfile.mkdtemp(prefix="hgw-", dir="/tmp"))
try:
short_home = short_root / ".hermes"
short_home.mkdir()
assert len(str(short_home / "gateway.sock").encode()) <= 100
bind, pointer = resolve_server_socket_path(short_home)
assert bind == short_home / "gateway.sock"
assert pointer is None
finally:
import shutil
shutil.rmtree(short_root, ignore_errors=True)
def test_long_home_uses_pointer_fallback(tmp_path: Path):
@@ -163,7 +177,11 @@ def test_stop_removes_socket_and_pointer(home: Path):
def test_stale_socket_file_is_replaced_on_bind(home: Path):
(home / "gateway.sock").touch() # crashed predecessor's leftover
# Plant the stale file at wherever the server will actually bind
# (in-home OR the temp-dir fallback, depending on path length).
bind, _ = resolve_server_socket_path(home)
bind.parent.mkdir(parents=True, exist_ok=True)
bind.touch() # crashed predecessor's leftover
async def scenario():
server = GatewayControlServer(