DebugClients/Python/coverage/python.py

changeset 6219
d6c795b5ce33
parent 5178
878ce843ca9f
child 6649
f1b3a73831c9
equal deleted inserted replaced
6218:bedab77d0fa3 6219:d6c795b5ce33
6 import os.path 6 import os.path
7 import types 7 import types
8 import zipimport 8 import zipimport
9 9
10 from coverage import env, files 10 from coverage import env, files
11 from coverage.misc import ( 11 from coverage.misc import contract, expensive, isolate_module, join_regex
12 contract, CoverageException, expensive, NoSource, join_regex, isolate_module, 12 from coverage.misc import CoverageException, NoSource
13 )
14 from coverage.parser import PythonParser 13 from coverage.parser import PythonParser
15 from coverage.phystokens import source_token_lines, source_encoding 14 from coverage.phystokens import source_token_lines, source_encoding
16 from coverage.plugin import FileReporter 15 from coverage.plugin import FileReporter
17 16
18 os = isolate_module(os) 17 os = isolate_module(os)
24 23
25 Returns bytes. 24 Returns bytes.
26 25
27 """ 26 """
28 with open(filename, "rb") as f: 27 with open(filename, "rb") as f:
29 return f.read().replace(b"\r\n", b"\n").replace(b"\r", b"\n") 28 source = f.read()
29
30 if env.IRONPYTHON:
31 # IronPython reads Unicode strings even for "rb" files.
32 source = bytes(source)
33
34 return source.replace(b"\r\n", b"\n").replace(b"\r", b"\n")
30 35
31 36
32 @contract(returns='unicode') 37 @contract(returns='unicode')
33 def get_python_source(filename): 38 def get_python_source(filename):
34 """Return the source code, as unicode.""" 39 """Return the source code, as unicode."""
49 source = get_zip_bytes(try_filename) 54 source = get_zip_bytes(try_filename)
50 if source is not None: 55 if source is not None:
51 break 56 break
52 else: 57 else:
53 # Couldn't find source. 58 # Couldn't find source.
54 raise NoSource("No source for code: '%s'." % filename) 59 exc_msg = "No source for code: '%s'.\n" % (filename,)
60 exc_msg += "Aborting report output, consider using -i."
61 raise NoSource(exc_msg)
55 62
56 # Replace \f because of http://bugs.python.org/issue19035 63 # Replace \f because of http://bugs.python.org/issue19035
57 source = source.replace(b'\f', b' ') 64 source = source.replace(b'\f', b' ')
58 source = source.decode(source_encoding(source), "replace") 65 source = source.decode(source_encoding(source), "replace")
59 66
71 Returns the bytestring data read from the zip file, or None if no zip file 78 Returns the bytestring data read from the zip file, or None if no zip file
72 could be found or `filename` isn't in it. The data returned will be 79 could be found or `filename` isn't in it. The data returned will be
73 an empty string if the file is empty. 80 an empty string if the file is empty.
74 81
75 """ 82 """
76 markers = ['.zip'+os.sep, '.egg'+os.sep] 83 markers = ['.zip'+os.sep, '.egg'+os.sep, '.pex'+os.sep]
77 for marker in markers: 84 for marker in markers:
78 if marker in filename: 85 if marker in filename:
79 parts = filename.split(marker) 86 parts = filename.split(marker)
80 try: 87 try:
81 zi = zipimport.zipimporter(parts[0]+marker[:-1]) 88 zi = zipimport.zipimporter(parts[0]+marker[:-1])
87 continue 94 continue
88 return data 95 return data
89 return None 96 return None
90 97
91 98
99 def source_for_file(filename):
100 """Return the source file for `filename`.
101
102 Given a file name being traced, return the best guess as to the source
103 file to attribute it to.
104
105 """
106 if filename.endswith(".py"):
107 # .py files are themselves source files.
108 return filename
109
110 elif filename.endswith((".pyc", ".pyo")):
111 # Bytecode files probably have source files near them.
112 py_filename = filename[:-1]
113 if os.path.exists(py_filename):
114 # Found a .py file, use that.
115 return py_filename
116 if env.WINDOWS:
117 # On Windows, it could be a .pyw file.
118 pyw_filename = py_filename + "w"
119 if os.path.exists(pyw_filename):
120 return pyw_filename
121 # Didn't find source, but it's probably the .py file we want.
122 return py_filename
123
124 elif filename.endswith("$py.class"):
125 # Jython is easy to guess.
126 return filename[:-9] + ".py"
127
128 # No idea, just use the file name as-is.
129 return filename
130
131
92 class PythonFileReporter(FileReporter): 132 class PythonFileReporter(FileReporter):
93 """Report support for a Python file.""" 133 """Report support for a Python file."""
94 134
95 def __init__(self, morf, coverage=None): 135 def __init__(self, morf, coverage=None):
96 self.coverage = coverage 136 self.coverage = coverage
102 # This could be a PEP-420 namespace package. 142 # This could be a PEP-420 namespace package.
103 raise CoverageException("Module {0} has no file".format(morf)) 143 raise CoverageException("Module {0} has no file".format(morf))
104 else: 144 else:
105 filename = morf 145 filename = morf
106 146
107 filename = files.unicode_filename(filename) 147 filename = source_for_file(files.unicode_filename(filename))
108
109 # .pyc files should always refer to a .py instead.
110 if filename.endswith(('.pyc', '.pyo')):
111 filename = filename[:-1]
112 elif filename.endswith('$py.class'): # Jython
113 filename = filename[:-9] + ".py"
114 148
115 super(PythonFileReporter, self).__init__(files.canonical_filename(filename)) 149 super(PythonFileReporter, self).__init__(files.canonical_filename(filename))
116 150
117 if hasattr(morf, '__name__'): 151 if hasattr(morf, '__name__'):
118 name = morf.__name__ 152 name = morf.__name__.replace(".", os.sep)
119 name = name.replace(".", os.sep) + ".py" 153 if os.path.basename(filename).startswith('__init__.'):
154 name += os.sep + "__init__"
155 name += ".py"
120 name = files.unicode_filename(name) 156 name = files.unicode_filename(name)
121 else: 157 else:
122 name = files.relative_filename(filename) 158 name = files.relative_filename(filename)
123 self.relname = name 159 self.relname = name
124 160
125 self._source = None 161 self._source = None
126 self._parser = None 162 self._parser = None
127 self._statements = None 163 self._statements = None
128 self._excluded = None 164 self._excluded = None
165
166 def __repr__(self):
167 return "<PythonFileReporter {0!r}>".format(self.filename)
129 168
130 @contract(returns='unicode') 169 @contract(returns='unicode')
131 def relative_filename(self): 170 def relative_filename(self):
132 return self.relname 171 return self.relname
133 172

eric ide

mercurial