From f107e6dc67276c940143782e09e9f46cc004aba8 Mon Sep 17 00:00:00 2001
From: Alexandre Boeglin <alex@nexedi.com>
Date: Fri, 15 Sep 2006 12:57:23 +0000
Subject: [PATCH] When an activity fails, all activities that depend on it
 should not be executed.

Note: earlier version of this test checked exactly the contrary, but it was
eventually agreed that this was a bug. If an activity fails, all the
activities that depend on it should be block until the first one is resolved.


git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@9994 20353a03-c40f-0410-a6d1-a30d3c3de9de
---
 .../activity/SQLDict_validateMessageList.zsql |   2 +-
 .../SQLQueue_validateMessageList.zsql         |   2 +-
 product/CMFActivity/tests/testCMFActivity.py  | 109 +++++++++++-------
 3 files changed, 67 insertions(+), 46 deletions(-)

diff --git a/product/CMFActivity/skins/activity/SQLDict_validateMessageList.zsql b/product/CMFActivity/skins/activity/SQLDict_validateMessageList.zsql
index 3f8ac6d491..70aa55dc81 100644
--- a/product/CMFActivity/skins/activity/SQLDict_validateMessageList.zsql
+++ b/product/CMFActivity/skins/activity/SQLDict_validateMessageList.zsql
@@ -17,7 +17,7 @@ SELECT
 FROM
     message
 WHERE
-    processing_node >= -1
+    processing_node >= -2
 <dtml-if method_id>
     AND (
 <dtml-in method_id>
diff --git a/product/CMFActivity/skins/activity/SQLQueue_validateMessageList.zsql b/product/CMFActivity/skins/activity/SQLQueue_validateMessageList.zsql
index 9fc103e8d2..38c24e7d30 100644
--- a/product/CMFActivity/skins/activity/SQLQueue_validateMessageList.zsql
+++ b/product/CMFActivity/skins/activity/SQLQueue_validateMessageList.zsql
@@ -17,7 +17,7 @@ SELECT
 FROM
     message_queue
 WHERE
-    processing_node >= -1
+    processing_node >= -2
 <dtml-if method_id>
     AND (
 <dtml-in method_id>
diff --git a/product/CMFActivity/tests/testCMFActivity.py b/product/CMFActivity/tests/testCMFActivity.py
index 1d557981fb..5619f4665a 100644
--- a/product/CMFActivity/tests/testCMFActivity.py
+++ b/product/CMFActivity/tests/testCMFActivity.py
@@ -82,7 +82,7 @@ class TestCMFActivity(ERP5TypeTestCase):
       base categories:
         - region
         - subordination
-      
+
       /organisation
     """
     return ('erp5_base',)
@@ -107,7 +107,7 @@ class TestCMFActivity(ERP5TypeTestCase):
     message_list = portal.portal_activities.getMessageList()
     for message in message_list:
       portal.portal_activities.manageCancel(message.object_path,message.method_id)
-      
+
     # Then add new components
     if not(hasattr(portal,'organisation')):
       portal.portal_types.constructContent(type_name='Organisation Module',
@@ -461,30 +461,30 @@ class TestCMFActivity(ERP5TypeTestCase):
     if not organisation_module.hasContent(self.company_id):
       organisation_module.newContent(id=self.company_id)
     o = portal.organisation._getOb(self.company_id)
-    
+
     o.setTitle('a')
     self.assertEquals(o.getTitle(), 'a')
     get_transaction().commit()
     self.tic()
-    
+
     def toto(self, value):
       self.setTitle(self.getTitle() + value)
     o.__class__.toto = toto
-    
+
     def titi(self, value):
       self.setTitle(self.getTitle() + value)
     o.__class__.titi = titi
-    
+
     o.activate(after_method_id = 'titi', activity = activity).toto('b')
     o.activate(activity = activity).titi('c')
     get_transaction().commit()
     self.tic()
     self.assertEquals(o.getTitle(), 'acb')
-     
+
   def ExpandedMethodWithDeletedSubObject(self, activity):
     """
     Do recursiveReindexObject, then delete a
-    subobject an see if there is only one activity 
+    subobject an see if there is only one activity
     in the queue
     """
     portal = self.getPortal()
@@ -511,7 +511,7 @@ class TestCMFActivity(ERP5TypeTestCase):
   def ExpandedMethodWithDeletedObject(self, activity):
     """
     Do recursiveReindexObject, then delete a
-    subobject an see if there is only one activity 
+    subobject an see if there is only one activity
     in the queue
     """
     portal = self.getPortal()
@@ -544,18 +544,18 @@ class TestCMFActivity(ERP5TypeTestCase):
     if not organisation_module.hasContent(self.company_id):
       organisation_module.newContent(id=self.company_id)
     o = portal.organisation._getOb(self.company_id)
-    
+
     o.setTitle('?')
     self.assertEquals(o.getTitle(), '?')
     get_transaction().commit()
     self.tic()
-    
+
     o.activate(after_tag = 'toto', activity = activity).setTitle('b')
     o.activate(tag = 'toto', activity = activity).setTitle('a')
     get_transaction().commit()
     self.tic()
     self.assertEquals(o.getTitle(), 'b')
-    
+
     o._v_activate_kw = {'tag':'toto'}
     def titi(self):
       self.setCorporateName(self.getTitle() + 'd')
@@ -565,7 +565,7 @@ class TestCMFActivity(ERP5TypeTestCase):
     get_transaction().commit()
     self.tic()
     self.assertEquals(o.getCorporateName(), 'cd')
-    
+
   def CheckScheduling(self, activity):
     """
       Check if active objects with different after parameters are executed in a correct order
@@ -575,16 +575,16 @@ class TestCMFActivity(ERP5TypeTestCase):
     if not organisation_module.hasContent(self.company_id):
       organisation_module.newContent(id=self.company_id)
     o = portal.organisation._getOb(self.company_id)
-    
+
     o.setTitle('?')
     self.assertEquals(o.getTitle(), '?')
     get_transaction().commit()
     self.tic()
-    
+
     def toto(self, s):
       self.setTitle(self.getTitle() + s)
     o.__class__.toto = toto
-    
+
     o.activate(tag = 'toto', activity = activity).toto('a')
     get_transaction().commit()
     o.activate(after_tag = 'titi', activity = activity).toto('b')
@@ -593,7 +593,7 @@ class TestCMFActivity(ERP5TypeTestCase):
     get_transaction().commit()
     self.tic()
     self.assertEquals(o.getTitle(), 'cb')
-        
+
   def CheckClearActivities(self, activity):
     """
       Check if active objects are held even after clearing the tables.
@@ -611,16 +611,16 @@ class TestCMFActivity(ERP5TypeTestCase):
       m = message_list[0]
       self.assertEquals(m.object_path, o.getPhysicalPath())
       self.assertEquals(m.method_id, '_setTitle')
-      
+
     o = portal.organisation._getOb(self.company_id)
     o.activate(activity=activity)._setTitle('foo')
     get_transaction().commit()
     check(o)
-    
+
     portal.portal_activities.manageClearActivities()
     get_transaction().commit()
     check(o)
-    
+
     get_transaction().commit()
     self.tic()
 
@@ -1135,7 +1135,7 @@ class TestCMFActivity(ERP5TypeTestCase):
       ZopeTestCase._print(message)
       LOG('Testing... ',0,message)
     self.TryMethodAfterMethod('SQLDict')
-    
+
   def test_55_TryAfterMethodIdWithSQLQueue(self, quiet=0, run=run_all_test):
     # Test if after_method_id can be used
     if not run: return
@@ -1144,7 +1144,7 @@ class TestCMFActivity(ERP5TypeTestCase):
       ZopeTestCase._print(message)
       LOG('Testing... ',0,message)
     self.TryMethodAfterMethod('SQLQueue')
-    
+
   def test_56_TryCallActivityWithRightUser(self, quiet=0, run=run_all_test):
     # Test if me execute methods with the right user
     # This should be independant of the activity used
@@ -1181,7 +1181,7 @@ class TestCMFActivity(ERP5TypeTestCase):
       ZopeTestCase._print(message)
       LOG('Testing... ',0,message)
     self.ExpandedMethodWithDeletedSubObject('SQLDict')
-    
+
   def test_58_ExpandedMethodWithDeletedObject(self, quiet=0, run=run_all_test):
     # Test if after_method_id can be used
     if not run: return
@@ -1200,7 +1200,7 @@ class TestCMFActivity(ERP5TypeTestCase):
       LOG('Testing... ',0,message)
     self.TryAfterTag('SQLDict')
 
-  def test_60_TryAfterTagWithSQLDict(self, quiet=0, run=run_all_test):
+  def test_60_TryAfterTagWithSQLQueue(self, quiet=0, run=run_all_test):
     # Test if after_tag can be used
     if not run: return
     if not quiet:
@@ -1245,7 +1245,7 @@ class TestCMFActivity(ERP5TypeTestCase):
       LOG('Testing... ',0,message)
     self.CheckClearActivities('SQLQueue')
 
-  def flushAllActivities(self):
+  def flushAllActivities(self, silent=0, loop_size=1000):
     """Executes all messages until the queue only contains failed
     messages.
     """
@@ -1254,12 +1254,14 @@ class TestCMFActivity(ERP5TypeTestCase):
     # flush activities
     while 1:
       loop_count += 1
-      if loop_count >= 1000:
+      if loop_count >= loop_size:
+        if silent:
+          return
         self.fail('flushAllActivities maximum loop count reached')
 
       activity_tool.distribute(node_count=1)
       activity_tool.tic(processing_node=1)
-      
+
       finished = 1
       for message in activity_tool.getMessageList():
         if message.processing_node not in (INVOKE_ERROR_STATE,
@@ -1269,7 +1271,7 @@ class TestCMFActivity(ERP5TypeTestCase):
       activity_tool.timeShift(3 * VALIDATION_ERROR_DELAY)
       get_transaction().commit()
       if finished:
-        return 
+        return
 
   def test_65_TestMessageValidationAndFailedActivities(self,
                                               quiet=0, run=run_all_test):
@@ -1277,16 +1279,23 @@ class TestCMFActivity(ERP5TypeTestCase):
 
     Tests that if we have an active method scheduled by
     after_method_id and a failed activity with this method id, the
-    method is executed."""
+    method is NOT executed.
+
+    Note: earlier version of this test checked exactly the contrary, but it
+    was eventually agreed that this was a bug. If an activity fails, all the
+    activities that depend on it should be block until the first one is
+    resolved."""
     if not run: return
     if not quiet:
       message = '\nafter_method_id and failed activities'
       ZopeTestCase._print(message)
       LOG('Testing... ', 0, message)
     activity_tool = self.getPortal().portal_activities
+    original_title = 'something'
     obj = self.getPortal().organisation_module.newContent(
-                    portal_type='Organisation')
-    
+                    portal_type='Organisation',
+                    title=original_title)
+
     # Monkey patch Organisation to add a failing method
     def failingMethod(self):
       raise ValueError, 'This method always fail'
@@ -1302,31 +1311,43 @@ class TestCMFActivity(ERP5TypeTestCase):
     activity_list = ['SQLQueue', 'SQLDict', ]
     for activity in activity_list:
       # reset
-      activity_tool.manageClearActivities()
-      obj.setTitle('something')
+      activity_tool.manageClearActivities(keep=0)
+      obj.setTitle(original_title)
       get_transaction().commit()
 
       # activate failing message and flush
       for fail_activity in activity_list:
         obj.activate(activity = fail_activity).failingMethod()
       get_transaction().commit()
-      self.flushAllActivities()
-      message_count = len(activity_tool.getMessageList())
-      if message_count == 0:
-        self.fail('Activity tool should have remaining messages')
-      
+      self.flushAllActivities(silent=1, loop_size=100)
+      full_message_list = activity_tool.getMessageList()
+      remaining_messages = [a for a in full_message_list if a.method_id !=
+          'failingMethod']
+      if len(full_message_list) != 2:
+        self.fail('failingMethod should not have been flushed')
+      if len(remaining_messages) != 0:
+        self.fail('Activity tool should have no other remaining messages')
+
       # activate our message
       new_title = 'nothing'
       obj.activate(after_method_id = ['failingMethod'],
                    activity = activity ).setTitle(new_title)
       get_transaction().commit()
-      self.flushAllActivities()
-      self.assertEquals(message_count,
-                        len(activity_tool.getMessageList()))
-      self.assertEquals(obj.getTitle(), new_title)
-
-    # restore notification
+      self.flushAllActivities(silent=1, loop_size=100)
+      full_message_list = activity_tool.getMessageList()
+      remaining_messages = [a for a in full_message_list if a.method_id !=
+          'failingMethod']
+      if len(full_message_list) != 3:
+        self.fail('failingMethod should not have been flushed')
+      if len(remaining_messages) != 1:
+        self.fail('Activity tool should have one blocked setTitle activity')
+      self.assertEquals(remaining_messages[0].activity_kw['after_method_id'],
+          ['failingMethod'])
+      self.assertEquals(obj.getTitle(), original_title)
+
+    # restore notification and flush failed and blocked activities
     Message.notifyUser = originalNotifyUser
+    activity_tool.manageClearActivities(keep=0)
 
   def test_66_TestCountMessageWithTagWithSQLDict(self, quiet=0, run=run_all_test):
     """
-- 
2.30.9