Getting Started¶
This guide will walk you through the process of writing your first test case using pytest-lsp
.
If you have not done so already, you can install the pytest-lsp
package using pip:
pip install pytest-lsp
A Simple Language Server¶
Before we can write any tests, we need a language server to test!
For the purposes of this example we’ll write a simple language server in Python using the pygls library but note that pytest-lsp
should work with language servers written in any language or framework.
The following server implements the textDocument/completion
method
from lsprotocol.types import TEXT_DOCUMENT_COMPLETION
from lsprotocol.types import CompletionItem
from lsprotocol.types import CompletionParams
from pygls.server import LanguageServer
server = LanguageServer("hello-world", "v1")
@server.feature(TEXT_DOCUMENT_COMPLETION)
def completion(ls: LanguageServer, params: CompletionParams):
return [
CompletionItem(label="hello"),
CompletionItem(label="world"),
]
Copy and paste the above code into a file named server.py
.
A Simple Test Case¶
Now we can go ahead and test it.
Copy the following code and save it into a file named test_server.py
, in the same directory as the server.py
file you created in the previous step.
import sys
from lsprotocol.types import ClientCapabilities
from lsprotocol.types import CompletionList
from lsprotocol.types import CompletionParams
from lsprotocol.types import InitializeParams
from lsprotocol.types import Position
from lsprotocol.types import TextDocumentIdentifier
import pytest
import pytest_lsp
from pytest_lsp import ClientServerConfig
from pytest_lsp import LanguageClient
@pytest_lsp.fixture(
config=ClientServerConfig(server_command=[sys.executable, "server.py"]),
)
async def client(lsp_client: LanguageClient):
# Setup
params = InitializeParams(capabilities=ClientCapabilities())
await lsp_client.initialize_session(params)
yield
# Teardown
await lsp_client.shutdown_session()
This creates a pytest fixture named client
, it uses the given server_command
to automatically launch the server in a background process and connect it to a LanguageClient
instance.
The setup code (everything before the yield
) statement is executed before any tests run, calling initialize_session()
on the client to open the LSP session.
Once all test cases have been called, the code after the yield
statement will be called to shutdown the server and close the session
With the framework in place, we can go ahead and define our first test case
@pytest.mark.asyncio
async def test_completions(client: LanguageClient):
"""Ensure that the server implements completions correctly."""
results = await client.text_document_completion_async(
params=CompletionParams(
position=Position(line=1, character=0),
text_document=TextDocumentIdentifier(uri="file:///path/to/file.txt"),
)
)
assert results is not None
if isinstance(results, CompletionList):
items = results.items
else:
items = results
labels = [item.label for item in items]
assert labels == ["hello", "world"]
All that’s left is to run the test suite!
$ 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
asyncio: mode=Mode.AUTO
collected 1 item
test_server.py E [100%]
====================================================== ERRORS =======================================================
________________________________________ ERROR at setup of test_completions _________________________________________
lsp_client = <pytest_lsp.client.LanguageClient object at 0x7fa2c4168310>
@pytest_lsp.fixture(
config=ClientServerConfig(server_command=[sys.executable, "server.py"]),
)
async def client(lsp_client: LanguageClient):
# Setup
params = InitializeParams(capabilities=ClientCapabilities())
> 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
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
return await self.protocol.send_request_async("initialize", params)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <pytest_lsp.protocol.LanguageClientProtocol object at 0x7fa2c417a190>, 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)
async def send_request_async(self, method, params=None):
> result = await super().send_request_async(method, params)
E asyncio.exceptions.CancelledError: Server process exited with return 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 ==================================================
We forgot to start the server!
Add the following to the bottom of server.py
.
if __name__ == "__main__":
server.start_io()
Let’s try again
$ pytest
================================================ test session starts =================================================
platform linux -- Python 3.11.2, pytest-7.2.0, pluggy-1.0.0
rootdir: /var/home/user/Projects/lsp-devtools/lib/pytest-lsp, configfile: pyproject.toml
plugins: typeguard-2.13.3, asyncio-0.20.2, lsp-0.2.1
asyncio: mode=Mode.AUTO
collected 1 item
test_server.py . [100%]
================================================= 1 passed in 0.96s ==================================================
Much better!