diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bfa8851..d6630bd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,7 +10,7 @@ repos: - id: trailing-whitespace - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.4.4 + rev: v0.4.8 hooks: - id: ruff args: [--fix] diff --git a/docs/pytest-lsp/guide/getting-started-fail-output.txt b/docs/pytest-lsp/guide/getting-started-fail-output.txt index 3cfde65..4c06111 100644 --- a/docs/pytest-lsp/guide/getting-started-fail-output.txt +++ b/docs/pytest-lsp/guide/getting-started-fail-output.txt @@ -1,17 +1,18 @@ $ pytest -================================================ test session starts ================================================ -platform linux -- Python 3.11.3, pytest-7.2.0, pluggy-1.0.0 -rootdir: /tmp/pytest-of-alex/pytest-38/test_getting_started_fail0, configfile: tox.ini -plugins: asyncio-0.21.0, typeguard-3.0.2, lsp-0.3.0 +============================= test session starts ============================== +platform linux -- Python 3.11.9, pytest-7.4.4, pluggy-1.5.0 +rootdir: /tmp/pytest-of-alex/pytest-12/test_getting_started_fail0 +configfile: tox.ini +plugins: lsp-0.4.2, asyncio-0.23.8 asyncio: mode=Mode.AUTO collected 1 item -test_server.py E [100%] +test_server.py E [100%] -====================================================== ERRORS ======================================================= -________________________________________ ERROR at setup of test_completions _________________________________________ +==================================== ERRORS ==================================== +______________________ ERROR at setup of test_completions ______________________ -lsp_client = +lsp_client = @pytest_lsp.fixture( config=ClientServerConfig(server_command=[sys.executable, "server.py"]), @@ -22,24 +23,45 @@ lsp_client = > await lsp_client.initialize_session(params) test_server.py:21: -_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ -/var/home/alex/Projects/lsp-devtools/.env/lib64/python3.11/site-packages/pytest_lsp/client.py:137: in initialize_sess -ion +_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ +/var/home/alex/Projects/swyddfa/lsp-devtools/release/lib/pytest-lsp/pytest_lsp/client.py:245: in initialize_session response = await self.initialize_async(params) -/var/home/alex/Projects/lsp-devtools/.env/lib64/python3.11/site-packages/pygls/lsp/client.py:349: in initialize_async +/var/home/alex/.local/share/hatch/env/virtual/pytest-lsp/oa_H1-lS/hatch-test.py3.11-7/lib/python3.11/site-packages/pygls/lsp/client.py:244: in initialize_async return await self.protocol.send_request_async("initialize", params) -_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ +_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ -self = , method = 'initialize' -params = InitializeParams(capabilities=ClientCapabilities(workspace=None, text_document=None, notebook_document=None, - window=No..., root_path=None, root_uri=None, initialization_options=None, trace=None, work_done_token=None, workspac -e_folders=None) +self = +method = 'initialize' +params = InitializeParams(capabilities=ClientCapabilities(workspace=None, text_document=None, notebook_document=None, window=No..., root_path=None, root_uri=None, initialization_options=None, trace=None, work_done_token=None, workspace_folders=None) async def send_request_async(self, method, params=None): + """Wrap pygls' ``send_request_async`` implementation. This will + + - Check the params to see if they're compatible with the client's stated + capabilities + - Check the result to see if it's compatible with the client's stated + capabilities + + Parameters + ---------- + method + The method name of the request to send + + params + The associated parameters to go with the request + + Returns + ------- + Any + The result + """ + check_params_against_client_capabilities( + self._server.capabilities, method, params + ) > result = await super().send_request_async(method, params) -E asyncio.exceptions.CancelledError: Server process exited with return code: 0 +E RuntimeError: Server process 42326 exited with code: 0 -/var/home/alex/Projects/lsp-devtools/.env/lib64/python3.11/site-packages/pytest_lsp/protocol.py:42: CancelledError -============================================== short test summary info ============================================== -ERROR test_server.py::test_completions - asyncio.exceptions.CancelledError: Server process exited with return code: 0 -================================================= 1 error in 1.15s ================================================== +/var/home/alex/Projects/swyddfa/lsp-devtools/release/lib/pytest-lsp/pytest_lsp/protocol.py:81: RuntimeError +=========================== short test summary info ============================ +ERROR test_server.py::test_completions - RuntimeError: Server process 42326 e... +=============================== 1 error in 1.12s =============================== diff --git a/lib/lsp-devtools/hatch.toml b/lib/lsp-devtools/hatch.toml index ec9d4ed..84eabdc 100644 --- a/lib/lsp-devtools/hatch.toml +++ b/lib/lsp-devtools/hatch.toml @@ -13,4 +13,4 @@ extra-dependencies = ["pytest-asyncio"] [envs.hatch-static-analysis] config-path = "ruff_defaults.toml" -dependencies = ["ruff==0.4.4"] +dependencies = ["ruff==0.4.8"] diff --git a/lib/pytest-lsp/changes/173.fix.md b/lib/pytest-lsp/changes/173.fix.md new file mode 100644 index 0000000..68dd5b4 --- /dev/null +++ b/lib/pytest-lsp/changes/173.fix.md @@ -0,0 +1 @@ +The client now waits for the server process to gracefully exit by @OhioDschungel6 diff --git a/lib/pytest-lsp/hatch.toml b/lib/pytest-lsp/hatch.toml index 3449ac5..6f34f4a 100644 --- a/lib/pytest-lsp/hatch.toml +++ b/lib/pytest-lsp/hatch.toml @@ -23,4 +23,4 @@ matrix.pytest.dependencies = [ [envs.hatch-static-analysis] config-path = "ruff_defaults.toml" -dependencies = ["ruff==0.4.4"] +dependencies = ["ruff==0.4.8"] diff --git a/lib/pytest-lsp/pytest_lsp/client.py b/lib/pytest-lsp/pytest_lsp/client.py index 33c6dd9..213bc44 100644 --- a/lib/pytest-lsp/pytest_lsp/client.py +++ b/lib/pytest-lsp/pytest_lsp/client.py @@ -104,16 +104,18 @@ async def stop(self): async def server_exit(self, server: asyncio.subprocess.Process): """Called when the server process exits.""" - logger.debug("Server process exited with code: %s", server.returncode) if self._stop_event.is_set(): return - loop = asyncio.get_running_loop() - loop.call_soon( - cancel_all_tasks, - f"Server process exited with return code: {server.returncode}", - ) + # TODO: Should the upstream base client be doing this? + # Cancel any pending futures. + reason = f"Server process {server.pid} exited with code: {server.returncode}" + + for id_, fut in self.protocol._request_futures.items(): + if not fut.done(): + fut.set_exception(RuntimeError(reason)) + logger.debug("Cancelled pending request '%s': %s", id_, reason) def report_server_error( self, error: Exception, source: Union[PyglsError, JsonRpcException] @@ -261,7 +263,10 @@ async def shutdown_session(self) -> None: return await self.shutdown_async(None) + self.exit(None) + if self._server: + await self._server.wait() async def wait_for_notification(self, method: str): """Block until a notification with the given method is received. diff --git a/lib/pytest-lsp/tests/test_examples.py b/lib/pytest-lsp/tests/test_examples.py index dd95c24..7ee0b17 100644 --- a/lib/pytest-lsp/tests/test_examples.py +++ b/lib/pytest-lsp/tests/test_examples.py @@ -1,5 +1,4 @@ import pathlib -import sys import pytest @@ -102,12 +101,8 @@ def test_getting_started_fail(pytester: pytest.Pytester): results = pytester.runpytest() results.assert_outcomes(errors=1) - if sys.version_info < (3, 9): - message = "E*CancelledError" - else: - message = "E*asyncio.exceptions.CancelledError: Server process exited with return code: 0" - - results.stdout.fnmatch_lines(message) + message = r"E\s+RuntimeError: Server process \d+ exited with code: 0" + results.stdout.re_match_lines(message) def test_generic_rpc(pytester: pytest.Pytester): diff --git a/lib/pytest-lsp/tests/test_plugin.py b/lib/pytest-lsp/tests/test_plugin.py index ecdd233..19a82be 100644 --- a/lib/pytest-lsp/tests/test_plugin.py +++ b/lib/pytest-lsp/tests/test_plugin.py @@ -129,12 +129,8 @@ async def test_capabilities(client): results.assert_outcomes(errors=1) - if sys.version_info < (3, 9): - message = "E*CancelledError" - else: - message = "E*asyncio.exceptions.CancelledError: Server process exited with return code: 0" - - results.stdout.fnmatch_lines(message) + message = r"E\s+RuntimeError: Server process \d+ exited with code: 0" + results.stdout.re_match_lines(message) def test_detect_server_exit_mid_request(pytester: pytest.Pytester): @@ -165,12 +161,8 @@ async def test_capabilities(client): results.assert_outcomes(failed=1, errors=1) - if sys.version_info < (3, 9): - message = "E*CancelledError" - else: - message = "E*asyncio.exceptions.CancelledError: Server process exited with return code: 0" - - results.stdout.fnmatch_lines(message) + message = r"E\s+RuntimeError: Server process \d+ exited with code: 0" + results.stdout.re_match_lines(message) results.stdout.fnmatch_lines("E*RuntimeError: Client has been stopped.") @@ -190,15 +182,9 @@ async def test_capabilities(client): results.assert_outcomes(errors=1) - if sys.version_info < (3, 9): - message = "E*CancelledError" - else: - message = [ - "E*asyncio.exceptions.CancelledError: Server process exited with return code: 1", - "ZeroDivisionError: division by zero", - ] - - results.stdout.fnmatch_lines(message) + message = r"E\s+RuntimeError: Server process \d+ exited with code: 1" + results.stdout.re_match_lines(message) + results.stdout.fnmatch_lines("ZeroDivisionError: division by zero") def test_detect_invalid_json(pytester: pytest.Pytester):