The launcher replaced TMPDIR with /tmp for the whole Electron process tree, so the Python backend inherited TMPDIR=/tmp while TMP/TEMP/HERMES_SCRATCH_DIR still named the scratch dir; apply_scratch_tmp_env then treated /tmp as user-set and the agent subtree wrote temp files to /tmp. The launcher now passes the original in HERMES_DESKTOP_TMPDIR and Electron main restores it right after requestSingleInstanceLock(), so every child gets the profile scratch dir back. The override threshold is Chromium's real budget instead of the shared 50-byte socket constant: sun_path holds 107 bytes and Chromium appends /scoped_dirXXXXXX/SingletonSocket (33 bytes, measured on Electron 40), so a TMPDIR up to 74 bytes is kept.
64 lines
2.1 KiB
Python
64 lines
2.1 KiB
Python
"""The Electron child needs a socket-safe temp root, not a long CLI scratch path (#124688)."""
|
|
|
|
import argparse
|
|
import os
|
|
import secrets
|
|
import shutil
|
|
import socket
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from hermes_cli import main_desktop
|
|
|
|
|
|
def _launch_env(tmp_path) -> dict:
|
|
env, _flags = main_desktop._desktop_launch_env(argparse.Namespace(cwd=str(tmp_path)))
|
|
return env
|
|
|
|
|
|
def _bind_singleton_socket(tmpdir: str) -> None:
|
|
# Chromium's ProcessSingleton binds $TMPDIR/scoped_dirXXXXXX/SingletonSocket (6 random chars).
|
|
directory = Path(tmpdir) / f"scoped_dir{secrets.token_hex(3)}"
|
|
directory.mkdir()
|
|
try:
|
|
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as server:
|
|
server.bind(str(directory / "SingletonSocket"))
|
|
finally:
|
|
shutil.rmtree(directory)
|
|
|
|
|
|
@pytest.mark.platforms("linux")
|
|
def test_long_scratch_tmpdir_hosts_the_singleton_socket_and_is_kept_for_children(monkeypatch, tmp_path):
|
|
scratch = tmp_path / ("long-home-" * 8) / "cache" / "scratch"
|
|
scratch.mkdir(parents=True)
|
|
for key in ("TMPDIR", "TMP", "TEMP", "HERMES_SCRATCH_DIR"):
|
|
monkeypatch.setenv(key, str(scratch))
|
|
monkeypatch.setattr(tempfile, "tempdir", None)
|
|
|
|
env = _launch_env(tmp_path)
|
|
|
|
_bind_singleton_socket(env["TMPDIR"])
|
|
# Electron main restores this for the backend, so the agent keeps writing to scratch.
|
|
assert env["HERMES_DESKTOP_TMPDIR"] == str(scratch)
|
|
|
|
|
|
@pytest.mark.platforms("linux")
|
|
def test_tmpdir_that_fits_the_socket_budget_is_passed_through(monkeypatch, tmp_path):
|
|
from hermes_constants import socket_safe_tmpdir
|
|
|
|
with tempfile.TemporaryDirectory(prefix="dt-", dir=socket_safe_tmpdir()) as root:
|
|
# Longest TMPDIR whose scoped_dirXXXXXX/SingletonSocket still fits sun_path.
|
|
tmpdir = os.path.join(root, "x" * (74 - len(root) - 1))
|
|
os.mkdir(tmpdir)
|
|
assert len(os.fsencode(tmpdir)) == 74
|
|
_bind_singleton_socket(tmpdir)
|
|
monkeypatch.setenv("TMPDIR", tmpdir)
|
|
monkeypatch.setattr(tempfile, "tempdir", None)
|
|
|
|
env = _launch_env(tmp_path)
|
|
|
|
assert env["TMPDIR"] == tmpdir
|
|
assert "HERMES_DESKTOP_TMPDIR" not in env
|