Speed up workspace tests and parallelize make test

make test was dominated by teardown-heavy workspace integration tests, not by coverage overhead. Service shutdown was treating zombie processes as live, which forced repeated timeout waits, and one shell test was leaving killpg monkeypatched during cleanup, which made shell close paths burn the full wait budget.\n\nTreat Linux zombie pids as stopped in the workspace manager so service teardown completes promptly. Restore the real killpg implementation before shell test cleanup so the shell close path no longer pays the artificial timeout. Also isolate sys.argv in the runtime-network-check main() test so parallel pytest flags do not leak into argparse-based tests.\n\nAdd pytest-xdist to the dev environment and run make test with pytest -n auto by default so available cores are used automatically during local iteration.\n\nValidation:\n- uv lock\n- targeted hot-spot pytest rerun after the fix dropped the worst tests from roughly 10-21s each to sub-second timings\n- UV_CACHE_DIR=.uv-cache make check\n- UV_CACHE_DIR=.uv-cache make dist-check
This commit is contained in:
Thales Maciel 2026-03-13 13:04:59 -03:00
parent d05fba6c15
commit cc5f566bcc
7 changed files with 60 additions and 2 deletions

View file

@ -1,5 +1,6 @@
PYTHON ?= uv run python
UV_CACHE_DIR ?= .uv-cache
PYTEST_FLAGS ?= -n auto
OLLAMA_BASE_URL ?= http://localhost:11434/v1
OLLAMA_MODEL ?= llama3.2:3b
OLLAMA_DEMO_FLAGS ?=
@ -27,7 +28,7 @@ help:
' lint Run Ruff lint checks' \
' format Run Ruff formatter' \
' typecheck Run mypy' \
' test Run pytest' \
' test Run pytest in parallel when multiple cores are available' \
' check Run lint, typecheck, and tests' \
' dist-check Smoke-test the installed pyro CLI and environment UX' \
' pypi-publish Build, validate, and upload the package to PyPI' \
@ -76,7 +77,7 @@ typecheck:
uv run mypy
test:
uv run pytest
uv run pytest $(PYTEST_FLAGS)
check: lint typecheck test

View file

@ -67,6 +67,7 @@ dev = [
"pre-commit>=4.5.1",
"pytest>=9.0.2",
"pytest-cov>=7.0.0",
"pytest-xdist>=3.8.0",
"ruff>=0.15.4",
]

View file

@ -1589,6 +1589,23 @@ def _stop_process_group(pid: int, *, wait_seconds: int = 5) -> tuple[bool, bool]
return True, True
def _linux_process_state(pid: int) -> str | None:
stat_path = Path("/proc") / str(pid) / "stat"
try:
raw_stat = stat_path.read_text(encoding="utf-8", errors="replace").strip()
except OSError:
return None
if raw_stat == "":
return None
closing_paren = raw_stat.rfind(")")
if closing_paren == -1:
return None
suffix = raw_stat[closing_paren + 2 :]
if suffix == "":
return None
return suffix.split(" ", 1)[0]
def _run_service_probe_command(
cwd: Path,
command: str,
@ -1962,6 +1979,11 @@ def _ensure_no_symlink_parents(root: Path, target_path: Path, member_name: str)
def _pid_is_running(pid: int | None) -> bool:
if pid is None:
return False
process_state = _linux_process_state(pid)
if process_state == "Z":
return False
if process_state is not None:
return True
try:
os.kill(pid, 0)
except ProcessLookupError:

View file

@ -1,5 +1,7 @@
from __future__ import annotations
import sys
import pytest
import pyro_mcp.runtime_network_check as runtime_network_check
@ -43,6 +45,7 @@ def test_network_check_uses_network_enabled_manager(monkeypatch: pytest.MonkeyPa
def test_network_check_main_fails_on_unsuccessful_command(
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
) -> None:
monkeypatch.setattr(sys, "argv", ["runtime-network-check"])
monkeypatch.setattr(
runtime_network_check,
"run_network_check",

View file

@ -2937,6 +2937,11 @@ def test_workspace_service_process_group_helpers(monkeypatch: pytest.MonkeyPatch
assert kill_calls == [signal.SIGTERM, signal.SIGKILL]
def test_pid_is_running_treats_zombies_as_stopped(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(vm_manager_module, "_linux_process_state", lambda _pid: "Z")
assert vm_manager_module._pid_is_running(123) is False # noqa: SLF001
def test_workspace_service_probe_and_refresh_helpers(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:

View file

@ -154,6 +154,7 @@ def test_workspace_shells_write_and_signal_runtime_errors(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
real_killpg = os.killpg
session = create_local_shell(
workspace_id="workspace-6",
shell_id="shell-6",
@ -189,6 +190,7 @@ def test_workspace_shells_write_and_signal_runtime_errors(
monkeypatch.setattr("pyro_mcp.workspace_shells.os.killpg", _raise_killpg)
with pytest.raises(RuntimeError, match="not running"):
session.send_signal("INT")
monkeypatch.setattr("pyro_mcp.workspace_shells.os.killpg", real_killpg)
finally:
try:
session.close()

24
uv.lock generated
View file

@ -275,6 +275,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" },
]
[[package]]
name = "execnet"
version = "2.1.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/bf/89/780e11f9588d9e7128a3f87788354c7946a9cbb1401ad38a48c4db9a4f07/execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd", size = 166622, upload-time = "2025-11-12T09:56:37.75Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec", size = 40708, upload-time = "2025-11-12T09:56:36.333Z" },
]
[[package]]
name = "filelock"
version = "3.25.0"
@ -718,6 +727,7 @@ dev = [
{ name = "pre-commit" },
{ name = "pytest" },
{ name = "pytest-cov" },
{ name = "pytest-xdist" },
{ name = "ruff" },
]
@ -730,6 +740,7 @@ dev = [
{ name = "pre-commit", specifier = ">=4.5.1" },
{ name = "pytest", specifier = ">=9.0.2" },
{ name = "pytest-cov", specifier = ">=7.0.0" },
{ name = "pytest-xdist", specifier = ">=3.8.0" },
{ name = "ruff", specifier = ">=0.15.4" },
]
@ -763,6 +774,19 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" },
]
[[package]]
name = "pytest-xdist"
version = "3.8.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "execnet" },
{ name = "pytest" },
]
sdist = { url = "https://files.pythonhosted.org/packages/78/b4/439b179d1ff526791eb921115fca8e44e596a13efeda518b9d845a619450/pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1", size = 88069, upload-time = "2025-07-01T13:30:59.346Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88", size = 46396, upload-time = "2025-07-01T13:30:56.632Z" },
]
[[package]]
name = "python-discovery"
version = "1.1.0"