eric6/DebugClients/Python/coverage/plugin.py

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

eric ide

mercurial