diff options
-rw-r--r-- | coverage/codeunit.py | 56 | ||||
-rw-r--r-- | coverage/control.py | 3 | ||||
-rw-r--r-- | coverage/execfile.py | 2 | ||||
-rw-r--r-- | coverage/files.py | 78 | ||||
-rw-r--r-- | coverage/parser.py | 2 | ||||
-rw-r--r-- | coverage/plugin.py | 2 | ||||
-rw-r--r-- | coverage/python.py | 136 | ||||
-rw-r--r-- | tests/test_codeunit.py | 3 | ||||
-rw-r--r-- | tests/test_files.py | 22 | ||||
-rw-r--r-- | tests/test_python.py | 27 |
10 files changed, 172 insertions, 159 deletions
diff --git a/coverage/codeunit.py b/coverage/codeunit.py index e9efa396..998aa098 100644 --- a/coverage/codeunit.py +++ b/coverage/codeunit.py @@ -1,12 +1,9 @@ """Code unit (module) handling for Coverage.""" import os -import sys from coverage.backward import unicode_class -from coverage.files import get_python_source, FileLocator -from coverage.parser import PythonParser -from coverage.phystokens import source_token_lines, source_encoding +from coverage.files import FileLocator class CodeUnit(object): @@ -114,54 +111,3 @@ class CodeUnit(object): def get_parser(self, exclude=None): raise NotImplementedError - - -class PythonCodeUnit(CodeUnit): - """Represents a Python file.""" - - def __init__(self, morf, file_locator=None): - super(PythonCodeUnit, self).__init__(morf, file_locator) - self._source = None - - def _adjust_filename(self, fname): - # .pyc files should always refer to a .py instead. - if fname.endswith(('.pyc', '.pyo')): - fname = fname[:-1] - elif fname.endswith('$py.class'): # Jython - fname = fname[:-9] + ".py" - return fname - - def source(self): - if self._source is None: - self._source = get_python_source(self.filename) - if sys.version_info < (3, 0): - encoding = source_encoding(self._source) - self._source = self._source.decode(encoding, "replace") - assert isinstance(self._source, unicode_class) - return self._source - - def get_parser(self, exclude=None): - return PythonParser(filename=self.filename, exclude=exclude) - - def should_be_python(self): - """Does it seem like this file should contain Python? - - This is used to decide if a file reported as part of the execution of - a program was really likely to have contained Python in the first - place. - - """ - # Get the file extension. - _, ext = os.path.splitext(self.filename) - - # Anything named *.py* should be Python. - if ext.startswith('.py'): - return True - # A file with no extension should be Python. - if not ext: - return True - # Everything else is probably not Python. - return False - - def source_token_lines(self): - return source_token_lines(self.source()) diff --git a/coverage/control.py b/coverage/control.py index 0ca1e95c..4aaf1af3 100644 --- a/coverage/control.py +++ b/coverage/control.py @@ -10,7 +10,7 @@ import sys from coverage.annotate import AnnotateReporter from coverage.backward import string_class, iitems -from coverage.codeunit import CodeUnit, PythonCodeUnit +from coverage.codeunit import CodeUnit from coverage.collector import Collector from coverage.config import CoverageConfig from coverage.data import CoverageData @@ -22,6 +22,7 @@ from coverage.files import ModuleMatcher from coverage.html import HtmlReporter from coverage.misc import CoverageException, bool_or_none, join_regex from coverage.misc import file_be_gone, overrides +from coverage.python import PythonCodeUnit from coverage.results import Analysis, Numbers from coverage.summary import SummaryReporter from coverage.xmlreport import XmlReporter diff --git a/coverage/execfile.py b/coverage/execfile.py index fc7ad174..2d856897 100644 --- a/coverage/execfile.py +++ b/coverage/execfile.py @@ -7,8 +7,8 @@ import types from coverage.backward import BUILTINS from coverage.backward import PYC_MAGIC_NUMBER, imp, importlib_util_find_spec -from coverage.files import get_python_source from coverage.misc import ExceptionDuringRun, NoCode, NoSource +from coverage.python import get_python_source class DummyLoader(object): diff --git a/coverage/files.py b/coverage/files.py index 9f0827fa..15ccabce 100644 --- a/coverage/files.py +++ b/coverage/files.py @@ -7,10 +7,8 @@ import os.path import posixpath import re import sys -import tokenize -from coverage.misc import CoverageException, NoSource, join_regex -from coverage.phystokens import source_encoding +from coverage.misc import CoverageException, join_regex class FileLocator(object): @@ -56,80 +54,6 @@ class FileLocator(object): return self.canonical_filename_cache[filename] -def read_python_source(filename): - """Read the Python source text from `filename`. - - Returns a str: unicode on Python 3, bytes on Python 2. - - """ - # Python 3.2 provides `tokenize.open`, the best way to open source files. - if sys.version_info >= (3, 2): - f = tokenize.open(filename) - else: - f = open(filename, "rU") - - with f: - return f.read() - - -def get_python_source(filename): - """Return the source code, as a str.""" - base, ext = os.path.splitext(filename) - if ext == ".py" and sys.platform == "win32": - exts = [".py", ".pyw"] - else: - exts = [ext] - - for ext in exts: - try_filename = base + ext - if os.path.exists(try_filename): - # A regular text file: open it. - source = read_python_source(try_filename) - break - - # Maybe it's in a zip file? - source = get_zip_bytes(try_filename) - if source is not None: - if sys.version_info >= (3, 0): - source = source.decode(source_encoding(source)) - break - else: - # Couldn't find source. - raise NoSource("No source for code: '%s'." % filename) - - # Python code should always end with a line with a newline. - if source and source[-1] != '\n': - source += '\n' - - return source - - -def get_zip_bytes(filename): - """Get data from `filename` if it is a zip file path. - - Returns the bytestring data read from the zip file, or None if no zip file - could be found or `filename` isn't in it. The data returned will be - an empty string if the file is empty. - - """ - import zipimport - markers = ['.zip'+os.sep, '.egg'+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 IOError: - continue - assert isinstance(data, bytes) - return data - return None - - if sys.platform == 'win32': def actual_path(path): diff --git a/coverage/parser.py b/coverage/parser.py index a2fa1dfe..f488367d 100644 --- a/coverage/parser.py +++ b/coverage/parser.py @@ -9,7 +9,6 @@ import tokenize from coverage.backward import range # pylint: disable=redefined-builtin from coverage.backward import bytes_to_ints from coverage.bytecode import ByteCodes, CodeObjects -from coverage.files import get_python_source from coverage.misc import nice_pair, expensive, join_regex from coverage.misc import CoverageException, NoSource, NotPython from coverage.phystokens import generate_tokens @@ -46,6 +45,7 @@ class PythonParser(CodeParser): self.filename = filename or "<code>" self.text = text if not self.text: + from coverage.python import get_python_source try: self.text = get_python_source(self.filename) except IOError as err: diff --git a/coverage/plugin.py b/coverage/plugin.py index a0a65e44..362e561a 100644 --- a/coverage/plugin.py +++ b/coverage/plugin.py @@ -1,7 +1,5 @@ """Plugin management for coverage.py""" -import sys - class CoveragePlugin(object): """Base class for coverage.py plugins.""" diff --git a/coverage/python.py b/coverage/python.py new file mode 100644 index 00000000..62376922 --- /dev/null +++ b/coverage/python.py @@ -0,0 +1,136 @@ +"""Python source expertise for coverage.py""" + +import os.path +import sys +import tokenize +import zipimport + +from coverage.backward import unicode_class +from coverage.codeunit import CodeUnit +from coverage.misc import NoSource +from coverage.parser import PythonParser +from coverage.phystokens import source_token_lines, source_encoding + + +def read_python_source(filename): + """Read the Python source text from `filename`. + + Returns a str: unicode on Python 3, bytes on Python 2. + + """ + # Python 3.2 provides `tokenize.open`, the best way to open source files. + if sys.version_info >= (3, 2): + f = tokenize.open(filename) + else: + f = open(filename, "rU") + + with f: + return f.read() + + +def get_python_source(filename): + """Return the source code, as a str.""" + base, ext = os.path.splitext(filename) + if ext == ".py" and sys.platform == "win32": + exts = [".py", ".pyw"] + else: + exts = [ext] + + for ext in exts: + try_filename = base + ext + if os.path.exists(try_filename): + # A regular text file: open it. + source = read_python_source(try_filename) + break + + # Maybe it's in a zip file? + source = get_zip_bytes(try_filename) + if source is not None: + if sys.version_info >= (3, 0): + source = source.decode(source_encoding(source)) + break + else: + # Couldn't find source. + raise NoSource("No source for code: '%s'." % filename) + + # Python code should always end with a line with a newline. + if source and source[-1] != '\n': + source += '\n' + + return source + + +def get_zip_bytes(filename): + """Get data from `filename` if it is a zip file path. + + Returns the bytestring data read from the zip file, or None if no zip file + could be found or `filename` isn't in it. The data returned will be + an empty string if the file is empty. + + """ + markers = ['.zip'+os.sep, '.egg'+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 IOError: + continue + assert isinstance(data, bytes) + return data + return None + + +class PythonCodeUnit(CodeUnit): + """Represents a Python file.""" + + def __init__(self, morf, file_locator=None): + super(PythonCodeUnit, self).__init__(morf, file_locator) + self._source = None + + def _adjust_filename(self, fname): + # .pyc files should always refer to a .py instead. + if fname.endswith(('.pyc', '.pyo')): + fname = fname[:-1] + elif fname.endswith('$py.class'): # Jython + fname = fname[:-9] + ".py" + return fname + + def source(self): + if self._source is None: + self._source = get_python_source(self.filename) + if sys.version_info < (3, 0): + encoding = source_encoding(self._source) + self._source = self._source.decode(encoding, "replace") + assert isinstance(self._source, unicode_class) + return self._source + + def get_parser(self, exclude=None): + return PythonParser(filename=self.filename, exclude=exclude) + + def should_be_python(self): + """Does it seem like this file should contain Python? + + This is used to decide if a file reported as part of the execution of + a program was really likely to have contained Python in the first + place. + + """ + # Get the file extension. + _, ext = os.path.splitext(self.filename) + + # Anything named *.py* should be Python. + if ext.startswith('.py'): + return True + # A file with no extension should be Python. + if not ext: + return True + # Everything else is probably not Python. + return False + + def source_token_lines(self): + return source_token_lines(self.source()) diff --git a/tests/test_codeunit.py b/tests/test_codeunit.py index c8389870..54e525b9 100644 --- a/tests/test_codeunit.py +++ b/tests/test_codeunit.py @@ -3,7 +3,8 @@ import os import sys -from coverage.codeunit import CodeUnit, PythonCodeUnit +from coverage.codeunit import CodeUnit +from coverage.python import PythonCodeUnit from tests.coveragetest import CoverageTest diff --git a/tests/test_files.py b/tests/test_files.py index d7b8bd47..ae56e728 100644 --- a/tests/test_files.py +++ b/tests/test_files.py @@ -2,11 +2,10 @@ import os import os.path -import sys from coverage.files import ( FileLocator, TreeMatcher, FnmatchMatcher, ModuleMatcher, PathAliases, - find_python_files, abs_file, get_zip_bytes + find_python_files, abs_file ) from coverage.misc import CoverageException @@ -249,22 +248,3 @@ class FindPythonFilesTest(CoverageTest): "sub/ssub/__init__.py", "sub/ssub/s.py", "sub/windows.pyw", ]) - - -class GetZipBytesTest(CoverageTest): - """Tests of `get_zip_bytes`.""" - - run_in_temp_dir = False - - def test_get_encoded_zip_files(self): - # See igor.py, do_zipmods, for the text of these files. - zip_file = "tests/zipmods.zip" - sys.path.append(zip_file) # So we can import the files. - for encoding in ["utf8", "gb2312", "hebrew", "shift_jis"]: - filename = zip_file + "/encoded_" + encoding + ".py" - filename = filename.replace("/", os.sep) - zip_data = get_zip_bytes(filename) - zip_text = zip_data.decode(encoding) - self.assertIn('All OK', zip_text) - # Run the code to see that we really got it encoded properly. - __import__("encoded_"+encoding) diff --git a/tests/test_python.py b/tests/test_python.py new file mode 100644 index 00000000..f2c18a10 --- /dev/null +++ b/tests/test_python.py @@ -0,0 +1,27 @@ +"""Tests of coverage/python.py""" + +import os +import sys + +from coverage.python import get_zip_bytes + +from tests.coveragetest import CoverageTest + + +class GetZipBytesTest(CoverageTest): + """Tests of `get_zip_bytes`.""" + + run_in_temp_dir = False + + def test_get_encoded_zip_files(self): + # See igor.py, do_zipmods, for the text of these files. + zip_file = "tests/zipmods.zip" + sys.path.append(zip_file) # So we can import the files. + for encoding in ["utf8", "gb2312", "hebrew", "shift_jis"]: + filename = zip_file + "/encoded_" + encoding + ".py" + filename = filename.replace("/", os.sep) + zip_data = get_zip_bytes(filename) + zip_text = zip_data.decode(encoding) + self.assertIn('All OK', zip_text) + # Run the code to see that we really got it encoded properly. + __import__("encoded_"+encoding) |