diff --git a/CHANGES.rst b/CHANGES.rst index 7e9c69383..856731d02 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -22,10 +22,16 @@ This list is detailed and covers changes in each pre-release version. Unreleased ---------- -- Fix: the sticky header on the HTML report didn't work unless you had branch - coverage enabled. This is now fixed, the sticky header works for everyone. +- Fix: The sticky header on the HTML report didn't work unless you had branch + coverage enabled. This is now fixed: the sticky header works for everyone. (Do people still use coverage without branch measurement!? j/k) +- Fix: When using explicitly declared namespace packages, the "already imported + a file that will be measured" warning would be issued (`issue 888`_). This + is now fixed. + +.. _issue 888: https://github.com/nedbat/coveragepy/issues/888 + .. _changes_61: diff --git a/coverage/inorout.py b/coverage/inorout.py index 3bc7e54e4..8badf4f77 100644 --- a/coverage/inorout.py +++ b/coverage/inorout.py @@ -479,6 +479,10 @@ def warn_already_imported_files(self): if filename in warned: continue + if len(getattr(mod, "__path__", ())) > 1: + # A namespace package, which confuses this code, so ignore it. + continue + disp = self.should_trace(filename) if disp.has_dynamic_filename: # A plugin with dynamic filenames: the Python file diff --git a/lab/treetopy.sh b/lab/treetopy.sh new file mode 100644 index 000000000..2dcf1cac3 --- /dev/null +++ b/lab/treetopy.sh @@ -0,0 +1,6 @@ +# Turn a tree of Python files into a series of make_file calls. +for f in **/*.py; do + echo 'make_file("'$1$f'", """\\' + sed -e 's/^/ /' <$f + echo ' """)' +done diff --git a/tests/test_venv.py b/tests/test_venv.py index e66103372..c9c204973 100644 --- a/tests/test_venv.py +++ b/tests/test_venv.py @@ -84,7 +84,6 @@ def fifth(x): def sixth(x): return 6 * x """) - # The setup.py to install everything. make_file("another_pkg/setup.py", """\ import setuptools setuptools.setup( @@ -93,9 +92,52 @@ def sixth(x): ) """) + # Bug888 code. + make_file("bug888/app/setup.py", """\ + from setuptools import setup + setup( + name='testcov', + packages=['testcov'], + namespace_packages=['testcov'], + ) + """) + make_file("bug888/app/testcov/__init__.py", """\ + try: # pragma: no cover + __import__('pkg_resources').declare_namespace(__name__) + except ImportError: # pragma: no cover + from pkgutil import extend_path + __path__ = extend_path(__path__, __name__) + """) + make_file("bug888/app/testcov/main.py", """\ + import pkg_resources + for entry_point in pkg_resources.iter_entry_points('plugins'): + entry_point.load()() + """) + make_file("bug888/plugin/setup.py", """\ + from setuptools import setup + setup( + name='testcov-plugin', + packages=['testcov'], + namespace_packages=['testcov'], + entry_points={'plugins': ['testp = testcov.plugin:testp']}, + ) + """) + make_file("bug888/plugin/testcov/__init__.py", """\ + try: # pragma: no cover + __import__('pkg_resources').declare_namespace(__name__) + except ImportError: # pragma: no cover + from pkgutil import extend_path + __path__ = extend_path(__path__, __name__) + """) + make_file("bug888/plugin/testcov/plugin.py", """\ + def testp(): + print("Plugin here") + """) + # Install the third-party packages. run_in_venv("python -m pip install --no-index ./third_pkg") run_in_venv("python -m pip install --no-index -e ./another_pkg") + run_in_venv("python -m pip install --no-index -e ./bug888/app -e ./bug888/plugin") shutil.rmtree("third_pkg") # Install coverage. @@ -141,7 +183,7 @@ def in_venv_world_fixture(self, venv_world): yield for fname in os.listdir("."): - if fname not in {"venv", "another_pkg"}: + if fname not in {"venv", "another_pkg", "bug888"}: os.remove(fname) def get_trace_output(self): @@ -274,3 +316,11 @@ def test_installed_namespace_packages(self, coverage_command): assert "colorsys" not in out assert "fifth" in out assert "sixth" in out + + def test_bug888(self, coverage_command): + out = run_in_venv( + coverage_command + + " run --source=bug888/app,bug888/plugin bug888/app/testcov/main.py" + ) + # When the test fails, the output includes "Already imported a file that will be measured" + assert out == "Plugin here\n"