| Index: tools/telemetry/third_party/coverage/coverage/python.py
|
| diff --git a/tools/telemetry/third_party/coverage/coverage/python.py b/tools/telemetry/third_party/coverage/coverage/python.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..33e6ec01045ebbf9aa5ba8bfd76a4768dcc2a148
|
| --- /dev/null
|
| +++ b/tools/telemetry/third_party/coverage/coverage/python.py
|
| @@ -0,0 +1,192 @@
|
| +# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
| +# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
|
| +
|
| +"""Python source expertise for coverage.py"""
|
| +
|
| +import os.path
|
| +import zipimport
|
| +
|
| +from coverage import env, files
|
| +from coverage.misc import contract, expensive, NoSource, join_regex
|
| +from coverage.parser import PythonParser
|
| +from coverage.phystokens import source_token_lines, source_encoding
|
| +from coverage.plugin import FileReporter
|
| +
|
| +
|
| +@contract(returns='bytes')
|
| +def read_python_source(filename):
|
| + """Read the Python source text from `filename`.
|
| +
|
| + Returns bytes.
|
| +
|
| + """
|
| + with open(filename, "rb") as f:
|
| + return f.read().replace(b"\r\n", b"\n").replace(b"\r", b"\n")
|
| +
|
| +
|
| +@contract(returns='unicode')
|
| +def get_python_source(filename):
|
| + """Return the source code, as unicode."""
|
| + base, ext = os.path.splitext(filename)
|
| + if ext == ".py" and env.WINDOWS:
|
| + 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:
|
| + break
|
| + else:
|
| + # Couldn't find source.
|
| + raise NoSource("No source for code: '%s'." % filename)
|
| +
|
| + source = source.decode(source_encoding(source), "replace")
|
| +
|
| + # Python code should always end with a line with a newline.
|
| + if source and source[-1] != '\n':
|
| + source += '\n'
|
| +
|
| + return source
|
| +
|
| +
|
| +@contract(returns='bytes|None')
|
| +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
|
| + return data
|
| + return None
|
| +
|
| +
|
| +class PythonFileReporter(FileReporter):
|
| + """Report support for a Python file."""
|
| +
|
| + def __init__(self, morf, coverage=None):
|
| + self.coverage = coverage
|
| +
|
| + if hasattr(morf, '__file__'):
|
| + filename = morf.__file__
|
| + else:
|
| + filename = morf
|
| +
|
| + # .pyc files should always refer to a .py instead.
|
| + if filename.endswith(('.pyc', '.pyo')):
|
| + filename = filename[:-1]
|
| + elif filename.endswith('$py.class'): # Jython
|
| + filename = filename[:-9] + ".py"
|
| +
|
| + super(PythonFileReporter, self).__init__(files.canonical_filename(filename))
|
| +
|
| + if hasattr(morf, '__name__'):
|
| + name = morf.__name__
|
| + name = name.replace(".", os.sep) + ".py"
|
| + else:
|
| + name = files.relative_filename(filename)
|
| + self.relname = name
|
| +
|
| + self._source = None
|
| + self._parser = None
|
| + self._statements = None
|
| + self._excluded = None
|
| +
|
| + def relative_filename(self):
|
| + return self.relname
|
| +
|
| + @property
|
| + def parser(self):
|
| + """Lazily create a :class:`PythonParser`."""
|
| + if self._parser is None:
|
| + self._parser = PythonParser(
|
| + filename=self.filename,
|
| + exclude=self.coverage._exclude_regex('exclude'),
|
| + )
|
| + return self._parser
|
| +
|
| + @expensive
|
| + def lines(self):
|
| + """Return the line numbers of statements in the file."""
|
| + if self._statements is None:
|
| + self._statements, self._excluded = self.parser.parse_source()
|
| + return self._statements
|
| +
|
| + @expensive
|
| + def excluded_lines(self):
|
| + """Return the line numbers of statements in the file."""
|
| + if self._excluded is None:
|
| + self._statements, self._excluded = self.parser.parse_source()
|
| + return self._excluded
|
| +
|
| + def translate_lines(self, lines):
|
| + return self.parser.translate_lines(lines)
|
| +
|
| + def translate_arcs(self, arcs):
|
| + return self.parser.translate_arcs(arcs)
|
| +
|
| + @expensive
|
| + def no_branch_lines(self):
|
| + no_branch = self.parser.lines_matching(
|
| + join_regex(self.coverage.config.partial_list),
|
| + join_regex(self.coverage.config.partial_always_list)
|
| + )
|
| + return no_branch
|
| +
|
| + @expensive
|
| + def arcs(self):
|
| + return self.parser.arcs()
|
| +
|
| + @expensive
|
| + def exit_counts(self):
|
| + return self.parser.exit_counts()
|
| +
|
| + @contract(returns='unicode')
|
| + def source(self):
|
| + if self._source is None:
|
| + self._source = get_python_source(self.filename)
|
| + return self._source
|
| +
|
| + 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())
|
|
|