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