From ac62febef99369d25a04fd68e300a7d1be75d46a Mon Sep 17 00:00:00 2001 From: Casey Brooks Date: Thu, 25 Dec 2025 20:31:42 +0000 Subject: [PATCH] fix(config): preserve conftest import casing Refs #17 --- src/_pytest/config/__init__.py | 25 ++++++++------- .../test_windows_conftest_import_casing.py | 31 +++++++++++++++++++ 2 files changed, 44 insertions(+), 12 deletions(-) create mode 100644 testing/test_windows_conftest_import_casing.py diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 3a0eca546a3..cd51e946a3e 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -432,30 +432,31 @@ 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) + # Use a normcased path for cache keys while keeping the + # case-preserving real path for import operations so we + # avoid duplicate loads but still respect filesystem casing. + conftestpath_key = unique_path(conftestpath) + real_conftestpath = conftestpath.realpath() try: - return self._conftestpath2mod[conftestpath] + return self._conftestpath2mod[conftestpath_key] except KeyError: - pkgpath = conftestpath.pypkgpath() + pkgpath = real_conftestpath.pypkgpath() if pkgpath is None: - _ensure_removed_sysmodule(conftestpath.purebasename) + _ensure_removed_sysmodule(real_conftestpath.purebasename) try: - mod = conftestpath.pyimport() + mod = real_conftestpath.pyimport() if ( hasattr(mod, "pytest_plugins") and self._configured and not self._using_pyargs ): - _fail_on_non_top_pytest_plugins(conftestpath, self._confcutdir) + _fail_on_non_top_pytest_plugins(real_conftestpath, self._confcutdir) except Exception: - raise ConftestImportFailure(conftestpath, sys.exc_info()) + raise ConftestImportFailure(real_conftestpath, sys.exc_info()) self._conftest_plugins.add(mod) - self._conftestpath2mod[conftestpath] = mod - dirpath = conftestpath.dirpath() + self._conftestpath2mod[conftestpath_key] = mod + dirpath = conftestpath_key.dirpath() if dirpath in self._dirpath2confmods: for path, mods in self._dirpath2confmods.items(): if path and path.relto(dirpath) or path == dirpath: diff --git a/testing/test_windows_conftest_import_casing.py b/testing/test_windows_conftest_import_casing.py new file mode 100644 index 00000000000..f6eb78a1c1d --- /dev/null +++ b/testing/test_windows_conftest_import_casing.py @@ -0,0 +1,31 @@ +import sys + +import pytest + + +pytestmark = pytest.mark.skipif( + sys.platform != "win32", + reason="requires Windows path casing behavior", +) + + +def test_conftest_import_preserves_package_casing(pytester): + pytester.makepyfile( + **{ + "imageProcessing/__init__.py": "", + "imageProcessing/subpkg/__init__.py": "", + "imageProcessing/subpkg/helper.py": "FOO = 1\n", + "conftest.py": ( + "from imageProcessing.subpkg import helper\n\n" + "def pytest_configure(config):\n" + " config._helper_value = helper.FOO\n" + ), + "test_sample.py": ( + "def test_conftest_import(pytestconfig):\n" + " assert pytestconfig._helper_value == 1\n" + ), + } + ) + + result = pytester.runpytest("-q") + result.assert_outcomes(passed=1)