From 475119988ce4960bc7356a82d8ea2518d2c6089d Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Fri, 22 Mar 2019 14:36:11 +0100 Subject: [PATCH 1/5] monkeypatch.syspath_prepend: call fixup_namespace_packages Without the patch the test fails as follows: # Prepending should call fixup_namespace_packages. monkeypatch.syspath_prepend("world") > import ns_pkg.world E ModuleNotFoundError: No module named 'ns_pkg.world' --- changelog/4980.feature.rst | 1 + src/_pytest/monkeypatch.py | 5 +++++ testing/test_monkeypatch.py | 25 +++++++++++++++++++++++++ 3 files changed, 31 insertions(+) create mode 100644 changelog/4980.feature.rst diff --git a/changelog/4980.feature.rst b/changelog/4980.feature.rst new file mode 100644 index 00000000000..1c42547c12c --- /dev/null +++ b/changelog/4980.feature.rst @@ -0,0 +1 @@ +``monkeypatch.syspath_prepend`` calls ``pkg_resources.fixup_namespace_packages`` to handle namespace packages better. diff --git a/src/_pytest/monkeypatch.py b/src/_pytest/monkeypatch.py index 46d9718da7f..f6c13466433 100644 --- a/src/_pytest/monkeypatch.py +++ b/src/_pytest/monkeypatch.py @@ -262,10 +262,15 @@ def delenv(self, name, raising=True): 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/testing/test_monkeypatch.py b/testing/test_monkeypatch.py index 0a953d3f1bf..d43fb6babdf 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" From 05d55b86df7c955a29ddd439a52bc66f78e5ab25 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Fri, 22 Mar 2019 16:20:55 +0100 Subject: [PATCH 2/5] tests: minor sys.path cleanup --- testing/python/collect.py | 2 -- testing/test_assertrewrite.py | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/testing/python/collect.py b/testing/python/collect.py index bc7462674e3..df6070b8d1d 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -34,8 +34,6 @@ def test_import_duplicate(self, testdir): ) 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 bdfbf823c59..a92ffcf7514 100644 --- a/testing/test_assertrewrite.py +++ b/testing/test_assertrewrite.py @@ -1335,7 +1335,7 @@ def test_cwd_changed(self, testdir, monkeypatch): # 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( From 5df45f5b278ce3ed5e8dbfba67d317416dcc6c84 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Fri, 22 Mar 2019 16:26:55 +0100 Subject: [PATCH 3/5] Use fixup_namespace_packages also with pytester.syspathinsert --- changelog/4980.feature.rst | 2 +- src/_pytest/pytester.py | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/changelog/4980.feature.rst b/changelog/4980.feature.rst index 1c42547c12c..40f1de9c13f 100644 --- a/changelog/4980.feature.rst +++ b/changelog/4980.feature.rst @@ -1 +1 @@ -``monkeypatch.syspath_prepend`` calls ``pkg_resources.fixup_namespace_packages`` to handle namespace packages better. +Namespace packages are handled better with ``monkeypatch.syspath_prepend`` and ``testdir.syspathinsert`` (via ``pkg_resources.fixup_namespace_packages``). diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index ffcd2982ade..bc1405176a7 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -593,11 +593,16 @@ def syspathinsert(self, path=None): 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 From 56dc01ffe04501089a3228267c60405007ecfa1a Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Fri, 22 Mar 2019 16:29:02 +0100 Subject: [PATCH 4/5] minor: revisit _possibly_invalidate_import_caches --- src/_pytest/pytester.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index bc1405176a7..f8a79ebc93b 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -611,12 +611,10 @@ def syspathinsert(self, path=None): 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.""" From fd64fa1863e347246b9f9bf0a0e3dd7af5f6284b Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Fri, 22 Mar 2019 16:56:00 +0100 Subject: [PATCH 5/5] Revisit test_importplugin_error_message Should be more helpful in case of errors than before: > assert re.match(expected_message, str(excinfo.value)) E _pytest.warning_types.PytestWarning: asserting the value None, please use "assert is None" https://travis-ci.org/pytest-dev/pytest/jobs/509970576#L208 --- testing/test_pluginmanager.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/testing/test_pluginmanager.py b/testing/test_pluginmanager.py index 6b44f3e0cc7..10b54c1125d 100644 --- a/testing/test_pluginmanager.py +++ b/testing/test_pluginmanager.py @@ -4,7 +4,6 @@ from __future__ import print_function import os -import re import sys import types @@ -165,10 +164,10 @@ def test_traceback(): 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):