make test run faster
This commit is contained in:
@@ -13,6 +13,7 @@ before calling pytest. If you prefer to run pytest directly, set these:
|
||||
"""
|
||||
|
||||
import os
|
||||
import socket
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
@@ -24,7 +25,6 @@ from mcp_client import McpClient
|
||||
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
||||
|
||||
# All paths read from env vars, with sensible project-local defaults.
|
||||
# Users only need to override these when GammaRay is installed elsewhere.
|
||||
GAMMARAY_BIN = Path(os.environ.get(
|
||||
"GAMMARAY_BIN",
|
||||
str(PROJECT_ROOT / "install-prefix" / "bin" / "gammaray"),
|
||||
@@ -88,9 +88,22 @@ def run_script_path() -> Path:
|
||||
return BRIDGE_RUN_SCRIPT
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def _probe_accepting_connections(timeout: int = 20) -> bool:
|
||||
"""Check if the probe is accepting connections via raw TCP."""
|
||||
deadline = time.monotonic() + timeout
|
||||
while time.monotonic() < deadline:
|
||||
try:
|
||||
s = socket.create_connection(("127.0.0.1", 11732), timeout=2)
|
||||
s.close()
|
||||
return True
|
||||
except (OSError, socket.timeout):
|
||||
time.sleep(1)
|
||||
return False
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def probe_process(request) -> subprocess.Popen | None:
|
||||
"""Start a GammaRay probe process (session-scoped, one per test run)."""
|
||||
"""Start a GammaRay probe process (session-scoped, one per entire test run)."""
|
||||
if request.config.getoption("--no-probe"):
|
||||
return None
|
||||
|
||||
@@ -105,10 +118,6 @@ def probe_process(request) -> subprocess.Popen | None:
|
||||
env["QT_QPA_PLATFORM"] = "offscreen"
|
||||
env["LD_LIBRARY_PATH"] = GAMMARAY_LIB_DIR
|
||||
|
||||
proc = None
|
||||
used_systemd = False
|
||||
|
||||
# Try systemd-run first for clean detachment
|
||||
subprocess.run(
|
||||
["systemctl", "--user", "stop", "gammaray-probe-test.service"],
|
||||
capture_output=True, timeout=5,
|
||||
@@ -133,83 +142,51 @@ def probe_process(request) -> subprocess.Popen | None:
|
||||
],
|
||||
capture_output=True, text=True, timeout=10,
|
||||
)
|
||||
if result.returncode == 0:
|
||||
used_systemd = True
|
||||
proc = subprocess.Popen(["true"]) # sentinel: probe is external
|
||||
else:
|
||||
|
||||
timeout = request.config.getoption("--probe-timeout")
|
||||
if not _probe_accepting_connections(timeout):
|
||||
# Fallback: direct spawn
|
||||
proc = subprocess.Popen(
|
||||
[str(GAMMARAY_BIN), "--inject-only", "--listen", "tcp://127.0.0.1:11732",
|
||||
"--injector", "preload", str(QML_RUNNER), str(TEST_QML)],
|
||||
env=env, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
|
||||
)
|
||||
if not _probe_accepting_connections(timeout):
|
||||
pytest.fail(f"Probe did not become ready within {timeout}s")
|
||||
else:
|
||||
proc = subprocess.Popen(["true"])
|
||||
|
||||
def cleanup():
|
||||
if proc:
|
||||
proc.terminate()
|
||||
try:
|
||||
proc.wait(timeout=5)
|
||||
except subprocess.TimeoutExpired:
|
||||
proc.kill()
|
||||
if used_systemd:
|
||||
subprocess.run(
|
||||
["systemctl", "--user", "stop", "gammaray-probe-test.service"],
|
||||
capture_output=True, timeout=5,
|
||||
)
|
||||
subprocess.run(
|
||||
["systemctl", "--user", "reset-failed", "gammaray-probe-test.service"],
|
||||
capture_output=True, timeout=5,
|
||||
)
|
||||
subprocess.run(
|
||||
["systemctl", "--user", "stop", "gammaray-probe-test.service"],
|
||||
capture_output=True, timeout=5,
|
||||
)
|
||||
subprocess.run(
|
||||
["systemctl", "--user", "reset-failed", "gammaray-probe-test.service"],
|
||||
capture_output=True, timeout=5,
|
||||
)
|
||||
|
||||
request.addfinalizer(cleanup)
|
||||
|
||||
# Wait for probe to be ready
|
||||
timeout = request.config.getoption("--probe-timeout")
|
||||
deadline = time.monotonic() + timeout
|
||||
while time.monotonic() < deadline:
|
||||
try:
|
||||
c = McpClient(bridge_path=BRIDGE_EXE)
|
||||
c.initialize()
|
||||
result = c.call_tool("connectProbeDefault")
|
||||
c.close()
|
||||
if isinstance(result, dict) and result.get("connected"):
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
time.sleep(1)
|
||||
else:
|
||||
pytest.fail(f"Probe did not become ready within {timeout}s")
|
||||
|
||||
return proc
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@pytest.fixture(scope="module")
|
||||
def bridge(run_script_path) -> McpClient:
|
||||
"""Start a fresh bridge instance per test."""
|
||||
"""Start a bridge instance per module, reused across tests in the module."""
|
||||
c = McpClient(bridge_path=run_script_path)
|
||||
c.initialize()
|
||||
yield c
|
||||
# Gracefully disconnect from the probe before killing the bridge.
|
||||
# GammaRay's QTcpSocket::disconnectFromHost() is asynchronous — the
|
||||
# Endpoint::disconnected signal (which clears m_socket) fires on a later
|
||||
# event loop iteration. If we kill the bridge (SIGTERM) while m_socket is
|
||||
# still set, the probe does not immediately detect the disconnection, and
|
||||
# the next test's bridge cannot connect. Sending disconnectProbe() first
|
||||
# triggers a clean GammaRay protocol-level shutdown that waits for the
|
||||
# async disconnect to complete, releasing the probe for the next test.
|
||||
try:
|
||||
c.call_tool("disconnectProbe", timeout=5)
|
||||
except Exception:
|
||||
pass # bridge may have already crashed; best-effort
|
||||
pass
|
||||
c.close()
|
||||
# Allow the probe to detect the disconnection before the next test
|
||||
# connects.
|
||||
time.sleep(1)
|
||||
time.sleep(0.2)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@pytest.fixture(scope="module")
|
||||
def connected_bridge(bridge, probe_process) -> McpClient:
|
||||
"""Bridge connected to a running probe (retries on transient failures)."""
|
||||
"""Bridge connected to a running probe (module-scoped, reused across tests)."""
|
||||
if probe_process is None:
|
||||
pytest.skip("No probe available (try --no-probe to skip probe-dependent tests)")
|
||||
deadline = time.monotonic() + 30
|
||||
@@ -219,5 +196,5 @@ def connected_bridge(bridge, probe_process) -> McpClient:
|
||||
if isinstance(result, dict) and result.get("connected"):
|
||||
return bridge
|
||||
last_error = result
|
||||
time.sleep(3)
|
||||
time.sleep(1)
|
||||
raise AssertionError(f"Failed to connect to probe after 30s: {last_error}")
|
||||
Reference in New Issue
Block a user