提交 74ea1680 编写于 作者: C Cleber Rosa 提交者: Lukáš Doktor

Result: introduce interface/dispatcher for event based result plugins

Even though Avocado already publishes an interface for result plugins
(in the avocado.core.plugin_interfaces module), that one is suitable
for plugins that will generate one output after the job as a whole
finishes.

There is another type of use case, already present in Avocado, but
still not implemented using the newer plugin mechanism.  The use
case is result plugins that must respond to events, some that come
from the job and some that come from tests.

The EventResult interface is a collection of Job interfaces that
would happen before tests start running (pre_tests and post_tests)
and events that are closer to the test themselves (start_test,
test_progress and end_test).

The new dispatcher calls plugins that implement the ResultEvents
interface.
Signed-off-by: NCleber Rosa <crosa@redhat.com>
上级 a56e7f24
......@@ -14,6 +14,7 @@
"""Extensions/plugins dispatchers."""
import logging
import sys
from stevedore import EnabledExtensionManager
......@@ -30,11 +31,12 @@ class Dispatcher(EnabledExtensionManager):
#: Default namespace prefix for Avocado extensions
NAMESPACE_PREFIX = 'avocado.plugins.'
def __init__(self, namespace):
def __init__(self, namespace, invoke_kwds={}):
self.load_failures = []
super(Dispatcher, self).__init__(namespace=namespace,
check_func=self.enabled,
invoke_on_load=True,
invoke_kwds=invoke_kwds,
on_load_failure_callback=self.store_load_failure,
propagate_map_exceptions=True)
......@@ -171,3 +173,26 @@ class ResultDispatcher(Dispatcher):
except:
job.log.error('Error running method "%s" of plugin "%s": %s',
method_name, ext.name, sys.exc_info()[1])
class ResultEventsDispatcher(Dispatcher):
def __init__(self, args):
super(ResultEventsDispatcher, self).__init__(
'avocado.plugins.result_events',
invoke_kwds={'args': args})
self.log = logging.getLogger("avocado.app")
def map_method(self, method_name, *args):
for ext in self.extensions:
try:
if hasattr(ext.obj, method_name):
method = getattr(ext.obj, method_name)
method(*args)
except SystemExit:
raise
except KeyboardInterrupt:
raise
except:
self.log.error('Error running method "%s" of plugin "%s": %s',
method_name, ext.name, sys.exc_info()[1])
......@@ -127,6 +127,13 @@ class Job(object):
# A job may not have a dispatcher for pre/post tests execution plugins
self._job_pre_post_dispatcher = None
# The result events dispatcher is shared with the test runner.
# Because of our goal to support using the phases of a job
# freely, let's get the result events dispatcher ready early.
# A future optimization may load it on demand.
self._result_events_dispatcher = dispatcher.ResultEventsDispatcher(self.args)
output.log_plugin_failures(self._result_events_dispatcher.load_failures)
def _setup_job_results(self):
"""
Prepares a job result directory, also known as logdir, for this job
......@@ -450,6 +457,7 @@ class Job(object):
self._job_pre_post_dispatcher = dispatcher.JobPrePostDispatcher()
output.log_plugin_failures(self._job_pre_post_dispatcher.load_failures)
self._job_pre_post_dispatcher.map_method('pre', self)
self._result_events_dispatcher.map_method('pre_tests', self)
def run_tests(self):
mux = getattr(self.args, "mux", None)
......
......@@ -134,3 +134,70 @@ class Result(Plugin):
:param job: the finished job for which a result will be written
:type job: :class:`avocado.core.job.Job`
"""
class JobPreTests(Plugin):
"""
Base plugin interface for adding actions before a job runs tests
This interface looks similar to :class:`JobPre`, but it's inteded
to be called at a very specific place, that is, between
:meth:`avocado.core.job.Job.create_test_suite` and
:meth:`avocado.core.job.Job.run_tests`.
"""
@abc.abstractmethod
def pre_tests(self, job):
"""
Entry point for job running actions before tests execution
"""
class JobPostTests(Plugin):
"""
Base plugin interface for adding actions after a job runs tests
Plugins using this interface will run at the a time equivalent to
plugins using the :class:`JobPost` interface, that is, at
:meth:`avocado.core.job.Job.post_tests`. This is because
:class:`JobPost` based plugins will eventually be modified to
really run after the job has finished, and not after it has run
tests.
"""
@abc.abstractmethod
def post_tests(self, job):
"""
Entry point for job running actions after the tests execution
"""
class ResultEvents(JobPreTests, JobPostTests):
"""
Base plugin interface for event based (streameable) results
Plugins that want to add actions to be run after a job runs,
should use the 'avocado.plugins.result_events' namespace and
implement the defined interface.
"""
@abc.abstractmethod
def start_test(self, result, state):
"""
Event triggered when a test starts running
"""
@abc.abstractmethod
def test_progress(self, progress=False):
"""
Interface to notify progress (or not) of the running test
"""
@abc.abstractmethod
def end_test(self, result, state):
"""
Event triggered when a test finishes running
"""
......@@ -243,8 +243,14 @@ class RemoteTestRunner(TestRunner):
state = test.get_state()
self.result_proxy.start_test(state)
self.result.start_test(state)
self.job._result_events_dispatcher.map_method('start_test',
self.result,
state)
self.result_proxy.check_test(state)
self.result.check_test(state)
self.job._result_events_dispatcher.map_method('end_test',
self.result,
state)
if state['status'] == "INTERRUPTED":
summary.add("INTERRUPTED")
elif not status.mapping[state['status']]:
......@@ -258,6 +264,8 @@ class RemoteTestRunner(TestRunner):
os.remove(zip_path_filename)
self.result_proxy.end_tests()
self.result.end_tests()
self.job._result_events_dispatcher.map_method('post_tests',
self.job)
finally:
try:
self.tear_down()
......
......@@ -176,6 +176,8 @@ class TestStatus(object):
elif "paused" in msg:
self.status = msg
self.job.result_proxy.notify_progress(False)
self.job._result_events_dispatcher.map_method('test_progress',
False)
if msg['paused']:
reason = msg['paused_msg']
if reason:
......@@ -315,6 +317,9 @@ class TestRunner(object):
self.result_proxy.start_test(early_state)
self.result.start_test(early_state)
self.job._result_events_dispatcher.map_method('start_test',
self.result,
early_state)
try:
instance.run_avocado()
finally:
......@@ -388,6 +393,7 @@ class TestRunner(object):
first = 0.01
step = 0.01
abort_reason = None
result_dispatcher = self.job._result_events_dispatcher
while True:
try:
......@@ -407,8 +413,11 @@ class TestRunner(object):
if (test_status.status.get('running') or
self.sigstopped):
self.job.result_proxy.notify_progress(False)
result_dispatcher.map_method('test_progress',
False)
else:
self.job.result_proxy.notify_progress(True)
result_dispatcher.map_method('test_progress', True)
else:
break
except KeyboardInterrupt:
......@@ -451,6 +460,7 @@ class TestRunner(object):
self.result_proxy.check_test(test_state)
self.result.check_test(test_state)
result_dispatcher.map_method('end_test', self.result, test_state)
if test_state['status'] == "INTERRUPTED":
summary.add("INTERRUPTED")
elif not mapping[test_state['status']]:
......@@ -557,6 +567,7 @@ class TestRunner(object):
self.job.sysinfo.end_job_hook()
self.result_proxy.end_tests()
self.result.end_tests()
self.job._result_events_dispatcher.map_method('post_tests', self.job)
self.job.funcatexit.run()
signal.signal(signal.SIGTSTP, signal.SIG_IGN)
return summary
......@@ -44,10 +44,13 @@ class RemoteTestRunnerTest(unittest.TestCase):
env_keep=None)
log = flexmock()
log.should_receive("info")
result_dispatcher = flexmock()
result_dispatcher.should_receive("map_method")
job = flexmock(args=Args, log=log,
references=['/tests/sleeptest', '/tests/other/test',
'passtest'], unique_id='1-sleeptest;0',
logdir="/local/path")
logdir="/local/path",
_result_events_dispatcher=result_dispatcher)
flexmock(remote.RemoteTestRunner).should_receive('__init__')
self.runner = remote.RemoteTestRunner(job, None)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册