diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..04bc3c2 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,16 @@ +language: python +python: +# - "2.6" + - "2.7" +# - "3.3" +# - "3.4" +# - "3.5" + - "3.6" +# - "3.6-dev" # 3.6 development branch +# - "3.7-dev" # 3.7 development branch +# command to install dependencies +#install: +# - pip install -r requirements.txt +# command to run tests +script: + - pytest diff --git a/README.md b/README.md index 2b9b03d..df83071 100644 --- a/README.md +++ b/README.md @@ -1 +1,9 @@ -# python-lsp \ No newline at end of file +# pylspclient +The library implements a LSP client in Python. + +[![Build Status](https://travis-ci.org/yeger00/pylspclient.svg?branch=master)](https://travis-ci.org/yeger00/pylspclient) + +# Run the tests +``` +tox +``` diff --git a/examples/clangd.py b/examples/clangd.py index cc7898a..ec73c20 100644 --- a/examples/clangd.py +++ b/examples/clangd.py @@ -3,13 +3,10 @@ import subprocess if __name__ == "__main__": - # clangd_path = "/usr/bin/clangd-6.0" - clangd_path = "/home/osboxes/projects/build/bin/clangd" + clangd_path = "/usr/bin/clangd-6.0" p = subprocess.Popen(clangd_path, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) json_rpc_endpoint = pylspclient.JsonRpcEndpoint(p.stdin, p.stdout) - # Working with socket: - # sock_fd = sock.makefile() - # json_rpc_endpoint = JsonRpcEndpoint(sock_fd, stext_document_res = lpc_client.send_notification(text_document_message)ock_fd) + # To work with socket: sock_fd = sock.makefile() lsp_endpoint = pylspclient.LspEndpoint(json_rpc_endpoint) lsp_client = pylspclient.LspClient(lsp_endpoint) @@ -125,12 +122,12 @@ if __name__ == "__main__": 25, 26]}},'workspaceEdit': {'documentChanges': True}, 'workspaceFolders': True}} - workspace_folders = [{'name': 'python-lsp', 'uri': 'file:///home/osboxes/projects/ctest'}] - root_uri = 'file:///home/osboxes/projects/ctest' + workspace_folders = [{'name': 'python-lsp', 'uri': 'file:///path/to/dir'}] + root_uri = 'file:///path/to/dir' print(lsp_client.initialize(p.pid, None, root_uri, None, capabilities, "off", workspace_folders)) print(lsp_client.initialized()) - file_path = "/home/osboxes/projects/ctest/test.c" + file_path = "/path/to/dir/file.c" uri = "file://" + file_path text = open(file_path, "r").read() languageId = pylspclient.lsp_structs.LANGUAGE_IDENTIFIER.C diff --git a/pylspclient/json_rpc_endpoint.py b/pylspclient/json_rpc_endpoint.py index a117360..df2a53a 100644 --- a/pylspclient/json_rpc_endpoint.py +++ b/pylspclient/json_rpc_endpoint.py @@ -4,7 +4,7 @@ import re from pylspclient import lsp_structs import threading -JSON_RPC_REQ_FORMAT = "Content-Length: {json_string_len}\r\n\r\n{json_string}\r\n\r\n" +JSON_RPC_REQ_FORMAT = "Content-Length: {json_string_len}\r\n\r\n{json_string}" JSON_RPC_RES_REGEX = "Content-Length: ([0-9]*)\r\n" # TODO: add content-type @@ -30,11 +30,19 @@ class JsonRpcEndpoint(object): @staticmethod def __add_header(json_string): + ''' + Adds a header for the given json string + + :param str json_string: The string + :return: the string with the header + ''' return JSON_RPC_REQ_FORMAT.format(json_string_len=len(json_string), json_string=json_string) def send_request(self, message): ''' + Sends the given message. + :param dict message: The message to send. ''' json_string = json.dumps(message, cls=MyEncoder) @@ -46,27 +54,27 @@ class JsonRpcEndpoint(object): def recv_response(self): - ''' + ''' + Recives a message. + + :return: a message ''' with self.read_lock: line = self.stdout.readline() - if line is None: + if not line: return None + print(line) line = line.decode() # TODO: handle content type as well. match = re.match(JSON_RPC_RES_REGEX, line) if match is None or not match.groups(): - # TODO: handle - print("error1: ", line) - return None + raise RuntimeError("Bad header: " + line) size = int(match.groups()[0]) line = self.stdout.readline() - if line is None: + if not line: return None line = line.decode() if line != "\r\n": - # TODO: handle - print("error2") - return None + raise RuntimeError("Bad header: missing newline") jsonrpc_res = self.stdout.read(size) return json.loads(jsonrpc_res) diff --git a/pylspclient/lsp_client.py b/pylspclient/lsp_client.py index d6912b6..d014b87 100644 --- a/pylspclient/lsp_client.py +++ b/pylspclient/lsp_client.py @@ -2,6 +2,11 @@ from pylspclient import lsp_structs class LspClient(object): def __init__(self, lsp_endpoint): + """ + Constructs a new LspClient instance. + + :param lsp_endpoint: TODO + """ self.lsp_endpoint = lsp_endpoint diff --git a/setup.py b/setup.py index 33bb6b7..cd47661 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ class PyTest(TestCommand): def initialize_options(self): TestCommand.initialize_options(self) - self.pytest_args = "" + self.pytest_args = [] def run_tests(self): # import here, cause outside the eggs aren't loaded diff --git a/tests/test_json_rpc_endpoint.py b/tests/test_json_rpc_endpoint.py index d15e259..39b4642 100644 --- a/tests/test_json_rpc_endpoint.py +++ b/tests/test_json_rpc_endpoint.py @@ -1,5 +1,7 @@ +import os +#from pytest_mock import mocker +import pytest import pylspclient -from pytest_mock import mocker class StdinMock(object): @@ -10,36 +12,72 @@ class StdinMock(object): pass -def test_sanity(mocker): - stdin_mock = StdinMock(); - mocker.patch.object(stdin_mock, 'write') - json_rpc_endpoint = pylspclient.JsonRpcEndpoint(stdin_mock, None) +class StdoutMock(object): + def readline(self): + pass + + def read(self): + pass + +JSON_RPC_RESULT_LIST = [ + 'Content-Length: 40\r\n\r\n{"key_str": "some_string", "key_num": 1}'.encode("utf-8"), + 'Content-Length: 40\r\n\r\n{"key_num": 1, "key_str": "some_string"}'.encode("utf-8") +] + +def test_send_sanity(): + pipein, pipeout = os.pipe() + pipein = os.fdopen(pipein, "rb") + pipeout = os.fdopen(pipeout, "wb") + json_rpc_endpoint = pylspclient.JsonRpcEndpoint(pipeout, None) json_rpc_endpoint.send_request({"key_num":1, "key_str":"some_string"}) - stdin_mock.write.assert_called() - - assert(stdin_mock.write.call_args not in [ - '''Content-Length: 40\r\n\r\n{"key_str": "some_string", "key_num": 1}\r\n\r\n'''.encode("utf-8"), - '''Content-Length: 40\r\n\r\n{"key_num": 1, "key_str": "some_string"}\r\n\r\n'''.encode("utf-8") - ]) + result = pipein.read(len(JSON_RPC_RESULT_LIST[0])) + assert(result in JSON_RPC_RESULT_LIST) -def test_class(mocker): +def test_send_class(): class RpcClass(object): def __init__(self, value_num, value_str): self.key_num = value_num self.key_str = value_str - - stdin_mock = StdinMock(); - mocker.patch.object(stdin_mock, 'write') - json_rpc_endpoint = pylspclient.JsonRpcEndpoint(stdin_mock, None) + + pipein, pipeout = os.pipe() + pipein = os.fdopen(pipein, "rb") + pipeout = os.fdopen(pipeout, "wb") + json_rpc_endpoint = pylspclient.JsonRpcEndpoint(pipeout, None) json_rpc_endpoint.send_request(RpcClass(1, "some_string")) - stdin_mock.write.assert_called() + result = pipein.read(len(JSON_RPC_RESULT_LIST[0])) + assert(result in JSON_RPC_RESULT_LIST) + + +def test_recv_sanity(): + pipein, pipeout = os.pipe() + pipein = os.fdopen(pipein, "rb") + pipeout = os.fdopen(pipeout, "wb") + json_rpc_endpoint = pylspclient.JsonRpcEndpoint(None, pipein) + pipeout.write('Content-Length: 40\r\n\r\n{"key_str": "some_string", "key_num": 1}'.encode("utf-8")) + pipeout.flush() + result = json_rpc_endpoint.recv_response() + assert({"key_num":1, "key_str":"some_string"} == result) + + +def test_recv_wrong_header(): + pipein, pipeout = os.pipe() + pipein = os.fdopen(pipein, "rb") + pipeout = os.fdopen(pipeout, "wb") + json_rpc_endpoint = pylspclient.JsonRpcEndpoint(None, pipein) + pipeout.write('Contentength: 40\r\n\r\n{"key_str": "some_string", "key_num": 1}'.encode("utf-8")) + pipeout.flush() + with pytest.raises(RuntimeError): + result = json_rpc_endpoint.recv_response() + + +def test_recv_close_pipe(): + pipein, pipeout = os.pipe() + pipein = os.fdopen(pipein, "rb") + pipeout = os.fdopen(pipeout, "wb") + json_rpc_endpoint = pylspclient.JsonRpcEndpoint(None, pipein) + pipeout.close() + result = json_rpc_endpoint.recv_response() + assert(result is None) - assert(stdin_mock.write.call_args not in [ - '''Content-Length: 40\r\n\r\n{"key_str": "some_string", "key_num": 1}\r\n\r\n'''.encode("utf-8"), - '''Content-Length: 40\r\n\r\n{"key_num": 1, "key_str": "some_string"}\r\n\r\n'''.encode("utf-8") - ]) -# content of test_sample.py -def func(x): - return x + 1 diff --git a/tox.ini b/tox.ini index d91cfea..5c6210d 100644 --- a/tox.ini +++ b/tox.ini @@ -2,8 +2,5 @@ envlist = py27,py36 [testenv] -deps = - pytest - pytest_mock commands = - pytest + python setup.py test