Skip to content

Commit 45e8e99

Browse files
committed
Fix issue where working dir becomes wrong on subst drive on Windows. Fixes pytest-dev#5965
1 parent dbae5a7 commit 45e8e99

File tree

5 files changed

+114
-5
lines changed

5 files changed

+114
-5
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 even with drives mapped with subst on Windows.

src/_pytest/config/__init__.py

+9-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,13 @@ 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+
466+
p = directory
467+
if sys.platform != 'win32':
468+
# Only resolve symlinks if not on windows. See: #5965
469+
p = p.realpath()
470+
471+
for parent in p.parts():
466472
if self._confcutdir and self._confcutdir.relto(parent):
467473
continue
468474
conftestpath = parent.join("conftest.py")
@@ -771,7 +777,7 @@ def __init__(self, pluginmanager, *, invocation_params=None) -> None:
771777

772778
if invocation_params is None:
773779
invocation_params = self.InvocationParams(
774-
args=(), plugins=None, dir=Path().resolve()
780+
args=(), plugins=None, dir=Path.cwd()
775781
)
776782

777783
self.option = argparse.Namespace()

src/_pytest/fixtures.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -1288,7 +1288,10 @@ 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__)
1292+
if sys.platform != 'win32':
1293+
# Only resolve symlinks if not on windows. See: #5965
1294+
p = p.realpath()
12921295
except AttributeError:
12931296
pass
12941297
else:

src/_pytest/main.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -642,7 +642,11 @@ 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()
645+
646+
if sys.platform != 'win32':
647+
# Only resolve symlinks if not on windows. See: #5965
648+
fspath = fspath.realpath()
649+
646650
return (fspath, parts)
647651

648652
def matchnodes(self, matching, names):

testing/test_windows_subst.py

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import os.path
2+
import subprocess
3+
import sys
4+
from string import ascii_lowercase
5+
6+
from py._path.local import LocalPath
7+
8+
import pytest
9+
from _pytest import pytester
10+
from _pytest.fixtures import FixtureRequest
11+
12+
13+
class _SubstTmpdirFactory:
14+
"""
15+
A temp dir factory which may do a subst on Windows drives.
16+
"""
17+
18+
def __init__(self, tmpdir, tmpdir_factory, subst):
19+
self.tmpdir = tmpdir
20+
self.tmpdir_factory = tmpdir_factory
21+
self.subst = subst
22+
self.subst_drives = {}
23+
24+
def _subst_path(self, filename):
25+
26+
if self.subst:
27+
for c in ascii_lowercase[7:]: # Create a subst drive from H-Z.
28+
c += ":"
29+
if not os.path.exists(c):
30+
drive = c
31+
break
32+
else:
33+
raise AssertionError("Unable to find suitable drive letter for subst.")
34+
35+
directory = os.path.dirname(filename)
36+
basename = os.path.basename(filename)
37+
38+
args = ["subst", drive, directory]
39+
subprocess.check_call(args)
40+
assert os.path.exists(drive)
41+
self.subst_drives[drive] = directory
42+
43+
filename = LocalPath(os.path.join(drive, basename))
44+
45+
return filename
46+
47+
def mktemp(self, name, numbered):
48+
filename = self.tmpdir_factory.mktemp(name, numbered=numbered)
49+
filename = self._subst_path(filename)
50+
return filename
51+
52+
def unsubst(self):
53+
for drive in self.subst_drives:
54+
args = ["subst", "/D", drive]
55+
subprocess.check_call(args)
56+
57+
58+
@pytest.fixture(params=[{"subst": True}, {"subst": False}])
59+
def _subst_tmpdir_factory(request: FixtureRequest, tmpdir, tmpdir_factory):
60+
if sys.platform != "win32":
61+
pytest.skip("Windows only test.")
62+
factory = _SubstTmpdirFactory(tmpdir, tmpdir_factory, **request.param)
63+
yield factory
64+
factory.unsubst()
65+
66+
67+
@pytest.fixture
68+
def _custom_testdir(
69+
request: FixtureRequest, _subst_tmpdir_factory
70+
) -> "pytester.Testdir":
71+
return pytester.Testdir(request, _subst_tmpdir_factory)
72+
73+
74+
def test_windows_subst_resolve(_custom_testdir, _subst_tmpdir_factory) -> None:
75+
"""
76+
Check that when we have a subst on Windows the errors reported are not
77+
in the subst target.
78+
See: https://github.com/pytest-dev/pytest/issues/5965
79+
"""
80+
testdir = _custom_testdir
81+
testdir.makepyfile(
82+
"""
83+
import pytest
84+
def test_foo():
85+
raise AssertionError()
86+
"""
87+
)
88+
result = testdir.runpytest()
89+
90+
# i.e.: Make sure that the error is reported as a relative path, not as an
91+
# absolute (resolved) path.
92+
# See: https://github.com/pytest-dev/pytest/issues/5965
93+
for _drive, directory in _subst_tmpdir_factory.subst_drives.items():
94+
for line in result.stdout.lines:
95+
assert directory.lower() not in line.lower()

0 commit comments

Comments
 (0)