summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNed Batchelder <ned@nedbatchelder.com>2014-08-21 10:35:15 -0400
committerNed Batchelder <ned@nedbatchelder.com>2014-08-21 10:35:15 -0400
commit44238cb66d14a6fb2f4d30f1cef9fdf0bb5f2e2d (patch)
tree4f9ef805140a7f18430d7e5bf8d1a47efca41698
parentcb17b350caf9e113b7a3a36c5add5d16a88ea46d (diff)
downloadpython-coveragepy-git-44238cb66d14a6fb2f4d30f1cef9fdf0bb5f2e2d.tar.gz
More-serious plugin support
-rw-r--r--coverage/config.py25
-rw-r--r--coverage/control.py12
-rw-r--r--coverage/plugin.py36
-rw-r--r--tests/test_config.py24
4 files changed, 75 insertions, 22 deletions
diff --git a/coverage/config.py b/coverage/config.py
index 1826e5c2..c671ef75 100644
--- a/coverage/config.py
+++ b/coverage/config.py
@@ -37,6 +37,13 @@ class HandyConfigParser(configparser.RawConfigParser):
section = self.section_prefix + section
return configparser.RawConfigParser.options(self, section)
+ def get_section(self, section):
+ """Get the contents of a section, as a dictionary."""
+ d = {}
+ for opt in self.options(section):
+ d[opt] = self.get(section, opt)
+ return d
+
def get(self, section, *args, **kwargs):
"""Get a value, replacing environment variables also.
@@ -163,6 +170,9 @@ class CoverageConfig(object):
# Defaults for [paths]
self.paths = {}
+ # Options for plugins
+ self.plugin_options = {}
+
def from_environment(self, env_var):
"""Read configuration from the `env_var` environment variable."""
# Timidity: for nose users, read an environment variable. This is a
@@ -200,17 +210,22 @@ class CoverageConfig(object):
self.config_files.extend(files_read)
for option_spec in self.CONFIG_FILE_OPTIONS:
- self.set_attr_from_config_option(cp, *option_spec)
+ self._set_attr_from_config_option(cp, *option_spec)
# [paths] is special
if cp.has_section('paths'):
for option in cp.options('paths'):
self.paths[option] = cp.getlist('paths', option)
+ # plugins can have options
+ for plugin in self.plugins:
+ if cp.has_section(plugin):
+ self.plugin_options[plugin] = cp.get_section(plugin)
+
return True
CONFIG_FILE_OPTIONS = [
- # These are *args for set_attr_from_config_option:
+ # These are *args for _set_attr_from_config_option:
# (attr, where, type_="")
#
# attr is the attribute to set on the CoverageConfig object.
@@ -250,9 +265,13 @@ class CoverageConfig(object):
('xml_output', 'xml:output'),
]
- def set_attr_from_config_option(self, cp, attr, where, type_=''):
+ def _set_attr_from_config_option(self, cp, attr, where, type_=''):
"""Set an attribute on self if it exists in the ConfigParser."""
section, option = where.split(":")
if cp.has_option(section, option):
method = getattr(cp, 'get'+type_)
setattr(self, attr, method(section, option))
+
+ def get_plugin_options(self, plugin):
+ """Get a dictionary of options for the plugin named `plugin`."""
+ return self.plugin_options.get(plugin, {})
diff --git a/coverage/control.py b/coverage/control.py
index deb4e00f..6d232e5b 100644
--- a/coverage/control.py
+++ b/coverage/control.py
@@ -135,8 +135,8 @@ class Coverage(object):
self.debug = DebugControl(self.config.debug, debug_file or sys.stderr)
# Load plugins
- tracer_classes = load_plugins(self.config.plugins, "tracer")
- self.tracer_plugins = [cls() for cls in tracer_classes]
+ plugins = load_plugins(self.config.plugins, self.config)
+ self.tracer_plugins = []#[cls() for cls in tracer_classes]
self.auto_data = auto_data
@@ -282,10 +282,10 @@ class Coverage(object):
# Try the plugins, see if they have an opinion about the file.
for tracer in self.tracer_plugins:
- ext_disp = tracer.should_trace(canonical)
- if ext_disp:
- ext_disp.extension = tracer
- return ext_disp
+ plugin_disp = tracer.should_trace(canonical)
+ if plugin_disp:
+ plugin_disp.plugin = tracer
+ return plugin_disp
# If the user specified source or include, then that's authoritative
# about the outer bound of what to measure and we don't have to apply
diff --git a/coverage/plugin.py b/coverage/plugin.py
index 0b557106..8e26ae6b 100644
--- a/coverage/plugin.py
+++ b/coverage/plugin.py
@@ -1,20 +1,30 @@
"""Plugin management for coverage.py"""
-def load_plugins(modules, name):
- """Load plugins from `modules`, finding them by `name`.
+import sys
- Yields the loaded plugins.
+
+class CoveragePlugin(object):
+ """Base class for coverage.py plugins."""
+ def __init__(self, options):
+ self.options = options
+
+
+def load_plugins(modules, config):
+ """Load plugins from `modules`.
+
+ Returns a list of loaded and configured plugins.
"""
+ plugins = []
for module in modules:
- try:
- __import__(module)
- mod = sys.modules[module]
- except ImportError:
- blah()
- continue
-
- entry = getattr(mod, name, None)
- if entry:
- yield entry
+ __import__(module)
+ mod = sys.modules[module]
+
+ plugin_class = getattr(mod, "Plugin", None)
+ if plugin_class:
+ options = config.get_plugin_options(module)
+ plugin = plugin_class(options)
+ plugins.append(plugin)
+
+ return plugins
diff --git a/tests/test_config.py b/tests/test_config.py
index 7409f4aa..bf84423d 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -1,6 +1,8 @@
# -*- coding: utf-8 -*-
"""Test the config file handling for coverage.py"""
+import sys, os
+
import coverage
from coverage.misc import CoverageException
@@ -125,6 +127,12 @@ class ConfigTest(CoverageTest):
class ConfigFileTest(CoverageTest):
"""Tests of the config file settings in particular."""
+ def setUp(self):
+ super(ConfigFileTest, self).setUp()
+ # Parent class saves and restores sys.path, we can just modify it.
+ # Add modules to the path so we can import plugins.
+ sys.path.append(self.nice_file(os.path.dirname(__file__), 'modules'))
+
# This sample file tries to use lots of variation of syntax...
# The {section} placeholder lets us nest these settings in another file.
LOTSA_SETTINGS = """\
@@ -136,6 +144,9 @@ class ConfigFileTest(CoverageTest):
cover_pylib = TRUE
parallel = on
include = a/ , b/
+ plugins =
+ plugins.a_plugin
+ plugins.another
[{section}report]
; these settings affect reporting.
@@ -174,6 +185,10 @@ class ConfigFileTest(CoverageTest):
other = other, /home/ned/other, c:\\Ned\\etc
+ [{section}plugins.a_plugin]
+ hello = world
+ ; comments still work.
+ names = Jane/John/Jenny
"""
# Just some sample setup.cfg text from the docs.
@@ -212,6 +227,9 @@ class ConfigFileTest(CoverageTest):
self.assertEqual(cov.config.partial_always_list,
["if 0:", "while True:"]
)
+ self.assertEqual(cov.config.plugins,
+ ["plugins.a_plugin", "plugins.another"]
+ )
self.assertTrue(cov.config.show_missing)
self.assertEqual(cov.config.html_dir, r"c:\tricky\dir.somewhere")
self.assertEqual(cov.config.extra_css, "something/extra.css")
@@ -224,6 +242,12 @@ class ConfigFileTest(CoverageTest):
'other': ['other', '/home/ned/other', 'c:\\Ned\\etc']
})
+ self.assertEqual(cov.config.get_plugin_options("plugins.a_plugin"), {
+ 'hello': 'world',
+ 'names': 'Jane/John/Jenny',
+ })
+ self.assertEqual(cov.config.get_plugin_options("plugins.another"), {})
+
def test_config_file_settings(self):
self.make_file(".coveragerc", self.LOTSA_SETTINGS.format(section=""))
cov = coverage.coverage()