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

Try to import module before creating dummy modules with 'importmode=importlib' #9681

Merged
merged 1 commit into from
Feb 14, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/9645.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed regression where ``--import-mode=importlib`` used together with :envvar:`PYTHONPATH` or :confval:`pythonpath` would cause import errors in test suites.
19 changes: 14 additions & 5 deletions src/_pytest/pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -603,11 +603,20 @@ def insert_missing_modules(modules: Dict[str, ModuleType], module_name: str) ->
module_parts = module_name.split(".")
while module_name:
if module_name not in modules:
module = ModuleType(
module_name,
doc="Empty module created by pytest's importmode=importlib.",
)
modules[module_name] = module
try:
# If sys.meta_path is empty, calling import_module will issue
# a warning and raise ModuleNotFoundError. To avoid the
# warning, we check sys.meta_path explicitly and raise the error
# ourselves to fall back to creating a dummy module.
if not sys.meta_path:
raise ModuleNotFoundError
importlib.import_module(module_name)
except ModuleNotFoundError:
module = ModuleType(
module_name,
doc="Empty module created by pytest's importmode=importlib.",
)
modules[module_name] = module
module_parts.pop(-1)
module_name = ".".join(module_parts)

Expand Down
29 changes: 29 additions & 0 deletions testing/test_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -1507,6 +1507,35 @@ def test_modules_not_importable_as_side_effect(self, pytester: Pytester) -> None
]
)

def test_using_python_path(self, pytester: Pytester) -> None:
"""
Dummy modules created by insert_missing_modules should not get in
the way of modules that could be imported via python path (#9645).
"""
pytester.makeini(
"""
[pytest]
pythonpath = .
addopts = --import-mode importlib
"""
)
pytester.makepyfile(
**{
"tests/__init__.py": "",
"tests/conftest.py": "",
"tests/subpath/__init__.py": "",
"tests/subpath/helper.py": "",
"tests/subpath/test_something.py": """
import tests.subpath.helper

def test_something():
assert True
""",
}
)
result = pytester.runpytest()
result.stdout.fnmatch_lines("*1 passed in*")


def test_does_not_crash_on_error_from_decorated_function(pytester: Pytester) -> None:
"""Regression test for an issue around bad exception formatting due to
Expand Down
19 changes: 12 additions & 7 deletions testing/test_pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -562,15 +562,20 @@ def test_module_name_from_path(self, tmp_path: Path) -> None:
result = module_name_from_path(Path("/home/foo/test_foo.py"), Path("/bar"))
assert result == "home.foo.test_foo"

def test_insert_missing_modules(self) -> None:
modules = {"src.tests.foo": ModuleType("src.tests.foo")}
insert_missing_modules(modules, "src.tests.foo")
assert sorted(modules) == ["src", "src.tests", "src.tests.foo"]
def test_insert_missing_modules(
self, monkeypatch: MonkeyPatch, tmp_path: Path
) -> None:
monkeypatch.chdir(tmp_path)
# Use 'xxx' and 'xxy' as parent names as they are unlikely to exist and
# don't end up being imported.
modules = {"xxx.tests.foo": ModuleType("xxx.tests.foo")}
insert_missing_modules(modules, "xxx.tests.foo")
assert sorted(modules) == ["xxx", "xxx.tests", "xxx.tests.foo"]

mod = ModuleType("mod", doc="My Module")
modules = {"src": mod}
insert_missing_modules(modules, "src")
assert modules == {"src": mod}
modules = {"xxy": mod}
insert_missing_modules(modules, "xxy")
assert modules == {"xxy": mod}

modules = {}
insert_missing_modules(modules, "")
Expand Down