Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix pytest with mixed up filename casing. #5792

Merged
merged 4 commits into from
Aug 28, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ Charnjit SiNGH (CCSJ)
Chris Lamb
Christian Boelsen
Christian Fetzer
Christian Neumüller
Christian Theunert
Christian Tismer
Christopher Gilling
Expand Down
3 changes: 3 additions & 0 deletions changelog/5792.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Windows: Fix error that occurs in certain circumstances when loading
nicoddemus marked this conversation as resolved.
Show resolved Hide resolved
``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``).
16 changes: 10 additions & 6 deletions src/_pytest/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from _pytest.compat import importlib_metadata
from _pytest.outcomes import fail
from _pytest.outcomes import Skipped
from _pytest.pathlib import unique_path
from _pytest.warning_types import PytestConfigWarning

hookimpl = HookimplMarker("pytest")
Expand Down Expand Up @@ -366,7 +367,7 @@ def _set_initial_conftests(self, namespace):
"""
current = py.path.local()
self._confcutdir = (
current.join(namespace.confcutdir, abs=True)
unique_path(current.join(namespace.confcutdir, abs=True))
if namespace.confcutdir
else None
)
Expand Down Expand Up @@ -405,19 +406,18 @@ def _getconftestmodules(self, path):
else:
directory = path

directory = unique_path(directory)

# XXX these days we may rather want to use config.rootdir
# and allow users to opt into looking into the rootdir parent
# directories instead of requiring to specify confcutdir
clist = []
for parent in directory.realpath().parts():
for parent in directory.parts():
if self._confcutdir and self._confcutdir.relto(parent):
continue
conftestpath = parent.join("conftest.py")
if conftestpath.isfile():
# Use realpath to avoid loading the same conftest twice
# with build systems that create build directories containing
# symlinks to actual files.
mod = self._importconftest(conftestpath.realpath())
mod = self._importconftest(conftestpath)
clist.append(mod)
self._dirpath2confmods[directory] = clist
return clist
Expand All @@ -432,6 +432,10 @@ def _rget_with_confmod(self, name, path):
raise KeyError(name)

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:
return self._conftestpath2mod[conftestpath]
except KeyError:
Expand Down
10 changes: 10 additions & 0 deletions src/_pytest/pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from os.path import expanduser
from os.path import expandvars
from os.path import isabs
from os.path import normcase
from os.path import sep
from posixpath import sep as posix_sep

Expand Down Expand Up @@ -334,3 +335,12 @@ def fnmatch_ex(pattern, path):
def parts(s):
parts = s.split(sep)
return {sep.join(parts[: i + 1]) or sep for i in range(len(parts))}


def unique_path(path):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was it considered to keep this private, i.e. as _unique_path?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was originally private, but given it was on a private module already, I asked @Oberon00 to remove the _ prefix for consistency with other functions in the module.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See 29bb0ed

"""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())))
25 changes: 21 additions & 4 deletions testing/test_conftest.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import os.path
import textwrap

import py

import pytest
from _pytest.config import PytestPluginManager
from _pytest.main import ExitCode
from _pytest.pathlib import unique_path


def ConftestWithSetinitial(path):
Expand Down Expand Up @@ -141,11 +143,11 @@ def test_conftestcutdir(testdir):
# but we can still import a conftest directly
conftest._importconftest(conf)
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
values = conftest._getconftestmodules(p)
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):
Expand All @@ -154,7 +156,7 @@ def test_conftestcutdir_inplace_considered(testdir):
conftest_setinitial(conftest, [conf.dirpath()], confcutdir=conf.dirpath())
values = conftest._getconftestmodules(conf.dirpath())
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())
Expand All @@ -164,7 +166,7 @@ def test_setinitial_conftest_subdirs(testdir, name):
conftest = PytestPluginManager()
conftest_setinitial(conftest, [sub.dirpath()], confcutdir=testdir.tmpdir)
if name not in ("whatever", ".dotdir"):
assert subconftest in conftest._conftestpath2mod
assert unique_path(subconftest) in conftest._conftestpath2mod
assert len(conftest._conftestpath2mod) == 1
else:
assert subconftest not in conftest._conftestpath2mod
Expand Down Expand Up @@ -275,6 +277,21 @@ def fixture():
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):
testdir.makeconftest("assert 0")
result = testdir.runpytest("--noconftest")
Expand Down