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()) |