Skip to content

Commit 1a252b3

Browse files
committed
LSP will use a temporary root dir when needed
1 parent 9654c12 commit 1a252b3

File tree

7 files changed

+110
-134
lines changed

7 files changed

+110
-134
lines changed

.ci/scripts/run_tests.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,9 @@ def _getNoseCommandLineArgs(args):
155155
if args.debugger:
156156
argv += ["--debugger"]
157157
if args.fail_fast:
158-
argv += ["--fail-fast", "--verbose"]
158+
argv += ["--fail-fast", ]
159+
# Need at least 2 verbose levels for this to work!
160+
argv += ["--verbose"] * (2 - len(args.verbose))
159161
if not args.log_to_stdout:
160162
argv += ["--log-capture"]
161163

@@ -199,6 +201,9 @@ def main():
199201
logging.getLogger("nose2").setLevel(logging.FATAL)
200202
logging.getLogger("vunit").setLevel(logging.ERROR)
201203
logging.getLogger("requests").setLevel(logging.WARNING)
204+
logging.getLogger("pyls").setLevel(logging.INFO)
205+
logging.getLogger("pyls.config.config").setLevel(logging.WARNING)
206+
logging.getLogger("pyls.python_ls").setLevel(logging.INFO)
202207
file_handler = logging.FileHandler(args.log_file)
203208
log_format = (
204209
"%(levelname)-7s | %(asctime)s | "

hdl_checker/base_server.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,17 @@ def _setupIfNeeded(self):
333333
_logger.warning("Not all directories exist, forcing setup")
334334
self.clean()
335335

336+
os.makedirs(str(self.work_dir))
337+
338+
del self._builder
339+
del self._database
340+
341+
database = Database()
342+
self._database = database
343+
self._builder = Fallback(self.work_dir, database)
344+
345+
self._builder.setup()
346+
336347
if self.config_file is None:
337348
return
338349

@@ -346,16 +357,6 @@ def clean(self):
346357
"""
347358
_logger.debug("Cleaning up project")
348359
removeDirIfExists(str(self.work_dir))
349-
os.makedirs(str(self.work_dir))
350-
351-
del self._builder
352-
del self._database
353-
354-
database = Database()
355-
self._database = database
356-
self._builder = Fallback(self.work_dir, database)
357-
358-
self._builder.setup()
359360

360361
@abc.abstractmethod
361362
def _handleUiInfo(self, message): # type: (AnyStr) -> None

hdl_checker/exceptions.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,3 @@ def __init__(self, path):
6666

6767
def __str__(self):
6868
return "Couldn't determine file type for path '%s'" % self._path
69-
70-
71-
class HdlCheckerNotInitializedError(HdlCheckerBaseException):
72-
"""
73-
Exception thrown by the LSP server when the client asks for info that
74-
depends on having BaseServer properly setup.
75-
"""
76-
def __str__(self):
77-
return "HDL Checker server needs a root URI to work correctly"

hdl_checker/lsp.py

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818

1919
import json
2020
import logging
21-
import os.path as p
21+
from os import getpid
22+
from os import path as p
23+
from tempfile import mkdtemp
2224
from typing import Any, Callable, Dict, Iterable, List, Optional, Set, Tuple, Union
2325

2426
import six
@@ -34,16 +36,21 @@
3436
from hdl_checker.config_generators.simple_finder import SimpleFinder
3537
from hdl_checker.database import _DEFAULT_LIBRARY_NAME
3638
from hdl_checker.diagnostics import CheckerDiagnostic, DiagType
37-
from hdl_checker.exceptions import HdlCheckerNotInitializedError, UnknownParameterError
39+
from hdl_checker.exceptions import UnknownParameterError
3840
from hdl_checker.parsers.elements.dependency_spec import DependencySpec
3941
from hdl_checker.parsers.elements.design_unit import (
4042
VerilogDesignUnit,
4143
VhdlDesignUnit,
4244
tAnyDesignUnit,
4345
)
44-
from hdl_checker.path import Path
46+
from hdl_checker.path import Path, TemporaryPath
4547
from hdl_checker.types import Location, MarkupKind
46-
from hdl_checker.utils import getTemporaryFilename, logCalls, onNewReleaseFound
48+
from hdl_checker.utils import (
49+
getTemporaryFilename,
50+
logCalls,
51+
onNewReleaseFound,
52+
removeIfExists,
53+
)
4754

4855
_logger = logging.getLogger(__name__)
4956

@@ -150,15 +157,16 @@ def __init__(self, *args, **kwargs):
150157

151158
@property
152159
def checker(self):
153-
# type: () -> HdlCodeCheckerServer
160+
# type: () -> Server
154161
"""
155-
Returns a valid checker, otherwise raise HdlCheckerNotInitializedError.
156-
In theory this shouldn't happen since root URI is mandatory when
157-
initializing the server.
162+
Returns a valid checker, either the one configured during
163+
HdlCheckerLanguageServer._onConfigUpdate or a new one using a temporary
164+
directory.
158165
"""
159166
if self._checker is None:
160-
_logger.error("Checker not initialized")
161-
raise HdlCheckerNotInitializedError()
167+
_logger.info("Server was not initialized, using a temporary one")
168+
root_dir = mkdtemp(prefix="temp_hdl_checker_pid{}_".format(getpid()))
169+
self._checker = Server(self.workspace, root_dir=TemporaryPath(root_dir))
162170
return self._checker
163171

164172
def showInfo(self, msg):

hdl_checker/tests/test_lsp.py

Lines changed: 28 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@
4646
from nose2.tools import such # type: ignore
4747

4848
from hdl_checker.base_server import WatchedFile
49-
from hdl_checker.exceptions import HdlCheckerNotInitializedError
5049
from hdl_checker.parsers.elements.dependency_spec import DependencySpec
5150
from hdl_checker.parsers.elements.design_unit import (
5251
DesignUnitType,
@@ -158,7 +157,7 @@ def test_workspace_notify(self):
158157
workspace = MagicMock(spec=Workspace)
159158

160159
server = lsp.Server(
161-
workspace, root_path=TemporaryPath(mkdtemp(prefix="hdl_checker_"))
160+
workspace, root_dir=TemporaryPath(mkdtemp(prefix="hdl_checker_"))
162161
)
163162

164163
server._handleUiInfo("some info") # pylint: disable=protected-access
@@ -197,7 +196,8 @@ def _initializeServer(server, params=None):
197196
},
198197
)
199198

200-
it.assertEqual(server.m_initialized(), None)
199+
with patch("hdl_checker.lsp.onNewReleaseFound"):
200+
it.assertEqual(server.m_initialized(), None)
201201

202202
def startLspServer():
203203
_logger.debug("Creating server")
@@ -421,81 +421,37 @@ def test():
421421
],
422422
)
423423

424-
# as per LSP spec, this is invalid. rootUri is a mandatory field
425-
with it.having("neither root URI nor project file set"):
426-
427-
@it.has_setup
428-
def setup():
429-
startLspServer()
430-
431-
@it.has_teardown
432-
def teardown():
433-
stopLspServer()
434-
435-
@it.should("respond capabilities upon initialization") # type: ignore
436-
def test():
437-
_initializeServer(
438-
it.server, params={"initializationOptions": {"project_file": None}}
439-
)
440-
441-
# Linting might happen on a different thread, so we might not actually
442-
# get the exception. In any case, its constructor must be called
443-
@it.should("fail to lint files") # type: ignore
444-
@patch.object(HdlCheckerNotInitializedError, "__init__", return_value=None)
445-
def test(exc):
446-
source = p.join(TEST_PROJECT, "basic_library", "clock_divider.vhd")
447-
try:
448-
checkLintFileOnOpen(source)
449-
it.fail(
450-
"Should have raised one of %s",
451-
HdlCheckerNotInitializedError,
452-
MockWaitTimeout,
453-
)
454-
except (HdlCheckerNotInitializedError, MockWaitTimeout):
455-
pass
424+
@it.should("lint file with neither root URI nor project file set") # type: ignore
425+
def test():
426+
startLspServer()
456427

457-
exc.assert_called_once()
428+
_initializeServer(
429+
it.server, params={"initializationOptions": {"project_file": None}}
430+
)
458431

459-
with it.having("no root URI but project file set"):
432+
source = p.join(TEST_PROJECT, "basic_library", "clock_divider.vhd")
433+
checkLintFileOnOpen(source)
460434

461-
@it.has_setup
462-
def setup():
463-
startLspServer()
435+
stopLspServer()
464436

465-
@it.has_teardown
466-
def teardown():
467-
stopLspServer()
437+
@it.should("lint file with no root URI but project file set") # type: ignore
438+
def test():
439+
startLspServer()
468440

469-
@it.should("respond capabilities upon initialization") # type: ignore
470-
def test():
471-
# In this case, project file is an absolute path, since there's no
472-
# root URI
473-
_initializeServer(
474-
it.server,
475-
params={
476-
"initializationOptions": {
477-
"project_file": p.join(TEST_PROJECT, "vimhdl.prj")
478-
}
441+
_initializeServer(
442+
it.server,
443+
params={
444+
"rootUri": None,
445+
"initializationOptions": {
446+
"project_file": p.join(TEST_PROJECT, "vimhdl.prj")
479447
},
480-
)
448+
},
449+
)
481450

482-
# Linting might happen on a different thread, so we might not actually
483-
# get the exception. In any case, its constructor must be called
484-
@it.should("fail to lint files") # type: ignore
485-
@patch.object(HdlCheckerNotInitializedError, "__init__", return_value=None)
486-
def test(exc):
487-
source = p.join(TEST_PROJECT, "basic_library", "clk_en_generator.vhd")
488-
try:
489-
checkLintFileOnOpen(source)
490-
it.fail(
491-
"Should have raised one of %s",
492-
HdlCheckerNotInitializedError,
493-
MockWaitTimeout,
494-
)
495-
except (HdlCheckerNotInitializedError, MockWaitTimeout):
496-
pass
451+
source = p.join(TEST_PROJECT, "basic_library", "clock_divider.vhd")
452+
checkLintFileOnOpen(source)
497453

498-
exc.assert_called_once()
454+
stopLspServer()
499455

500456

501457
class TestValidProject(TestCase):
@@ -531,7 +487,8 @@ def setUp(self):
531487
)
532488

533489
_logger.info("Calling m_initialized")
534-
self.assertIsNone(self.server.m_initialized())
490+
with patch("hdl_checker.lsp.onNewReleaseFound"):
491+
self.assertIsNone(self.server.m_initialized())
535492

536493
def teardown(self):
537494
_logger.debug("Shutting down server")

hdl_checker/tests/test_misc.py

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828

2929
import parameterized # type: ignore
3030
import unittest2 # type: ignore
31-
from mock import MagicMock, patch
31+
from mock import MagicMock, Mock, patch
3232

3333
from hdl_checker.builder_utils import BuilderName, getBuilderByName
3434
from hdl_checker.builders.fallback import Fallback
@@ -100,29 +100,34 @@ def test_getBuilderByName(self):
100100

101101

102102
class TestReportingRelease(unittest2.TestCase):
103-
@patch(
104-
"hdl_checker.utils.subp.check_output",
105-
return_value=b"""\
106-
ee3443bee4ba4ef91a7f8282d6af25c9c75e85d5 refs/tags/v0.1.0
107-
db561d5c26f8371650a60e9c3f1d3f6c2cb564d5 refs/tags/v0.2
108-
b23cce9938ea6723ed9969473f1c84c668f86bab refs/tags/v0.3
109-
3b1b105060ab8dd91e73619191bf44d7a3e70a95 refs/tags/v0.4
110-
23ce39aaaa494ebc33f9aefec317feaea4200222 refs/tags/v0.4.1
111-
8bca9a3b5764da502d22d9a4c3563bbcdbc6aaa6 refs/tags/v0.4.2
112-
c455f5ca764d17365d77c23d14f2fe3a6234960b refs/tags/v0.4.3
113-
09a1ee298f30a7412d766fe61974ae7de429d00c refs/tags/v0.5
114-
ab8d07a45195cc70403cbe31b571f593dfc18c56 refs/tags/v0.5.1
115-
3deeffb3c3e75c385782db66613babfdab60c019 refs/tags/v0.5.2
116-
95444ca3051c0174dab747d4b4d5792ced5f87b6 refs/tags/v0.6
117-
108016f86c3e36ceb7f54bcd12227fb335cd9e25 refs/tags/v0.6.1
103+
@patch("hdl_checker.utils.subp.Popen")
104+
def test_GetCorrectVersion(self, popen):
105+
process_mock = Mock()
106+
stdout = b"""\
118107
7e5264355da66f71e8d1b80887ac6df55b9829ef refs/tags/v0.6.2
119-
a65602477ef860b48bacfa90a96d8518eb51f030 refs/tags/v0.6.3""",
120-
)
121-
def test_GetCorrectVersion(self, *_):
108+
a65602477ef860b48bacfa90a96d8518eb51f030 refs/tags/v0.6.3"""
109+
110+
stderr = ""
111+
112+
attrs = {"communicate.return_value": (stdout, stderr)}
113+
process_mock.configure_mock(**attrs)
114+
popen.return_value = process_mock
115+
122116
self.assertEqual(_getLatestReleaseVersion(), "0.6.3")
123117

124-
@patch("hdl_checker.utils.subp.check_output", return_value=b"0.6.3")
125-
def test_RejectsInvalidFormats(self, *_):
118+
119+
@patch("hdl_checker.utils.subp.Popen")
120+
def test_RejectsInvalidFormats(self, popen):
121+
process_mock = Mock()
122+
stdout = b"""\
123+
7e5264355da66f71e8d1b80887ac6df55b9829ef refs/tags/v0.6.2
124+
a65602477ef860b48bacfa90a96d8518eb51f030 refs/tags/0.6.3"""
125+
126+
stderr = ""
127+
128+
attrs = {"communicate.return_value": (stdout, stderr)}
129+
process_mock.configure_mock(**attrs)
130+
popen.return_value = process_mock
126131
self.assertIsNone(_getLatestReleaseVersion())
127132

128133
@patch("hdl_checker.utils.REPO_URL", "localhost")

hdl_checker/utils.py

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import sys
2929
from collections import Counter
3030
from tempfile import NamedTemporaryFile
31+
from threading import Timer
3132
from typing import Callable, Dict, Iterable, List, Optional, Tuple, TypeVar, Union
3233

3334
import six
@@ -61,10 +62,10 @@ def setupLogging(stream, level): # pragma: no cover
6162

6263
logging.getLogger("urllib3").setLevel(logging.WARNING)
6364
logging.getLogger("pynvim").setLevel(logging.WARNING)
65+
logging.getLogger("matplotlib").setLevel(logging.INFO)
6466
logging.getLogger("pyls").setLevel(logging.INFO)
65-
logging.getLogger("pyls.python_ls").setLevel(logging.INFO)
6667
logging.getLogger("pyls.config.config").setLevel(logging.WARNING)
67-
logging.getLogger("matplotlib").setLevel(logging.INFO)
68+
logging.getLogger("pyls.python_ls").setLevel(logging.INFO)
6869
logging.getLogger("pyls_jsonrpc.endpoint").setLevel(logging.INFO)
6970

7071

@@ -400,19 +401,27 @@ def _getLatestReleaseVersion():
400401
the leading 'v' (so that v1.0.0 becomes simply 1.0.0). If the connection to
401402
the URL fails, return None
402403
"""
404+
proc = subp.Popen(
405+
["git", "ls-remote", "--sort=v:refname", "--tags", REPO_URL],
406+
env={"GIT_TERMINAL_PROMPT": "0"},
407+
stdout=subp.PIPE,
408+
stderr=subp.PIPE,
409+
)
410+
411+
timer = Timer(5, proc.kill)
412+
timer.start()
413+
403414
try:
404-
tags = [
405-
x.decode()
406-
for x in subp.check_output(
407-
["git", "ls-remote", "--sort=v:refname", "--tags", REPO_URL],
408-
env={"GIT_TERMINAL_PROMPT": "0"},
409-
stderr=subp.PIPE,
410-
).splitlines()
411-
]
412-
except subp.CalledProcessError:
413-
_logger.warning("Couldn't fetch latest tag from %s", REPO_URL)
415+
stdout, stderr = proc.communicate()
416+
finally:
417+
timer.cancel()
418+
419+
if not stdout or stderr:
420+
_logger.info("Couldn't fetch latest tag from %s", REPO_URL)
414421
return None
415422

423+
tags = [x.decode() for x in stdout.splitlines()]
424+
416425
latest = tags[-1].split("/")[-1]
417426

418427
if not re.match(r"v\d+\.\d+\.\d+", latest):

0 commit comments

Comments
 (0)