Skip to content

Commit 955e542

Browse files
authored
Merge pull request #5792 from dynatrace-oss-contrib/bugfix/badcase
Fix pytest with mixed up filename casing.
2 parents 0215bcd + 29bb0ed commit 955e542

File tree

5 files changed

+45
-10
lines changed

5 files changed

+45
-10
lines changed

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ Charnjit SiNGH (CCSJ)
5555
Chris Lamb
5656
Christian Boelsen
5757
Christian Fetzer
58+
Christian Neumüller
5859
Christian Theunert
5960
Christian Tismer
6061
Christopher Gilling

changelog/5792.bugfix.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Windows: Fix error that occurs in certain circumstances when loading
2+
``conftest.py`` from a working directory that has casing other than the one stored
3+
in the filesystem (e.g., ``c:\test`` instead of ``C:\test``).

src/_pytest/config/__init__.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from _pytest.compat import importlib_metadata
3131
from _pytest.outcomes import fail
3232
from _pytest.outcomes import Skipped
33+
from _pytest.pathlib import unique_path
3334
from _pytest.warning_types import PytestConfigWarning
3435

3536
hookimpl = HookimplMarker("pytest")
@@ -366,7 +367,7 @@ def _set_initial_conftests(self, namespace):
366367
"""
367368
current = py.path.local()
368369
self._confcutdir = (
369-
current.join(namespace.confcutdir, abs=True)
370+
unique_path(current.join(namespace.confcutdir, abs=True))
370371
if namespace.confcutdir
371372
else None
372373
)
@@ -405,19 +406,18 @@ def _getconftestmodules(self, path):
405406
else:
406407
directory = path
407408

409+
directory = unique_path(directory)
410+
408411
# XXX these days we may rather want to use config.rootdir
409412
# and allow users to opt into looking into the rootdir parent
410413
# directories instead of requiring to specify confcutdir
411414
clist = []
412-
for parent in directory.realpath().parts():
415+
for parent in directory.parts():
413416
if self._confcutdir and self._confcutdir.relto(parent):
414417
continue
415418
conftestpath = parent.join("conftest.py")
416419
if conftestpath.isfile():
417-
# Use realpath to avoid loading the same conftest twice
418-
# with build systems that create build directories containing
419-
# symlinks to actual files.
420-
mod = self._importconftest(conftestpath.realpath())
420+
mod = self._importconftest(conftestpath)
421421
clist.append(mod)
422422
self._dirpath2confmods[directory] = clist
423423
return clist
@@ -432,6 +432,10 @@ def _rget_with_confmod(self, name, path):
432432
raise KeyError(name)
433433

434434
def _importconftest(self, conftestpath):
435+
# Use realpath to avoid loading the same conftest twice
436+
# with build systems that create build directories containing
437+
# symlinks to actual files.
438+
conftestpath = unique_path(conftestpath)
435439
try:
436440
return self._conftestpath2mod[conftestpath]
437441
except KeyError:

src/_pytest/pathlib.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from os.path import expanduser
1212
from os.path import expandvars
1313
from os.path import isabs
14+
from os.path import normcase
1415
from os.path import sep
1516
from posixpath import sep as posix_sep
1617

@@ -334,3 +335,12 @@ def fnmatch_ex(pattern, path):
334335
def parts(s):
335336
parts = s.split(sep)
336337
return {sep.join(parts[: i + 1]) or sep for i in range(len(parts))}
338+
339+
340+
def unique_path(path):
341+
"""Returns a unique path in case-insensitive (but case-preserving) file
342+
systems such as Windows.
343+
344+
This is needed only for ``py.path.local``; ``pathlib.Path`` handles this
345+
natively with ``resolve()``."""
346+
return type(path)(normcase(str(path.realpath())))

testing/test_conftest.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
import os.path
12
import textwrap
23

34
import py
45

56
import pytest
67
from _pytest.config import PytestPluginManager
78
from _pytest.main import ExitCode
9+
from _pytest.pathlib import unique_path
810

911

1012
def ConftestWithSetinitial(path):
@@ -141,11 +143,11 @@ def test_conftestcutdir(testdir):
141143
# but we can still import a conftest directly
142144
conftest._importconftest(conf)
143145
values = conftest._getconftestmodules(conf.dirpath())
144-
assert values[0].__file__.startswith(str(conf))
146+
assert values[0].__file__.startswith(str(unique_path(conf)))
145147
# and all sub paths get updated properly
146148
values = conftest._getconftestmodules(p)
147149
assert len(values) == 1
148-
assert values[0].__file__.startswith(str(conf))
150+
assert values[0].__file__.startswith(str(unique_path(conf)))
149151

150152

151153
def test_conftestcutdir_inplace_considered(testdir):
@@ -154,7 +156,7 @@ def test_conftestcutdir_inplace_considered(testdir):
154156
conftest_setinitial(conftest, [conf.dirpath()], confcutdir=conf.dirpath())
155157
values = conftest._getconftestmodules(conf.dirpath())
156158
assert len(values) == 1
157-
assert values[0].__file__.startswith(str(conf))
159+
assert values[0].__file__.startswith(str(unique_path(conf)))
158160

159161

160162
@pytest.mark.parametrize("name", "test tests whatever .dotdir".split())
@@ -164,7 +166,7 @@ def test_setinitial_conftest_subdirs(testdir, name):
164166
conftest = PytestPluginManager()
165167
conftest_setinitial(conftest, [sub.dirpath()], confcutdir=testdir.tmpdir)
166168
if name not in ("whatever", ".dotdir"):
167-
assert subconftest in conftest._conftestpath2mod
169+
assert unique_path(subconftest) in conftest._conftestpath2mod
168170
assert len(conftest._conftestpath2mod) == 1
169171
else:
170172
assert subconftest not in conftest._conftestpath2mod
@@ -275,6 +277,21 @@ def fixture():
275277
assert result.ret == ExitCode.OK
276278

277279

280+
@pytest.mark.skipif(
281+
os.path.normcase("x") != os.path.normcase("X"),
282+
reason="only relevant for case insensitive file systems",
283+
)
284+
def test_conftest_badcase(testdir):
285+
"""Check conftest.py loading when directory casing is wrong."""
286+
testdir.tmpdir.mkdir("JenkinsRoot").mkdir("test")
287+
source = {"setup.py": "", "test/__init__.py": "", "test/conftest.py": ""}
288+
testdir.makepyfile(**{"JenkinsRoot/%s" % k: v for k, v in source.items()})
289+
290+
testdir.tmpdir.join("jenkinsroot/test").chdir()
291+
result = testdir.runpytest()
292+
assert result.ret == ExitCode.NO_TESTS_COLLECTED
293+
294+
278295
def test_no_conftest(testdir):
279296
testdir.makeconftest("assert 0")
280297
result = testdir.runpytest("--noconftest")

0 commit comments

Comments
 (0)