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
6from coverage import files
7from coverage.misc import contract, _needs_to_implement
8
9
10class 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
104class 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
184class 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
382