Skip to content

Commit 1fd7028

Browse files
committed
Limit the shadow FS initialization to three attempts,
emit a useful warning and add a test for permission error
1 parent b576383 commit 1fd7028

File tree

2 files changed

+77
-9
lines changed

2 files changed

+77
-9
lines changed

python_packages/jupyter_lsp/jupyter_lsp/tests/test_virtual_documents_shadow.py

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1+
import logging
2+
import stat
13
from pathlib import Path
24
from types import SimpleNamespace
35
from typing import List
46

57
import pytest
68

9+
from jupyter_lsp import LanguageServerManager
10+
711
from ..virtual_documents_shadow import (
812
EditableFile,
913
ShadowFilesystemError,
@@ -93,9 +97,16 @@ def shadow_path(tmpdir):
9397

9498
@pytest.fixture
9599
def manager():
96-
return SimpleNamespace(
97-
language_servers={"python-lsp-server": {"requires_documents_on_disk": True}}
98-
)
100+
manager = LanguageServerManager()
101+
manager.language_servers = {
102+
"python-lsp-server": {
103+
"requires_documents_on_disk": True,
104+
"argv": [],
105+
"languages": ["python"],
106+
"version": 2,
107+
}
108+
}
109+
return manager
99110

100111

101112
@pytest.mark.asyncio
@@ -198,3 +209,43 @@ def run_shadow(message):
198209
"params": {"textDocument": {"uri": ok_file_uri}},
199210
}
200211
)
212+
213+
214+
@pytest.fixture
215+
def forbidden_shadow_path(tmpdir):
216+
path = Path(tmpdir) / "no_permission_dir"
217+
path.mkdir(mode=0)
218+
219+
yield path
220+
221+
# re-adjust the permissions, see https://github.com/pytest-dev/pytest/issues/7821
222+
path.chmod(0o755)
223+
224+
225+
@pytest.mark.asyncio
226+
async def test_io_failure(forbidden_shadow_path, manager, caplog):
227+
file_uri = (forbidden_shadow_path / "test.py").as_uri()
228+
229+
shadow = setup_shadow_filesystem(forbidden_shadow_path.as_uri())
230+
231+
def send_change():
232+
message = did_open(file_uri, "content")
233+
return shadow("client", message, "python-lsp-server", manager)
234+
235+
with caplog.at_level(logging.WARNING):
236+
assert await send_change() is None
237+
assert await send_change() is None
238+
# no message should be emitted during the first two attempts
239+
assert caplog.text == ""
240+
241+
# a wargning should be emitted on third failure
242+
with caplog.at_level(logging.WARNING):
243+
assert await send_change() is None
244+
assert "initialization of shadow filesystem failed three times" in caplog.text
245+
assert "PermissionError" in caplog.text
246+
caplog.clear()
247+
248+
# no message should be emitted in subsequent attempts
249+
with caplog.at_level(logging.WARNING):
250+
assert await send_change() is None
251+
assert caplog.text == ""

python_packages/jupyter_lsp/jupyter_lsp/virtual_documents_shadow.py

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@ def setup_shadow_filesystem(virtual_documents_uri: str):
111111
)
112112

113113
initialized = False
114+
failures = []
115+
114116
shadow_filesystem = Path(file_uri_to_path(virtual_documents_uri))
115117

116118
@lsp_message_listener("client")
@@ -145,12 +147,27 @@ async def shadow_virtual_documents(scope, message, language_server, manager):
145147

146148
# initialization (/any file system operations) delayed until needed
147149
if not initialized:
148-
# create if does no exist (so that removal does not raise)
149-
shadow_filesystem.mkdir(parents=True, exist_ok=True)
150-
# remove with contents
151-
rmtree(str(shadow_filesystem))
152-
# create again
153-
shadow_filesystem.mkdir(parents=True, exist_ok=True)
150+
if len(failures) == 3:
151+
return
152+
try:
153+
# create if does no exist (so that removal does not raise)
154+
shadow_filesystem.mkdir(parents=True, exist_ok=True)
155+
# remove with contents
156+
rmtree(str(shadow_filesystem))
157+
# create again
158+
shadow_filesystem.mkdir(parents=True, exist_ok=True)
159+
except (OSError, PermissionError, FileNotFoundError) as e:
160+
failures.append(e)
161+
if len(failures) == 3:
162+
manager.log.warn(
163+
"[lsp] initialization of shadow filesystem failed three times"
164+
" check if the path set by `LanguageServerManager.virtual_documents_dir`"
165+
" or `JP_LSP_VIRTUAL_DIR` is correct; if this is happening with a server"
166+
" for which which you control (or wish to override) jupyter-lsp specification"
167+
" you can try switching `requires_documents_on_disk` off. The errors were: %s",
168+
failures,
169+
)
170+
return
154171
initialized = True
155172

156173
path = file_uri_to_path(uri)

0 commit comments

Comments
 (0)