summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNed Batchelder <ned@nedbatchelder.com>2022-11-30 18:44:40 -0500
committerNed Batchelder <ned@nedbatchelder.com>2022-12-01 10:21:45 -0500
commit2adeb8bb5c2a12a080d7a528e35e5df9ffe7785f (patch)
tree2f95b6017927c5a1dfeb62c6f0169f022ca0d401
parentc2e35658d8311fd2b5d1460c2cb56762d5fe0ec7 (diff)
downloadpython-coveragepy-git-2adeb8bb5c2a12a080d7a528e35e5df9ffe7785f.tar.gz
fix: when checking source existence for remapping, zipfiles are ok
-rw-r--r--coverage/files.py31
-rw-r--r--coverage/python.py27
-rw-r--r--tests/test_files.py31
3 files changed, 73 insertions, 16 deletions
diff --git a/coverage/files.py b/coverage/files.py
index 63e9afb2..5f222419 100644
--- a/coverage/files.py
+++ b/coverage/files.py
@@ -153,6 +153,35 @@ def abs_file(path):
return actual_path(os.path.abspath(os.path.realpath(path)))
+def zip_location(filename):
+ """Split a filename into a zipfile / inner name pair.
+
+ Only return a pair if the zipfile exists. No check is made if the inner
+ name is in the zipfile.
+
+ """
+ for ext in ['.zip', '.egg', '.pex']:
+ zipbase, extension, inner = filename.partition(ext + sep(filename))
+ if extension:
+ zipfile = zipbase + ext
+ if os.path.exists(zipfile):
+ return zipfile, inner
+ return None
+
+
+def source_exists(path):
+ """Determine if a source file path exists."""
+ if os.path.exists(path):
+ return True
+
+ if zip_location(path):
+ # If zip_location returns anything, then it's a zipfile that
+ # exists. That's good enough for us.
+ return True
+
+ return False
+
+
def python_reported_file(filename):
"""Return the string as Python would describe this file name."""
if env.PYBEHAVIOR.report_absolute_files:
@@ -408,7 +437,7 @@ class PathAliases:
result = result.rstrip(r"\/") + result_sep
self.aliases.append((original_pattern, regex, result))
- def map(self, path, exists=os.path.exists):
+ def map(self, path, exists=source_exists):
"""Map `path` through the aliases.
`path` is checked against all of the patterns. The first pattern to
diff --git a/coverage/python.py b/coverage/python.py
index c8b8e774..b3232085 100644
--- a/coverage/python.py
+++ b/coverage/python.py
@@ -9,7 +9,7 @@ import zipimport
from coverage import env
from coverage.exceptions import CoverageException, NoSource
-from coverage.files import canonical_filename, relative_filename
+from coverage.files import canonical_filename, relative_filename, zip_location
from coverage.misc import contract, expensive, isolate_module, join_regex
from coverage.parser import PythonParser
from coverage.phystokens import source_token_lines, source_encoding
@@ -79,19 +79,18 @@ def get_zip_bytes(filename):
an empty string if the file is empty.
"""
- markers = ['.zip'+os.sep, '.egg'+os.sep, '.pex'+os.sep]
- for marker in markers:
- if marker in filename:
- parts = filename.split(marker)
- try:
- zi = zipimport.zipimporter(parts[0]+marker[:-1])
- except zipimport.ZipImportError:
- continue
- try:
- data = zi.get_data(parts[1])
- except OSError:
- continue
- return data
+ zipfile_inner = zip_location(filename)
+ if zipfile_inner is not None:
+ zipfile, inner = zipfile_inner
+ try:
+ zi = zipimport.zipimporter(zipfile)
+ except zipimport.ZipImportError:
+ return None
+ try:
+ data = zi.get_data(inner)
+ except OSError:
+ return None
+ return data
return None
diff --git a/tests/test_files.py b/tests/test_files.py
index 4baac072..9e49628d 100644
--- a/tests/test_files.py
+++ b/tests/test_files.py
@@ -69,7 +69,7 @@ class FilesTest(CoverageTest):
assert files.canonical_filename('sub/proj1/file1.py') == self.abs_path('file1.py')
@pytest.mark.parametrize(
- ["curdir", "sep"], [
+ "curdir, sep", [
("/", "/"),
("X:\\", "\\"),
]
@@ -81,6 +81,21 @@ class FilesTest(CoverageTest):
files.set_relative_directory()
assert files.relative_directory() == curdir
+ @pytest.mark.parametrize(
+ "to_make, to_check, answer", [
+ ("a/b/c/foo.py", "a/b/c/foo.py", True),
+ ("a/b/c/foo.py", "a/b/c/bar.py", False),
+ ("src/files.zip", "src/files.zip/foo.py", True),
+ ("src/files.egg", "src/files.egg/foo.py", True),
+ ("src/files.pex", "src/files.pex/foo.py", True),
+ ("src/files.zip", "src/morefiles.zip/foo.py", False),
+ ("src/files.pex", "src/files.pex/zipfiles/files.zip/foo.py", True),
+ ]
+ )
+ def test_source_exists(self, to_make, to_check, answer):
+ self.make_file(to_make, "")
+ assert files.source_exists(to_check) == answer
+
@pytest.mark.parametrize("original, flat", [
("abc.py", "abc_py"),
@@ -117,6 +132,7 @@ def globs_to_regex_params(
Everything is yielded so that `test_globs_to_regex` can call
`globs_to_regex` once and check one result.
+
"""
pat_id = "|".join(patterns)
for text in matches:
@@ -578,6 +594,19 @@ class PathAliasesTest(CoverageTest):
self.assert_mapped(aliases, the_file, '/the/source/a.py')
+class PathAliasesRealFilesTest(CoverageTest):
+ """Tests for coverage/files.py:PathAliases using real files."""
+
+ def test_aliasing_zip_files(self):
+ self.make_file("src/zipfiles/code.zip", "fake zip, doesn't matter")
+ aliases = PathAliases()
+ aliases.add("*/d1", "./src")
+ aliases.add("*/d2", "./src")
+
+ expected = files.canonical_filename("src/zipfiles/code.zip/p1.py")
+ assert aliases.map("tox/d1/zipfiles/code.zip/p1.py") == expected
+
+
class FindPythonFilesTest(CoverageTest):
"""Tests of `find_python_files`."""