Merge "Run action-engine tests with worker-based engine"

This commit is contained in:
Jenkins
2014-02-20 11:12:48 +00:00
committed by Gerrit Code Review
4 changed files with 153 additions and 50 deletions

View File

@@ -14,7 +14,6 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import contextlib
import logging import logging
import os import os
import sys import sys
@@ -34,6 +33,7 @@ import taskflow.engines
from taskflow import exceptions from taskflow import exceptions
from taskflow.patterns import unordered_flow as uf from taskflow.patterns import unordered_flow as uf
from taskflow import task from taskflow import task
from taskflow.tests import utils
from taskflow.utils import misc from taskflow.utils import misc
# INTRO: In this example we create two tasks which can trigger exceptions # INTRO: In this example we create two tasks which can trigger exceptions
@@ -60,20 +60,6 @@ def print_wrapped(text):
print("-" * (len(text))) print("-" * (len(text)))
@contextlib.contextmanager
def wrap_all_failures():
"""Convert any exceptions to WrappedFailure.
When you expect several failures, it may be convenient
to wrap any exception with WrappedFailure in order to
unify error handling.
"""
try:
yield
except Exception:
raise exceptions.WrappedFailure([misc.Failure()])
class FirstException(Exception): class FirstException(Exception):
"""Exception that first task raises.""" """Exception that first task raises."""
@@ -110,7 +96,7 @@ def run(**store):
SecondTask() SecondTask()
) )
try: try:
with wrap_all_failures(): with utils.wrap_all_failures():
taskflow.engines.run(flow, store=store, taskflow.engines.run(flow, store=store,
engine_conf='parallel') engine_conf='parallel')
except exceptions.WrappedFailure as ex: except exceptions.WrappedFailure as ex:

View File

@@ -20,6 +20,9 @@ from testtools import compat
from testtools import matchers from testtools import matchers
from testtools import testcase from testtools import testcase
from taskflow import exceptions
from taskflow.tests import utils
class GreaterThanEqual(object): class GreaterThanEqual(object):
"""Matches if the item is geq than the matchers reference object.""" """Matches if the item is geq than the matchers reference object."""
@@ -33,6 +36,24 @@ class GreaterThanEqual(object):
return matchers.Mismatch("%s was not >= %s" % (other, self.source)) return matchers.Mismatch("%s was not >= %s" % (other, self.source))
class FailureRegexpMatcher(object):
"""Matches if the failure was caused by the given exception and its string
matches to the given pattern.
"""
def __init__(self, exc_class, pattern):
self.exc_class = exc_class
self.pattern = pattern
def match(self, failure):
for cause in failure:
if cause.check(self.exc_class) is not None:
return matchers.MatchesRegex(
self.pattern).match(cause.exception_str)
return matchers.Mismatch("The `%s` wasn't caused by the `%s`" %
(failure, self.exc_class))
class TestCase(testcase.TestCase): class TestCase(testcase.TestCase):
"""Test case base class for all taskflow unit tests.""" """Test case base class for all taskflow unit tests."""
@@ -89,6 +110,17 @@ class TestCase(testcase.TestCase):
else: else:
current_tail = current_tail[super_index + 1:] current_tail = current_tail[super_index + 1:]
def assertFailuresRegexp(self, exc_class, pattern, callable_obj, *args,
**kwargs):
"""Assert that the callable failed with the given exception and its
string matches to the given pattern.
"""
try:
with utils.wrap_all_failures():
callable_obj(*args, **kwargs)
except exceptions.WrappedFailure as e:
self.assertThat(e, FailureRegexpMatcher(exc_class, pattern))
class MockTestCase(TestCase): class MockTestCase(TestCase):

View File

@@ -17,6 +17,7 @@
import contextlib import contextlib
import networkx import networkx
import testtools import testtools
import threading
from concurrent import futures from concurrent import futures
@@ -27,6 +28,8 @@ from taskflow.patterns import unordered_flow as uf
import taskflow.engines import taskflow.engines
from taskflow.engines.action_engine import engine as eng from taskflow.engines.action_engine import engine as eng
from taskflow.engines.worker_based import engine as w_eng
from taskflow.engines.worker_based import worker as wkr
from taskflow import exceptions as exc from taskflow import exceptions as exc
from taskflow.persistence import logbook from taskflow.persistence import logbook
from taskflow import states from taskflow import states
@@ -87,11 +90,11 @@ class EngineTaskTest(utils.EngineTestBase):
'fail reverted(Failure: RuntimeError: Woot!)', 'fail reverted(Failure: RuntimeError: Woot!)',
'fail REVERTED', 'fail REVERTED',
'flow REVERTED'] 'flow REVERTED']
self.assertRaisesRegexp(RuntimeError, '^Woot', engine.run) self.assertFailuresRegexp(RuntimeError, '^Woot', engine.run)
self.assertEqual(self.values, expected) self.assertEqual(self.values, expected)
self.assertEqual(engine.storage.get_flow_state(), states.REVERTED) self.assertEqual(engine.storage.get_flow_state(), states.REVERTED)
self.assertRaisesRegexp(RuntimeError, '^Woot', engine.run) self.assertFailuresRegexp(RuntimeError, '^Woot', engine.run)
now_expected = expected + ['fail PENDING', 'flow PENDING'] + expected now_expected = expected + ['fail PENDING', 'flow PENDING'] + expected
self.assertEqual(self.values, now_expected) self.assertEqual(self.values, now_expected)
self.assertEqual(engine.storage.get_flow_state(), states.REVERTED) self.assertEqual(engine.storage.get_flow_state(), states.REVERTED)
@@ -119,7 +122,7 @@ class EngineTaskTest(utils.EngineTestBase):
def test_nasty_failing_task_exception_reraised(self): def test_nasty_failing_task_exception_reraised(self):
flow = utils.NastyFailingTask() flow = utils.NastyFailingTask()
engine = self._make_engine(flow) engine = self._make_engine(flow)
self.assertRaisesRegexp(RuntimeError, '^Gotcha', engine.run) self.assertFailuresRegexp(RuntimeError, '^Gotcha', engine.run)
class EngineLinearFlowTest(utils.EngineTestBase): class EngineLinearFlowTest(utils.EngineTestBase):
@@ -152,7 +155,7 @@ class EngineLinearFlowTest(utils.EngineTestBase):
utils.FailingTask(name='fail') utils.FailingTask(name='fail')
) )
engine = self._make_engine(flow) engine = self._make_engine(flow)
self.assertRaisesRegexp(RuntimeError, '^Woot', engine.run) self.assertFailuresRegexp(RuntimeError, '^Woot', engine.run)
self.assertEqual(engine.storage.fetch_all(), {}) self.assertEqual(engine.storage.fetch_all(), {})
def test_sequential_flow_nested_blocks(self): def test_sequential_flow_nested_blocks(self):
@@ -171,7 +174,7 @@ class EngineLinearFlowTest(utils.EngineTestBase):
utils.FailingTask(name='fail') utils.FailingTask(name='fail')
) )
engine = self._make_engine(flow) engine = self._make_engine(flow)
self.assertRaisesRegexp(RuntimeError, '^Gotcha', engine.run) self.assertFailuresRegexp(RuntimeError, '^Gotcha', engine.run)
def test_revert_not_run_task_is_not_reverted(self): def test_revert_not_run_task_is_not_reverted(self):
flow = lf.Flow('revert-not-run').add( flow = lf.Flow('revert-not-run').add(
@@ -179,7 +182,7 @@ class EngineLinearFlowTest(utils.EngineTestBase):
utils.NeverRunningTask(), utils.NeverRunningTask(),
) )
engine = self._make_engine(flow) engine = self._make_engine(flow)
self.assertRaisesRegexp(RuntimeError, '^Woot', engine.run) self.assertFailuresRegexp(RuntimeError, '^Woot', engine.run)
self.assertEqual( self.assertEqual(
self.values, self.values,
['fail reverted(Failure: RuntimeError: Woot!)']) ['fail reverted(Failure: RuntimeError: Woot!)'])
@@ -193,32 +196,13 @@ class EngineLinearFlowTest(utils.EngineTestBase):
) )
) )
engine = self._make_engine(flow) engine = self._make_engine(flow)
self.assertRaisesRegexp(RuntimeError, '^Woot', engine.run) self.assertFailuresRegexp(RuntimeError, '^Woot', engine.run)
self.assertEqual( self.assertEqual(
self.values, self.values,
['task1', 'task2', ['task1', 'task2',
'fail reverted(Failure: RuntimeError: Woot!)', 'fail reverted(Failure: RuntimeError: Woot!)',
'task2 reverted(5)', 'task1 reverted(5)']) 'task2 reverted(5)', 'task1 reverted(5)'])
def test_flow_failures_are_passed_to_revert(self):
class CheckingTask(task.Task):
def execute(m_self):
return 'RESULT'
def revert(m_self, result, flow_failures):
self.assertEqual(result, 'RESULT')
self.assertEqual(list(flow_failures.keys()), ['fail1'])
fail = flow_failures['fail1']
self.assertIsInstance(fail, misc.Failure)
self.assertEqual(str(fail), 'Failure: RuntimeError: Woot!')
flow = lf.Flow('test').add(
CheckingTask(),
utils.FailingTask('fail1')
)
engine = self._make_engine(flow)
self.assertRaisesRegexp(RuntimeError, '^Woot', engine.run)
class EngineParallelFlowTest(utils.EngineTestBase): class EngineParallelFlowTest(utils.EngineTestBase):
@@ -252,7 +236,7 @@ class EngineParallelFlowTest(utils.EngineTestBase):
utils.TaskNoRequiresNoReturns(name='task2') utils.TaskNoRequiresNoReturns(name='task2')
) )
engine = self._make_engine(flow) engine = self._make_engine(flow)
self.assertRaisesRegexp(RuntimeError, '^Woot', engine.run) self.assertFailuresRegexp(RuntimeError, '^Woot', engine.run)
self.assertIn('fail reverted(Failure: RuntimeError: Woot!)', self.assertIn('fail reverted(Failure: RuntimeError: Woot!)',
self.values) self.values)
@@ -269,7 +253,7 @@ class EngineParallelFlowTest(utils.EngineTestBase):
utils.FailingTask() utils.FailingTask()
) )
engine = self._make_engine(flow) engine = self._make_engine(flow)
self.assertRaisesRegexp(RuntimeError, '^Gotcha', engine.run) self.assertFailuresRegexp(RuntimeError, '^Gotcha', engine.run)
def test_sequential_flow_two_tasks_with_resumption(self): def test_sequential_flow_two_tasks_with_resumption(self):
flow = lf.Flow('lf-2-r').add( flow = lf.Flow('lf-2-r').add(
@@ -307,7 +291,7 @@ class EngineLinearAndUnorderedExceptionsTest(utils.EngineTestBase):
) )
) )
engine = self._make_engine(flow) engine = self._make_engine(flow)
self.assertRaisesRegexp(RuntimeError, '^Woot', engine.run) self.assertFailuresRegexp(RuntimeError, '^Woot', engine.run)
# NOTE(imelnikov): we don't know if task 3 was run, but if it was, # NOTE(imelnikov): we don't know if task 3 was run, but if it was,
# it should have been reverted in correct order. # it should have been reverted in correct order.
@@ -336,7 +320,7 @@ class EngineLinearAndUnorderedExceptionsTest(utils.EngineTestBase):
) )
) )
engine = self._make_engine(flow) engine = self._make_engine(flow)
self.assertRaisesRegexp(RuntimeError, '^Gotcha', engine.run) self.assertFailuresRegexp(RuntimeError, '^Gotcha', engine.run)
# NOTE(imelnikov): we don't know if task 3 was run, but if it was, # NOTE(imelnikov): we don't know if task 3 was run, but if it was,
# it should have been reverted in correct order. # it should have been reverted in correct order.
@@ -356,7 +340,7 @@ class EngineLinearAndUnorderedExceptionsTest(utils.EngineTestBase):
) )
) )
engine = self._make_engine(flow) engine = self._make_engine(flow)
self.assertRaisesRegexp(RuntimeError, '^Woot', engine.run) self.assertFailuresRegexp(RuntimeError, '^Woot', engine.run)
self.assertIn('fail reverted(Failure: RuntimeError: Woot!)', self.assertIn('fail reverted(Failure: RuntimeError: Woot!)',
self.values) self.values)
@@ -377,7 +361,7 @@ class EngineLinearAndUnorderedExceptionsTest(utils.EngineTestBase):
) )
) )
engine = self._make_engine(flow) engine = self._make_engine(flow)
self.assertRaisesRegexp(RuntimeError, '^Gotcha', engine.run) self.assertFailuresRegexp(RuntimeError, '^Gotcha', engine.run)
self.assertNotIn('task2 reverted(5)', self.values) self.assertNotIn('task2 reverted(5)', self.values)
@@ -443,7 +427,7 @@ class EngineGraphFlowTest(utils.EngineTestBase):
utils.SaveOrderTask(name='task1', provides='a')) utils.SaveOrderTask(name='task1', provides='a'))
engine = self._make_engine(flow) engine = self._make_engine(flow)
self.assertRaisesRegexp(RuntimeError, '^Woot', engine.run) self.assertFailuresRegexp(RuntimeError, '^Woot', engine.run)
self.assertEqual( self.assertEqual(
self.values, self.values,
['task1', 'task2', ['task1', 'task2',
@@ -458,7 +442,7 @@ class EngineGraphFlowTest(utils.EngineTestBase):
utils.SaveOrderTask(name='task1', provides='a')) utils.SaveOrderTask(name='task1', provides='a'))
engine = self._make_engine(flow) engine = self._make_engine(flow)
self.assertRaisesRegexp(RuntimeError, '^Gotcha', engine.run) self.assertFailuresRegexp(RuntimeError, '^Gotcha', engine.run)
self.assertEqual(engine.storage.get_flow_state(), states.FAILURE) self.assertEqual(engine.storage.get_flow_state(), states.FAILURE)
def test_graph_flow_with_multireturn_and_multiargs_tasks(self): def test_graph_flow_with_multireturn_and_multiargs_tasks(self):
@@ -498,11 +482,34 @@ class EngineGraphFlowTest(utils.EngineTestBase):
self.assertIsInstance(graph, networkx.DiGraph) self.assertIsInstance(graph, networkx.DiGraph)
class EngineCheckingTaskTest(utils.EngineTestBase):
def test_flow_failures_are_passed_to_revert(self):
class CheckingTask(task.Task):
def execute(m_self):
return 'RESULT'
def revert(m_self, result, flow_failures):
self.assertEqual(result, 'RESULT')
self.assertEqual(list(flow_failures.keys()), ['fail1'])
fail = flow_failures['fail1']
self.assertIsInstance(fail, misc.Failure)
self.assertEqual(str(fail), 'Failure: RuntimeError: Woot!')
flow = lf.Flow('test').add(
CheckingTask(),
utils.FailingTask('fail1')
)
engine = self._make_engine(flow)
self.assertRaisesRegexp(RuntimeError, '^Woot', engine.run)
class SingleThreadedEngineTest(EngineTaskTest, class SingleThreadedEngineTest(EngineTaskTest,
EngineLinearFlowTest, EngineLinearFlowTest,
EngineParallelFlowTest, EngineParallelFlowTest,
EngineLinearAndUnorderedExceptionsTest, EngineLinearAndUnorderedExceptionsTest,
EngineGraphFlowTest, EngineGraphFlowTest,
EngineCheckingTaskTest,
test.TestCase): test.TestCase):
def _make_engine(self, flow, flow_detail=None): def _make_engine(self, flow, flow_detail=None):
return taskflow.engines.load(flow, return taskflow.engines.load(flow,
@@ -524,6 +531,7 @@ class MultiThreadedEngineTest(EngineTaskTest,
EngineParallelFlowTest, EngineParallelFlowTest,
EngineLinearAndUnorderedExceptionsTest, EngineLinearAndUnorderedExceptionsTest,
EngineGraphFlowTest, EngineGraphFlowTest,
EngineCheckingTaskTest,
test.TestCase): test.TestCase):
def _make_engine(self, flow, flow_detail=None, executor=None): def _make_engine(self, flow, flow_detail=None, executor=None):
engine_conf = dict(engine='parallel', engine_conf = dict(engine='parallel',
@@ -554,6 +562,7 @@ class ParallelEngineWithEventletTest(EngineTaskTest,
EngineParallelFlowTest, EngineParallelFlowTest,
EngineLinearAndUnorderedExceptionsTest, EngineLinearAndUnorderedExceptionsTest,
EngineGraphFlowTest, EngineGraphFlowTest,
EngineCheckingTaskTest,
test.TestCase): test.TestCase):
def _make_engine(self, flow, flow_detail=None, executor=None): def _make_engine(self, flow, flow_detail=None, executor=None):
@@ -564,3 +573,63 @@ class ParallelEngineWithEventletTest(EngineTaskTest,
return taskflow.engines.load(flow, flow_detail=flow_detail, return taskflow.engines.load(flow, flow_detail=flow_detail,
engine_conf=engine_conf, engine_conf=engine_conf,
backend=self.backend) backend=self.backend)
class WorkerBasedEngineTest(EngineTaskTest,
EngineLinearFlowTest,
EngineParallelFlowTest,
EngineLinearAndUnorderedExceptionsTest,
EngineGraphFlowTest,
test.TestCase):
def setUp(self):
super(WorkerBasedEngineTest, self).setUp()
self.exchange = 'test'
self.topic = 'topic'
self.transport = 'memory'
worker_conf = {
'exchange': self.exchange,
'topic': self.topic,
'tasks': [
'taskflow.tests.utils'
],
'transport': self.transport
}
self.worker = wkr.Worker(**worker_conf)
self.worker_thread = threading.Thread(target=self.worker.run)
self.worker_thread.daemon = True
self.worker_thread.start()
# make sure worker is started before we can continue
self.worker.wait()
def tearDown(self):
self.worker.stop()
self.worker_thread.join()
super(WorkerBasedEngineTest, self).tearDown()
def _make_engine(self, flow, flow_detail=None):
engine_conf = {
'engine': 'worker-based',
'exchange': self.exchange,
'workers_info': {
self.topic: [
'taskflow.tests.utils.SaveOrderTask',
'taskflow.tests.utils.FailingTask',
'taskflow.tests.utils.TaskOneReturn',
'taskflow.tests.utils.TaskMultiReturn',
'taskflow.tests.utils.TaskMultiArgOneReturn',
'taskflow.tests.utils.NastyTask',
'taskflow.tests.utils.NastyFailingTask',
'taskflow.tests.utils.NeverRunningTask',
'taskflow.tests.utils.TaskNoRequiresNoReturns'
]
},
'transport': self.transport
}
return taskflow.engines.load(flow, flow_detail=flow_detail,
engine_conf=engine_conf,
backend=self.backend)
def test_correct_load(self):
engine = self._make_engine(utils.TaskNoRequiresNoReturns)
self.assertIsInstance(engine, w_eng.WorkerBasedActionEngine)

View File

@@ -18,14 +18,30 @@ import contextlib
import six import six
from taskflow import exceptions
from taskflow.persistence.backends import impl_memory from taskflow.persistence.backends import impl_memory
from taskflow import task from taskflow import task
from taskflow.utils import misc
ARGS_KEY = '__args__' ARGS_KEY = '__args__'
KWARGS_KEY = '__kwargs__' KWARGS_KEY = '__kwargs__'
ORDER_KEY = '__order__' ORDER_KEY = '__order__'
@contextlib.contextmanager
def wrap_all_failures():
"""Convert any exceptions to WrappedFailure.
When you expect several failures, it may be convenient
to wrap any exception with WrappedFailure in order to
unify error handling.
"""
try:
yield
except Exception:
raise exceptions.WrappedFailure([misc.Failure()])
def make_reverting_task(token, blowup=False): def make_reverting_task(token, blowup=False):
def do_revert(context, *args, **kwargs): def do_revert(context, *args, **kwargs):