diff options
author | Ned Batchelder <ned@nedbatchelder.com> | 2015-06-14 09:41:01 -0400 |
---|---|---|
committer | Ned Batchelder <ned@nedbatchelder.com> | 2015-06-14 09:41:01 -0400 |
commit | 910c9db116f250fd1b26408e1d1eb52c4f3e2f0a (patch) | |
tree | 130a4dc479ec256a28f34974fa46580d0fc7198c | |
parent | 4dc7c52ffa20e9b8444f61092950618066f1a736 (diff) | |
download | python-coveragepy-git-910c9db116f250fd1b26408e1d1eb52c4f3e2f0a.tar.gz |
Debugging plugin wrappers
-rw-r--r-- | coverage/control.py | 2 | ||||
-rw-r--r-- | coverage/plugin.py | 13 | ||||
-rw-r--r-- | coverage/plugin_support.py | 143 | ||||
-rw-r--r-- | coverage/python.py | 5 | ||||
-rw-r--r-- | tests/test_filereporter.py | 24 |
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") |