bed4abd106
Two LSP freshness bugs reported by @tobific (#108882, #108881): - `_current_diags_async()` keyed the client lookup by the enclosing workspace root while `_get_or_spawn()` stores single-root servers under `srv.resolve_root(...)` (a nested package.json project). The lookup returned [] for a live client with diagnostics, so the delta baseline was refreshed from nothing. Use the same resolved-root key. - `open_or_change()` published `_DocState.version` only after awaiting the didChange write. A versionless publishDiagnostics read during that await was credited with the OLD version and judged stale once the send resumed. Bump the version before the send; a failed send (swallowed by `_send_notification`) leaves a version nothing satisfies, i.e. "no verdict", which is the existing contract. The mock server gains a push-only `versionless` script so the race is reproducible without a real language server.
51 lines
2.0 KiB
Python
51 lines
2.0 KiB
Python
"""A versionless publishDiagnostics read while didChange is still being written must count as fresh.
|
|
|
|
``open_or_change`` used to bump ``_DocState.version`` only after awaiting the send. Servers that omit
|
|
``version`` are credited with ``doc.version`` at receipt, so a reply that landed during that await was
|
|
tagged with the OLD version and then rejected as stale once the send resumed. The mock replies
|
|
versionless; the paused send wrapper holds the await open until its reply has been read.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import os
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from agent.lsp.client import LSPClient
|
|
|
|
MOCK_SERVER = str(Path(__file__).parent / "_mock_lsp_server.py")
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_versionless_push_read_during_didchange_send_is_fresh(tmp_path, monkeypatch):
|
|
src = tmp_path / "x.py"
|
|
src.write_text("bad\n", encoding="utf-8")
|
|
client = LSPClient(
|
|
server_id="mock-versionless", workspace_root=str(tmp_path),
|
|
command=[sys.executable, MOCK_SERVER], cwd=str(tmp_path),
|
|
env={"MOCK_LSP_SCRIPT": "versionless", "PYTHONPATH": os.environ.get("PYTHONPATH", "")},
|
|
)
|
|
await client.start()
|
|
try:
|
|
first = await client.open_file(str(src), language_id="python")
|
|
assert await client.wait_for_diagnostics(str(src), first, timeout=5)
|
|
real_send = client._send_notification
|
|
|
|
async def send_and_let_reply_land(method, params):
|
|
seen = client._push_counter
|
|
await real_send(method, params)
|
|
if method == "textDocument/didChange":
|
|
while client._push_counter == seen:
|
|
await asyncio.sleep(0.001)
|
|
|
|
monkeypatch.setattr(client, "_send_notification", send_and_let_reply_land)
|
|
src.write_text("clean\n", encoding="utf-8")
|
|
version = await client.open_file(str(src), language_id="python")
|
|
assert await client.wait_for_diagnostics(str(src), version, timeout=1)
|
|
assert client.diagnostics_for(str(src), fresh_only=True) == []
|
|
finally:
|
|
await client.shutdown()
|