diff options
author | Ned Batchelder <ned@nedbatchelder.com> | 2014-08-21 10:35:15 -0400 |
---|---|---|
committer | Ned Batchelder <ned@nedbatchelder.com> | 2014-08-21 10:35:15 -0400 |
commit | 44238cb66d14a6fb2f4d30f1cef9fdf0bb5f2e2d (patch) | |
tree | 4f9ef805140a7f18430d7e5bf8d1a47efca41698 | |
parent | cb17b350caf9e113b7a3a36c5add5d16a88ea46d (diff) | |
download | python-coveragepy-git-44238cb66d14a6fb2f4d30f1cef9fdf0bb5f2e2d.tar.gz |
More-serious plugin support
-rw-r--r-- | coverage/config.py | 25 | ||||
-rw-r--r-- | coverage/control.py | 12 | ||||
-rw-r--r-- | coverage/plugin.py | 36 | ||||
-rw-r--r-- | tests/test_config.py | 24 |
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() |