Skip to content

Commit

Permalink
module __file__ attribute does not return the canonical path
Browse files Browse the repository at this point in the history
  • Loading branch information
woutdenolf committed Jan 15, 2024
1 parent 9af6d46 commit 9fd22b7
Show file tree
Hide file tree
Showing 6 changed files with 34 additions and 18 deletions.
1 change: 1 addition & 0 deletions src/_pytest/_py/path.py
Original file line number Diff line number Diff line change
Expand Up @@ -1128,6 +1128,7 @@ def pyimport(self, modname=None, ensuresyspath=True):
# be in a namespace package ... too icky to check
modfile = mod.__file__
assert modfile is not None
modfile = os.path.realpath(modfile)

Check warning on line 1131 in src/_pytest/_py/path.py

View check run for this annotation

Codecov / codecov/patch

src/_pytest/_py/path.py#L1131

Added line #L1131 was not covered by tests
if modfile[-4:] in (".pyc", ".pyo"):
modfile = modfile[:-1]
elif modfile.endswith("$py.class"):
Expand Down
12 changes: 5 additions & 7 deletions src/_pytest/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
from _pytest.pathlib import bestrelpath
from _pytest.pathlib import import_path
from _pytest.pathlib import ImportMode
from _pytest.pathlib import module_realfile
from _pytest.pathlib import resolve_package_path
from _pytest.pathlib import safe_exists
from _pytest.stash import Stash
Expand Down Expand Up @@ -631,8 +632,7 @@ def _rget_with_confmod(
def _importconftest(
self, conftestpath: Path, importmode: Union[str, ImportMode], rootpath: Path
) -> types.ModuleType:
conftestpath_plugin_name = str(conftestpath)
existing = self.get_plugin(conftestpath_plugin_name)
existing = self.get_plugin(str(conftestpath))
if existing is not None:
return cast(types.ModuleType, existing)

Expand Down Expand Up @@ -668,7 +668,7 @@ def _importconftest(
)
mods.append(mod)
self.trace(f"loading conftestmodule {mod!r}")
self.consider_conftest(mod, registration_name=conftestpath_plugin_name)
self.consider_conftest(mod)
return mod

def _check_non_top_pytest_plugins(
Expand Down Expand Up @@ -748,11 +748,9 @@ def consider_pluginarg(self, arg: str) -> None:
del self._name2plugin["pytest_" + name]
self.import_plugin(arg, consider_entry_points=True)

def consider_conftest(
self, conftestmodule: types.ModuleType, registration_name: str
) -> None:
def consider_conftest(self, conftestmodule: types.ModuleType) -> None:
""":meta private:"""
self.register(conftestmodule, name=registration_name)
self.register(conftestmodule, name=module_realfile(conftestmodule))

def consider_env(self) -> None:
""":meta private:"""
Expand Down
21 changes: 13 additions & 8 deletions src/_pytest/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
from _pytest.outcomes import TEST_OUTCOME
from _pytest.pathlib import absolutepath
from _pytest.pathlib import bestrelpath
from _pytest.pathlib import module_realfile
from _pytest.scope import _ScopeName
from _pytest.scope import HIGH_SCOPES
from _pytest.scope import Scope
Expand Down Expand Up @@ -1486,16 +1487,20 @@ def getfixtureinfo(
def pytest_plugin_registered(self, plugin: _PluggyPlugin) -> None:
nodeid = None
try:
p = absolutepath(plugin.__file__) # type: ignore[attr-defined]
module_file = module_realfile(plugin) # type: ignore[arg-type]
except AttributeError:
pass
else:
# Construct the base nodeid which is later used to check
# what fixtures are visible for particular tests (as denoted
# by their test id).
if p.name == "conftest.py":
module_file = None

# Construct the base nodeid which is later used to check
# what fixtures are visible for particular tests (as denoted
# by their test id).
if module_file is not None:
module_purefile = Path(module_file)
if module_purefile.name == "conftest.py":
try:
nodeid = str(p.parent.relative_to(self.config.rootpath))
nodeid = str(
module_purefile.parent.relative_to(self.config.rootpath)
)
except ValueError:
nodeid = ""
if nodeid == ".":
Expand Down
3 changes: 2 additions & 1 deletion src/_pytest/helpconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from _pytest.config import ExitCode
from _pytest.config import PrintHelp
from _pytest.config.argparsing import Parser
from _pytest.pathlib import module_realfile
from _pytest.terminal import TerminalReporter


Expand Down Expand Up @@ -265,7 +266,7 @@ def pytest_report_header(config: Config) -> List[str]:
items = config.pluginmanager.list_name_plugin()
for name, plugin in items:
if hasattr(plugin, "__file__"):
r = plugin.__file__
r = module_realfile(plugin)

Check warning on line 269 in src/_pytest/helpconfig.py

View check run for this annotation

Codecov / codecov/patch

src/_pytest/helpconfig.py#L269

Added line #L269 was not covered by tests
else:
r = repr(plugin)
lines.append(f" {name:<20}: {r}")
Expand Down
13 changes: 12 additions & 1 deletion src/_pytest/pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -572,7 +572,7 @@ def import_path(

ignore = os.environ.get("PY_IGNORE_IMPORTMISMATCH", "")
if ignore != "1":
module_file = mod.__file__
module_file = module_realfile(mod)
if module_file is None:
raise ImportPathMismatchError(module_name, module_file, path)

Expand Down Expand Up @@ -788,3 +788,14 @@ def safe_exists(p: Path) -> bool:
# ValueError: stat: path too long for Windows
# OSError: [WinError 123] The filename, directory name, or volume label syntax is incorrect
return False


def module_realfile(module: ModuleType) -> Optional[str]:
"""Return the canonical __file__ of the module without resolving symlinks."""
filename = module.__file__
if filename is None:
return None

Check warning on line 797 in src/_pytest/pathlib.py

View check run for this annotation

Codecov / codecov/patch

src/_pytest/pathlib.py#L797

Added line #L797 was not covered by tests
resolved_filename = os.path.realpath(filename)
if resolved_filename.lower() == filename.lower():
return resolved_filename
return filename

Check warning on line 801 in src/_pytest/pathlib.py

View check run for this annotation

Codecov / codecov/patch

src/_pytest/pathlib.py#L801

Added line #L801 was not covered by tests
2 changes: 1 addition & 1 deletion testing/test_pluginmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@ def test_consider_conftest_deps(
pytester.makepyfile("pytest_plugins='xyz'"), root=pytester.path
)
with pytest.raises(ImportError):
pytestpm.consider_conftest(mod, registration_name="unused")
pytestpm.consider_conftest(mod)


class TestPytestPluginManagerBootstrapming:
Expand Down

0 comments on commit 9fd22b7

Please sign in to comment.