|
1 | 1 | # Copyright 2022- Python Language Server Contributors.
|
2 | 2 |
|
3 | 3 | from typing import Any, Dict, List
|
4 |
| -from unittest.mock import Mock |
| 4 | +from unittest.mock import Mock, patch |
| 5 | + |
| 6 | +from test.test_notebook_document import wait_for_condition |
| 7 | +from test.test_utils import send_initialize_request, send_notebook_did_open |
5 | 8 |
|
6 | 9 | import jedi
|
7 | 10 | import parso
|
8 | 11 | import pytest
|
9 | 12 |
|
10 |
| -from pylsp import lsp, uris |
| 13 | +from pylsp import IS_WIN, lsp, uris |
11 | 14 | from pylsp.config.config import Config
|
12 | 15 | from pylsp.plugins.rope_autoimport import (
|
13 | 16 | _get_score,
|
|
25 | 28 | DOC_URI = uris.from_fs_path(__file__)
|
26 | 29 |
|
27 | 30 |
|
28 |
| -def contains_autoimport(suggestion: Dict[str, Any], module: str) -> bool: |
29 |
| - """Checks if `suggestion` contains an autoimport for `module`.""" |
| 31 | +def contains_autoimport_completion(suggestion: Dict[str, Any], module: str) -> bool: |
| 32 | + """Checks if `suggestion` contains an autoimport completion for `module`.""" |
30 | 33 | return suggestion.get("label", "") == module and "import" in suggestion.get(
|
31 | 34 | "detail", ""
|
32 | 35 | )
|
33 | 36 |
|
34 | 37 |
|
| 38 | +def contains_autoimport_quickfix(suggestion: Dict[str, Any], module: str) -> bool: |
| 39 | + """Checks if `suggestion` contains an autoimport quick fix for `module`.""" |
| 40 | + return suggestion.get("title", "") == f"import {module}" |
| 41 | + |
| 42 | + |
35 | 43 | @pytest.fixture(scope="session")
|
36 | 44 | def autoimport_workspace(tmp_path_factory) -> Workspace:
|
37 | 45 | "Special autoimport workspace. Persists across sessions to make in-memory sqlite3 database fast."
|
@@ -248,82 +256,89 @@ def test_autoimport_code_actions_get_correct_module_name(autoimport_workspace, m
|
248 | 256 | assert module_name == "os"
|
249 | 257 |
|
250 | 258 |
|
251 |
| -# rope autoimport launches a sqlite database which checks from which thread it is called. |
252 |
| -# This makes the test below fail because we access the db from a different thread. |
253 |
| -# See https://stackoverflow.com/questions/48218065/objects-created-in-a-thread-can-only-be-used-in-that-same-thread |
254 |
| -# @pytest.mark.skipif(IS_WIN, reason="Flaky on Windows") |
255 |
| -# def test_autoimport_completions_for_notebook_document( |
256 |
| -# client_server_pair, |
257 |
| -# ): |
258 |
| -# client, server = client_server_pair |
259 |
| -# send_initialize_request(client) |
260 |
| - |
261 |
| -# with patch.object(server._endpoint, "notify") as mock_notify: |
262 |
| -# # Expectations: |
263 |
| -# # 1. We receive an autoimport suggestion for "os" in the first cell because |
264 |
| -# # os is imported after that. |
265 |
| -# # 2. We don't receive an autoimport suggestion for "os" in the second cell because it's |
266 |
| -# # already imported in the second cell. |
267 |
| -# # 3. We don't receive an autoimport suggestion for "os" in the third cell because it's |
268 |
| -# # already imported in the second cell. |
269 |
| -# # 4. We receive an autoimport suggestion for "sys" because it's not already imported |
270 |
| -# send_notebook_did_open(client, ["os", "import os\nos", "os", "sys"]) |
271 |
| -# wait_for_condition(lambda: mock_notify.call_count >= 3) |
272 |
| - |
273 |
| -# server.m_workspace__did_change_configuration( |
274 |
| -# settings={ |
275 |
| -# "pylsp": { |
276 |
| -# "plugins": { |
277 |
| -# "rope_autoimport": { |
278 |
| -# "memory": True, |
279 |
| -# "completions": {"enabled": True}, |
280 |
| -# }, |
281 |
| -# } |
282 |
| -# } |
283 |
| -# } |
284 |
| -# ) |
285 |
| -# rope_autoimport_settings = server.workspace._config.plugin_settings( |
286 |
| -# "rope_autoimport" |
287 |
| -# ) |
288 |
| -# assert rope_autoimport_settings.get("completions", {}).get("enabled", False) is True |
289 |
| -# assert rope_autoimport_settings.get("memory", False) is True |
290 |
| - |
291 |
| -# # 1. |
292 |
| -# suggestions = server.completions("cell_1_uri", {"line": 0, "character": 2}).get( |
293 |
| -# "items" |
294 |
| -# ) |
295 |
| -# assert any( |
296 |
| -# suggestion |
297 |
| -# for suggestion in suggestions |
298 |
| -# if contains_autoimport(suggestion, "os") |
299 |
| -# ) |
300 |
| - |
301 |
| -# # 2. |
302 |
| -# suggestions = server.completions("cell_2_uri", {"line": 1, "character": 2}).get( |
303 |
| -# "items" |
304 |
| -# ) |
305 |
| -# assert not any( |
306 |
| -# suggestion |
307 |
| -# for suggestion in suggestions |
308 |
| -# if contains_autoimport(suggestion, "os") |
309 |
| -# ) |
310 |
| - |
311 |
| -# # 3. |
312 |
| -# suggestions = server.completions("cell_3_uri", {"line": 0, "character": 2}).get( |
313 |
| -# "items" |
314 |
| -# ) |
315 |
| -# assert not any( |
316 |
| -# suggestion |
317 |
| -# for suggestion in suggestions |
318 |
| -# if contains_autoimport(suggestion, "os") |
319 |
| -# ) |
320 |
| - |
321 |
| -# # 4. |
322 |
| -# suggestions = server.completions("cell_4_uri", {"line": 0, "character": 3}).get( |
323 |
| -# "items" |
324 |
| -# ) |
325 |
| -# assert any( |
326 |
| -# suggestion |
327 |
| -# for suggestion in suggestions |
328 |
| -# if contains_autoimport(suggestion, "sys") |
329 |
| -# ) |
| 259 | +def make_context(module_name, line, character_start, character_end): |
| 260 | + return { |
| 261 | + "diagnostics": [ |
| 262 | + { |
| 263 | + "message": f"undefined name '{module_name}'", |
| 264 | + "range": { |
| 265 | + "start": {"line": line, "character": character_start}, |
| 266 | + "end": {"line": line, "character": character_end}, |
| 267 | + }, |
| 268 | + } |
| 269 | + ] |
| 270 | + } |
| 271 | + |
| 272 | + |
| 273 | +def position(line, character): |
| 274 | + return {"line": line, "character": character} |
| 275 | + |
| 276 | + |
| 277 | +@pytest.mark.skipif(IS_WIN, reason="Flaky on Windows") |
| 278 | +def test_autoimport_code_actions_and_completions_for_notebook_document( |
| 279 | + client_server_pair, |
| 280 | +): |
| 281 | + client, server = client_server_pair |
| 282 | + send_initialize_request( |
| 283 | + client, |
| 284 | + { |
| 285 | + "pylsp": { |
| 286 | + "plugins": { |
| 287 | + "rope_autoimport": { |
| 288 | + "memory": True, |
| 289 | + "enabled": True, |
| 290 | + "completions": {"enabled": True}, |
| 291 | + }, |
| 292 | + } |
| 293 | + } |
| 294 | + }, |
| 295 | + ) |
| 296 | + |
| 297 | + with patch.object(server._endpoint, "notify") as mock_notify: |
| 298 | + # Expectations: |
| 299 | + # 1. We receive an autoimport suggestion for "os" in the first cell because |
| 300 | + # os is imported after that. |
| 301 | + # 2. We don't receive an autoimport suggestion for "os" in the second cell because it's |
| 302 | + # already imported in the second cell. |
| 303 | + # 3. We don't receive an autoimport suggestion for "os" in the third cell because it's |
| 304 | + # already imported in the second cell. |
| 305 | + # 4. We receive an autoimport suggestion for "sys" because it's not already imported. |
| 306 | + # 5. If diagnostics doesn't contain "undefined name ...", we send empty quick fix suggestions. |
| 307 | + send_notebook_did_open(client, ["os", "import os\nos", "os", "sys"]) |
| 308 | + wait_for_condition(lambda: mock_notify.call_count >= 3) |
| 309 | + |
| 310 | + rope_autoimport_settings = server.workspace._config.plugin_settings( |
| 311 | + "rope_autoimport" |
| 312 | + ) |
| 313 | + assert rope_autoimport_settings.get("completions", {}).get("enabled", False) is True |
| 314 | + assert rope_autoimport_settings.get("memory", False) is True |
| 315 | + |
| 316 | + # 1. |
| 317 | + quick_fixes = server.code_actions("cell_1_uri", {}, make_context("os", 0, 0, 2)) |
| 318 | + assert any(s for s in quick_fixes if contains_autoimport_quickfix(s, "os")) |
| 319 | + |
| 320 | + completions = server.completions("cell_1_uri", position(0, 2)).get("items") |
| 321 | + assert any(s for s in completions if contains_autoimport_completion(s, "os")) |
| 322 | + |
| 323 | + # 2. |
| 324 | + # We don't test code actions here as in this case, there would be no code actions sent bc |
| 325 | + # there wouldn't be a diagnostics message. |
| 326 | + completions = server.completions("cell_2_uri", position(1, 2)).get("items") |
| 327 | + assert not any(s for s in completions if contains_autoimport_completion(s, "os")) |
| 328 | + |
| 329 | + # 3. |
| 330 | + # Same as in 2. |
| 331 | + completions = server.completions("cell_3_uri", position(0, 2)).get("items") |
| 332 | + assert not any(s for s in completions if contains_autoimport_completion(s, "os")) |
| 333 | + |
| 334 | + # 4. |
| 335 | + quick_fixes = server.code_actions("cell_4_uri", {}, make_context("sys", 0, 0, 3)) |
| 336 | + assert any(s for s in quick_fixes if contains_autoimport_quickfix(s, "sys")) |
| 337 | + |
| 338 | + completions = server.completions("cell_4_uri", position(0, 3)).get("items") |
| 339 | + assert any(s for s in completions if contains_autoimport_completion(s, "sys")) |
| 340 | + |
| 341 | + # 5. |
| 342 | + context = {"diagnostics": [{"message": "A random message"}]} |
| 343 | + quick_fixes = server.code_actions("cell_4_uri", {}, context) |
| 344 | + assert len(quick_fixes) == 0 |
0 commit comments