DebugClients/Python/coverage/plugin.py

changeset 4489
d0d6e4ad31bd
child 5051
3586ebd9fac8
equal deleted inserted replaced
4481:456c58fc64b0 4489:d0d6e4ad31bd
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 """Plugin interfaces for coverage.py"""
5
6 from coverage import files
7 from coverage.misc import contract, _needs_to_implement
8
9
10 class CoveragePlugin(object):
11 """Base class for coverage.py plugins.
12
13 To write a coverage.py plugin, create a module with a subclass of
14 :class:`CoveragePlugin`. You will override methods in your class to
15 participate in various aspects of coverage.py's processing.
16
17 Currently the only plugin type is a file tracer, for implementing
18 measurement support for non-Python files. File tracer plugins implement
19 the :meth:`file_tracer` method to claim files and the :meth:`file_reporter`
20 method to report on those files.
21
22 Any plugin can optionally implement :meth:`sys_info` to provide debugging
23 information about their operation.
24
25 Coverage.py will store its own information on your plugin object, using
26 attributes whose names start with ``_coverage_``. Don't be startled.
27
28 To register your plugin, define a function called `coverage_init` in your
29 module::
30
31 def coverage_init(reg, options):
32 reg.add_file_tracer(MyPlugin())
33
34 You use the `reg` parameter passed to your `coverage_init` function to
35 register your plugin object. It has one method, `add_file_tracer`, which
36 takes a newly created instance of your plugin.
37
38 If your plugin takes options, the `options` parameter is a dictionary of
39 your plugin's options from the coverage.py configuration file. Use them
40 however you want to configure your object before registering it.
41
42 """
43
44 def file_tracer(self, filename): # pylint: disable=unused-argument
45 """Get a :class:`FileTracer` object for a file.
46
47 Every Python source file is offered to the plugin to give it a chance
48 to take responsibility for tracing the file. If your plugin can handle
49 the file, then return a :class:`FileTracer` object. Otherwise return
50 None.
51
52 There is no way to register your plugin for particular files. Instead,
53 this method is invoked for all files, and the plugin decides whether it
54 can trace the file or not. Be prepared for `filename` to refer to all
55 kinds of files that have nothing to do with your plugin.
56
57 The file name will be a Python file being executed. There are two
58 broad categories of behavior for a plugin, depending on the kind of
59 files your plugin supports:
60
61 * Static file names: each of your original source files has been
62 converted into a distinct Python file. Your plugin is invoked with
63 the Python file name, and it maps it back to its original source
64 file.
65
66 * Dynamic file names: all of your source files are executed by the same
67 Python file. In this case, your plugin implements
68 :meth:`FileTracer.dynamic_source_filename` to provide the actual
69 source file for each execution frame.
70
71 `filename` is a string, the path to the file being considered. This is
72 the absolute real path to the file. If you are comparing to other
73 paths, be sure to take this into account.
74
75 Returns a :class:`FileTracer` object to use to trace `filename`, or
76 None if this plugin cannot trace this file.
77
78 """
79 return None
80
81 def file_reporter(self, filename): # pylint: disable=unused-argument
82 """Get the :class:`FileReporter` class to use for a file.
83
84 This will only be invoked if `filename` returns non-None from
85 :meth:`file_tracer`. It's an error to return None from this method.
86
87 Returns a :class:`FileReporter` object to use to report on `filename`.
88
89 """
90 _needs_to_implement(self, "file_reporter")
91
92 def sys_info(self):
93 """Get a list of information useful for debugging.
94
95 This method will be invoked for ``--debug=sys``. Your
96 plugin can return any information it wants to be displayed.
97
98 Returns a list of pairs: `[(name, value), ...]`.
99
100 """
101 return []
102
103
104 class FileTracer(object):
105 """Support needed for files during the execution phase.
106
107 You may construct this object from :meth:`CoveragePlugin.file_tracer` any
108 way you like. A natural choice would be to pass the file name given to
109 `file_tracer`.
110
111 `FileTracer` objects should only be created in the
112 :meth:`CoveragePlugin.file_tracer` method.
113
114 See :ref:`howitworks` for details of the different coverage.py phases.
115
116 """
117
118 def source_filename(self):
119 """The source file name for this file.
120
121 This may be any file name you like. A key responsibility of a plugin
122 is to own the mapping from Python execution back to whatever source
123 file name was originally the source of the code.
124
125 See :meth:`CoveragePlugin.file_tracer` for details about static and
126 dynamic file names.
127
128 Returns the file name to credit with this execution.
129
130 """
131 _needs_to_implement(self, "source_filename")
132
133 def has_dynamic_source_filename(self):
134 """Does this FileTracer have dynamic source file names?
135
136 FileTracers can provide dynamically determined file names by
137 implementing :meth:`dynamic_source_filename`. Invoking that function
138 is expensive. To determine whether to invoke it, coverage.py uses the
139 result of this function to know if it needs to bother invoking
140 :meth:`dynamic_source_filename`.
141
142 See :meth:`CoveragePlugin.file_tracer` for details about static and
143 dynamic file names.
144
145 Returns True if :meth:`dynamic_source_filename` should be called to get
146 dynamic source file names.
147
148 """
149 return False
150
151 def dynamic_source_filename(self, filename, frame): # pylint: disable=unused-argument
152 """Get a dynamically computed source file name.
153
154 Some plugins need to compute the source file name dynamically for each
155 frame.
156
157 This function will not be invoked if
158 :meth:`has_dynamic_source_filename` returns False.
159
160 Returns the source file name for this frame, or None if this frame
161 shouldn't be measured.
162
163 """
164 return None
165
166 def line_number_range(self, frame):
167 """Get the range of source line numbers for a given a call frame.
168
169 The call frame is examined, and the source line number in the original
170 file is returned. The return value is a pair of numbers, the starting
171 line number and the ending line number, both inclusive. For example,
172 returning (5, 7) means that lines 5, 6, and 7 should be considered
173 executed.
174
175 This function might decide that the frame doesn't indicate any lines
176 from the source file were executed. Return (-1, -1) in this case to
177 tell coverage.py that no lines should be recorded for this frame.
178
179 """
180 lineno = frame.f_lineno
181 return lineno, lineno
182
183
184 class FileReporter(object):
185 """Support needed for files during the analysis and reporting phases.
186
187 See :ref:`howitworks` for details of the different coverage.py phases.
188
189 `FileReporter` objects should only be created in the
190 :meth:`CoveragePlugin.file_reporter` method.
191
192 There are many methods here, but only :meth:`lines` is required, to provide
193 the set of executable lines in the file.
194
195 """
196
197 def __init__(self, filename):
198 """Simple initialization of a `FileReporter`.
199
200 The `filename` argument is the path to the file being reported. This
201 will be available as the `.filename` attribute on the object. Other
202 method implementations on this base class rely on this attribute.
203
204 """
205 self.filename = filename
206
207 def __repr__(self):
208 return "<{0.__class__.__name__} filename={0.filename!r}>".format(self)
209
210 def relative_filename(self):
211 """Get the relative file name for this file.
212
213 This file path will be displayed in reports. The default
214 implementation will supply the actual project-relative file path. You
215 only need to supply this method if you have an unusual syntax for file
216 paths.
217
218 """
219 return files.relative_filename(self.filename)
220
221 @contract(returns='unicode')
222 def source(self):
223 """Get the source for the file.
224
225 Returns a Unicode string.
226
227 The base implementation simply reads the `self.filename` file and
228 decodes it as UTF8. Override this method if your file isn't readable
229 as a text file, or if you need other encoding support.
230
231 """
232 with open(self.filename, "rb") as f:
233 return f.read().decode("utf8")
234
235 def lines(self):
236 """Get the executable lines in this file.
237
238 Your plugin must determine which lines in the file were possibly
239 executable. This method returns a set of those line numbers.
240
241 Returns a set of line numbers.
242
243 """
244 _needs_to_implement(self, "lines")
245
246 def excluded_lines(self):
247 """Get the excluded executable lines in this file.
248
249 Your plugin can use any method it likes to allow the user to exclude
250 executable lines from consideration.
251
252 Returns a set of line numbers.
253
254 The base implementation returns the empty set.
255
256 """
257 return set()
258
259 def translate_lines(self, lines):
260 """Translate recorded lines into reported lines.
261
262 Some file formats will want to report lines slightly differently than
263 they are recorded. For example, Python records the last line of a
264 multi-line statement, but reports are nicer if they mention the first
265 line.
266
267 Your plugin can optionally define this method to perform these kinds of
268 adjustment.
269
270 `lines` is a sequence of integers, the recorded line numbers.
271
272 Returns a set of integers, the adjusted line numbers.
273
274 The base implementation returns the numbers unchanged.
275
276 """
277 return set(lines)
278
279 def arcs(self):
280 """Get the executable arcs in this file.
281
282 To support branch coverage, your plugin needs to be able to indicate
283 possible execution paths, as a set of line number pairs. Each pair is
284 a `(prev, next)` pair indicating that execution can transition from the
285 `prev` line number to the `next` line number.
286
287 Returns a set of pairs of line numbers. The default implementation
288 returns an empty set.
289
290 """
291 return set()
292
293 def no_branch_lines(self):
294 """Get the lines excused from branch coverage in this file.
295
296 Your plugin can use any method it likes to allow the user to exclude
297 lines from consideration of branch coverage.
298
299 Returns a set of line numbers.
300
301 The base implementation returns the empty set.
302
303 """
304 return set()
305
306 def translate_arcs(self, arcs):
307 """Translate recorded arcs into reported arcs.
308
309 Similar to :meth:`translate_lines`, but for arcs. `arcs` is a set of
310 line number pairs.
311
312 Returns a set of line number pairs.
313
314 The default implementation returns `arcs` unchanged.
315
316 """
317 return arcs
318
319 def exit_counts(self):
320 """Get a count of exits from that each line.
321
322 To determine which lines are branches, coverage.py looks for lines that
323 have more than one exit. This function creates a dict mapping each
324 executable line number to a count of how many exits it has.
325
326 To be honest, this feels wrong, and should be refactored. Let me know
327 if you attempt to implement this...
328
329 """
330 return {}
331
332 def source_token_lines(self):
333 """Generate a series of tokenized lines, one for each line in `source`.
334
335 These tokens are used for syntax-colored reports.
336
337 Each line is a list of pairs, each pair is a token::
338
339 [('key', 'def'), ('ws', ' '), ('nam', 'hello'), ('op', '('), ... ]
340
341 Each pair has a token class, and the token text. The token classes
342 are:
343
344 * ``'com'``: a comment
345 * ``'key'``: a keyword
346 * ``'nam'``: a name, or identifier
347 * ``'num'``: a number
348 * ``'op'``: an operator
349 * ``'str'``: a string literal
350 * ``'txt'``: some other kind of text
351
352 If you concatenate all the token texts, and then join them with
353 newlines, you should have your original source back.
354
355 The default implementation simply returns each line tagged as
356 ``'txt'``.
357
358 """
359 for line in self.source().splitlines():
360 yield [('txt', line)]
361
362 # Annoying comparison operators. Py3k wants __lt__ etc, and Py2k needs all
363 # of them defined.
364
365 def __eq__(self, other):
366 return isinstance(other, FileReporter) and self.filename == other.filename
367
368 def __ne__(self, other):
369 return not (self == other)
370
371 def __lt__(self, other):
372 return self.filename < other.filename
373
374 def __le__(self, other):
375 return self.filename <= other.filename
376
377 def __gt__(self, other):
378 return self.filename > other.filename
379
380 def __ge__(self, other):
381 return self.filename >= other.filename

eric ide

mercurial