Skip to content

Commit 203889b

Browse files
committed
Normalize conftest paths
Refs #9765. While this is probably not needed for fixing the `assert mod not in mods` bug (previous commit takes care of it), I believe this is the right thing to do to avoid other inconsistencies (see discussion in issue).
1 parent a7c2549 commit 203889b

File tree

2 files changed

+30
-17
lines changed

2 files changed

+30
-17
lines changed

src/_pytest/config/__init__.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -340,12 +340,20 @@ def _prepareconfig(
340340
raise
341341

342342

343-
def _get_directory(path: Path) -> Path:
344-
"""Get the directory of a path - itself if already a directory."""
343+
def _get_conftest_directory(path: Path) -> Path:
344+
"""Get the conftest directory of a path.
345+
346+
If the path is a directory, this is the path itself, otherwise its parent
347+
directory.
348+
349+
The path is normalized to remove differences between different sources of
350+
conftest paths which can cause inconsistencies (issue #9765).
351+
"""
345352
if path.is_file():
346-
return path.parent
353+
conftest_directory = path.parent
347354
else:
348-
return path
355+
conftest_directory = path
356+
return Path(os.path.normcase(os.path.normpath(conftest_directory)))
349357

350358

351359
def _get_legacy_hook_marks(
@@ -407,10 +415,10 @@ def __init__(self) -> None:
407415
# If set, conftest loading is skipped.
408416
self._noconftest = False
409417

410-
# _getconftestmodules()'s call to _get_directory() causes a stat
418+
# _getconftestmodules()'s call to _get_conftest_directory() causes a stat
411419
# storm when it's called potentially thousands of times in a test
412420
# session (#9478), often with the same path, so cache it.
413-
self._get_directory = lru_cache(256)(_get_directory)
421+
self._get_conftest_directory = lru_cache(256)(_get_conftest_directory)
414422

415423
# plugins that were explicitly skipped with pytest.skip
416424
# list of (module name, skip reason)
@@ -595,7 +603,7 @@ def _loadconftestmodules(
595603
if self._noconftest:
596604
return
597605

598-
directory = self._get_directory(path)
606+
directory = self._get_conftest_directory(path)
599607

600608
# Optimization: avoid repeated searches in the same directory.
601609
# Assumes always called with same importmode and rootpath.
@@ -615,7 +623,7 @@ def _loadconftestmodules(
615623
self._dirpath2confmods[directory] = clist
616624

617625
def _getconftestmodules(self, path: Path) -> Sequence[types.ModuleType]:
618-
directory = self._get_directory(path)
626+
directory = self._get_conftest_directory(path)
619627
return self._dirpath2confmods.get(directory, ())
620628

621629
def _rget_with_confmod(

testing/test_pluginmanager.py

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -110,25 +110,30 @@ def test_conftestpath_case_sensitivity(self, pytester: Pytester) -> None:
110110
conftest = pytester.path.joinpath("tests/conftest.py")
111111
conftest_upper_case = pytester.path.joinpath("TESTS/conftest.py")
112112

113-
mod = config.pluginmanager._importconftest(
113+
config.pluginmanager._loadconftestmodules(
114114
conftest,
115115
importmode="prepend",
116116
rootpath=pytester.path,
117117
)
118-
plugin = config.pluginmanager.get_plugin(str(conftest))
119-
assert plugin is mod
118+
plugins = config.pluginmanager._getconftestmodules(
119+
conftest,
120+
)
121+
assert len(plugins) == 1
120122

121-
mod_uppercase = config.pluginmanager._importconftest(
123+
config.pluginmanager._loadconftestmodules(
122124
conftest_upper_case,
123125
importmode="prepend",
124126
rootpath=pytester.path,
125127
)
126-
plugin_uppercase = config.pluginmanager.get_plugin(str(conftest_upper_case))
127-
assert plugin_uppercase is mod_uppercase
128+
# conftest paths are normalized, so the plugin is registered in
129+
# lower-case and the mod is not re-imported.
130+
plugins_upper_case = config.pluginmanager._getconftestmodules(
131+
conftest_upper_case,
132+
)
133+
assert plugins_upper_case == plugins
128134

129-
# No str(conftestpath) normalization so conftest should be imported
130-
# twice and modules should be different objects
131-
assert mod is not mod_uppercase
135+
plugin_uppercase = config.pluginmanager.get_plugin(str(conftest_upper_case))
136+
assert plugin_uppercase is None
132137

133138
def test_hook_tracing(self, _config_for_test: Config) -> None:
134139
pytestpm = _config_for_test.pluginmanager # fully initialized with plugins

0 commit comments

Comments
 (0)