Client Capabilities

The initialize request at the start of an LSP session allows the client and server to exchange information about each other. Of particular interest is the ClientCapabilities field which is used to inform the server which parts of the specification the client supports.

Setting this field to the right value pytest-lsp can pretend to be a particular editor at a particular version and check to see if the server adapts accordingly.

Supported Clients

pytest-lsp currently supports the following clients and versions.

Client

Versions

Neovim

0.6.1, 0.7.0, 0.8.0, 0.9.1

Visual Studio Code

1.65.2

Emacs

29.1

The client_capabilities() function can be used to load the capabilities corresponding to a given client name

@pytest_lsp.fixture(
    config=ClientServerConfig(server_command=[sys.executable, "server.py"]),
)
async def client(lsp_client: LanguageClient):
    # Setup
    await lsp_client.initialize_session(
        InitializeParams(
            capabilities=client_capabilities("neovim"),
        ),
    )

    yield

    # Teardown
    await lsp_client.shutdown_session()

Specification Compliance Checks

By setting the client’s capabilities to anything other than ClientCapabilities(), pytest-lsp will automatically enable checks to ensure that the server respects the capabilities published by the client. If any issues are found, pytest-lsp will emit an LspSpecificationWarning.

Tip

For full details on the checks that have been implemented see the pytest_lsp.checks module.

As an example, let’s write a test for the following language server.

@server.feature(TEXT_DOCUMENT_COMPLETION)
def completion(ls: LanguageServer, params: CompletionParams):
    return [
        CompletionItem(
            label="greet",
            insert_text='"Hello, ${1:name}!"$0',
            insert_text_format=InsertTextFormat.Snippet,
        ),
    ]

When it receives a completion request it returns a single item called greet which, when selected, expands into a snippet making it easier to type the sequence "Hello, world!". Let’s write a test to confirm it works as expected.

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 == ["greet"]

Running this test while pretending to be neovim we should see that while it passes, pytest-lsp will emit a warning saying that neovim does not support snippets.

Note

Vanilla Neovim v0.6.1 does not support snippets, though there are many plugins that can be installed to enable support for them.

$ pytest
======================================== test session starts ========================================
platform linux -- Python 3.11.3, pytest-7.2.0, pluggy-1.0.0
rootdir: test_client_capabilities0, configfile: tox.ini
plugins: typeguard-2.13.3, asyncio-0.20.2, lsp-0.2.1
asyncio: mode=Mode.AUTO
collected 1 item

test_server.py .                                                                              [100%]

========================================= warnings summary ==========================================
test_server.py::test_completions
  test_client_capabilities0/test_server.py:35: LspSpecificationWarning: Client does not support snippets.
  assert False
    results = await client.text_document_completion_async(

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=================================== 1 passed, 1 warning in 1.02s ====================================

Strict Checks

You can upgrade these warnings to be errors if you wish by passing -W error::pytest_lsp.LspSpecificationWarning to pytest.

$ pytest -W error::pytest_lsp.LspSpecificationWarning
======================================== test session starts ========================================
platform linux -- Python 3.11.3, pytest-7.2.0, pluggy-1.0.0
rootdir: test_client_capabilities_error0, configfile: tox.ini
plugins: typeguard-2.13.3, asyncio-0.20.2, lsp-0.2.1
asyncio: mode=Mode.AUTO
collected 1 item

test_server.py F                                                                              [100%]

============================================= FAILURES ==============================================
_________________________________________ test_completions __________________________________________

...
        try:
            result_checker(capabilities, result)
        except AssertionError as e:
>           warnings.warn(str(e), LspSpecificationWarning, stacklevel=4)
E           pytest_lsp.checks.LspSpecificationWarning: Client does not support snippets.
E           assert False

/.../site-packages/pytest_lsp/checks.py:73: LspSpecificationWarning
====================================== short test summary info ======================================
FAILED test_server.py::test_completions - pytest_lsp.checks.LspSpecificationWarning: Client does n...
========================================= 1 failed in 1.16s =========================================

Disabling Checks

Alternatively, you can ignore these warnings by passing -W ignore::pytest_lsp.LspSpecificationWarning to pytest.

$ pytest -W ignore::pytest_lsp.LspSpecificationWarning
======================================== test session starts ========================================
platform linux -- Python 3.11.3, pytest-7.2.0, pluggy-1.0.0
rootdir: test_client_capabilities_ignore0, configfile: tox.ini
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 1.02s =========================================

See also

Controlling warnings

Pytest’s documentation on configuring how warnings should be handled

The Warnings Filter

Python’s built in warning filter syntax