summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.rst7
-rw-r--r--coverage/control.py49
-rw-r--r--tests/test_api.py10
-rw-r--r--tests/test_python.py36
4 files changed, 64 insertions, 38 deletions
diff --git a/CHANGES.rst b/CHANGES.rst
index 886cc6d8..90cb43d2 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -13,6 +13,12 @@ Unreleased
would cause a "No data to report" error, as reported in `issue 549`_. This is
now fixed; thanks, Loïc Dachary.
+- If you specified ``--source`` as a directory, then coverage.py would look for
+ importable Python files in that directory, and could identify ones that had
+ never been executed at all. But if you specified it as a package name, that
+ detection wasn't performed. Now it is, closing `issue 426`_. Thanks to Loïc
+ Dachary for the fix.
+
- If you started and stopped coverage measurement thousands of times in your
process, you could crash Python with a "Fatal Python error: deallocating
None" error. This is now fixed. Thanks to Alex Groce for the bug report.
@@ -34,6 +40,7 @@ Unreleased
left behind. This file is now properly deleted.
.. _issue 322: https://bitbucket.org/ned/coveragepy/issues/322/cannot-use-coverage-with-jython
+.. _issue 426: https://bitbucket.org/ned/coveragepy/issues/426/difference-between-coverage-results-with
.. _issue 549: https://bitbucket.org/ned/coveragepy/issues/549/skip-covered-with-100-coverage-throws-a-no
.. _issue 551: https://bitbucket.org/ned/coveragepy/issues/551/coveragepy-cannot-be-imported-in-jython27
diff --git a/coverage/control.py b/coverage/control.py
index ebb449c3..8ea51537 100644
--- a/coverage/control.py
+++ b/coverage/control.py
@@ -794,8 +794,8 @@ class Coverage(object):
self.collector.save_data(self.data)
- # If there are still entries in the source_pkgs_unmatched list, then we never
- # encountered those packages.
+ # If there are still entries in the source_pkgs_unmatched list,
+ # then we never encountered those packages.
if self._warn_unimported_source:
for pkg in self.source_pkgs_unmatched:
if pkg not in sys.modules:
@@ -812,35 +812,23 @@ class Coverage(object):
if not self.data and self._warn_no_data:
self._warn("No data was collected.")
- src_directories = self.source[:]
-
+ # Find files that were never executed at all.
for pkg in self.source_pkgs:
if (not pkg in sys.modules or
not hasattr(sys.modules[pkg], '__file__') or
not os.path.exists(sys.modules[pkg].__file__)):
continue
pkg_file = source_for_file(sys.modules[pkg].__file__)
- #
- # Do not explore the souce tree of a module that is
- # not a directory tree. For instance if
- # sys.modules['args'].__file__ == /lib/python2.7/site-packages/args.pyc
- # we do not want to explore all of /lib/python2.7/site-packages
- #
+
if not pkg_file.endswith('__init__.py'):
+ # We only want to explore the source tree of packages.
+ # For instance if pkg_file is /lib/python2.7/site-packages/args.pyc,
+ # we do not want to explore all of /lib/python2.7/site-packages.
continue
- src_directories.append(self._canonical_dir(sys.modules[pkg]))
+ self._find_unexecuted_files(self._canonical_dir(pkg_file))
- # Find files that were never executed at all.
- for src in src_directories:
- for py_file in find_python_files(src):
- py_file = files.canonical_filename(py_file)
-
- if self.omit_match and self.omit_match.match(py_file):
- # Turns out this file was omitted, so don't pull it back
- # in as unexecuted.
- continue
-
- self.data.touch_file(py_file)
+ for src in self.source:
+ self._find_unexecuted_files(src)
if self.config.note:
self.data.add_run_info(note=self.config.note)
@@ -848,6 +836,23 @@ class Coverage(object):
self._measured = False
return self.data
+ def _find_unexecuted_files(self, src_dir):
+ """Find unexecuted files in `src_dir`.
+
+ Search for files in `src_dir` that are probably importable,
+ and add them as unexecuted files in `self.data`.
+
+ """
+ for py_file in find_python_files(src_dir):
+ py_file = files.canonical_filename(py_file)
+
+ if self.omit_match and self.omit_match.match(py_file):
+ # Turns out this file was omitted, so don't pull it back
+ # in as unexecuted.
+ continue
+
+ self.data.touch_file(py_file)
+
# Backward compatibility with version 1.
def analysis(self, morf):
"""Like `analysis2` but doesn't return excluded line numbers."""
diff --git a/tests/test_api.py b/tests/test_api.py
index c7fa5ca2..0ab0b75c 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -461,7 +461,15 @@ class SourceOmitIncludeTest(OmitIncludeTestsMixin, CoverageTest):
summary[k[:-3]] = v
return summary
- def test_source_package_unexecuted(self):
+ def test_source_package_as_dir(self):
+ # pkg1 is a directory, since we cd'd into tests/modules in setUp.
+ lines = self.coverage_usepkgs(source=["pkg1"])
+ self.filenames_in(lines, "p1a p1b")
+ self.filenames_not_in(lines, "p2a p2b othera otherb osa osb")
+ # Because source= was specified, we do search for unexecuted files.
+ self.assertEqual(lines['p1c'], 0)
+
+ def test_source_package_as_package(self):
lines = self.coverage_usepkgs(source=["pkg1.sub"])
self.filenames_not_in(lines, "p2a p2b othera otherb osa osb")
# Because source= was specified, we do search for unexecuted files.
diff --git a/tests/test_python.py b/tests/test_python.py
index c4e333ef..9027aa6c 100644
--- a/tests/test_python.py
+++ b/tests/test_python.py
@@ -5,8 +5,10 @@
import os
import sys
-from coverage import env
+import pytest
+
+from coverage import env
from coverage.python import get_zip_bytes, source_for_file
from tests.coveragetest import CoverageTest
@@ -30,26 +32,30 @@ class GetZipBytesTest(CoverageTest):
# Run the code to see that we really got it encoded properly.
__import__("encoded_"+encoding)
+
def test_source_for_file(tmpdir):
path = tmpdir.join("a.py")
src = str(path)
- assert src == source_for_file(src)
- assert src == source_for_file(src + 'c')
- assert src == source_for_file(src + 'o')
+ assert source_for_file(src) == src
+ assert source_for_file(src + 'c') == src
+ assert source_for_file(src + 'o') == src
unknown = src + 'FOO'
- assert unknown == source_for_file(unknown)
- #
+ assert source_for_file(unknown) == unknown
+
+
+@pytest.mark.skipif(not env.WINDOWS, reason="not windows")
+def test_source_for_file_windows(tmpdir):
+ path = tmpdir.join("a.py")
+ src = str(path)
+
# On windows if a pyw exists, it is an acceptable source
- #
- windows = env.WINDOWS
- env.WINDOWS = True
path_windows = tmpdir.ensure("a.pyw")
assert str(path_windows) == source_for_file(src + 'c')
- #
+
# If both pyw and py exist, py is preferred
- #
path.ensure(file=True)
- assert src == source_for_file(src + 'c')
- env.WINDOWS = windows
- jython_src = "a"
- assert jython_src + ".py" == source_for_file(jython_src + "$py.class")
+ assert source_for_file(src + 'c') == src
+
+
+def test_source_for_file_jython():
+ assert source_for_file("a$py.class") == "a.py"