summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNed Batchelder <ned@nedbatchelder.com>2015-06-14 09:41:01 -0400
committerNed Batchelder <ned@nedbatchelder.com>2015-06-14 09:41:01 -0400
commit910c9db116f250fd1b26408e1d1eb52c4f3e2f0a (patch)
tree130a4dc479ec256a28f34974fa46580d0fc7198c
parent4dc7c52ffa20e9b8444f61092950618066f1a736 (diff)
downloadpython-coveragepy-git-910c9db116f250fd1b26408e1d1eb52c4f3e2f0a.tar.gz
Debugging plugin wrappers
-rw-r--r--coverage/control.py2
-rw-r--r--coverage/plugin.py13
-rw-r--r--coverage/plugin_support.py143
-rw-r--r--coverage/python.py5
-rw-r--r--tests/test_filereporter.py24
5 files changed, 169 insertions, 18 deletions
diff --git a/coverage/control.py b/coverage/control.py
index 3cae1d36..b982c792 100644
--- a/coverage/control.py
+++ b/coverage/control.py
@@ -202,7 +202,7 @@ class Coverage(object):
self.debug = DebugControl(self.config.debug, self._debug_file)
# Load plugins
- self.plugins = Plugins.load_plugins(self.config.plugins, self.config)
+ self.plugins = Plugins.load_plugins(self.config.plugins, self.config, self.debug)
self.file_tracing_plugins = []
for plugin in self.plugins:
diff --git a/coverage/plugin.py b/coverage/plugin.py
index 6648d7a6..3fbb43aa 100644
--- a/coverage/plugin.py
+++ b/coverage/plugin.py
@@ -3,8 +3,10 @@
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_*".
@@ -25,7 +27,7 @@ class CoveragePlugin(object):
"""
- def __init__(self, options):
+ def __init__(self, options=None):
"""
When the plugin is constructed, it will be passed a dictionary of
plugin-specific options read from the .coveragerc configuration file.
@@ -36,7 +38,7 @@ class CoveragePlugin(object):
.coveragerc configuration file.
"""
- self.options = options
+ self.options = options or {}
def file_tracer(self, filename): # pylint: disable=unused-argument
"""Return a FileTracer object for a file.
@@ -175,6 +177,9 @@ class FileReporter(object):
" filename={self.filename!r}>".format(self=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.
@@ -247,6 +252,8 @@ class FileReporter(object):
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.name)[1]
+ name = os.path.splitdrive(self.relative_filename())[1]
return re.sub(r"[\\/.:]", "_", name)
diff --git a/coverage/plugin_support.py b/coverage/plugin_support.py
index 9ab79544..8bdc7213 100644
--- a/coverage/plugin_support.py
+++ b/coverage/plugin_support.py
@@ -3,6 +3,7 @@
import sys
from coverage.misc import CoverageException
+from coverage.plugin import CoveragePlugin, FileTracer, FileReporter
class Plugins(object):
@@ -13,7 +14,7 @@ class Plugins(object):
self.names = {}
@classmethod
- def load_plugins(cls, modules, config):
+ def load_plugins(cls, modules, config, debug=None):
"""Load plugins from `modules`.
Returns a list of loaded and configured plugins.
@@ -31,6 +32,10 @@ class Plugins(object):
options = config.get_plugin_options(module)
plugin = plugin_class(options)
+ if debug and debug.should('plugin'):
+ debug.write("Loaded plugin %r: %r" % (module, plugin))
+ labelled = LabelledDebug("plugin %r" % (module,), debug)
+ plugin = DebugPluginWrapper(plugin, labelled)
plugin._coverage_plugin_name = module
plugin._coverage_enabled = True
plugins.order.append(plugin)
@@ -49,3 +54,139 @@ class Plugins(object):
def get(self, plugin_name):
"""Return a plugin by name."""
return self.names[plugin_name]
+
+
+class LabelledDebug(object):
+ """A Debug writer, but with labels for prepending to the messages."""
+
+ def __init__(self, label, debug, prev_labels=()):
+ self.labels = list(prev_labels) + [label]
+ self.debug = debug
+
+ def add_label(self, label):
+ """Add a label to the writer, and return a new `LabelledDebug`."""
+ return LabelledDebug(label, self.debug, self.labels)
+
+ def write(self, message):
+ """Write `message`, but with the labels prepended."""
+ self.debug.write("%s: %s" % (", ".join(self.labels), message))
+
+
+class DebugPluginWrapper(CoveragePlugin):
+ """Wrap a plugin, and use debug to report on what it's doing."""
+
+ def __init__(self, plugin, debug):
+ super(DebugPluginWrapper, self).__init__()
+ self.plugin = plugin
+ self.debug = debug
+
+ def file_tracer(self, filename):
+ tracer = self.plugin.file_tracer(filename)
+ self.debug.write("file_tracer(%r) --> %r" % (filename, tracer))
+ if tracer:
+ debug = self.debug.add_label("file %r" % (filename,))
+ tracer = DebugFileTracerWrapper(tracer, debug)
+ return tracer
+
+ def file_reporter(self, filename):
+ reporter = self.plugin.file_reporter(filename)
+ self.debug.write("file_reporter(%r) --> %r" % (filename, reporter))
+ if reporter:
+ debug = self.debug.add_label("file %r" % (filename,))
+ reporter = DebugFileReporterWrapper(filename, reporter, debug)
+ return reporter
+
+ def sys_info(self):
+ return self.plugin.sys_info()
+
+
+class DebugFileTracerWrapper(FileTracer):
+ """A debugging `FileTracer`."""
+
+ def __init__(self, tracer, debug):
+ self.tracer = tracer
+ self.debug = debug
+
+ def source_filename(self):
+ sfilename = self.tracer.source_filename()
+ self.debug.write("source_filename() --> %r" % (sfilename,))
+ return sfilename
+
+ def has_dynamic_source_filename(self):
+ has = self.tracer.has_dynamic_source_filename()
+ self.debug.write("has_dynamic_source_filename() --> %r" % (has,))
+ return has
+
+ def dynamic_source_filename(self, filename, frame):
+ dyn = self.tracer.dynamic_source_filename(filename, frame)
+ self.debug.write("dynamic_source_filename(%r, frame) --> %r" % (filename, dyn))
+ return dyn
+
+ def line_number_range(self, frame):
+ pair = self.tracer.line_number_range(frame)
+ self.debug.write("line_number_range(frame) --> %r" % (pair,))
+ return pair
+
+
+class DebugFileReporterWrapper(FileReporter):
+ """A debugging `FileReporter`."""
+
+ def __init__(self, filename, reporter, debug):
+ super(DebugFileReporterWrapper, self).__init__(filename)
+ self.reporter = reporter
+ self.debug = debug
+
+ def relative_filename(self):
+ ret = self.reporter.relative_filename()
+ self.debug.write("relative_filename() --> %r" % (ret,))
+ return ret
+
+ def statements(self):
+ ret = self.reporter.statements()
+ self.debug.write("statements() --> %r" % (ret,))
+ return ret
+
+ def excluded_statements(self):
+ ret = self.reporter.excluded_statements()
+ self.debug.write("excluded_statements() --> %r" % (ret,))
+ return ret
+
+ def translate_lines(self, lines):
+ ret = self.reporter.translate_lines(lines)
+ self.debug.write("translate_lines(%r) --> %r" % (lines, ret))
+ return ret
+
+ def translate_arcs(self, arcs):
+ ret = self.reporter.translate_arcs(arcs)
+ self.debug.write("translate_arcs(%r) --> %r" % (arcs, ret))
+ return ret
+
+ def no_branch_lines(self):
+ ret = self.reporter.no_branch_lines()
+ self.debug.write("no_branch_lines() --> %r" % (ret,))
+ return ret
+
+ def exit_counts(self):
+ ret = self.reporter.exit_counts()
+ self.debug.write("exit_counts() --> %r" % (ret,))
+ return ret
+
+ def arcs(self):
+ ret = self.reporter.arcs()
+ self.debug.write("arcs() --> %r" % (ret,))
+ return ret
+
+ def source(self):
+ ret = self.reporter.source()
+ self.debug.write("source() --> %d chars" % (len(ret),))
+ return ret
+
+ def source_token_lines(self):
+ ret = list(self.reporter.source_token_lines())
+ self.debug.write("source_token_lines() --> %d tokens" % (len(ret),))
+ return ret
+
+ def should_be_python(self):
+ ret = self.reporter.should_be_python()
+ self.debug.write("should_be_python() --> %r" % (ret,))
+ return ret
diff --git a/coverage/python.py b/coverage/python.py
index 8dc163df..b1667f6d 100644
--- a/coverage/python.py
+++ b/coverage/python.py
@@ -103,13 +103,16 @@ class PythonFileReporter(FileReporter):
name = name.replace(".", os.sep) + ".py"
else:
name = files.relative_filename(filename)
- self.name = name
+ self.relname = name
self._source = None
self._parser = None
self._statements = None
self._excluded = None
+ def relative_filename(self):
+ return self.relname
+
@property
def parser(self):
if self._parser is None:
diff --git a/tests/test_filereporter.py b/tests/test_filereporter.py
index 9db4c0c3..045081cf 100644
--- a/tests/test_filereporter.py
+++ b/tests/test_filereporter.py
@@ -32,9 +32,9 @@ class FileReporterTest(CoverageTest):
acu = PythonFileReporter("aa/afile.py")
bcu = PythonFileReporter("aa/bb/bfile.py")
ccu = PythonFileReporter("aa/bb/cc/cfile.py")
- self.assertEqual(acu.name, "aa/afile.py")
- self.assertEqual(bcu.name, "aa/bb/bfile.py")
- self.assertEqual(ccu.name, "aa/bb/cc/cfile.py")
+ self.assertEqual(acu.relative_filename(), "aa/afile.py")
+ self.assertEqual(bcu.relative_filename(), "aa/bb/bfile.py")
+ self.assertEqual(ccu.relative_filename(), "aa/bb/cc/cfile.py")
self.assertEqual(acu.flat_rootname(), "aa_afile_py")
self.assertEqual(bcu.flat_rootname(), "aa_bb_bfile_py")
self.assertEqual(ccu.flat_rootname(), "aa_bb_cc_cfile_py")
@@ -46,9 +46,9 @@ class FileReporterTest(CoverageTest):
acu = PythonFileReporter("aa/afile.odd.py")
bcu = PythonFileReporter("aa/bb/bfile.odd.py")
b2cu = PythonFileReporter("aa/bb.odd/bfile.py")
- self.assertEqual(acu.name, "aa/afile.odd.py")
- self.assertEqual(bcu.name, "aa/bb/bfile.odd.py")
- self.assertEqual(b2cu.name, "aa/bb.odd/bfile.py")
+ self.assertEqual(acu.relative_filename(), "aa/afile.odd.py")
+ self.assertEqual(bcu.relative_filename(), "aa/bb/bfile.odd.py")
+ self.assertEqual(b2cu.relative_filename(), "aa/bb.odd/bfile.py")
self.assertEqual(acu.flat_rootname(), "aa_afile_odd_py")
self.assertEqual(bcu.flat_rootname(), "aa_bb_bfile_odd_py")
self.assertEqual(b2cu.flat_rootname(), "aa_bb_odd_bfile_py")
@@ -64,9 +64,9 @@ class FileReporterTest(CoverageTest):
acu = PythonFileReporter(aa)
bcu = PythonFileReporter(aa.bb)
ccu = PythonFileReporter(aa.bb.cc)
- self.assertEqual(acu.name, native("aa.py"))
- self.assertEqual(bcu.name, native("aa/bb.py"))
- self.assertEqual(ccu.name, native("aa/bb/cc.py"))
+ self.assertEqual(acu.relative_filename(), native("aa.py"))
+ self.assertEqual(bcu.relative_filename(), native("aa/bb.py"))
+ self.assertEqual(ccu.relative_filename(), native("aa/bb/cc.py"))
self.assertEqual(acu.flat_rootname(), "aa_py")
self.assertEqual(bcu.flat_rootname(), "aa_bb_py")
self.assertEqual(ccu.flat_rootname(), "aa_bb_cc_py")
@@ -82,9 +82,9 @@ class FileReporterTest(CoverageTest):
acu = PythonFileReporter(aa.afile)
bcu = PythonFileReporter(aa.bb.bfile)
ccu = PythonFileReporter(aa.bb.cc.cfile)
- self.assertEqual(acu.name, native("aa/afile.py"))
- self.assertEqual(bcu.name, native("aa/bb/bfile.py"))
- self.assertEqual(ccu.name, native("aa/bb/cc/cfile.py"))
+ self.assertEqual(acu.relative_filename(), native("aa/afile.py"))
+ self.assertEqual(bcu.relative_filename(), native("aa/bb/bfile.py"))
+ self.assertEqual(ccu.relative_filename(), native("aa/bb/cc/cfile.py"))
self.assertEqual(acu.flat_rootname(), "aa_afile_py")
self.assertEqual(bcu.flat_rootname(), "aa_bb_bfile_py")
self.assertEqual(ccu.flat_rootname(), "aa_bb_cc_cfile_py")