mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-16 10:15:17 +00:00
fix(dart): wait for analyzer readiness signal before returning from start()
DartLanguageServer registered handlers for $/analyzerStatus and experimental/serverStatus but both were no-ops, so start() returned as soon as initialized was sent instead of waiting for either signal, unlike every other backend (pyright, basedpyright, rust-analyzer, clojure-lsp, jedi, omnisharp, clangd) that receives the same notifications. A request issued right after activation could return before the workspace scan finished. Gate on a threading.Event set by either notification, bounded by 60s, mirroring pyright_server.py's analysis_complete pattern.
This commit is contained in:
1 parent
813fd98f4f
commit
e1322a3b99
3 files changed
+137
-2
No files matched your search
@@ -1,6 +1,7 @@
|
||||
"""Regression test for Dart analyzer-status notification handling."""
|
||||
|
||||
import logging
|
||||
import threading
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from unittest.mock import patch
|
||||
@@ -61,6 +62,8 @@ def test_analyzer_status_notification_is_handled_after_startup(
|
||||
"_create_language_server_interface",
|
||||
return_value=server_interface,
|
||||
),
|
||||
# No readiness signal is ever sent here, so keep the fallback wait short.
|
||||
patch.object(DartLanguageServer, "_TIMEOUT_FOR_INITIAL_ANALYSIS", 0.05),
|
||||
):
|
||||
server = DartLanguageServer(
|
||||
LanguageServerConfig(ls_id=LanguageServerId.DART),
|
||||
@@ -73,3 +76,104 @@ def test_analyzer_status_notification_is_handled_after_startup(
|
||||
server_interface.receive_notification("$/analyzerStatus", {"isAnalyzing": True})
|
||||
|
||||
assert "Unhandled method '$/analyzerStatus'" not in caplog.messages
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("method", "params"),
|
||||
[
|
||||
("experimental/serverStatus", {"quiescent": True}),
|
||||
("$/analyzerStatus", {"isAnalyzing": False}),
|
||||
],
|
||||
)
|
||||
def test_start_waits_for_analysis_readiness_signal(
|
||||
tmp_path: Path,
|
||||
method: str,
|
||||
params: dict[str, Any],
|
||||
) -> None:
|
||||
"""`start()` must block until the Dart analysis server reports it has finished its
|
||||
initial workspace scan, instead of returning as soon as `initialized` is sent.
|
||||
"""
|
||||
settings = SolidLSPSettings(
|
||||
solidlsp_dir=str(tmp_path / "global"),
|
||||
project_data_path=str(tmp_path / "project"),
|
||||
ls_specific_settings={LanguageServerId.DART: {}},
|
||||
)
|
||||
server_interface = _FakeLanguageServerInterface()
|
||||
|
||||
with (
|
||||
patch.object(
|
||||
DartLanguageServer,
|
||||
"_setup_runtime_dependencies",
|
||||
return_value=str(tmp_path / "dart-sdk"),
|
||||
),
|
||||
patch.object(
|
||||
DartLanguageServer,
|
||||
"_create_language_server_interface",
|
||||
return_value=server_interface,
|
||||
),
|
||||
patch.object(DartLanguageServer, "_TIMEOUT_FOR_INITIAL_ANALYSIS", 2.0, create=True),
|
||||
):
|
||||
server = DartLanguageServer(
|
||||
LanguageServerConfig(ls_id=LanguageServerId.DART),
|
||||
str(tmp_path),
|
||||
settings,
|
||||
)
|
||||
|
||||
start_thread = threading.Thread(target=server.start)
|
||||
start_thread.start()
|
||||
try:
|
||||
# No readiness signal has been sent yet: start() must still be blocked.
|
||||
start_thread.join(timeout=0.3)
|
||||
assert start_thread.is_alive(), "start() returned before any readiness signal was received"
|
||||
|
||||
server_interface.receive_notification(method, params)
|
||||
|
||||
start_thread.join(timeout=2.0)
|
||||
assert not start_thread.is_alive(), f"start() did not resume after {method} {params}"
|
||||
finally:
|
||||
if start_thread.is_alive():
|
||||
server_interface.receive_notification("experimental/serverStatus", {"quiescent": True})
|
||||
start_thread.join(timeout=2.0)
|
||||
|
||||
|
||||
def test_start_proceeds_after_readiness_timeout_if_no_signal_arrives(
|
||||
tmp_path: Path,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""If neither readiness notification ever arrives, start() must still return once
|
||||
`_TIMEOUT_FOR_INITIAL_ANALYSIS` elapses, rather than blocking forever.
|
||||
"""
|
||||
settings = SolidLSPSettings(
|
||||
solidlsp_dir=str(tmp_path / "global"),
|
||||
project_data_path=str(tmp_path / "project"),
|
||||
ls_specific_settings={LanguageServerId.DART: {}},
|
||||
)
|
||||
server_interface = _FakeLanguageServerInterface()
|
||||
|
||||
with (
|
||||
patch.object(
|
||||
DartLanguageServer,
|
||||
"_setup_runtime_dependencies",
|
||||
return_value=str(tmp_path / "dart-sdk"),
|
||||
),
|
||||
patch.object(
|
||||
DartLanguageServer,
|
||||
"_create_language_server_interface",
|
||||
return_value=server_interface,
|
||||
),
|
||||
patch.object(DartLanguageServer, "_TIMEOUT_FOR_INITIAL_ANALYSIS", 0.2, create=True),
|
||||
):
|
||||
server = DartLanguageServer(
|
||||
LanguageServerConfig(ls_id=LanguageServerId.DART),
|
||||
str(tmp_path),
|
||||
settings,
|
||||
)
|
||||
|
||||
start_thread = threading.Thread(target=server.start)
|
||||
with caplog.at_level(logging.WARNING):
|
||||
start_thread.start()
|
||||
start_thread.join(timeout=2.0)
|
||||
|
||||
assert not start_thread.is_alive(), "start() never returned despite the readiness timeout elapsing"
|
||||
assert any("Timeout waiting for dart-language-server analysis completion" in m for m in caplog.messages)
|
||||
assert server.analysis_complete.is_set()
|
||||
Reference in new issue
Block a user