Add server tests
This commit is contained in:
@@ -23,8 +23,8 @@ class CMakeLanguageServer(LanguageServer):
|
|||||||
_parser: ListParser
|
_parser: ListParser
|
||||||
_api: API
|
_api: API
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, *args):
|
||||||
super().__init__()
|
super().__init__(*args)
|
||||||
|
|
||||||
self._parser = ListParser()
|
self._parser = ListParser()
|
||||||
self._api = None
|
self._api = None
|
||||||
|
|||||||
@@ -1,9 +1,19 @@
|
|||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import pprint
|
||||||
|
from subprocess import PIPE, run
|
||||||
|
from threading import Thread
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from pygls import features
|
||||||
|
from pygls.server import LanguageServer
|
||||||
|
|
||||||
|
from cmake_language_server.server import CMakeLanguageServer
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def cmake_build(shared_datadir):
|
def cmake_build(shared_datadir):
|
||||||
from subprocess import run, PIPE
|
|
||||||
source = shared_datadir / 'cmake'
|
source = shared_datadir / 'cmake'
|
||||||
build = source / 'build'
|
build = source / 'build'
|
||||||
build.mkdir()
|
build.mkdir()
|
||||||
@@ -13,11 +23,37 @@ def cmake_build(shared_datadir):
|
|||||||
stderr=PIPE,
|
stderr=PIPE,
|
||||||
universal_newlines=True)
|
universal_newlines=True)
|
||||||
if p.returncode != 0:
|
if p.returncode != 0:
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import pprint
|
|
||||||
logging.error('env:\n' + pprint.pformat(os.environ))
|
logging.error('env:\n' + pprint.pformat(os.environ))
|
||||||
logging.error('stdout:\n' + p.stdout)
|
logging.error('stdout:\n' + p.stdout)
|
||||||
logging.error('stderr:\n' + p.stderr)
|
logging.error('stderr:\n' + p.stderr)
|
||||||
raise RuntimeError("CMake failed")
|
raise RuntimeError("CMake failed")
|
||||||
yield build
|
yield build
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def client_server():
|
||||||
|
c2s_r, c2s_w = os.pipe()
|
||||||
|
s2c_r, s2c_w = os.pipe()
|
||||||
|
|
||||||
|
def start(ls: LanguageServer, fdr, fdw):
|
||||||
|
# TODO: better patch is needed
|
||||||
|
# disable `close()` to avoid error messages
|
||||||
|
close = ls.loop.close
|
||||||
|
ls.loop.close = lambda: None
|
||||||
|
ls.start_io(os.fdopen(fdr, 'rb'), os.fdopen(fdw, 'wb'))
|
||||||
|
ls.loop.close = close
|
||||||
|
|
||||||
|
server = CMakeLanguageServer(asyncio.new_event_loop())
|
||||||
|
server_thread = Thread(target=start, args=(server, c2s_r, s2c_w))
|
||||||
|
server_thread.start()
|
||||||
|
|
||||||
|
client = LanguageServer(asyncio.new_event_loop())
|
||||||
|
client_thread = Thread(target=start, args=(client, s2c_r, c2s_w))
|
||||||
|
client_thread.start()
|
||||||
|
|
||||||
|
yield client, server
|
||||||
|
|
||||||
|
client.send_notification(features.EXIT)
|
||||||
|
server.send_notification(features.EXIT)
|
||||||
|
server_thread.join()
|
||||||
|
client_thread.join()
|
||||||
|
|||||||
130
tests/test_server.py
Normal file
130
tests/test_server.py
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
from concurrent import futures
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from pygls.features import (COMPLETION, FORMATTING, HOVER, INITIALIZE,
|
||||||
|
TEXT_DOCUMENT_DID_OPEN)
|
||||||
|
from pygls.server import LanguageServer
|
||||||
|
from pygls.types import (CompletionContext, CompletionParams,
|
||||||
|
CompletionTriggerKind, DidOpenTextDocumentParams,
|
||||||
|
DocumentFormattingParams, FormattingOptions,
|
||||||
|
InitializeParams, Position, TextDocumentIdentifier,
|
||||||
|
TextDocumentItem, TextDocumentPositionParams)
|
||||||
|
|
||||||
|
CALL_TIMEOUT = 2
|
||||||
|
|
||||||
|
|
||||||
|
def _init(client: LanguageServer, root: Path):
|
||||||
|
retry = 3
|
||||||
|
while retry > 0:
|
||||||
|
try:
|
||||||
|
client.lsp.send_request(
|
||||||
|
INITIALIZE,
|
||||||
|
InitializeParams(
|
||||||
|
process_id=1234, root_uri=root.as_uri(),
|
||||||
|
capabilities=None)).result(timeout=CALL_TIMEOUT)
|
||||||
|
except futures.TimeoutError:
|
||||||
|
retry -= 1
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
def _open(client: LanguageServer, path: Path, text: Optional[str] = None):
|
||||||
|
if text is None:
|
||||||
|
with open(path) as fp:
|
||||||
|
text = fp.read()
|
||||||
|
|
||||||
|
client.lsp.notify(
|
||||||
|
TEXT_DOCUMENT_DID_OPEN,
|
||||||
|
DidOpenTextDocumentParams(
|
||||||
|
TextDocumentItem(path.as_uri(), 'cmake', 1, text)))
|
||||||
|
|
||||||
|
|
||||||
|
def test_initialize(client_server, datadir):
|
||||||
|
client, server = client_server
|
||||||
|
|
||||||
|
assert server._api is None
|
||||||
|
_init(client, datadir)
|
||||||
|
assert server._api is not None
|
||||||
|
|
||||||
|
|
||||||
|
def test_completions_invoked(client_server, datadir):
|
||||||
|
client, server = client_server
|
||||||
|
_init(client, datadir)
|
||||||
|
path = datadir / 'CMakeLists.txt'
|
||||||
|
_open(client, path, 'projec')
|
||||||
|
response = client.lsp.send_request(
|
||||||
|
COMPLETION,
|
||||||
|
CompletionParams(TextDocumentIdentifier(path.as_uri()), Position(
|
||||||
|
0, 6), CompletionContext(
|
||||||
|
CompletionTriggerKind.Invoked))).result(timeout=CALL_TIMEOUT)
|
||||||
|
item = next(filter(lambda x: x.label == 'project', response.items), None)
|
||||||
|
assert item is not None
|
||||||
|
assert '<PROJECT-NAME>' in item.documentation
|
||||||
|
|
||||||
|
|
||||||
|
def test_completions_triggercharacter_variable(client_server, datadir):
|
||||||
|
client, server = client_server
|
||||||
|
_init(client, datadir)
|
||||||
|
path = datadir / 'CMakeLists.txt'
|
||||||
|
_open(client, path, '${')
|
||||||
|
response = client.lsp.send_request(
|
||||||
|
COMPLETION,
|
||||||
|
CompletionParams(
|
||||||
|
TextDocumentIdentifier(path.as_uri()), Position(0, 2),
|
||||||
|
CompletionContext(CompletionTriggerKind.TriggerCharacter,
|
||||||
|
'{'))).result(timeout=CALL_TIMEOUT)
|
||||||
|
assert 'PROJECT_VERSION' in [x.label for x in response.items]
|
||||||
|
|
||||||
|
|
||||||
|
def test_completions_triggercharacter_module(client_server, datadir):
|
||||||
|
client, server = client_server
|
||||||
|
_init(client, datadir)
|
||||||
|
path = datadir / 'CMakeLists.txt'
|
||||||
|
_open(client, path, 'include(')
|
||||||
|
response = client.lsp.send_request(
|
||||||
|
COMPLETION,
|
||||||
|
CompletionParams(
|
||||||
|
TextDocumentIdentifier(path.as_uri()), Position(0, 8),
|
||||||
|
CompletionContext(CompletionTriggerKind.TriggerCharacter,
|
||||||
|
'('))).result(timeout=CALL_TIMEOUT)
|
||||||
|
assert 'GoogleTest' in [x.label for x in response.items]
|
||||||
|
|
||||||
|
|
||||||
|
def test_completions_triggercharacter_package(client_server, datadir):
|
||||||
|
client, server = client_server
|
||||||
|
_init(client, datadir)
|
||||||
|
path = datadir / 'CMakeLists.txt'
|
||||||
|
_open(client, path, 'find_package(')
|
||||||
|
response = client.lsp.send_request(
|
||||||
|
COMPLETION,
|
||||||
|
CompletionParams(
|
||||||
|
TextDocumentIdentifier(path.as_uri()), Position(0, 13),
|
||||||
|
CompletionContext(CompletionTriggerKind.TriggerCharacter,
|
||||||
|
'('))).result(timeout=CALL_TIMEOUT)
|
||||||
|
assert 'Boost' in [x.label for x in response.items]
|
||||||
|
|
||||||
|
|
||||||
|
def test_formatting(client_server, datadir):
|
||||||
|
client, server = client_server
|
||||||
|
_init(client, datadir)
|
||||||
|
path = datadir / 'CMakeLists.txt'
|
||||||
|
_open(client, path, 'a ( b c ) ')
|
||||||
|
response = client.lsp.send_request(
|
||||||
|
FORMATTING,
|
||||||
|
DocumentFormattingParams(TextDocumentIdentifier(path.as_uri()),
|
||||||
|
FormattingOptions(
|
||||||
|
2, True))).result(timeout=CALL_TIMEOUT)
|
||||||
|
assert response[0].newText == 'a(b c)\n'
|
||||||
|
|
||||||
|
|
||||||
|
def test_hover(client_server, datadir):
|
||||||
|
client, server = client_server
|
||||||
|
_init(client, datadir)
|
||||||
|
path = datadir / 'CMakeLists.txt'
|
||||||
|
_open(client, path, 'project()')
|
||||||
|
response = client.lsp.send_request(
|
||||||
|
HOVER,
|
||||||
|
TextDocumentPositionParams(TextDocumentIdentifier(path.as_uri()),
|
||||||
|
Position())).result(timeout=CALL_TIMEOUT)
|
||||||
|
assert '<PROJECT-NAME>' in response.contents.value
|
||||||
2
tox.ini
2
tox.ini
@@ -16,7 +16,7 @@ passenv = INCLUDE LIB LIBPATH Platform VCTools* VSCMD_* WindowsSDK*
|
|||||||
commands_pre =
|
commands_pre =
|
||||||
poetry install
|
poetry install
|
||||||
commands =
|
commands =
|
||||||
poetry run pytest --cov-report=xml --cov=src -sv tests
|
poetry run pytest --cov-report=term --cov-report=xml --cov=src -sv tests
|
||||||
|
|
||||||
[testenv:lint]
|
[testenv:lint]
|
||||||
commands =
|
commands =
|
||||||
|
|||||||
Reference in New Issue
Block a user