Skip to content

Commit a420acc

Browse files
committed
Add integration tests for python language server
* Add integration tests to test that a version of the python LSP server is compatible with the implementation of this plugin. The ambition with the integration tests is to try to test as much as possible end to end. E.g. only mocking the actual vim.* calls. * Minor bugfixes.
1 parent 052bf44 commit a420acc

File tree

8 files changed

+177
-19
lines changed

8 files changed

+177
-19
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,8 @@ plugin/log/*.log.*
44
plugin/log/*.log
55
plugin/vimlsp_servers
66
plugin/supported_servers.json
7+
plugin/tests/.lsp_install_dir
78
*.swp
89
*.cache
10+
*.tox
11+
*.coverage*

README.rst

+14-9
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ the lines of vim- "Language IQ" or "Lingustic Intelligence".
77
Development Status
88
------------------
99

10-
This project is in an early alpha state and may not be suitable for general use. But for those
10+
This project is in a beta state and may not be suitable for general use. But for those
1111
willing to tinker a bit it might be worth a try :-).
1212

1313
The following high level, LSP, features have support:
@@ -20,11 +20,10 @@ The following high level, LSP, features have support:
2020
Todo
2121
----
2222

23-
#. It is now due time to refactor, clean-up and refactor
23+
#. Clean up, refactor
2424
#. Add documentation availible via help in vim
25-
#. Add unit tests (start with all python code)
2625
#. Add support for more lsp servers
27-
#. Verify python3 and python2 support
26+
#. Verify the plugin is working with vim built for python3 as well as 2
2827
#. Add rename support
2928
#. And much more
3029

@@ -51,7 +50,9 @@ all supported language servers the following should do the trick::
5150
plugin/install_lsp_server.py
5251

5352
Currently only python language server is "supported", it is however possible to manually edit
54-
the supported_servers.json file to test with other language servers as well.
53+
the supported_servers.json file to test with other language servers as well. The file does not
54+
exist until at least one lsp server has been installed. See plugin/supported_servers_example.json
55+
for an example of what the contents of the file should look like.
5556

5657
Usage
5758
-----
@@ -88,18 +89,22 @@ Requirements
8889
* pytest, tox, mock for testing
8990
* pip (for installing python-language-server)
9091

91-
Testing
92-
-------
92+
Testing (for developers)
93+
------------------------
9394

9495
Testing of the pythoncode is done with tox. So first make sure you have tox installed. E.g.::
9596

9697
pip install tox
9798

98-
Once tox is installed simply run tox::
99+
Once tox is installed simply run tox from the plugin folder of vim-liq::
99100

101+
cd ~/.vim/bundle/vim-liq/plugin
100102
tox
101103

102-
This will run unittests and linting (currently flake8 is used for linting).
104+
This will run unittests and linting (currently flake8 is used for linting). As a part of testing
105+
the lsp servers supported will be downloaded/installed locally in plugin/tests/.lsp_install_dir.
106+
This is however only done the first time the test is run. To force a re-install one must manually
107+
remove the .lsp_install_dir.
103108

104109
There currently is no tests for the vimscript code.
105110

plugin/tests/context.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,7 @@
2424

2525
log = logging.getLogger()
2626
handler = logging.StreamHandler()
27-
formatter = logging.Formatter(
28-
'%(asctime)s %(name)-12s %(levelname)-8s %(message)s')
27+
formatter = logging.Formatter('%(asctime)s %(name)-12s %(levelname)-8s %(message)s')
2928
handler.setFormatter(formatter)
3029
log.addHandler(handler)
3130
log.setLevel(logging.DEBUG)
@@ -34,7 +33,7 @@
3433
import sys
3534
test_dir = os.path.abspath(os.path.dirname(__file__))
3635
sys.path.insert(0, os.path.join(test_dir, '..'))
37-
sys.path.insert(0, os.path.join(test_dir, '../vendor/lsp_client_py'))
36+
sys.path.insert(0, os.path.join(test_dir, '../vendor/lsp'))
3837

3938
try:
4039
import unittest.mock as mock
@@ -60,9 +59,11 @@ def v_filetype(monkeypatch):
6059
monkeypatch.setattr("vimliq.vimutils.filetype", mock_)
6160
return mock_
6261

62+
6363
class Partial(str):
6464
def __eq__(self, other):
6565
return self in other
6666

67+
6768
import vimliq.client
6869
import vimliq.clientmanager

plugin/tests/python_test.py

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
"""This is a just a phony python source file used for testing."""
2+
3+
4+
def a_function():
5+
return "test"
6+
7+
8+
a_variable = a_function()
9+
print(a_variable)
10+

plugin/tests/test_python_lsp.py

+135
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
# Copyright 2017 Kristopher Heijari
2+
#
3+
# This file is part of vim-liq.
4+
#
5+
# vim-liq.is free software: you can redistribute it and/or modify
6+
# it under the terms of the GNU General Public License as published by
7+
# the Free Software Foundation, either version 3 of the License, or
8+
# (at your option) any later version.
9+
#
10+
# vim-liq.is distributed in the hope that it will be useful,
11+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
# GNU General Public License for more details.
14+
#
15+
# You should have received a copy of the GNU General Public License
16+
# along with vim-liq. If not, see <http://www.gnu.org/licenses/>.
17+
18+
"""This module tests installation and using the python language serverer "e2e"."""
19+
import shutil
20+
import tempfile
21+
22+
from context import *
23+
24+
import vimliq.install.python_lsp
25+
26+
# Hackky workaroudn to access MonkeyPatch class in module scoped fixture
27+
# Suggestion taken from here: https://github.com/pytest-dev/pytest/issues/363
28+
from _pytest.monkeypatch import MonkeyPatch
29+
30+
# Line is "one based" while col is "zero based" since vim seems to behave that way. E.g. call
31+
# ":py import vim; vim.current.window.cursor = (4, 4)" jumps to line 4 col 5 (if counting 1 based)
32+
FUNC_LINE = 4
33+
FUNC_COL = 4
34+
FUNC_CALL_LINE = 8
35+
FUNC_CALL_COL = 13
36+
VAR_LINE = 8
37+
VAR_COL = 0
38+
VAR_REF_LINE = 9
39+
VAR_REF_COL = 6
40+
41+
install_dir = os.path.join(os.path.dirname(__file__), ".lsp_install_dir")
42+
f_type = "python"
43+
f_path = os.path.join(os.path.dirname(__file__), "python_test.py")
44+
f_content = ""
45+
with open(f_path) as f:
46+
f_content = f.read()
47+
48+
# Override static vim stuff
49+
@pytest.fixture(scope="module")
50+
def vim_static():
51+
mp = MonkeyPatch()
52+
mp.setattr("vimliq.vimutils.filetype", mock.Mock(return_value=f_type))
53+
mp.setattr("vimliq.vimutils.current_file", mock.Mock(return_value=f_path))
54+
mp.setattr("vimliq.vimutils.current_source", mock.Mock(return_value=f_content))
55+
56+
57+
# This is the client manager used by all tests
58+
@pytest.fixture(scope="module")
59+
def LSP(request, vim_static):
60+
if not os.path.exists(install_dir):
61+
log.debug("Installing python lsp to %s", install_dir)
62+
os.makedirs(install_dir)
63+
langserver = vimliq.install.python_lsp.install(install_dir)
64+
else:
65+
# Just doing like this to avoid re-downloading everytime. If the dict returned from
66+
# the python lsp installation this needs to be updated.
67+
langserver = {'python': {'start_cmd': install_dir + '/python_lsp_server/bin/pyls',
68+
'log_arg': '--log-file', 'transport': 'STDIO'}}
69+
70+
log.debug("langserver: %s", langserver)
71+
client_manager = vimliq.clientmanager.ClientManager(langserver)
72+
client_manager.add_client()
73+
74+
# Start the newly added server and open our fake file
75+
client_manager.start_server()
76+
client_manager.td_did_open()
77+
78+
def fin():
79+
# not using the convinience vim_mock since the scope is module
80+
sys.modules["vim"].eval.return_value = f_path
81+
client_manager.td_did_close()
82+
client_manager.shutdown_all()
83+
84+
request.addfinalizer(fin)
85+
86+
return client_manager
87+
88+
89+
def test_did_save(LSP):
90+
LSP.td_did_save()
91+
92+
93+
def test_did_change(LSP):
94+
LSP.td_did_change()
95+
96+
97+
def test_definition(LSP, vim_mock):
98+
vim_mock.current.window.cursor = (FUNC_CALL_LINE, FUNC_CALL_COL)
99+
LSP.td_definition()
100+
vim_mock.command.assert_called_with("e {}".format(f_path))
101+
assert vim_mock.current.window.cursor == (FUNC_LINE, FUNC_COL)
102+
103+
104+
def test_reference(LSP, vim_mock):
105+
vim_mock.current.window.cursor = (VAR_REF_LINE, VAR_REF_COL)
106+
LSP.td_references()
107+
print(vim_mock.eval.mock_calls)
108+
vim_mock.eval.assert_called_with(Partial('"filename":"{}"'.format(f_path)))
109+
vim_mock.eval.assert_called_with(Partial('"lnum":{}'.format(VAR_LINE)))
110+
vim_mock.eval.assert_called_with(Partial('"col":{}'.format(VAR_COL)))
111+
112+
def test_diagnostics(LSP):
113+
# For now just check the diagnostics list is updated
114+
LSP.process_diagnostics()
115+
print(LSP.diagnostics)
116+
assert LSP.diagnostics[f_path][0].start_line == 9
117+
assert LSP.diagnostics[f_path][0].message == "W391 blank line at end of file"
118+
119+
120+
def test_symbols(LSP, vim_mock):
121+
LSP.td_symbols()
122+
print(vim_mock.eval.mock_calls)
123+
vim_mock.eval.assert_any_call(Partial('"text":"{}'.format("a_variable")))
124+
vim_mock.eval.assert_any_call(Partial('"text":"{}'.format("def a_function():")))
125+
126+
127+
def test_completion(LSP, vim_mock, monkeypatch):
128+
# Mock omni_add_base directly to avoid problems with omnifunc call
129+
monkeypatch.setattr("vimliq.client.omni_add_base",
130+
mock.Mock(return_value=("a_var", f_content)))
131+
vim_mock.current.window.cursor = (VAR_REF_LINE, VAR_REF_COL + 5)
132+
LSP.td_completion()
133+
print(vim_mock.command.mock_calls)
134+
# Check that a_variable is returned from the omnifunc function
135+
vim_mock.command.assert_called_with(Partial('"word":"a_variable"'))

plugin/vimliq/client.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ def td_did_close(self):
209209
if filepath:
210210
log.debug("Closing %s", filepath)
211211

212-
self._client.td_did_close(V.current_file())
212+
self._client.td_did_close(filepath)
213213

214214
def td_definition(self):
215215
row, col = V.cursor()

plugin/vimliq/clientmanager.py

+7-2
Original file line numberDiff line numberDiff line change
@@ -103,5 +103,10 @@ def __getattr__(self, name):
103103
except KeyError:
104104
raise AttributeError("filetype: {}, name: {}".format(filetype, name))
105105

106-
func = getattr(client_, name)
107-
return handle_error(func)
106+
attr = getattr(client_, name)
107+
108+
# If function return with wrapper
109+
if callable(attr):
110+
return handle_error(attr)
111+
else:
112+
return attr

plugin/vimliq/install/python_lsp.py

+3-4
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,14 @@
2222
import logging
2323
import os
2424
import shutil
25+
import subprocess
2526
import tempfile
2627
try:
2728
from urllib import urlretrieve as http_download
2829
except ImportError:
2930
from urllib.request import urlretrieve as http_download
3031
import zipfile
3132

32-
import pip
33-
3433
log = logging.getLogger(__name__)
3534
log.addHandler(logging.NullHandler())
3635

@@ -62,8 +61,8 @@ def install(dest_dir):
6261
log.debug("Unzipping %s to %s", zip_path, tempdir)
6362
unzipit.extractall(path=tempdir)
6463

65-
pip.main(["install", "--prefix", INSTALL_DIR, "--ignore-installed", "--upgrade",
66-
os.path.join(tempdir, UNZIPPED_NAME)])
64+
subprocess.check_call(["pip", "install", "--prefix", INSTALL_DIR, "--ignore-installed",
65+
"--upgrade", os.path.join(tempdir, UNZIPPED_NAME)])
6766

6867
# Do the nasty, how can this possibly go wrong :-)
6968
glob_path = glob.glob(os.path.join(INSTALL_DIR, "lib/python*"))[0]

0 commit comments

Comments
 (0)