Home | History | Annotate | Download | only in coverage
      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