test: retain ownership of sockets and subprocess groups

This commit is contained in:
Alexandre Teixeira
2026-10-03 03:46:52 +01:00
parent 6eb0bbfe70
commit e1bc13b634
3 changed files with 21 additions and 22 deletions
+5 -5
View File
@@ -92,11 +92,11 @@ def test_a_chromadb_we_did_not_start_is_refused_not_adopted(cli, worktree, monke
ports = cli.derive_ports(worktree)
foreign = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
foreign.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
foreign.bind(("127.0.0.1", ports["chroma"]))
except OSError:
pytest.skip(f"derived chroma port {ports['chroma']} is unavailable on this host")
foreign.bind(("127.0.0.1", 0))
ports["chroma"] = foreign.getsockname()[1]
# Port derivation is covered above. This refusal test owns a held socket
# rather than depending on a derived port being free on the host.
monkeypatch.setattr(cli, "derive_ports", lambda _root: ports)
foreign.listen(1)
try:
with pytest.raises(SystemExit):
+10 -12
View File
@@ -12,19 +12,17 @@ import pytest
import src.chroma_client as cc
def _free_port() -> int:
"""Bind to port 0, grab the assigned port, release it — nothing listens."""
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(("127.0.0.1", 0))
port = s.getsockname()[1]
s.close()
return port
@pytest.fixture
def closed_port():
"""Reserve a port without listening, so another worker cannot take it."""
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as reserved:
reserved.bind(("127.0.0.1", 0))
yield reserved.getsockname()[1]
def test_port_open_false_for_closed_port_and_is_fast():
port = _free_port()
def test_port_open_false_for_closed_port_and_is_fast(closed_port):
t0 = time.monotonic()
assert cc._port_open("127.0.0.1", port, timeout=1.0) is False
assert cc._port_open("127.0.0.1", closed_port, timeout=1.0) is False
# The whole point: we fail fast, nowhere near the 30-60s OS timeout.
assert time.monotonic() - t0 < 5.0
@@ -40,11 +38,11 @@ def test_port_open_true_for_listening_socket():
srv.close()
def test_get_chroma_client_does_not_cache_when_unreachable(monkeypatch):
def test_get_chroma_client_does_not_cache_when_unreachable(monkeypatch, closed_port):
pytest.importorskip("chromadb")
cc.reset_client()
monkeypatch.setenv("CHROMADB_HOST", "127.0.0.1")
monkeypatch.setenv("CHROMADB_PORT", str(_free_port()))
monkeypatch.setenv("CHROMADB_PORT", str(closed_port))
with pytest.raises(RuntimeError):
cc.get_chroma_client()
# A failed connection must leave the singleton unset so a later call
+6 -5
View File
@@ -15,6 +15,7 @@ because an absent mechanism read as a successful answer.
"""
import os
import signal
import subprocess
import pytest
@@ -35,11 +36,11 @@ def sleeper():
yield _spawn
for proc in procs:
try:
proc.kill()
proc.wait(timeout=5)
except Exception:
pass
# Each child owns a session/process group. Keep the leader unreaped
# until its group is signalled, so the group id cannot be recycled.
if proc.returncode is None:
os.killpg(proc.pid, signal.SIGKILL)
proc.wait(timeout=5)
# ── Verdicts, against real processes ────────────────────────────────────────