summaryrefslogtreecommitdiff
path: root/coverage/plugin.py
blob: 7d67762a26ea84d884d1b6f1e5889dc1fb49d941 (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
"""Plugin management for coverage.py"""

import sys


class CoveragePlugin(object):
    """Base class for coverage.py plugins."""
    def __init__(self, options):
        self.options = options

    def file_tracer(self, filename):
        """Return a FileTracer object for this 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 `FileTracer` object.  Otherwise return None.

        There is no way to register your plugin for particular files.  This
        method is how your plugin applies itself to files.  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 `FileTracer` object to use to trace this file, or
                None if this plugin cannot trace this file.

        """
        return None

    def file_reporter(self, filename):
        """Return the FileReporter class to use for filename.

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

        """
        raise NotImplementedError(
            "Plugin %r needs to implement file_reporter" % self.plugin_name
            )


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

    You may construct this object from 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.

        """
        return None

    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 it should invoke it, coverage.py uses
        the result of this function to know if it needs to bother invoking
        dynamic_source_filename.

        Returns:
            A boolean, true if `dynamic_source_filename` should be called to
            get dynamic source filenames.

        """
        return False

    def dynamic_source_filename(self, filename, frame):
        """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 `has_dynamic_source_filename`
        returns False.

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

        Can return None if dynamic filenames aren't needed.

        """
        return None

    def line_number_range(self, frame):
        """Given a call frame, return the range of source line numbers.

        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):
        self.filename = filename


class Plugins(object):
    """The currently loaded collection of coverage.py plugins."""

    def __init__(self):
        self.order = []
        self.names = {}

    @classmethod
    def load_plugins(cls, modules, config):
        """Load plugins from `modules`.

        Returns a list of loaded and configured plugins.

        """
        plugins = cls()

        for module in modules:
            __import__(module)
            mod = sys.modules[module]

            plugin_class = getattr(mod, "Plugin", None)
            if plugin_class:
                options = config.get_plugin_options(module)
                plugin = plugin_class(options)
                plugin.plugin_name = module
                plugins.order.append(plugin)
                plugins.names[module] = plugin

        return plugins

    def __iter__(self):
        return iter(self.order)

    def get(self, module):
        return self.names[module]