test suite
This commit is contained in:
170
tests/conftest.py
Normal file
170
tests/conftest.py
Normal file
@@ -0,0 +1,170 @@
|
||||
"""pytest fixtures for gammaray-mcp bridge tests."""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from mcp_client import McpClient
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
||||
BRIDGE_EXE = PROJECT_ROOT / "bridge" / "build" / "qml-sg-mcp-bridge"
|
||||
RUN_SCRIPT = PROJECT_ROOT / "bridge" / "run.sh"
|
||||
PROBE_BIN = PROJECT_ROOT / "install-prefix" / "bin" / "gammaray"
|
||||
TEST_QML = Path(os.environ.get("TEST_QML", str(Path.home() / "Sources" / "testqml" / "relayout.qml")))
|
||||
QML_RUNNER = "/usr/lib/qt6/bin/qml"
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
parser.addoption(
|
||||
"--no-probe",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Skip tests that need a running probe",
|
||||
)
|
||||
parser.addoption(
|
||||
"--probe-timeout",
|
||||
type=int,
|
||||
default=15,
|
||||
help="Seconds to wait for probe to be ready",
|
||||
)
|
||||
|
||||
|
||||
def pytest_collection_modifyitems(config, items):
|
||||
if config.getoption("--no-probe"):
|
||||
skip_probe = pytest.mark.skip(reason="Skipping probe-dependent tests (--no-probe)")
|
||||
for item in items:
|
||||
if "probe" in item.keywords:
|
||||
item.add_marker(skip_probe)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def bridge_path() -> Path:
|
||||
"""Path to the bridge executable."""
|
||||
if not BRIDGE_EXE.exists():
|
||||
pytest.skip(f"Bridge not built at {BRIDGE_EXE}")
|
||||
return BRIDGE_EXE
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def run_script_path() -> Path:
|
||||
"""Path to the run.sh wrapper."""
|
||||
if not RUN_SCRIPT.exists():
|
||||
pytest.skip(f"run.sh not found at {RUN_SCRIPT}")
|
||||
return RUN_SCRIPT
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def probe_process(request) -> subprocess.Popen | None:
|
||||
"""Start a GammaRay probe process (session-scoped, one per test run)."""
|
||||
if request.config.getoption("--no-probe"):
|
||||
return None
|
||||
|
||||
if not PROBE_BIN.exists():
|
||||
pytest.skip(f"GammaRay probe binary not found at {PROBE_BIN}")
|
||||
if not TEST_QML.exists():
|
||||
pytest.skip(f"Test QML file not found at {TEST_QML}")
|
||||
if not QML_RUNNER.exists():
|
||||
pytest.skip(f"QML runner not found at {QML_RUNNER}")
|
||||
|
||||
env = os.environ.copy()
|
||||
env["QT_QPA_PLATFORM"] = "offscreen"
|
||||
env["LD_LIBRARY_PATH"] = str(PROJECT_ROOT / "install-prefix" / "lib")
|
||||
|
||||
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,
|
||||
)
|
||||
subprocess.run(
|
||||
["systemctl", "--user", "reset-failed", "gammaray-probe-test.service"],
|
||||
capture_output=True, timeout=5,
|
||||
)
|
||||
|
||||
result = subprocess.run(
|
||||
[
|
||||
"systemd-run", "--user", "--no-block",
|
||||
"--unit=gammaray-probe-test",
|
||||
"--same-dir",
|
||||
"--working-directory", str(TEST_QML.parent),
|
||||
"-E", f"QT_QPA_PLATFORM={env['QT_QPA_PLATFORM']}",
|
||||
"-E", f"LD_LIBRARY_PATH={env['LD_LIBRARY_PATH']}",
|
||||
str(PROBE_BIN),
|
||||
"--inject-only", "--listen", "tcp://127.0.0.1:11732",
|
||||
"--injector", "preload",
|
||||
QML_RUNNER, str(TEST_QML),
|
||||
],
|
||||
capture_output=True, text=True, timeout=10,
|
||||
)
|
||||
if result.returncode == 0:
|
||||
used_systemd = True
|
||||
else:
|
||||
# Fallback: direct spawn
|
||||
proc = subprocess.Popen(
|
||||
[str(PROBE_BIN), "--inject-only", "--listen", "tcp://127.0.0.1:11732",
|
||||
"--injector", "preload", QML_RUNNER, str(TEST_QML)],
|
||||
env=env, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
|
||||
)
|
||||
|
||||
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,
|
||||
)
|
||||
|
||||
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
|
||||
def bridge(run_script_path) -> McpClient:
|
||||
"""Start a fresh bridge instance per test."""
|
||||
c = McpClient(bridge_path=run_script_path)
|
||||
c.initialize()
|
||||
yield c
|
||||
c.close()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def connected_bridge(bridge, probe_process) -> McpClient:
|
||||
"""Bridge connected to a running probe."""
|
||||
if probe_process is None:
|
||||
pytest.skip("No probe available")
|
||||
result = bridge.call_tool("connectProbeDefault")
|
||||
assert isinstance(result, dict), f"connectProbeDefault failed: {result}"
|
||||
assert result.get("connected"), f"Not connected: {result}"
|
||||
return bridge
|
||||
Reference in New Issue
Block a user