• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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"""Support for plugins."""
5
6import os
7import os.path
8import sys
9
10from coverage.misc import CoverageException, isolate_module
11from coverage.plugin import CoveragePlugin, FileTracer, FileReporter
12
13os = isolate_module(os)
14
15
16class Plugins(object):
17    """The currently loaded collection of coverage.py plugins."""
18
19    def __init__(self):
20        self.order = []
21        self.names = {}
22        self.file_tracers = []
23
24        self.current_module = None
25        self.debug = None
26
27    @classmethod
28    def load_plugins(cls, modules, config, debug=None):
29        """Load plugins from `modules`.
30
31        Returns a list of loaded and configured plugins.
32
33        """
34        plugins = cls()
35        plugins.debug = debug
36
37        for module in modules:
38            plugins.current_module = module
39            __import__(module)
40            mod = sys.modules[module]
41
42            coverage_init = getattr(mod, "coverage_init", None)
43            if not coverage_init:
44                raise CoverageException(
45                    "Plugin module %r didn't define a coverage_init function" % module
46                )
47
48            options = config.get_plugin_options(module)
49            coverage_init(plugins, options)
50
51        plugins.current_module = None
52        return plugins
53
54    def add_file_tracer(self, plugin):
55        """Add a file tracer plugin.
56
57        `plugin` is an instance of a third-party plugin class.  It must
58        implement the :meth:`CoveragePlugin.file_tracer` method.
59
60        """
61        self._add_plugin(plugin, self.file_tracers)
62
63    def add_noop(self, plugin):
64        """Add a plugin that does nothing.
65
66        This is only useful for testing the plugin support.
67
68        """
69        self._add_plugin(plugin, None)
70
71    def _add_plugin(self, plugin, specialized):
72        """Add a plugin object.
73
74        `plugin` is a :class:`CoveragePlugin` instance to add.  `specialized`
75        is a list to append the plugin to.
76
77        """
78        plugin_name = "%s.%s" % (self.current_module, plugin.__class__.__name__)
79        if self.debug and self.debug.should('plugin'):
80            self.debug.write("Loaded plugin %r: %r" % (self.current_module, plugin))
81            labelled = LabelledDebug("plugin %r" % (self.current_module,), self.debug)
82            plugin = DebugPluginWrapper(plugin, labelled)
83
84        # pylint: disable=attribute-defined-outside-init
85        plugin._coverage_plugin_name = plugin_name
86        plugin._coverage_enabled = True
87        self.order.append(plugin)
88        self.names[plugin_name] = plugin
89        if specialized is not None:
90            specialized.append(plugin)
91
92    def __nonzero__(self):
93        return bool(self.order)
94
95    __bool__ = __nonzero__
96
97    def __iter__(self):
98        return iter(self.order)
99
100    def get(self, plugin_name):
101        """Return a plugin by name."""
102        return self.names[plugin_name]
103
104
105class LabelledDebug(object):
106    """A Debug writer, but with labels for prepending to the messages."""
107
108    def __init__(self, label, debug, prev_labels=()):
109        self.labels = list(prev_labels) + [label]
110        self.debug = debug
111
112    def add_label(self, label):
113        """Add a label to the writer, and return a new `LabelledDebug`."""
114        return LabelledDebug(label, self.debug, self.labels)
115
116    def message_prefix(self):
117        """The prefix to use on messages, combining the labels."""
118        prefixes = self.labels + ['']
119        return ":\n".join("  "*i+label for i, label in enumerate(prefixes))
120
121    def write(self, message):
122        """Write `message`, but with the labels prepended."""
123        self.debug.write("%s%s" % (self.message_prefix(), message))
124
125
126class DebugPluginWrapper(CoveragePlugin):
127    """Wrap a plugin, and use debug to report on what it's doing."""
128
129    def __init__(self, plugin, debug):
130        super(DebugPluginWrapper, self).__init__()
131        self.plugin = plugin
132        self.debug = debug
133
134    def file_tracer(self, filename):
135        tracer = self.plugin.file_tracer(filename)
136        self.debug.write("file_tracer(%r) --> %r" % (filename, tracer))
137        if tracer:
138            debug = self.debug.add_label("file %r" % (filename,))
139            tracer = DebugFileTracerWrapper(tracer, debug)
140        return tracer
141
142    def file_reporter(self, filename):
143        reporter = self.plugin.file_reporter(filename)
144        self.debug.write("file_reporter(%r) --> %r" % (filename, reporter))
145        if reporter:
146            debug = self.debug.add_label("file %r" % (filename,))
147            reporter = DebugFileReporterWrapper(filename, reporter, debug)
148        return reporter
149
150    def sys_info(self):
151        return self.plugin.sys_info()
152
153
154class DebugFileTracerWrapper(FileTracer):
155    """A debugging `FileTracer`."""
156
157    def __init__(self, tracer, debug):
158        self.tracer = tracer
159        self.debug = debug
160
161    def _show_frame(self, frame):
162        """A short string identifying a frame, for debug messages."""
163        return "%s@%d" % (
164            os.path.basename(frame.f_code.co_filename),
165            frame.f_lineno,
166        )
167
168    def source_filename(self):
169        sfilename = self.tracer.source_filename()
170        self.debug.write("source_filename() --> %r" % (sfilename,))
171        return sfilename
172
173    def has_dynamic_source_filename(self):
174        has = self.tracer.has_dynamic_source_filename()
175        self.debug.write("has_dynamic_source_filename() --> %r" % (has,))
176        return has
177
178    def dynamic_source_filename(self, filename, frame):
179        dyn = self.tracer.dynamic_source_filename(filename, frame)
180        self.debug.write("dynamic_source_filename(%r, %s) --> %r" % (
181            filename, self._show_frame(frame), dyn,
182        ))
183        return dyn
184
185    def line_number_range(self, frame):
186        pair = self.tracer.line_number_range(frame)
187        self.debug.write("line_number_range(%s) --> %r" % (self._show_frame(frame), pair))
188        return pair
189
190
191class DebugFileReporterWrapper(FileReporter):
192    """A debugging `FileReporter`."""
193
194    def __init__(self, filename, reporter, debug):
195        super(DebugFileReporterWrapper, self).__init__(filename)
196        self.reporter = reporter
197        self.debug = debug
198
199    def relative_filename(self):
200        ret = self.reporter.relative_filename()
201        self.debug.write("relative_filename() --> %r" % (ret,))
202        return ret
203
204    def lines(self):
205        ret = self.reporter.lines()
206        self.debug.write("lines() --> %r" % (ret,))
207        return ret
208
209    def excluded_lines(self):
210        ret = self.reporter.excluded_lines()
211        self.debug.write("excluded_lines() --> %r" % (ret,))
212        return ret
213
214    def translate_lines(self, lines):
215        ret = self.reporter.translate_lines(lines)
216        self.debug.write("translate_lines(%r) --> %r" % (lines, ret))
217        return ret
218
219    def translate_arcs(self, arcs):
220        ret = self.reporter.translate_arcs(arcs)
221        self.debug.write("translate_arcs(%r) --> %r" % (arcs, ret))
222        return ret
223
224    def no_branch_lines(self):
225        ret = self.reporter.no_branch_lines()
226        self.debug.write("no_branch_lines() --> %r" % (ret,))
227        return ret
228
229    def exit_counts(self):
230        ret = self.reporter.exit_counts()
231        self.debug.write("exit_counts() --> %r" % (ret,))
232        return ret
233
234    def arcs(self):
235        ret = self.reporter.arcs()
236        self.debug.write("arcs() --> %r" % (ret,))
237        return ret
238
239    def source(self):
240        ret = self.reporter.source()
241        self.debug.write("source() --> %d chars" % (len(ret),))
242        return ret
243
244    def source_token_lines(self):
245        ret = list(self.reporter.source_token_lines())
246        self.debug.write("source_token_lines() --> %d tokens" % (len(ret),))
247        return ret
248