Skip to content

Commit 41a8852

Browse files
committed
Fix test_implementations by using real on-FS file
1 parent b7fb666 commit 41a8852

File tree

2 files changed

+85
-94
lines changed

2 files changed

+85
-94
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from abc import ABC, abstractmethod
2+
3+
class Animal(ABC):
4+
@abstractmethod
5+
def breathe(self):
6+
pass
7+
8+
@property
9+
@abstractmethod
10+
def size(self) -> str:
11+
pass
12+
13+
class WingedAnimal(Animal):
14+
@abstractmethod
15+
def fly(self, destination):
16+
pass
17+
18+
class Bird(WingedAnimal):
19+
def breathe(self):
20+
print("*inhales like a bird*")
21+
22+
def fly(self, destination):
23+
print("*flies like a bird*")
24+
25+
@property
26+
def size(self) -> str:
27+
return "bird-sized"
28+
29+
print("not a method at all")

test/plugins/test_implementations.py

+56-94
Original file line numberDiff line numberDiff line change
@@ -1,142 +1,104 @@
11
# Copyright 2017-2020 Palantir Technologies, Inc.
22
# Copyright 2021- Python Language Server Contributors.
33

4-
import os
4+
from collections.abc import Iterable
5+
from importlib.resources import as_file, files
6+
from pathlib import Path
57

68
import pytest
9+
from rope.base.exceptions import BadIdentifierError
710

811
from pylsp import uris
12+
from pylsp.config.config import Config
913
from pylsp.plugins.rope_implementation import pylsp_implementations
10-
from pylsp.workspace import Document
14+
from pylsp.workspace import Workspace
1115

12-
DOC_URI = uris.from_fs_path(__file__)
13-
DOC = """
14-
from abc import ABC, abstractmethod
1516

16-
class Animal(ABC):
17-
@abstractmethod
18-
def breathe(self):
19-
...
17+
# We use a real file because the part of Rope that this feature uses
18+
# (`rope.findit.find_implementations`) *always* loads files from the
19+
# filesystem, in contrast to e.g. `code_assist` which takes a `source` argument
20+
# that can be more easily faked.
21+
# An alternative to using real files would be `unittest.mock.patch`, but that
22+
# ends up being more trouble than it's worth...
23+
@pytest.fixture
24+
def examples_dir_path() -> Iterable[Path]:
25+
with as_file(files("test.data.implementations_examples")) as path:
26+
yield path
2027

21-
@property
22-
@abstractmethod
23-
def size(self) -> str:
24-
...
2528

26-
class WingedAnimal(Animal):
27-
@abstractmethod
28-
def fly(self, destination):
29-
...
29+
@pytest.fixture
30+
def doc_uri(examples_dir_path: Path) -> str:
31+
return uris.from_fs_path(str(examples_dir_path / "example.py"))
3032

31-
class Bird(WingedAnimal):
32-
def breathe(self):
33-
print("*inhales like a bird*")
3433

35-
def fly(self, destination):
36-
print("*flies like a bird*")
34+
# Similarly to the above, we need our workspace to point to the actual location
35+
# on the filesystem containing the example modules, so we override the fixture:
36+
@pytest.fixture
37+
def workspace(examples_dir_path: Path, endpoint) -> None:
38+
ws = Workspace(uris.from_fs_path(str(examples_dir_path)), endpoint)
39+
ws._config = Config(ws.root_uri, {}, 0, {})
40+
yield ws
41+
ws.close()
3742

38-
@property
39-
def size(self) -> str:
40-
return "bird-sized"
4143

42-
print("not a method at all")
43-
"""
44-
45-
46-
def test_implementations(config, workspace) -> None:
44+
def test_implementations(config, workspace, doc_uri) -> None:
4745
# Over 'fly' in WingedAnimal.fly
48-
cursor_pos = {"line": 15, "character": 9}
46+
cursor_pos = {"line": 14, "character": 8}
4947

5048
# The implementation of 'Bird.fly'
5149
def_range = {
52-
"start": {"line": 22, "character": 0},
53-
"end": {"line": 22, "character": 999},
50+
"start": {"line": 21, "character": 0},
51+
"end": {"line": 21, "character": 999},
5452
}
5553

56-
workspace.put_document(DOC_URI, DOC)
57-
doc = workspace.get_document(DOC_URI)
58-
assert [{"uri": DOC_URI, "range": def_range}] == pylsp_implementations(
54+
doc = workspace.get_document(doc_uri)
55+
assert [{"uri": doc_uri, "range": def_range}] == pylsp_implementations(
5956
config, workspace, doc, cursor_pos
6057
)
6158

6259

63-
def test_implementations_skipping_one_class(config, workspace) -> None:
60+
def test_implementations_skipping_one_class(config, workspace, doc_uri) -> None:
6461
# Over 'Animal.breathe'
65-
cursor_pos = {"line": 15, "character": 9}
62+
cursor_pos = {"line": 4, "character": 8}
6663

6764
# The implementation of 'breathe', skipping intermediate classes
6865
def_range = {
69-
"start": {"line": 19, "character": 0},
70-
"end": {"line": 19, "character": 999},
66+
"start": {"line": 18, "character": 0},
67+
"end": {"line": 18, "character": 999},
7168
}
7269

73-
doc = Document(DOC_URI, workspace, DOC)
74-
assert [{"uri": DOC_URI, "range": def_range}] == pylsp_implementations(
70+
doc = workspace.get_document(doc_uri)
71+
assert [{"uri": doc_uri, "range": def_range}] == pylsp_implementations(
7572
config, workspace, doc, cursor_pos
7673
)
7774

7875

79-
@pytest.mark.xfail(reason="not implemented upstream (Rope)", strict=True)
80-
def test_property_implementations(config, workspace) -> None:
76+
@pytest.mark.xfail(
77+
reason="not implemented upstream (Rope)", strict=True, raises=BadIdentifierError
78+
)
79+
def test_property_implementations(config, workspace, doc_uri) -> None:
8180
# Over 'Animal.size'
82-
cursor_pos = {"line": 10, "character": 9}
81+
cursor_pos = {"line": 9, "character": 9}
8382

8483
# The property implementation 'Bird.size'
8584
def_range = {
86-
"start": {"line": 26, "character": 0},
87-
"end": {"line": 26, "character": 999},
85+
"start": {"line": 25, "character": 0},
86+
"end": {"line": 25, "character": 999},
8887
}
8988

90-
doc = Document(DOC_URI, workspace, DOC)
91-
assert [{"uri": DOC_URI, "range": def_range}] == pylsp_implementations(
89+
doc = workspace.get_document(doc_uri)
90+
assert [{"uri": doc_uri, "range": def_range}] == pylsp_implementations(
9291
config, workspace, doc, cursor_pos
9392
)
9493

9594

96-
def test_no_implementations_if_not_a_method(config, workspace) -> None:
97-
# Over 'print(...)' call
98-
cursor_pos = {"line": 26, "character": 0}
99-
100-
doc = Document(DOC_URI, workspace, DOC)
101-
assert [] == pylsp_implementations(config, workspace, doc, cursor_pos)
102-
95+
def test_implementations_not_a_method(config, workspace, doc_uri) -> None:
96+
# Over 'print(...)' call => Rope error because not a method.
97+
cursor_pos = {"line": 28, "character": 0}
10398

104-
def test_document_path_implementations(
105-
config, workspace, workspace_other_root_path, tmpdir
106-
) -> None:
107-
# Create a dummy module out of the workspace's root_path and try to get
108-
# a implementation on it in another file placed next to it.
109-
module_content = """
110-
class A:
111-
def f():
112-
"""
99+
doc = workspace.get_document(doc_uri)
113100

114-
p = tmpdir.join("mymodule.py")
115-
p.write(module_content)
116-
117-
# Content of doc to test implementation
118-
doc_content = """
119-
from mymodule import A
120-
class B(A):
121-
def f():
122-
"""
123-
doc_path = str(tmpdir) + os.path.sep + "myfile.py"
124-
doc_uri = uris.from_fs_path(doc_path)
125-
doc = Document(doc_uri, workspace_other_root_path, doc_content)
126-
127-
# The range where f is defined in mymodule.py
128-
def_range = {
129-
"start": {"line": 2, "character": 0},
130-
"end": {"line": 2, "character": 999},
131-
}
132-
133-
# The position where foo is called in myfile.py
134-
cursor_pos = {"line": 3, "character": 9}
135-
136-
# The uri for mymodule.py
137-
module_path = str(p)
138-
module_uri = uris.from_fs_path(module_path)
139-
140-
assert [{"uri": module_uri, "range": def_range}] == pylsp_implementations(
141-
config, workspace, doc, cursor_pos
142-
)
101+
# This exception is turned into an empty result set automatically in upper
102+
# layers, so we just check that it is raised to document this behavior:
103+
with pytest.raises(BadIdentifierError):
104+
pylsp_implementations(config, workspace, doc, cursor_pos)

0 commit comments

Comments
 (0)