DebugClients/Python2/coverage/python.py

branch
jsonrpc
changeset 5133
b7fe69c6cb1c
parent 5051
3586ebd9fac8
equal deleted inserted replaced
5132:a094eee9f862 5133:b7fe69c6cb1c
1 # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
2 # For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
3
4 """Python source expertise for coverage.py"""
5
6 import os.path
7 import types
8 import zipimport
9
10 from coverage import env, files
11 from coverage.misc import (
12 contract, CoverageException, expensive, NoSource, join_regex, isolate_module,
13 )
14 from coverage.parser import PythonParser
15 from coverage.phystokens import source_token_lines, source_encoding
16 from coverage.plugin import FileReporter
17
18 os = isolate_module(os)
19
20
21 @contract(returns='bytes')
22 def read_python_source(filename):
23 """Read the Python source text from `filename`.
24
25 Returns bytes.
26
27 """
28 with open(filename, "rb") as f:
29 return f.read().replace(b"\r\n", b"\n").replace(b"\r", b"\n")
30
31
32 @contract(returns='unicode')
33 def get_python_source(filename):
34 """Return the source code, as unicode."""
35 base, ext = os.path.splitext(filename)
36 if ext == ".py" and env.WINDOWS:
37 exts = [".py", ".pyw"]
38 else:
39 exts = [ext]
40
41 for ext in exts:
42 try_filename = base + ext
43 if os.path.exists(try_filename):
44 # A regular text file: open it.
45 source = read_python_source(try_filename)
46 break
47
48 # Maybe it's in a zip file?
49 source = get_zip_bytes(try_filename)
50 if source is not None:
51 break
52 else:
53 # Couldn't find source.
54 raise NoSource("No source for code: '%s'." % filename)
55
56 # Replace \f because of http://bugs.python.org/issue19035
57 source = source.replace(b'\f', b' ')
58 source = source.decode(source_encoding(source), "replace")
59
60 # Python code should always end with a line with a newline.
61 if source and source[-1] != '\n':
62 source += '\n'
63
64 return source
65
66
67 @contract(returns='bytes|None')
68 def get_zip_bytes(filename):
69 """Get data from `filename` if it is a zip file path.
70
71 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
73 an empty string if the file is empty.
74
75 """
76 markers = ['.zip'+os.sep, '.egg'+os.sep]
77 for marker in markers:
78 if marker in filename:
79 parts = filename.split(marker)
80 try:
81 zi = zipimport.zipimporter(parts[0]+marker[:-1])
82 except zipimport.ZipImportError:
83 continue
84 try:
85 data = zi.get_data(parts[1])
86 except IOError:
87 continue
88 return data
89 return None
90
91
92 class PythonFileReporter(FileReporter):
93 """Report support for a Python file."""
94
95 def __init__(self, morf, coverage=None):
96 self.coverage = coverage
97
98 if hasattr(morf, '__file__'):
99 filename = morf.__file__
100 elif isinstance(morf, types.ModuleType):
101 # A module should have had .__file__, otherwise we can't use it.
102 # This could be a PEP-420 namespace package.
103 raise CoverageException("Module {0} has no file".format(morf))
104 else:
105 filename = morf
106
107 filename = 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
115 super(PythonFileReporter, self).__init__(files.canonical_filename(filename))
116
117 if hasattr(morf, '__name__'):
118 name = morf.__name__
119 name = name.replace(".", os.sep) + ".py"
120 name = files.unicode_filename(name)
121 else:
122 name = files.relative_filename(filename)
123 self.relname = name
124
125 self._source = None
126 self._parser = None
127 self._statements = None
128 self._excluded = None
129
130 @contract(returns='unicode')
131 def relative_filename(self):
132 return self.relname
133
134 @property
135 def parser(self):
136 """Lazily create a :class:`PythonParser`."""
137 if self._parser is None:
138 self._parser = PythonParser(
139 filename=self.filename,
140 exclude=self.coverage._exclude_regex('exclude'),
141 )
142 self._parser.parse_source()
143 return self._parser
144
145 def lines(self):
146 """Return the line numbers of statements in the file."""
147 return self.parser.statements
148
149 def excluded_lines(self):
150 """Return the line numbers of statements in the file."""
151 return self.parser.excluded
152
153 def translate_lines(self, lines):
154 return self.parser.translate_lines(lines)
155
156 def translate_arcs(self, arcs):
157 return self.parser.translate_arcs(arcs)
158
159 @expensive
160 def no_branch_lines(self):
161 no_branch = self.parser.lines_matching(
162 join_regex(self.coverage.config.partial_list),
163 join_regex(self.coverage.config.partial_always_list)
164 )
165 return no_branch
166
167 @expensive
168 def arcs(self):
169 return self.parser.arcs()
170
171 @expensive
172 def exit_counts(self):
173 return self.parser.exit_counts()
174
175 def missing_arc_description(self, start, end, executed_arcs=None):
176 return self.parser.missing_arc_description(start, end, executed_arcs)
177
178 @contract(returns='unicode')
179 def source(self):
180 if self._source is None:
181 self._source = get_python_source(self.filename)
182 return self._source
183
184 def should_be_python(self):
185 """Does it seem like this file should contain Python?
186
187 This is used to decide if a file reported as part of the execution of
188 a program was really likely to have contained Python in the first
189 place.
190
191 """
192 # Get the file extension.
193 _, ext = os.path.splitext(self.filename)
194
195 # Anything named *.py* should be Python.
196 if ext.startswith('.py'):
197 return True
198 # A file with no extension should be Python.
199 if not ext:
200 return True
201 # Everything else is probably not Python.
202 return False
203
204 def source_token_lines(self):
205 return source_token_lines(self.source())
206
207 #
208 # eflag: FileType = Python2

eric ide

mercurial