Skip to content

Commit 129687d

Browse files
committed
Fix issue where working dir becomes wrong on symlinks. Fixes pytest-dev#5965
This addresses subst drive on Windows and symlinks on Linux.
1 parent dbae5a7 commit 129687d

File tree

8 files changed

+113
-29
lines changed

8 files changed

+113
-29
lines changed

changelog/5965.bugfix.rst

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
No longer resolve symlinks so that the current directory remains correct with drives mapped with subst on Windows or symlinks on Linux.

src/_pytest/config/__init__.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ def get_config(args=None, plugins=None):
208208
config = Config(
209209
pluginmanager,
210210
invocation_params=Config.InvocationParams(
211-
args=args or (), plugins=plugins, dir=Path().resolve()
211+
args=args or (), plugins=plugins, dir=Path.cwd()
212212
),
213213
)
214214

@@ -462,7 +462,7 @@ def _getconftestmodules(self, path):
462462
# and allow users to opt into looking into the rootdir parent
463463
# directories instead of requiring to specify confcutdir
464464
clist = []
465-
for parent in directory.realpath().parts():
465+
for parent in directory.parts():
466466
if self._confcutdir and self._confcutdir.relto(parent):
467467
continue
468468
conftestpath = parent.join("conftest.py")
@@ -771,7 +771,7 @@ def __init__(self, pluginmanager, *, invocation_params=None) -> None:
771771

772772
if invocation_params is None:
773773
invocation_params = self.InvocationParams(
774-
args=(), plugins=None, dir=Path().resolve()
774+
args=(), plugins=None, dir=Path.cwd()
775775
)
776776

777777
self.option = argparse.Namespace()

src/_pytest/fixtures.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1288,7 +1288,7 @@ def getfixtureinfo(self, node, func, cls, funcargs=True):
12881288
def pytest_plugin_registered(self, plugin):
12891289
nodeid = None
12901290
try:
1291-
p = py.path.local(plugin.__file__).realpath()
1291+
p = py.path.local(plugin.__file__)
12921292
except AttributeError:
12931293
pass
12941294
else:

src/_pytest/main.py

-1
Original file line numberDiff line numberDiff line change
@@ -642,7 +642,6 @@ def _parsearg(self, arg):
642642
"file or package not found: " + arg + " (missing __init__.py?)"
643643
)
644644
raise UsageError("file not found: " + arg)
645-
fspath = fspath.realpath()
646645
return (fspath, parts)
647646

648647
def matchnodes(self, matching, names):

testing/acceptance_test.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -822,8 +822,8 @@ def test_cmdline_python_package_symlink(self, testdir, monkeypatch):
822822
if hasattr(py.path.local, "mksymlinkto"):
823823
result.stdout.fnmatch_lines(
824824
[
825-
"lib/foo/bar/test_bar.py::test_bar PASSED*",
826-
"lib/foo/bar/test_bar.py::test_other PASSED*",
825+
"local/lib/foo/bar/test_bar.py::test_bar PASSED*",
826+
"local/lib/foo/bar/test_bar.py::test_other PASSED*",
827827
"*2 passed*",
828828
]
829829
)

testing/test_collection.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1171,13 +1171,13 @@ def test_collect_symlink_file_arg(testdir):
11711171
real = testdir.makepyfile(
11721172
real="""
11731173
def test_nodeid(request):
1174-
assert request.node.nodeid == "real.py::test_nodeid"
1174+
assert request.node.nodeid == "symlink.py::test_nodeid"
11751175
"""
11761176
)
11771177
symlink = testdir.tmpdir.join("symlink.py")
11781178
symlink.mksymlinkto(real)
11791179
result = testdir.runpytest("-v", symlink)
1180-
result.stdout.fnmatch_lines(["real.py::test_nodeid PASSED*", "*1 passed in*"])
1180+
result.stdout.fnmatch_lines(["symlink.py::test_nodeid PASSED*", "*1 passed in*"])
11811181
assert result.ret == 0
11821182

11831183

testing/test_conftest.py

+19-20
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,21 @@ def pytest_addoption(parser):
195195
reason="symlink not available on this platform",
196196
)
197197
def test_conftest_symlink(testdir):
198-
"""Ensure that conftest.py is used for resolved symlinks."""
198+
"""
199+
Ensure that conftest.py is not found unless it's not a parent in the current
200+
directory structure (i.e.: symlinks are not resolved).
201+
"""
202+
# Structure:
203+
# /real
204+
# /real/conftest.py
205+
# /real/app
206+
# /real/app/tests
207+
# /real/app/tests/test_foo.py
208+
209+
# Links:
210+
# /symlinktests -> /real/app/tests (running at symlinktests should fail)
211+
# /symlink -> /real (running at /symlink should work)
212+
199213
real = testdir.tmpdir.mkdir("real")
200214
realtests = real.mkdir("app").mkdir("tests")
201215
testdir.tmpdir.join("symlinktests").mksymlinkto(realtests)
@@ -216,31 +230,16 @@ def fixture():
216230
),
217231
}
218232
)
233+
234+
# Should fail because conftest cannot be found from the link structure.
219235
result = testdir.runpytest("-vs", "symlinktests")
220-
result.stdout.fnmatch_lines(
221-
[
222-
"*conftest_loaded*",
223-
"real/app/tests/test_foo.py::test1 fixture_used",
224-
"PASSED",
225-
]
226-
)
227-
assert result.ret == ExitCode.OK
236+
result.stdout.fnmatch_lines(["*fixture 'fixture' not found*"])
237+
assert result.ret == ExitCode.TESTS_FAILED
228238

229239
# Should not cause "ValueError: Plugin already registered" (#4174).
230240
result = testdir.runpytest("-vs", "symlink")
231241
assert result.ret == ExitCode.OK
232242

233-
realtests.ensure("__init__.py")
234-
result = testdir.runpytest("-vs", "symlinktests/test_foo.py::test1")
235-
result.stdout.fnmatch_lines(
236-
[
237-
"*conftest_loaded*",
238-
"real/app/tests/test_foo.py::test1 fixture_used",
239-
"PASSED",
240-
]
241-
)
242-
assert result.ret == ExitCode.OK
243-
244243

245244
@pytest.mark.skipif(
246245
not hasattr(py.path.local, "mksymlinkto"),

testing/test_link_resolve.py

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import os.path
2+
import subprocess
3+
import sys
4+
import textwrap
5+
from contextlib import contextmanager
6+
from string import ascii_lowercase
7+
8+
import py.path
9+
10+
from _pytest import pytester
11+
12+
13+
@contextmanager
14+
def subst_path_windows(filename):
15+
for c in ascii_lowercase[7:]: # Create a subst drive from H-Z.
16+
c += ":"
17+
if not os.path.exists(c):
18+
drive = c
19+
break
20+
else:
21+
raise AssertionError("Unable to find suitable drive letter for subst.")
22+
23+
directory = filename.dirpath()
24+
basename = filename.basename
25+
26+
args = ["subst", drive, str(directory)]
27+
subprocess.check_call(args)
28+
assert os.path.exists(drive)
29+
try:
30+
filename = py.path.local(drive) / basename
31+
yield filename
32+
finally:
33+
args = ["subst", "/D", drive]
34+
subprocess.check_call(args)
35+
36+
37+
@contextmanager
38+
def subst_path_linux(filename):
39+
directory = filename.dirpath()
40+
basename = filename.basename
41+
42+
target = directory / ".." / "sub2"
43+
os.symlink(str(directory), str(target), target_is_directory=True)
44+
try:
45+
filename = target / basename
46+
yield filename
47+
finally:
48+
# We don't need to unlink (it's all in the tempdir).
49+
pass
50+
51+
52+
def test_link_resolve(testdir: pytester.Testdir) -> None:
53+
"""
54+
See: https://github.com/pytest-dev/pytest/issues/5965
55+
"""
56+
sub1 = testdir.mkpydir("sub1")
57+
p = sub1.join("test_foo.py")
58+
p.write(
59+
textwrap.dedent(
60+
"""
61+
import pytest
62+
def test_foo():
63+
raise AssertionError()
64+
"""
65+
)
66+
)
67+
68+
subst = subst_path_linux
69+
if sys.platform == "win32":
70+
subst = subst_path_windows
71+
72+
with subst(p) as subst_p:
73+
result = testdir.runpytest(str(subst_p), "-v")
74+
# i.e.: Make sure that the error is reported as a relative path, not as a
75+
# resolved path.
76+
# See: https://github.com/pytest-dev/pytest/issues/5965
77+
stdout = result.stdout.str()
78+
assert "sub1/test_foo.py" not in stdout
79+
80+
# i.e.: Expect drive on windows because we just have drive:filename, whereas
81+
# we expect a relative path on Linux.
82+
expect = (
83+
"*{}*".format(subst_p) if sys.platform == "win32" else "*sub2/test_foo.py*"
84+
)
85+
result.stdout.fnmatch_lines([expect])

0 commit comments

Comments
 (0)