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 6 import os 7 import os.path 8 import sys 9 10 from coverage.misc import CoverageException, isolate_module 11 from coverage.plugin import CoveragePlugin, FileTracer, FileReporter 12 13 os = isolate_module(os) 14 15 16 class 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 105 class 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 126 class 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 154 class 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 191 class 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