Commit 0474c88f authored by Vincent Pelletier's avatar Vincent Pelletier

ProcessingNodeTestCase: Do not retry failed activities.

Including activities which would be allowed to retry later.
parent 2cddee38
...@@ -3,6 +3,11 @@ import base64, errno, os, select, socket, sys, time ...@@ -3,6 +3,11 @@ import base64, errno, os, select, socket, sys, time
from threading import Thread from threading import Thread
from UserDict import IterableUserDict from UserDict import IterableUserDict
import Lifetime import Lifetime
from AccessControl.SecurityManagement import (
newSecurityManager,
setSecurityManager,
getSecurityManager,
)
import transaction import transaction
from Testing import ZopeTestCase from Testing import ZopeTestCase
from ZODB.POSException import ConflictError from ZODB.POSException import ConflictError
...@@ -10,7 +15,7 @@ from zLOG import LOG, ERROR ...@@ -10,7 +15,7 @@ from zLOG import LOG, ERROR
from Products.CMFActivity.Activity.Queue import VALIDATION_ERROR_DELAY from Products.CMFActivity.Activity.Queue import VALIDATION_ERROR_DELAY
from Products.ERP5Type.tests.utils import addUserToDeveloperRole from Products.ERP5Type.tests.utils import addUserToDeveloperRole
from Products.ERP5Type.tests.utils import createZServer from Products.ERP5Type.tests.utils import createZServer
from Products.CMFActivity.ActivityTool import getCurrentNode from Products.CMFActivity.ActivityTool import getCurrentNode, Message
class DictPersistentWrapper(IterableUserDict, object): class DictPersistentWrapper(IterableUserDict, object):
...@@ -31,6 +36,20 @@ class DictPersistentWrapper(IterableUserDict, object): ...@@ -31,6 +36,20 @@ class DictPersistentWrapper(IterableUserDict, object):
self.data = dict self.data = dict
self._persistent_object = persistent_object self._persistent_object = persistent_object
class ActivityFailed(RuntimeError):
def __init__(self, activity_list, last_error):
self.activity_list = activity_list
self.last_error = last_error
super(ActivityFailed, self).__init__()
def __str__(self):
return 'tic is looping forever. These messages are pending: %r\n%s' % (
[
('/'.join(m.object_path), m.method_id, m.processing_node, m.retry)
for m in self.activity_list
],
self.last_error,
)
def patchActivityTool(): def patchActivityTool():
"""Redefine several methods of ActivityTool for unit tests """Redefine several methods of ActivityTool for unit tests
...@@ -186,6 +205,16 @@ class ProcessingNodeTestCase(ZopeTestCase.TestCase): ...@@ -186,6 +205,16 @@ class ProcessingNodeTestCase(ZopeTestCase.TestCase):
transaction.commit() transaction.commit()
self._close() self._close()
def _getLastError(self):
error_log = self.portal.error_log._getLog()
if len(error_log):
return (
'Last error message:\n'
'%(type)s\n'
'%(value)s\n'
'%(tb_text)s' % error_log[-1]
)
def assertNoPendingMessage(self): def assertNoPendingMessage(self):
"""Get the last error message from error_log""" """Get the last error message from error_log"""
message_list = self.portal.portal_activities.getMessageList() message_list = self.portal.portal_activities.getMessageList()
...@@ -193,11 +222,9 @@ class ProcessingNodeTestCase(ZopeTestCase.TestCase): ...@@ -193,11 +222,9 @@ class ProcessingNodeTestCase(ZopeTestCase.TestCase):
error_message = 'These messages are pending: %r' % [ error_message = 'These messages are pending: %r' % [
('/'.join(m.object_path), m.method_id, m.processing_node, m.retry) ('/'.join(m.object_path), m.method_id, m.processing_node, m.retry)
for m in message_list] for m in message_list]
error_log = self.portal.error_log._getLog() last_error = self._getLastError()
if len(error_log): if last_error:
error_message += '\nLast error message:' \ error_message += '\n' + last_error
'\n%(type)s\n%(value)s\n%(tb_text)s' \
% error_log[-1]
self.fail(error_message) self.fail(error_message)
def abort(self): def abort(self):
...@@ -213,45 +240,58 @@ class ProcessingNodeTestCase(ZopeTestCase.TestCase): ...@@ -213,45 +240,58 @@ class ProcessingNodeTestCase(ZopeTestCase.TestCase):
transaction.commit() transaction.commit()
# Some tests like testDeferredStyle require that we use self.getPortal() # Some tests like testDeferredStyle require that we use self.getPortal()
# instead of self.portal in order to setup current skin. # instead of self.portal in order to setup current skin.
portal_activities = self.getPortal().portal_activities portal = self.getPortal()
portal_activities = portal.portal_activities
if verbose: if verbose:
ZopeTestCase._print('Executing pending activities ...') ZopeTestCase._print('Executing pending activities ...')
old_message_count = 0
start = time.time() start = time.time()
count = 1000
getMessageList = portal_activities.getMessageList getMessageList = portal_activities.getMessageList
message_list = getMessageList() pre_failed_uid_set = {
message_count = len(message_list) x.uid
while message_count and not stop_condition(message_list): for x in getMessageList()
if verbose and old_message_count != message_count: if x.processing_node < -1
ZopeTestCase._print(' %i' % message_count) }
old_message_count = message_count portal.changeSkin(None)
portal_activities.process_timer(None, None) old_sm = getSecurityManager()
if Lifetime._shutdown_phase: old_Message_load = Message.load
# XXX CMFActivity contains bare excepts def Message_load(s, **kw):
raise KeyboardInterrupt """
message_list = getMessageList() Prevent activity retries, as activities must succeed from the first try
message_count = len(message_list) in a unit test environment.
# This prevents an infinite loop. This is to catch missing activity dependencies which only work because
count -= 1 activities are being retried until they eventually succeed.
if not count or message_count and all(x.processing_node == -2 """
for x in message_list): kw['max_retry'] = 0
# We're about to raise RuntimeError, but maybe we've reached kw['conflict_retry'] = False
# the stop condition, so check just once more: return old_Message_load(s, **kw)
if stop_condition(message_list): try:
Message.load = staticmethod(Message_load)
newSecurityManager(None, portal.portal_catalog.getWrappedOwner())
while True:
if verbose:
ZopeTestCase._print(' %i' % len(getMessageList()))
# Put everything in the past - hopefully no activity will have been
# pushed that far in the future.
portal_activities.timeShift(30 * VALIDATION_ERROR_DELAY)
portal_activities.distribute()
portal_activities.tic()
self.commit()
message_list = getMessageList()
if not message_list or stop_condition(message_list):
break break
error_message = 'tic is looping forever. ' failed_message_set = [
try: x
self.assertNoPendingMessage() for x in message_list
except AssertionError, e: if x.processing_node < -1
error_message += str(e) ]
raise RuntimeError(error_message) if failed_message_set:
# This give some time between messages raise ActivityFailed(failed_message_set, self._getLastError())
if count % 10 == 0: finally:
portal_activities.timeShift(3 * VALIDATION_ERROR_DELAY) Message.load = staticmethod(old_Message_load)
setSecurityManager(old_sm)
if verbose: if verbose:
ZopeTestCase._print(' done (%.3fs)\n' % (time.time() - start)) ZopeTestCase._print(' done (%.3fs)\n' % (time.time() - start))
self.abort() self.commit()
def afterSetUp(self): def afterSetUp(self):
"""Initialize a node that will only process activities""" """Initialize a node that will only process activities"""
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment