126 lines
4.2 KiB
Python
126 lines
4.2 KiB
Python
"""Multi-root servers share ONE process across project roots.
|
|
|
|
A profiled session with subagents editing across ~30 git worktrees ran
|
|
30-60 pyright processes. Pyright supports multi-root workspaces, so
|
|
the service keys such clients by ``server_id`` alone and attaches each
|
|
new root via ``workspace/didChangeWorkspaceFolders``. Single-root
|
|
servers keep the one-client-per-root behaviour.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from agent.lsp.manager import LSPService
|
|
from agent.lsp.servers import SERVERS, ServerContext, ServerDef, SpawnSpec
|
|
from agent.lsp.workspace import clear_cache
|
|
|
|
MOCK_SERVER = str(Path(__file__).parent / "_mock_lsp_server.py")
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _clear_workspace_cache():
|
|
clear_cache()
|
|
yield
|
|
clear_cache()
|
|
|
|
|
|
def _make_repo(tmp_path: Path, name: str) -> Path:
|
|
repo = tmp_path / name
|
|
repo.mkdir()
|
|
(repo / ".git").mkdir()
|
|
(repo / "pyproject.toml").write_text("", encoding="utf-8")
|
|
(repo / "x.py").write_text("print('hi')\n", encoding="utf-8")
|
|
return repo
|
|
|
|
|
|
@pytest.fixture
|
|
def two_repos(tmp_path):
|
|
return _make_repo(tmp_path, "repo-a"), _make_repo(tmp_path, "repo-b")
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_pyright(monkeypatch, tmp_path):
|
|
"""Install the mock as ``pyright``; yield (spawn_count, folders_log, set_multi_root)."""
|
|
idx = next(i for i, s in enumerate(SERVERS) if s.server_id == "pyright")
|
|
original = SERVERS[idx]
|
|
spawns = {"value": 0}
|
|
folders_log = tmp_path / "folders.jsonl"
|
|
|
|
def _spawn(root: str, ctx: ServerContext) -> SpawnSpec:
|
|
spawns["value"] += 1
|
|
return SpawnSpec(
|
|
command=[sys.executable, MOCK_SERVER],
|
|
workspace_root=root,
|
|
cwd=root,
|
|
env={"MOCK_LSP_SCRIPT": "errors", "MOCK_LSP_FOLDERS_LOG": str(folders_log)},
|
|
)
|
|
|
|
def _install(multi_root: bool) -> None:
|
|
SERVERS[idx] = ServerDef(
|
|
server_id="pyright",
|
|
extensions=original.extensions,
|
|
resolve_root=lambda fp, ws: ws,
|
|
build_spawn=_spawn,
|
|
multi_root=multi_root,
|
|
description="mock pyright",
|
|
)
|
|
|
|
yield spawns, folders_log, _install
|
|
SERVERS[idx] = original
|
|
|
|
|
|
def _service() -> LSPService:
|
|
return LSPService(
|
|
enabled=True, wait_mode="document", wait_timeout=3.0, install_strategy="manual"
|
|
)
|
|
|
|
|
|
def test_multi_root_server_shares_one_client_across_roots(two_repos, mock_pyright, monkeypatch):
|
|
repo_a, repo_b = two_repos
|
|
spawns, folders_log, install = mock_pyright
|
|
install(multi_root=True)
|
|
svc = _service()
|
|
try:
|
|
monkeypatch.chdir(str(repo_a))
|
|
diags_a = svc.get_diagnostics_sync(str(repo_a / "x.py"))
|
|
monkeypatch.chdir(str(repo_b))
|
|
diags_b = svc.get_diagnostics_sync(str(repo_b / "x.py"))
|
|
|
|
# Exactly one process; the second root arrived as a folder change.
|
|
assert spawns["value"] == 1
|
|
assert len(svc._clients) == 1
|
|
client = next(iter(svc._clients.values()))
|
|
assert client.workspace_folders == [str(repo_a), str(repo_b)]
|
|
events = [json.loads(line) for line in folders_log.read_text(encoding="utf-8").splitlines()]
|
|
assert [f["uri"] for e in events for f in e["event"]["added"]] == [
|
|
Path(repo_b).as_uri()
|
|
]
|
|
# Diagnostics still resolve per file in both folders.
|
|
assert len(diags_a) == 1 and len(diags_b) == 1
|
|
status = svc.get_status()["clients"][0]
|
|
assert status["workspace_root"] == str(repo_a)
|
|
assert status["workspace_folders"] == [str(repo_a), str(repo_b)]
|
|
finally:
|
|
svc.shutdown()
|
|
|
|
|
|
def test_single_root_server_still_spawns_per_root(two_repos, mock_pyright, monkeypatch):
|
|
repo_a, repo_b = two_repos
|
|
spawns, folders_log, install = mock_pyright
|
|
install(multi_root=False)
|
|
svc = _service()
|
|
try:
|
|
monkeypatch.chdir(str(repo_a))
|
|
svc.get_diagnostics_sync(str(repo_a / "x.py"))
|
|
monkeypatch.chdir(str(repo_b))
|
|
svc.get_diagnostics_sync(str(repo_b / "x.py"))
|
|
assert spawns["value"] == 2
|
|
assert set(svc._clients) == {("pyright", str(repo_a)), ("pyright", str(repo_b))}
|
|
assert not folders_log.exists()
|
|
finally:
|
|
svc.shutdown()
|