diff options
-rw-r--r-- | CHANGES.rst | 7 | ||||
-rw-r--r-- | coverage/control.py | 49 | ||||
-rw-r--r-- | tests/test_api.py | 10 | ||||
-rw-r--r-- | tests/test_python.py | 36 |
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" |