未验证 提交 1ee9abb2 编写于 作者: L Lukáš Doktor

Merging pull request 1579

* https://github.com/avocado-framework/avocado:
  Plugins: introduce archive result plugin
  Plugins Dispatcher: add configurable execution order support
  Plugins Dispatcher: introduce settings_section()
  Plugins Dispatcher: introduce fully_qualified_name()
  Plugins Dispatcher: introduce plugin_type()
  Plugins Dispatcher: default (sorted) order of execution
......@@ -27,6 +27,9 @@ class Dispatcher(EnabledExtensionManager):
Base dispatcher for various extension types
"""
#: Default namespace prefix for Avocado extensions
NAMESPACE_PREFIX = 'avocado.plugins.'
def __init__(self, namespace):
self.load_failures = []
super(Dispatcher, self).__init__(namespace=namespace,
......@@ -35,15 +38,62 @@ class Dispatcher(EnabledExtensionManager):
on_load_failure_callback=self.store_load_failure,
propagate_map_exceptions=True)
def enabled(self, extension):
namespace_prefix = 'avocado.plugins.'
if self.namespace.startswith(namespace_prefix):
namespace = self.namespace[len(namespace_prefix):]
def plugin_type(self):
"""
Subset of entry points namespace for this dispatcher
Given an entry point `avocado.plugins.foo`, plugin type is `foo`. If
entry point does not conform to the Avocado standard prefix, it's
returned unchanged.
"""
if self.namespace.startswith(self.NAMESPACE_PREFIX):
return self.namespace[len(self.NAMESPACE_PREFIX):]
else:
namespace = self.namespace
return self.namespace
def fully_qualified_name(self, extension):
"""
Returns the Avocado fully qualified plugin name
:param extension: an Stevedore Extension instance
:type extension: :class:`stevedore.extension.Extension`
"""
return "%s.%s" % (self.plugin_type(), extension.entry_point.name)
def settings_section(self):
"""
Returns the config section name for the plugin type handled by itself
"""
return "plugins.%s" % self.plugin_type()
def enabled(self, extension):
disabled = settings.get_value('plugins', 'disable', key_type=list)
fqn = "%s.%s" % (namespace, extension.entry_point.name)
return fqn not in disabled
return self.fully_qualified_name(extension) not in disabled
def names(self):
"""
Returns the names of the discovered extensions
This differs from :func:`stevedore.extension.ExtensionManager.names`
in that it returns names in a predictable order, by using standard
:func:`order`.
"""
return sorted(super(Dispatcher, self).names())
def _init_plugins(self, extensions):
super(Dispatcher, self)._init_plugins(extensions)
self.extensions.sort(key=lambda x: x.name)
configured_order = settings.get_value(self.settings_section(), "order",
key_type=list, default=[])
ordered = []
for name in configured_order:
for ext in self.extensions:
if name == ext.name:
ordered.append(ext)
for ext in self.extensions:
if ext not in ordered:
ordered.append(ext)
self.extensions = ordered
@staticmethod
def store_load_failure(manager, entrypoint, exception):
......
......@@ -44,7 +44,6 @@ from . import test
from . import jobdata
from .output import STD_OUTPUT
from .settings import settings
from ..utils import archive
from ..utils import astring
from ..utils import path
from ..utils import runtime
......@@ -472,10 +471,6 @@ class Job(object):
# If it's all good so far, set job status to 'PASS'
if self.status == 'RUNNING':
self.status = 'PASS'
# Let's clean up test artifacts
if getattr(self.args, 'archive', False):
filename = self.logdir + '.zip'
archive.create(filename, self.logdir)
_TEST_LOGGER.info('Test results available in %s', self.logdir)
if summary is None:
......
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# See LICENSE for more details.
#
# Copyright: Red Hat Inc. 2016
# Authors: Cleber Rosa <crosa@redhat.com>
"""Result Archive Plugin"""
from avocado.core.plugin_interfaces import CLI, Result
from avocado.utils import archive
class Archive(Result):
name = 'zip_archive'
description = 'Result archive (ZIP) support'
def render(self, result, job):
if getattr(job.args, 'archive', False):
archive.compress("%s.zip" % job.logdir, job.logdir)
class ArchiveCLI(CLI):
name = 'zip_archive'
description = 'Result archive (ZIP) support to run command'
def configure(self, parser):
run_subcommand_parser = parser.subcommands.choices.get('run', None)
if run_subcommand_parser is None:
return
run_subcommand_parser.output.add_argument(
'-z', '--archive', action='store_true',
dest='archive', default=False,
help='Archive (ZIP) files generated by tests')
def run(self, args):
pass
......@@ -53,7 +53,7 @@ class Plugins(CLICmd):
for plugins_active, msg in plugin_types:
log.info(msg)
plugin_matrix = []
for plugin in sorted(plugins_active):
for plugin in sorted(plugins_active, key=lambda x: x.name):
plugin_matrix.append((plugin.name, plugin.obj.description))
if not plugin_matrix:
......
......@@ -55,10 +55,6 @@ class Run(CLICmd):
help="Instead of running the test only "
"list them and log their params.")
parser.add_argument('-z', '--archive', action='store_true',
default=False, help='Archive (ZIP) files generated'
' by tests')
parser.add_argument('--force-job-id', dest='unique_job_id',
type=str, default=None,
help='Forces the use of a particular job ID. Used '
......
......@@ -131,6 +131,46 @@ the Avocado command line application. Now, by disabling a
``job.prepost`` plugin, those won't be executed before/after the
execution of the jobs.
Default plugin execution order
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In many situations, such as result generation, not one, but all of the
enabled plugin types will be executed. The order in which the plugins
are executed follows the lexical order of the entry point name.
For example, for the JSON result plugin, whose fully qualified name
is ``result.json``, has an entry point name of ``json``, as can be seen
on its registration code in ``setup.py``::
...
entry_points={
'avocado.plugins.result': [
'json = avocado.plugins.jsonresult:JSONResult',
...
If it sounds too complicated, it isn't. It just means that for
plugins of the same type, a plugin named ``automated`` will be
executed before the plugin named ``uploader``.
In the default Avocado set of result plugins, it means that the JSON
plugin (``json``) will be executed before the XUnit plugin (``xunit``).
If the HTML result plugin is installed and enabled (``html``) it will
be executed before both JSON and XUnit.
Configuring the plugin execution order
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
On some circumstances it may be necessary to change the order in which plugins
are executed. To do so, add a ``order`` entry a configuration file section
named after the plugin type. For ``job.prepost`` plugin types, the section name
has to be named ``plugins.job.prepost``, and it would look like this::
[plugins.job.prepost]
order = ['myplugin', 'jobscripts']
That configuration sets the ``job.prepost.myplugin`` plugin to execute before
the standard Avocado ``job.prepost.jobscripts`` does.
Wrap Up
~~~~~~~
......
import aexpect
import glob
import json
import os
import re
import shutil
import time
import signal
import sys
import tempfile
import time
import xml.dom.minidom
import glob
import aexpect
import signal
import re
import zipfile
import pkg_resources
if sys.version_info[:2] == (2, 6):
import unittest2 as unittest
......@@ -912,6 +915,79 @@ class PluginsTest(AbsPluginsTest, unittest.TestCase):
(expected_rc, result))
self.assertNotIn("Collect system information", result.stdout)
def test_plugin_order(self):
"""
Tests plugin order by configuration file
First it checks if html, json, xunit and zip_archive plugins are enabled.
Then it runs a test with zip_archive running first, which means the html,
json and xunit output files do not make into the archive.
Then it runs with zip_archive set to run last, which means the html,
json and xunit output files *do* make into the archive.
"""
def run_config(config_path):
cmd = ('./scripts/avocado --config %s run passtest.py --archive '
'--job-results-dir %s') % (config_path, self.base_outputdir)
result = process.run(cmd, ignore_status=True)
expected_rc = exit_codes.AVOCADO_ALL_OK
self.assertEqual(result.exit_status, expected_rc,
"Avocado did not return rc %d:\n%s" %
(expected_rc, result))
result_plugins = ["json", "xunit", "zip_archive"]
result_outputs = ["results.json", "results.xml"]
try:
pkg_resources.require('avocado_result_html')
result_plugins.append("html")
result_outputs.append("html/results.html")
except pkg_resources.DistributionNotFound:
pass
os.chdir(basedir)
cmd_line = './scripts/avocado plugins'
result = process.run(cmd_line, ignore_status=True)
expected_rc = exit_codes.AVOCADO_ALL_OK
self.assertEqual(result.exit_status, expected_rc,
"Avocado did not return rc %d:\n%s" %
(expected_rc, result))
for result_plugin in result_plugins:
self.assertIn(result_plugin, result.stdout)
config_content_zip_first = "[plugins.result]\norder=['zip_archive']"
config_zip_first = script.TemporaryScript("zip_first.conf",
config_content_zip_first)
with config_zip_first:
run_config(config_zip_first)
archives = glob.glob(os.path.join(self.base_outputdir, '*.zip'))
self.assertEqual(len(archives), 1, "ZIP Archive not generated")
zip_file = zipfile.ZipFile(archives[0], 'r')
zip_file_list = zip_file.namelist()
for result_output in result_outputs:
self.assertNotIn(result_output, zip_file_list)
os.unlink(archives[0])
config_content_zip_last = ("[plugins.result]\norder=['html', 'json',"
"'xunit', 'zip_archive']")
config_zip_last = script.TemporaryScript("zip_last.conf",
config_content_zip_last)
with config_zip_last:
run_config(config_zip_last)
archives = glob.glob(os.path.join(self.base_outputdir, '*.zip'))
self.assertEqual(len(archives), 1, "ZIP Archive not generated")
zip_file = zipfile.ZipFile(archives[0], 'r')
zip_file_list = zip_file.namelist()
for result_output in result_outputs:
self.assertIn(result_output, zip_file_list)
config_content_missing = ("[plugins.result]\norder=['foo', "
"'html', 'json', 'xunit', "
"'zip_archive', 'bar']")
config_missing = script.TemporaryScript("missing.conf",
config_content_missing)
with config_missing:
run_config(config_missing)
def test_Namespace_object_has_no_attribute(self):
os.chdir(basedir)
cmd_line = './scripts/avocado plugins'
......
import sys
if sys.version_info[:2] == (2, 6):
import unittest2 as unittest
else:
import unittest
from avocado.core import dispatcher
class DispatcherTest(unittest.TestCase):
def test_order(self):
namespaces = ['avocado.plugins.cli',
'avocado.plugins.cli.cmd',
'avocado.plugins.job.prepost',
'avocado.plugins.result']
for namespace in namespaces:
names = dispatcher.Dispatcher(namespace).names()
ext_names = [ext.name for ext in
dispatcher.Dispatcher(namespace).extensions]
self.assertEqual(names, ext_names)
self.assertEqual(names, sorted(names))
self.assertEqual(ext_names, sorted(ext_names))
if __name__ == '__main__':
unittest.main()
......@@ -139,6 +139,7 @@ if __name__ == '__main__':
'vm = avocado.plugins.vm:VM',
'docker = avocado.plugins.docker:Docker',
'yaml_to_mux = avocado.plugins.yaml_to_mux:YamlToMux',
'zip_archive = avocado.plugins.archive:ArchiveCLI',
],
'avocado.plugins.cli.cmd': [
'config = avocado.plugins.config:Config',
......@@ -157,6 +158,7 @@ if __name__ == '__main__':
'avocado.plugins.result': [
'xunit = avocado.plugins.xunit:XUnitResult',
'json = avocado.plugins.jsonresult:JSONResult',
'zip_archive = avocado.plugins.archive:Archive',
],
},
zip_safe=False,
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册