diff -r bedab77d0fa3 -r d6c795b5ce33 DebugClients/Python/coverage/python.py --- a/DebugClients/Python/coverage/python.py Sat Apr 07 13:17:06 2018 +0200 +++ b/DebugClients/Python/coverage/python.py Sat Apr 07 13:35:10 2018 +0200 @@ -8,9 +8,8 @@ import zipimport from coverage import env, files -from coverage.misc import ( - contract, CoverageException, expensive, NoSource, join_regex, isolate_module, -) +from coverage.misc import contract, expensive, isolate_module, join_regex +from coverage.misc import CoverageException, NoSource from coverage.parser import PythonParser from coverage.phystokens import source_token_lines, source_encoding from coverage.plugin import FileReporter @@ -26,7 +25,13 @@ """ with open(filename, "rb") as f: - return f.read().replace(b"\r\n", b"\n").replace(b"\r", b"\n") + source = f.read() + + if env.IRONPYTHON: + # IronPython reads Unicode strings even for "rb" files. + source = bytes(source) + + return source.replace(b"\r\n", b"\n").replace(b"\r", b"\n") @contract(returns='unicode') @@ -51,7 +56,9 @@ break else: # Couldn't find source. - raise NoSource("No source for code: '%s'." % filename) + exc_msg = "No source for code: '%s'.\n" % (filename,) + exc_msg += "Aborting report output, consider using -i." + raise NoSource(exc_msg) # Replace \f because of http://bugs.python.org/issue19035 source = source.replace(b'\f', b' ') @@ -73,7 +80,7 @@ an empty string if the file is empty. """ - markers = ['.zip'+os.sep, '.egg'+os.sep] + markers = ['.zip'+os.sep, '.egg'+os.sep, '.pex'+os.sep] for marker in markers: if marker in filename: parts = filename.split(marker) @@ -89,6 +96,39 @@ return None +def source_for_file(filename): + """Return the source file for `filename`. + + Given a file name being traced, return the best guess as to the source + file to attribute it to. + + """ + if filename.endswith(".py"): + # .py files are themselves source files. + return filename + + elif filename.endswith((".pyc", ".pyo")): + # Bytecode files probably have source files near them. + py_filename = filename[:-1] + if os.path.exists(py_filename): + # Found a .py file, use that. + return py_filename + if env.WINDOWS: + # On Windows, it could be a .pyw file. + pyw_filename = py_filename + "w" + if os.path.exists(pyw_filename): + return pyw_filename + # Didn't find source, but it's probably the .py file we want. + return py_filename + + elif filename.endswith("$py.class"): + # Jython is easy to guess. + return filename[:-9] + ".py" + + # No idea, just use the file name as-is. + return filename + + class PythonFileReporter(FileReporter): """Report support for a Python file.""" @@ -104,19 +144,15 @@ else: filename = morf - filename = files.unicode_filename(filename) - - # .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" + filename = source_for_file(files.unicode_filename(filename)) super(PythonFileReporter, self).__init__(files.canonical_filename(filename)) if hasattr(morf, '__name__'): - name = morf.__name__ - name = name.replace(".", os.sep) + ".py" + name = morf.__name__.replace(".", os.sep) + if os.path.basename(filename).startswith('__init__.'): + name += os.sep + "__init__" + name += ".py" name = files.unicode_filename(name) else: name = files.relative_filename(filename) @@ -127,6 +163,9 @@ self._statements = None self._excluded = None + def __repr__(self): + return "<PythonFileReporter {0!r}>".format(self.filename) + @contract(returns='unicode') def relative_filename(self): return self.relname