Merge pull request #5792 from dynatrace-oss-contrib/bugfix/badcase

Fix pytest with mixed up filename casing.
This commit is contained in:
Bruno Oliveira 2019-08-28 08:44:16 -03:00 committed by GitHub
commit 955e542210
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 45 additions and 10 deletions

View File

@ -55,6 +55,7 @@ Charnjit SiNGH (CCSJ)
Chris Lamb Chris Lamb
Christian Boelsen Christian Boelsen
Christian Fetzer Christian Fetzer
Christian Neumüller
Christian Theunert Christian Theunert
Christian Tismer Christian Tismer
Christopher Gilling Christopher Gilling

View File

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

View File

@ -30,6 +30,7 @@ from _pytest._code import filter_traceback
from _pytest.compat import importlib_metadata from _pytest.compat import importlib_metadata
from _pytest.outcomes import fail from _pytest.outcomes import fail
from _pytest.outcomes import Skipped from _pytest.outcomes import Skipped
from _pytest.pathlib import unique_path
from _pytest.warning_types import PytestConfigWarning from _pytest.warning_types import PytestConfigWarning
hookimpl = HookimplMarker("pytest") hookimpl = HookimplMarker("pytest")
@ -366,7 +367,7 @@ class PytestPluginManager(PluginManager):
""" """
current = py.path.local() current = py.path.local()
self._confcutdir = ( self._confcutdir = (
current.join(namespace.confcutdir, abs=True) unique_path(current.join(namespace.confcutdir, abs=True))
if namespace.confcutdir if namespace.confcutdir
else None else None
) )
@ -405,19 +406,18 @@ class PytestPluginManager(PluginManager):
else: else:
directory = path directory = path
directory = unique_path(directory)
# XXX these days we may rather want to use config.rootdir # XXX these days we may rather want to use config.rootdir
# and allow users to opt into looking into the rootdir parent # and allow users to opt into looking into the rootdir parent
# directories instead of requiring to specify confcutdir # directories instead of requiring to specify confcutdir
clist = [] clist = []
for parent in directory.realpath().parts(): for parent in directory.parts():
if self._confcutdir and self._confcutdir.relto(parent): if self._confcutdir and self._confcutdir.relto(parent):
continue continue
conftestpath = parent.join("conftest.py") conftestpath = parent.join("conftest.py")
if conftestpath.isfile(): if conftestpath.isfile():
# Use realpath to avoid loading the same conftest twice mod = self._importconftest(conftestpath)
# with build systems that create build directories containing
# symlinks to actual files.
mod = self._importconftest(conftestpath.realpath())
clist.append(mod) clist.append(mod)
self._dirpath2confmods[directory] = clist self._dirpath2confmods[directory] = clist
return clist return clist
@ -432,6 +432,10 @@ class PytestPluginManager(PluginManager):
raise KeyError(name) raise KeyError(name)
def _importconftest(self, conftestpath): def _importconftest(self, conftestpath):
# Use realpath to avoid loading the same conftest twice
# with build systems that create build directories containing
# symlinks to actual files.
conftestpath = unique_path(conftestpath)
try: try:
return self._conftestpath2mod[conftestpath] return self._conftestpath2mod[conftestpath]
except KeyError: except KeyError:

View File

@ -11,6 +11,7 @@ from functools import partial
from os.path import expanduser from os.path import expanduser
from os.path import expandvars from os.path import expandvars
from os.path import isabs from os.path import isabs
from os.path import normcase
from os.path import sep from os.path import sep
from posixpath import sep as posix_sep from posixpath import sep as posix_sep
@ -334,3 +335,12 @@ def fnmatch_ex(pattern, path):
def parts(s): def parts(s):
parts = s.split(sep) parts = s.split(sep)
return {sep.join(parts[: i + 1]) or sep for i in range(len(parts))} return {sep.join(parts[: i + 1]) or sep for i in range(len(parts))}
def unique_path(path):
"""Returns a unique path in case-insensitive (but case-preserving) file
systems such as Windows.
This is needed only for ``py.path.local``; ``pathlib.Path`` handles this
natively with ``resolve()``."""
return type(path)(normcase(str(path.realpath())))

View File

@ -1,3 +1,4 @@
import os.path
import textwrap import textwrap
import py import py
@ -5,6 +6,7 @@ import py
import pytest import pytest
from _pytest.config import PytestPluginManager from _pytest.config import PytestPluginManager
from _pytest.main import ExitCode from _pytest.main import ExitCode
from _pytest.pathlib import unique_path
def ConftestWithSetinitial(path): def ConftestWithSetinitial(path):
@ -141,11 +143,11 @@ def test_conftestcutdir(testdir):
# but we can still import a conftest directly # but we can still import a conftest directly
conftest._importconftest(conf) conftest._importconftest(conf)
values = conftest._getconftestmodules(conf.dirpath()) values = conftest._getconftestmodules(conf.dirpath())
assert values[0].__file__.startswith(str(conf)) assert values[0].__file__.startswith(str(unique_path(conf)))
# and all sub paths get updated properly # and all sub paths get updated properly
values = conftest._getconftestmodules(p) values = conftest._getconftestmodules(p)
assert len(values) == 1 assert len(values) == 1
assert values[0].__file__.startswith(str(conf)) assert values[0].__file__.startswith(str(unique_path(conf)))
def test_conftestcutdir_inplace_considered(testdir): def test_conftestcutdir_inplace_considered(testdir):
@ -154,7 +156,7 @@ def test_conftestcutdir_inplace_considered(testdir):
conftest_setinitial(conftest, [conf.dirpath()], confcutdir=conf.dirpath()) conftest_setinitial(conftest, [conf.dirpath()], confcutdir=conf.dirpath())
values = conftest._getconftestmodules(conf.dirpath()) values = conftest._getconftestmodules(conf.dirpath())
assert len(values) == 1 assert len(values) == 1
assert values[0].__file__.startswith(str(conf)) assert values[0].__file__.startswith(str(unique_path(conf)))
@pytest.mark.parametrize("name", "test tests whatever .dotdir".split()) @pytest.mark.parametrize("name", "test tests whatever .dotdir".split())
@ -164,7 +166,7 @@ def test_setinitial_conftest_subdirs(testdir, name):
conftest = PytestPluginManager() conftest = PytestPluginManager()
conftest_setinitial(conftest, [sub.dirpath()], confcutdir=testdir.tmpdir) conftest_setinitial(conftest, [sub.dirpath()], confcutdir=testdir.tmpdir)
if name not in ("whatever", ".dotdir"): if name not in ("whatever", ".dotdir"):
assert subconftest in conftest._conftestpath2mod assert unique_path(subconftest) in conftest._conftestpath2mod
assert len(conftest._conftestpath2mod) == 1 assert len(conftest._conftestpath2mod) == 1
else: else:
assert subconftest not in conftest._conftestpath2mod assert subconftest not in conftest._conftestpath2mod
@ -275,6 +277,21 @@ def test_conftest_symlink_files(testdir):
assert result.ret == ExitCode.OK assert result.ret == ExitCode.OK
@pytest.mark.skipif(
os.path.normcase("x") != os.path.normcase("X"),
reason="only relevant for case insensitive file systems",
)
def test_conftest_badcase(testdir):
"""Check conftest.py loading when directory casing is wrong."""
testdir.tmpdir.mkdir("JenkinsRoot").mkdir("test")
source = {"setup.py": "", "test/__init__.py": "", "test/conftest.py": ""}
testdir.makepyfile(**{"JenkinsRoot/%s" % k: v for k, v in source.items()})
testdir.tmpdir.join("jenkinsroot/test").chdir()
result = testdir.runpytest()
assert result.ret == ExitCode.NO_TESTS_COLLECTED
def test_no_conftest(testdir): def test_no_conftest(testdir):
testdir.makeconftest("assert 0") testdir.makeconftest("assert 0")
result = testdir.runpytest("--noconftest") result = testdir.runpytest("--noconftest")