summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNed Batchelder <ned@nedbatchelder.com>2017-05-04 21:18:43 -0400
committerNed Batchelder <ned@nedbatchelder.com>2017-05-04 21:18:43 -0400
commit594eb5ba5b69956bfd4c12e03c4e2c7c1155302a (patch)
tree2d754887461f0198c5becfa81a4eb74bd95ccc42
parente062672509aea7cc7fb302c5f086a26ec6840989 (diff)
downloadpython-coveragepy-594eb5ba5b69956bfd4c12e03c4e2c7c1155302a.tar.gz
Don't warn that namespace packages have no code. #572
-rw-r--r--CHANGES.rst4
-rw-r--r--coverage/control.py44
-rw-r--r--tests/modules/namespace_420/sub1/__init__.py4
-rw-r--r--tests/moremodules/namespace_420/sub2/__init__.py4
-rw-r--r--tests/test_api.py17
5 files changed, 57 insertions, 16 deletions
diff --git a/CHANGES.rst b/CHANGES.rst
index 4aac4a4..fd2d7b8 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -14,6 +14,9 @@ Unreleased
instead of the correct ``pkg/__init__.py``. This is now fixed, closing
`issue 526`_. Thanks, Dirk Thomas.
+- Namespace packages are no longer warned as having no code, as described in
+ `issue 572`_.
+
- Code that uses ``sys.settrace(sys.gettrace())`` in a file that wasn't being
coverage-measured would prevent correct coverage measurement in following
code. An example of this was running doctests programmatically, as described
@@ -26,6 +29,7 @@ Unreleased
fail under Python 2, as reported in `issue 573`_. This is now fixed.
.. _issue 526: https://bitbucket.org/ned/coveragepy/issues/526/generated-xml-invalid-paths-for-cobertura
+.. _issue 572: https://bitbucket.org/ned/coveragepy/issues/572/no-python-source-warning-for-namespace
.. _issue 573: https://bitbucket.org/ned/coveragepy/issues/573/cant-generate-xml-report-if-some-source
.. _issue 575: https://bitbucket.org/ned/coveragepy/issues/575/running-doctest-prevents-complete-coverage
diff --git a/coverage/control.py b/coverage/control.py
index fb03361..2cbe491 100644
--- a/coverage/control.py
+++ b/coverage/control.py
@@ -837,18 +837,7 @@ class Coverage(object):
# then we never encountered those packages.
if self._warn_unimported_source:
for pkg in self.source_pkgs_unmatched:
- if pkg not in sys.modules:
- self._warn("Module %s was never imported." % pkg, slug="module-not-imported")
- elif not (
- hasattr(sys.modules[pkg], '__file__') and
- os.path.exists(sys.modules[pkg].__file__)
- ):
- self._warn("Module %s has no Python source." % pkg, slug="module-not-python")
- else:
- self._warn(
- "Module %s was previously imported, but not measured." % pkg,
- slug="module-not-measured",
- )
+ self._warn_about_unmeasured_code(pkg)
# Find out if we got any data.
if not self.data and self._warn_no_data:
@@ -869,6 +858,37 @@ class Coverage(object):
if self.config.note:
self.data.add_run_info(note=self.config.note)
+ def _warn_about_unmeasured_code(self, pkg):
+ """Warn about a package or module that we never traced.
+
+ `pkg` is a string, the name of the package or module.
+
+ """
+ mod = sys.modules.get(pkg)
+ if mod is None:
+ self._warn("Module %s was never imported." % pkg, slug="module-not-imported")
+ return
+
+ is_namespace = hasattr(mod, '__path__') and not hasattr(mod, '__file__')
+ has_file = hasattr(mod, '__file__') and os.path.exists(mod.__file__)
+
+ if is_namespace:
+ # A namespace package. It's OK for this not to have been traced,
+ # since there is no code directly in it.
+ return
+
+ if not has_file:
+ self._warn("Module %s has no Python source." % pkg, slug="module-not-python")
+ return
+
+ # The module was in sys.modules, and seems like a module with code, but
+ # we never measured it. I guess that means it was imported before
+ # coverage even started.
+ self._warn(
+ "Module %s was previously imported, but not measured." % pkg,
+ slug="module-not-measured",
+ )
+
def _find_plugin_files(self, src_dir):
"""Get executable files from the plugins."""
for plugin in self.plugins:
diff --git a/tests/modules/namespace_420/sub1/__init__.py b/tests/modules/namespace_420/sub1/__init__.py
new file mode 100644
index 0000000..94bb295
--- /dev/null
+++ b/tests/modules/namespace_420/sub1/__init__.py
@@ -0,0 +1,4 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
+
+sub1 = "namespace_420 sub1"
diff --git a/tests/moremodules/namespace_420/sub2/__init__.py b/tests/moremodules/namespace_420/sub2/__init__.py
new file mode 100644
index 0000000..0839688
--- /dev/null
+++ b/tests/moremodules/namespace_420/sub2/__init__.py
@@ -0,0 +1,4 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
+
+sub2 = "namespace_420 sub2"
diff --git a/tests/test_api.py b/tests/test_api.py
index 90f9654..3a06da3 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -445,7 +445,7 @@ class ApiTest(CoverageTest):
self.assertNotIn("no-data-collected", err)
-class NamespaceModuleTest(CoverageTest):
+class NamespaceModuleTest(UsingModulesMixin, CoverageTest):
"""Test PEP-420 namespace modules."""
def setUp(self):
@@ -454,14 +454,23 @@ class NamespaceModuleTest(CoverageTest):
self.skipTest("Python before 3.3 doesn't have namespace packages")
def test_explicit_namespace_module(self):
- self.make_file("namespace/package/module.py", "VAR = 1\n")
- self.make_file("main.py", "import namespace\n")
+ self.make_file("main.py", "import namespace_420\n")
cov = coverage.Coverage()
self.start_import_stop(cov, "main")
with self.assertRaisesRegex(CoverageException, r"Module .* has no file"):
- cov.analysis(sys.modules['namespace'])
+ cov.analysis(sys.modules['namespace_420'])
+
+ def test_bug_572(self):
+ self.make_file("main.py", "import namespace_420\n")
+
+ # Use source=namespace_420 to trigger the check that used to fail,
+ # and use source=main so that something is measured.
+ cov = coverage.Coverage(source=["namespace_420", "main"])
+ with self.assert_warnings(cov, []):
+ self.start_import_stop(cov, "main")
+ cov.report()
class OmitIncludeTestsMixin(UsingModulesMixin, CoverageTestMethodsMixin):