summaryrefslogtreecommitdiff
path: root/coverage/plugin.py
blob: 44dab55f47b3364037413431594b46706824385c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
"""Plugin interfaces for coverage.py"""

import os
import re

from coverage import files
from coverage.misc import _needs_to_implement


# TODO: document that the plugin objects may be decorated with attributes with
# named "_coverage_*".

class CoveragePlugin(object):
    """Base class for coverage.py plugins.

    To write a coverage.py plugin, create a subclass of `CoveragePlugin`.
    You can override methods here to participate in various aspects of
    coverage.py's processing.

    Currently the only plugin type is a file tracer, for implementing
    measurement support for non-Python files.  File tracer plugins implement
    the :meth:`file_tracer` method to claim files and the :meth:`file_reporter`
    method to report on those files.

    Any plugin can optionally implement :meth:`sys_info` to provide debugging
    information about their operation.

    """

    def file_tracer(self, filename):        # pylint: disable=unused-argument
        """Return a FileTracer object for a file.

        Every source file is offered to the plugin to give it a chance to take
        responsibility for tracing the file.  If your plugin can handle the
        file, then return a :class:`FileTracer` object.  Otherwise return None.

        There is no way to register your plugin for particular files.  Instead,
        this method is invoked for all files, and can decide whether it can
        trace the file or not.  Be prepared for `filename` to refer to all
        kinds of files that have nothing to do with your plugin.

        Arguments:
            filename (str): The path to the file being considered.  This is the
                absolute real path to the file.  If you are comparing to other
                paths, be sure to take this into account.

        Returns:
            FileTracer: the :class:`FileTracer` object to use to trace
                `filename`, or None if this plugin cannot trace this file.

        """
        return None

    def file_reporter(self, filename):      # pylint: disable=unused-argument
        """Return the FileReporter class to use for filename.

        This will only be invoked if `filename` returns non-None from
        :meth:`file_tracer`.  It's an error to return None.

        """
        _needs_to_implement(self, "file_reporter")

    def sys_info(self):
        """Return a list of information useful for debugging.

        This method will be invoked for ``--debug=sys``.  Your
        plugin can return any information it wants to be displayed.

        The return value is a list of pairs: (name, value).

        """
        return []


class FileTracer(object):
    """Support needed for files during the tracing phase.

    You may construct this object from :meth:`CoveragePlugin.file_tracer` any
    way you like.  A natural choice would be to pass the filename given to
    `file_tracer`.

    """

    def source_filename(self):
        """The source filename for this file.

        This may be any filename you like.  A key responsibility of a plugin is
        to own the mapping from Python execution back to whatever source
        filename was originally the source of the code.

        Returns:
            The filename to credit with this execution.

        """
        _needs_to_implement(self, "source_filename")

    def has_dynamic_source_filename(self):
        """Does this FileTracer have dynamic source filenames?

        FileTracers can provide dynamically determined filenames by
        implementing dynamic_source_filename.  Invoking that function is
        expensive. To determine whether to invoke it, coverage.py uses
        the result of this function to know if it needs to bother invoking
        :meth:`dynamic_source_filename`.

        Returns:
            boolean: True if :meth:`dynamic_source_filename` should be called
                to get dynamic source filenames.

        """
        return False

    def dynamic_source_filename(self, filename, frame):     # pylint: disable=unused-argument
        """Returns a dynamically computed source filename.

        Some plugins need to compute the source filename dynamically for each
        frame.

        This function will not be invoked if
        :meth:`has_dynamic_source_filename` returns False.

        Returns:
            The source filename for this frame, or None if this frame shouldn't
                be measured.

        """
        return None

    def line_number_range(self, frame):
        """Return the range of source line numbers for a given a call frame.

        The call frame is examined, and the source line number in the original
        file is returned.  The return value is a pair of numbers, the starting
        line number and the ending line number, both inclusive.  For example,
        returning (5, 7) means that lines 5, 6, and 7 should be considered
        executed.

        This function might decide that the frame doesn't indicate any lines
        from the source file were executed.  Return (-1, -1) in this case to
        tell coverage.py that no lines should be recorded for this frame.

        Arguments:
            frame: the call frame to examine.

        Returns:
            int, int: a pair of line numbers, the start and end lines
                executed in the source, inclusive.

        """
        lineno = frame.f_lineno
        return lineno, lineno


class FileReporter(object):
    """Support needed for files during the reporting phase."""
    def __init__(self, filename):
        # TODO: document that this init happens.
        self.filename = filename

    def __repr__(self):
        return "<{0.__class__.__name__} filename={0.filename!r}>".format(self)

    def relative_filename(self):
        return files.relative_filename(self.filename)

    # Annoying comparison operators. Py3k wants __lt__ etc, and Py2k needs all
    # of them defined.

    def __lt__(self, other):
        return self.filename < other.filename

    def __le__(self, other):
        return self.filename <= other.filename

    def __eq__(self, other):
        return self.filename == other.filename

    def __ne__(self, other):
        return self.filename != other.filename

    def __gt__(self, other):
        return self.filename > other.filename

    def __ge__(self, other):
        return self.filename >= other.filename

    def statements(self):
        _needs_to_implement(self, "statements")

    def excluded_statements(self):
        return set([])

    def translate_lines(self, lines):
        return set(lines)

    def translate_arcs(self, arcs):
        return arcs

    def no_branch_lines(self):
        return set()

    def exit_counts(self):
        return {}

    def arcs(self):
        return []

    def source(self):
        """Return the source for the code, a Unicode string."""
        # A generic implementation: read the text of self.filename
        with open(self.filename) as f:
            return f.read()

    def source_token_lines(self):
        """Return the 'tokenized' text for the code."""
        # A generic implementation, each line is one "txt" token.
        for line in self.source().splitlines():
            yield [('txt', line)]

    def should_be_python(self):
        """Does it seem like this file should contain Python?

        This is used to decide if a file reported as part of the execution of
        a program was really likely to have contained Python in the first
        place.
        """
        return False

    def flat_rootname(self):
        """A base for a flat filename to correspond to this file.

        Useful for writing files about the code where you want all the files in
        the same directory, but need to differentiate same-named files from
        different directories.

        For example, the file a/b/c.py will return 'a_b_c_py'

        You should not need to override this method.

        """
        name = os.path.splitdrive(self.relative_filename())[1]
        return re.sub(r"[\\/.:]", "_", name)