diff --git a/changelog/4980.feature.rst b/changelog/4980.feature.rst new file mode 100644 index 000000000..40f1de9c1 --- /dev/null +++ b/changelog/4980.feature.rst @@ -0,0 +1 @@ +Namespace packages are handled better with ``monkeypatch.syspath_prepend`` and ``testdir.syspathinsert`` (via ``pkg_resources.fixup_namespace_packages``). diff --git a/src/_pytest/monkeypatch.py b/src/_pytest/monkeypatch.py index 46d9718da..f6c134664 100644 --- a/src/_pytest/monkeypatch.py +++ b/src/_pytest/monkeypatch.py @@ -262,10 +262,15 @@ class MonkeyPatch(object): def syspath_prepend(self, path): """ Prepend ``path`` to ``sys.path`` list of import locations. """ + from pkg_resources import fixup_namespace_packages + if self._savesyspath is None: self._savesyspath = sys.path[:] sys.path.insert(0, str(path)) + # https://github.com/pypa/setuptools/blob/d8b901bc/docs/pkg_resources.txt#L162-L171 + fixup_namespace_packages(str(path)) + def chdir(self, path): """ Change the current working directory to the specified path. Path can be a string or a py.path.local object. diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index ffcd2982a..f8a79ebc9 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -593,11 +593,16 @@ class Testdir(object): This is undone automatically when this object dies at the end of each test. - """ + from pkg_resources import fixup_namespace_packages + if path is None: path = self.tmpdir - sys.path.insert(0, str(path)) + + dirname = str(path) + sys.path.insert(0, dirname) + fixup_namespace_packages(dirname) + # a call to syspathinsert() usually means that the caller wants to # import some dynamically created files, thus with python3 we # invalidate its import caches @@ -606,12 +611,10 @@ class Testdir(object): def _possibly_invalidate_import_caches(self): # invalidate caches if we can (py33 and above) try: - import importlib + from importlib import invalidate_caches except ImportError: - pass - else: - if hasattr(importlib, "invalidate_caches"): - importlib.invalidate_caches() + return + invalidate_caches() def mkdir(self, name): """Create a new (sub)directory.""" diff --git a/testing/python/collect.py b/testing/python/collect.py index bc7462674..df6070b8d 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -34,8 +34,6 @@ class TestModule(object): ) def test_import_prepend_append(self, testdir, monkeypatch): - syspath = list(sys.path) - monkeypatch.setattr(sys, "path", syspath) root1 = testdir.mkdir("root1") root2 = testdir.mkdir("root2") root1.ensure("x456.py") diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py index bdfbf823c..a92ffcf75 100644 --- a/testing/test_assertrewrite.py +++ b/testing/test_assertrewrite.py @@ -1335,7 +1335,7 @@ class TestEarlyRewriteBailout(object): # Setup conditions for py's fspath trying to import pathlib on py34 # always (previously triggered via xdist only). # Ref: https://github.com/pytest-dev/py/pull/207 - monkeypatch.setattr(sys, "path", [""] + sys.path) + monkeypatch.syspath_prepend("") monkeypatch.delitem(sys.modules, "pathlib", raising=False) testdir.makepyfile( diff --git a/testing/test_monkeypatch.py b/testing/test_monkeypatch.py index 0a953d3f1..d43fb6bab 100644 --- a/testing/test_monkeypatch.py +++ b/testing/test_monkeypatch.py @@ -437,3 +437,28 @@ def test_context(): m.setattr(functools, "partial", 3) assert not inspect.isclass(functools.partial) assert inspect.isclass(functools.partial) + + +def test_syspath_prepend_with_namespace_packages(testdir, monkeypatch): + for dirname in "hello", "world": + d = testdir.mkdir(dirname) + ns = d.mkdir("ns_pkg") + ns.join("__init__.py").write( + "__import__('pkg_resources').declare_namespace(__name__)" + ) + lib = ns.mkdir(dirname) + lib.join("__init__.py").write("def check(): return %r" % dirname) + + monkeypatch.syspath_prepend("hello") + import ns_pkg.hello + + assert ns_pkg.hello.check() == "hello" + + with pytest.raises(ImportError): + import ns_pkg.world + + # Prepending should call fixup_namespace_packages. + monkeypatch.syspath_prepend("world") + import ns_pkg.world + + assert ns_pkg.world.check() == "world" diff --git a/testing/test_pluginmanager.py b/testing/test_pluginmanager.py index 6b44f3e0c..10b54c112 100644 --- a/testing/test_pluginmanager.py +++ b/testing/test_pluginmanager.py @@ -4,7 +4,6 @@ from __future__ import division from __future__ import print_function import os -import re import sys import types @@ -165,10 +164,10 @@ def test_importplugin_error_message(testdir, pytestpm): with pytest.raises(ImportError) as excinfo: pytestpm.import_plugin("qwe") - expected_message = '.*Error importing plugin "qwe": Not possible to import: .' - expected_traceback = ".*in test_traceback" - assert re.match(expected_message, str(excinfo.value)) - assert re.match(expected_traceback, str(excinfo.traceback[-1])) + assert str(excinfo.value).endswith( + 'Error importing plugin "qwe": Not possible to import: ☺' + ) + assert "in test_traceback" in str(excinfo.traceback[-1]) class TestPytestPluginManager(object):