diff --git a/product/ERP5/tests/testSimulationPerformance.py b/product/ERP5/tests/testSimulationPerformance.py
new file mode 100644
index 0000000000000000000000000000000000000000..339b0011fe8802d09eba44ced39351280ebfdb3e
--- /dev/null
+++ b/product/ERP5/tests/testSimulationPerformance.py
@@ -0,0 +1,766 @@
+##############################################################################
+#
+# Copyright (c) 2010 Nexedi SA and Contributors. All Rights Reserved.
+#          Yoshinori Okuji <yo@nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# 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 the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+r"""Performance tests for simulation.
+
+The purpose of these tests is to measure the performance of the following
+basic operations in simulation:
+
+  - the creation of new Applied Rules and the expansion of simulation
+  - the expansion of simulation from existing Applied Rules
+  - the expansion of simulation which results in convergence
+  - the expansion of simulation which results in divergence
+  - the adoption of previsions by a Target Solver
+  - the acceptance of decisions by a Target Solver
+  - the creation of new Applied Rules from partially simulated documents
+  - the creation of new Deliveries by Delivery Builders
+  - the addition of new Movements into existing Deliveries by Delivery Builders
+
+For the testing environment, these document types are used for Deliveries:
+
+  - Sale Order
+  - Sale Packing List
+  - Sale Invoice
+
+Since Sale Invoices consist of Movements generated from Sale Packing Lists,
+and Movements generated by Trade Model Lines, the simulation trees consist
+of at most 4 levels in this order:
+
+  - Order Simulation Rule
+  - Packing List Simulation Rule
+  - Invoice Simulation Rule
+  - Trade Model Simulation Rule
+
+In order to simulate various patterns of building, the following rules
+are defined:
+
+  - Sale Order is completed at "ordered"
+  - Sale Packing List is completed at "stopped"
+  - Sale Packing Lists are grouped by destination and by Sale Order
+  - Sale Invoices are not
+
+So, the relationship between Sale Order and Sale Packing List is 1:N,
+and that between Sale Packing List and Sale Invoice is M:N.
+
+"""
+
+import unittest
+from time import time
+import gc
+import subprocess
+
+import transaction
+from DateTime import DateTime
+from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
+from zLOG import LOG
+from Products.ERP5Type.tests.utils import LogInterceptor
+from Products.ERP5Type.tests.Sequence import SequenceList
+
+# Define variable to chek if performance are good or not
+# XXX These variable are specific to the testing environment
+#     (pystone results: min: < 35373.2 - mean: ~ 35990.7 - max: > 36589.8)
+# Historical values are here to remember original values on this
+# specific testing environment. We must always try to stay below max
+# historical values.
+
+EXPECTED_MIN_MAX_TIME = {
+  'Creation Of New Applied Rules': (1.0, 1.0),
+  'Expansion of Existing Applied Rules': (1.0, 1.0),
+  'Creation of New Sale Packing Lists': (1.0, 1.0),
+  'Expansion of Converged Changes': (1.0, 1.0),
+  'Expansion of Diverged Changes': (1.0, 1.0),
+  'Adoption of Previsions': (1.0, 1.0),
+  'Acceptance of Decisions': (1.0, 1.0),
+  'Creation of New Applied Rules from Partially Simulated Deliveries':
+    (1.0, 1.0),
+  'Creation Of New Sale Invoices': (1.0, 1.0),
+  'Addition of New Invoices Lines': (1.0, 1.0),
+}
+
+class TestSimulationPerformance(ERP5TypeTestCase, LogInterceptor):
+
+    def getTitle(self):
+      return "Simulation Performance"
+
+    def getBusinessTemplateList(self):
+      """
+        Return the list of business templates.
+      """
+      return ('erp5_base',
+              'erp5_pdm',
+              'erp5_trade', 
+              'erp5_invoicing',
+              'erp5_accounting',
+              'erp5_advanced_invoicing',
+              'erp5_simulation_performance_test',)
+
+    def afterSetUp(self):
+      """
+        Executed before each test_*.
+      """
+      self.login()
+      portal = self.getPortal()
+
+      # Validate some documents.
+      for rule in portal.portal_rules.objectValues():
+        if rule.getValidationState() != 'validated' \
+                and rule.getId().startswith('test_'):
+          rule.validate()
+
+      for trade_condition in portal.sale_trade_condition_module.objectValues():
+        if trade_condition.getValidationState() != 'validated':
+          trade_condition.validate()
+
+      for business_process in portal.business_process_module.objectValues():
+        if business_process.getValidationState() != 'validated':
+          business_process.validate()
+
+      subprocess.call('sync')
+
+    def beforeTearDown(self):
+      transaction.abort()
+      portal = self.getPortal()
+      for module_id in ('sale_order_module', 'sale_packing_list_module',
+              'sale_invoice_module', 'portal_simulation'):
+        module = portal[module_id]
+        module.manage_delObjects(list(module.objectIds()))
+        transaction.commit()
+        self.tic()
+
+    def _getMinMaxTime(self, target):
+      return EXPECTED_MIN_MAX_TIME[target]
+
+    def _checkSimulation(self,
+            number_of_sale_orders=None,
+            number_of_sale_order_lines=None,
+            number_of_additional_sale_packing_list_lines=None,
+            measurable=True):
+      sequence_string = """
+              Initialize
+              Tic
+              UseClientAsDestinationDecision
+              UseClientAsDestinationAdministration
+              UseResourceAsCommodityProduct
+              CreateSaleOrders
+              Tic
+              PlanSaleOrders
+              MeasureCreationOfNewAppliedRules
+              Tic
+              CheckSaleOrderSimulation
+              UseRecipient1AsDestinationDecision
+              ModifySaleOrders
+              MeasureExpansionOfExistingAppliedRules
+              Tic
+              CheckSaleOrderSimulation
+              OrderSaleOrders
+              MeasureCreationOfNewSalePackingLists
+              Tic
+              CheckSaleOrderSimulation
+              CheckBuiltSalePackingLists
+              UseRecipient2AsDestinationDecision
+              ModifySaleOrders
+              MeasureExpansionOfConvergedChanges
+              Tic
+              CheckSaleOrderSimulation
+              CheckConvergedSalePackingLists
+              UseRecipient1AsDestinationAdministration
+              ModifySaleOrders
+              MeasureExpansionOfDivergedChanges
+              Tic
+              CheckSaleOrderSimulation
+              CheckDivergedSalePackingLists
+              SetSolverTargetToDestinationAdministration
+              AdoptPrevisionToSalePackingLists
+              MeasureAdoptionOfPrevisions
+              Tic
+              CheckConvergedSalePackingLists
+              UseRecipient2AsDestinationAdministration
+              ModifySaleOrders
+              Tic
+              CheckSaleOrderSimulation
+              CheckDivergedSalePackingLists
+              SetSolverTargetToDestinationAdministration
+              AcceptDecisionOfSalePackingLists
+              MeasureAcceptanceOfDecisions
+              Tic
+              CheckConvergedSalePackingLists
+              ConfirmSaleOrders
+              Tic
+              UseResourceAsCommodityProduct
+              AddNewSalePackingLinesIntoSalePackingLists
+              MeasureCreationOfNewAppliedRulesFromPartiallySimulatedDeliveries
+              Tic
+              CheckSalePackingListSimulation
+              StopSalePackingLists
+              MeasureCreationOfNewSaleInvoices
+              Tic
+              CheckBuiltSaleInvoices
+              UseResourceAsLuxuryProduct
+              AddNewSalePackingLinesIntoSalePackingLists
+              DeliverSalePackingLists
+              MeasureAdditionOfNewInvoiceLinesIntoSaleInvoices
+              Tic
+              CheckAddedSaleInvoices
+              TestResult
+              """
+
+      sequence = SequenceList()
+
+      self.assertTrue(isinstance(number_of_sale_orders, int))
+      self.assertTrue(number_of_sale_orders > 0)
+
+      self.assertTrue(isinstance(number_of_sale_order_lines, int))
+      self.assertTrue(number_of_sale_order_lines > 0)
+
+      self.assertTrue(isinstance(number_of_additional_sale_packing_list_lines,
+                                 int))
+      self.assertTrue(number_of_additional_sale_packing_list_lines > 0)
+
+      self.assertTrue(measurable in (True, False))
+
+      self._init_dict = dict(number_of_sale_orders=number_of_sale_orders,
+              number_of_sale_order_lines=number_of_sale_order_lines,
+              number_of_additional_sale_packing_list_lines=number_of_additional_sale_packing_list_lines,
+              measurable=measurable)
+
+      sequence.addSequenceString(sequence_string)
+      sequence.play(self)
+
+    def stepInitialize(self, sequence=None, sequence_list=None, **kw):
+      sequence.edit(**self._init_dict)
+
+    def stepTic(self, sequence=None, sequence_list=None, **kw):
+      if sequence is None:
+        transaction.commit()
+
+      measurable = False
+      if sequence is not None:
+        target = sequence.get('measure_target')
+        if target is not None:
+          measurable = sequence.get('measurable')
+
+      # Reduce noise effect.
+      subprocess.call('sync')
+      gc.collect()
+
+      if measurable:
+        before_time = time()
+
+      self.tic()
+
+      if measurable:
+        after_time = time()
+        amount_of_time = after_time - before_time
+        min_time, max_time = self._getMinMaxTime()
+        print "%s took %.4f (%.4f < %.4f < %.4f)" \
+                % (target, amount_of_time, min_time, amount_of_time, max_time)
+        # Reset the target to make sure that the same target is not
+        # measured again.
+        sequence.edit(measure_target=None)
+        # Set the result.
+        result = sequence.get('result', [])
+        result.append((target, min_time, amount_of_time, max_time))
+
+    def stepMeasureCreationOfNewAppliedRules(self, sequence=None,
+            sequence_list=None, **kw):
+      sequence.set('measure_target', 'Creation Of New Applied Rules')
+
+    def stepMeasureExpansionOfExistingAppliedRules(self, sequence=None,
+            sequence_list=None, **kw):
+      sequence.set('measure_target', 'Expansion of Existing Applied Rules')
+
+    def stepMeasureCreationOfNewSalePackingLists(self, sequence=None,
+            sequence_list=None, **kw):
+      sequence.set('measure_target', 'Creation of New Sale Packing Lists')
+
+    def stepMeasureExpansionOfConvergedChanges(self, sequence=None,
+            sequence_list=None, **kw):
+      sequence.set('measure_target', 'Expansion of Converged Changes')
+
+    def stepMeasureExpansionOfDivergedChanges(self, sequence=None,
+            sequence_list=None, **kw):
+      sequence.set('measure_target', 'Expansion of Diverged Changes')
+
+    def stepMeasureAdoptionOfPrevisions(self, sequence=None,
+            sequence_list=None, **kw):
+      sequence.set('measure_target', 'Adoption of Previsions')
+
+    def stepMeasureAcceptanceOfDecisions(self, sequence=None,
+            sequence_list=None, **kw):
+      sequence.set('measure_target', 'Acceptance of Decisions')
+
+    def stepMeasureCreationOfNewAppliedRulesFromPartiallySimulatedDeliveries(self, sequence=None,
+            sequence_list=None, **kw):
+      sequence.set('measure_target',
+              'Creation of New Applied Rules from Partially Simulated Deliveries')
+
+    def stepCreationOfNewSaleInvoices(self, sequence=None,
+            sequence_list=None, **kw):
+      sequence.set('measure_target', 'Creation Of New Sale Invoices')
+
+    def stepAdditionOfNewInvoiceLinesIntoSaleInvoices(self, sequence=None,
+            sequence_list=None, **kw):
+      sequence.set('measure_target', 'Addition of New Invoices Lines')
+
+    def stepUseClientAsDestinationDecision(self, sequence=None,
+            sequence_list=None, **kw):
+      sequence.set('destination_decision', 'organisation_module/client')
+
+    def stepUseRecipient1AsDestinationDecision(self, sequence=None,
+            sequence_list=None, **kw):
+      sequence.set('destination_decision', 'organisation_module/recipient1')
+
+    def stepUseRecipient2AsDestinationDecision(self, sequence=None,
+            sequence_list=None, **kw):
+      sequence.set('destination_decision', 'organisation_module/recipient2')
+
+    def stepUseClientAsDestinationAdministration(self, sequence=None,
+            sequence_list=None, **kw):
+      sequence.set('destination_administration', 'organisation_module/client')
+
+    def stepUseRecipient1AsDestinationAdministration(self, sequence=None,
+            sequence_list=None, **kw):
+      sequence.set('destination_administration',
+              'organisation_module/recipient1')
+
+    def stepUseResourceAsCommodityProduct(self, sequence=None,
+            sequence_list=None, **kw):
+      sequence.set('resource', 'product_module/commodity')
+
+    def stepUseResourceAsLuxuryProduct(self, sequence=None,
+            sequence_list=None, **kw):
+      sequence.set('resource', 'product_module/luxury')
+
+    def stepCreateSaleOrders(self, sequence=None, sequence_list=None, **kw):
+      portal = self.getPortal()
+      module = portal.sale_order_module
+      base_date = DateTime('2010-08-08')
+      number_of_sale_orders = sequence.get('number_of_sale_orders')
+      number_of_sale_order_lines = sequence.get('number_of_sale_order_lines')
+      destination_decision = sequence.get('destination_decision')
+      destination_administration = sequence.get('destination_administration')
+      resource = sequence.get('resource')
+      for i in xrange(number_of_sale_orders):
+        start_date = base_date + i
+        stop_date = base_date + i + 1
+        order = module.newContent(
+                portal_type='Sale Order',
+                specialise='sale_trade_condition_module/test_stc',
+                destination_decision=destination_decision,
+                destination_administration=destination_administration,
+                start_date=start_date,
+                stop_date=stop_date)
+        # Set the rest through the trade condition.
+        order.SaleOrder_applySaleTradeCondition()
+        for j in xrange(number_of_sale_order_lines):
+          order.newContent(portal_type='Sale Order Line',
+                  resource=resource, quantity=1.0)
+
+    def stepModifySaleOrders(self, sequence=None, sequence_list=None, **kw):
+      portal = self.getPortal()
+      module = portal.sale_order_module
+      destination_decision = sequence.get('destination_decision')
+      destination_administration = sequence.get('destination_administration')
+      for order in module.contentValues(portal_type='Sale Order'):
+        order.edit(destination_decision=destination_decision,
+                destination_administration=destination_administration)
+        self.assertEquals(order.getDestinationDecision(), destination_decision)
+        self.assertEquals(order.getDestinationAdministration(),
+                destination_administration)
+
+    def stepPlanSaleOrders(self, sequence=None, sequence_list=None, **kw):
+      portal = self.getPortal()
+      module = portal.sale_order_module
+      for order in module.contentValues(portal_type='Sale Order'):
+        self.assertEquals(order.getSimulationState(), 'draft')
+        order.plan()
+        self.assertEquals(order.getSimulationState(), 'planned')
+
+    def stepOrderSaleOrders(self, sequence=None, sequence_list=None, **kw):
+      portal = self.getPortal()
+      module = portal.sale_order_module
+      for order in module.contentValues(portal_type='Sale Order'):
+        self.assertEquals(order.getSimulationState(), 'planned')
+        order.order()
+        self.assertEquals(order.getSimulationState(), 'ordered')
+
+    def stepConfirmSaleOrders(self, sequence=None, sequence_list=None, **kw):
+      portal = self.getPortal()
+      module = portal.sale_order_module
+      for order in module.contentValues(portal_type='Sale Order'):
+        self.assertEquals(order.getSimulationState(), 'ordered')
+        order.confirm()
+        self.assertEquals(order.getSimulationState(), 'confirm')
+
+    def stepCheckSaleOrderSimulation(self, sequence=None, sequence_list=None,
+            **kw):
+      portal = self.getPortal()
+      module = portal.sale_order_module
+      number_of_sale_orders = sequence.get('number_of_sale_orders')
+      number_of_sale_order_lines = sequence.get('number_of_sale_order_lines')
+      destination_decision = sequence.get('destination_decision')
+      destination_administration = sequence.get('destination_administration')
+      resource = sequence.get('resource')
+      self.assertEquals(module.objectCount(), number_of_sale_orders)
+      for order in module.contentValues(portal_type='Sale Order'):
+        causality_list = order.getCausalityRelatedValueList()
+        self.assertEquals(len(causality_list), 1)
+        applied_rule = causality_list[0]
+        self.assertEquals(applied_rule.getPortalType(), 'Applied Rule')
+        rule = applied_rule.getSpecialiseValue()
+        self.assertNotEquals(rule, None)
+        self.assertEquals(rule.getReference(),
+                'test_order_root_simulation_rule')
+        self.assertEquals(applied_rule.objectCount(),
+                number_of_sale_order_lines)
+        for simulation_movement in applied_rule.objectValues():
+          self.assertEquals(simulation_movement.getPortalType(),
+                  'Simulation Movement')
+          self.assertEquals(simulation_movement.getQuantity(), 1.0)
+          self.assertEquals(simulation_movement.getResource(), resource)
+          self.assertEquals(simulation_movement.getCausality(),
+                  'business_process_module/test_bp/order')
+          self.assertEquals(simulation_movement.getDestinationDecision(),
+                  destination_decision)
+          self.assertEquals(simulation_movement.getDestinationAdministration(),
+                  destination_administration)
+
+    def stepCheckBuiltSalePackingLists(self, sequence=None, sequence_list=None,
+            **kw):
+      portal = self.getPortal()
+      module = portal.sale_packing_list_module
+      number_of_sale_orders = sequence.get('number_of_sale_orders')
+      number_of_sale_order_lines = sequence.get('number_of_sale_order_lines')
+      destination_decision = sequence.get('destination_decision')
+      destination_administration = sequence.get('destination_administration')
+      resource = sequence.get('resource')
+      self.assertEquals(module.objectCount(), number_of_sale_orders)
+      for packing_list in module.contentValues():
+        self.assertEquals(packing_list.getSimulationState(), 'confirmed')
+        self.assertEquals(packing_list.getCausalityState(), 'solved')
+        self.assertEquals(packing_list.getDestinationDecision(),
+                destination_decision)
+        self.assertEquals(packing_list.getDestinationAdministration(),
+                destination_administration)
+        self.assertEquals(packing_list.objectCount(), 1)
+        for line in packing_list.objectValues():
+          self.assertEquals(line.getResource(), resource)
+          self.assertAlmostEquals(line.getQuantity(),
+                  1.0 * number_of_sale_order_lines)
+          simulation_movement_list = line.getDeliveryRelatedValueList()
+          self.assertEquals(len(simulation_movement_list), 1)
+          simulation_movement = simulation_movement_list[0]
+          self.assertEquals(simulation_movement.getPortalType(),
+                  'Simulation Movement')
+          self.assertEquals(simulation_movement.getCausality(),
+                  'business_process_module/test_bp/shipping')
+
+    def stepCheckBuiltSaleInvoices(self, sequence=None, sequence_list=None,
+            **kw):
+      portal = self.getPortal()
+      module = portal.sale_invoice_module
+      self.assertEquals(module.objectCount(), 1)
+      number_of_sale_orders = sequence.get('number_of_sale_orders')
+      number_of_sale_order_lines = sequence.get('number_of_sale_order_lines')
+      number_of_additional_sale_packing_list_lines \
+              = sequence.get('number_of_additional_sale_packing_list_lines')
+      for invoice in module.contentValues():
+        self.assertEquals(invoice.getSimulationState(), 'confirmed')
+        self.assertEquals(invoice.getCausalityState(), 'solved')
+        self.assertEquals(invoice.objectCount(), 2)
+        invoice_line_list = invoice.objectValues(sort_on='resource')
+
+        commodity_invoice_line = invoice_line_list[0]
+        self.assertEquals(commodity_invoice_line.getResource(),
+                'product_module/commodity')
+        self.assertAlmostEquals(commodity_invoice_line.getQuantity(),
+                1.0 * (number_of_sale_order_lines \
+                        + number_of_additional_sale_packing_list_lines) \
+                        * number_of_sale_orders)
+        simulation_movement_list \
+                = commodity_invoice_line.getDeliveryRelatedValueList()
+        self.assertEquals(len(simulation_movement_list),
+                (number_of_sale_order_lines \
+                        + number_of_additional_sale_packing_list_lines) \
+                        * number_of_sale_orders)
+        for simulation_movement in simulation_movement_list:
+          self.assertEquals(simulation_movement.getPortalType(),
+                  'Simulation Movement')
+          self.assertEquals(simulation_movement.getCausality(),
+                  'business_process_module/test_bp/shipping')
+
+        tax_invoice_line = invoice_line_list[1]
+        self.assertEquals(tax_invoice_line.getResource(),
+                'service_module/vat_low')
+        self.assertAlmostEquals(tax_invoice_line.getQuantity(),
+                1.0 * (number_of_sale_order_lines \
+                        + number_of_additional_sale_packing_list_lines) \
+                        * number_of_sale_orders)
+        simulation_movement_list \
+                = tax_invoice_line.getDeliveryRelatedValueList()
+        self.assertEquals(len(simulation_movement_list),
+                (number_of_sale_order_lines \
+                        + number_of_additional_sale_packing_list_lines) \
+                        * number_of_sale_orders)
+        for simulation_movement in simulation_movement_list:
+          self.assertEquals(simulation_movement.getPortalType(),
+                  'Simulation Movement')
+          self.assertEquals(simulation_movement.getCausality(),
+                  'business_process_module/test_bp/tax')
+
+    def stepCheckAddedSaleInvoices(self, sequence=None, sequence_list=None,
+            **kw):
+      portal = self.getPortal()
+      module = portal.sale_invoice_module
+      self.assertEquals(module.objectCount(), 1)
+      number_of_sale_orders = sequence.get('number_of_sale_orders')
+      number_of_sale_order_lines = sequence.get('number_of_sale_order_lines')
+      number_of_additional_sale_packing_list_lines \
+              = sequence.get('number_of_additional_sale_packing_list_lines')
+      for invoice in module.contentValues():
+        self.assertEquals(invoice.getSimulationState(), 'confirmed')
+        self.assertEquals(invoice.getCausalityState(), 'solved')
+        self.assertEquals(invoice.objectCount(), 4)
+        invoice_line_list = invoice.objectValues(sort_on='resource')
+
+        commodity_invoice_line = invoice_line_list[0]
+        self.assertEquals(commodity_invoice_line.getResource(),
+                'product_module/commodity')
+        self.assertAlmostEquals(commodity_invoice_line.getQuantity(),
+                1.0 * (number_of_sale_order_lines \
+                        + number_of_additional_sale_packing_list_lines) \
+                        * number_of_sale_orders)
+        simulation_movement_list \
+                = commodity_invoice_line.getDeliveryRelatedValueList()
+        self.assertEquals(len(simulation_movement_list),
+                (number_of_sale_order_lines \
+                        + number_of_additional_sale_packing_list_lines) \
+                        * number_of_sale_orders)
+        for simulation_movement in simulation_movement_list:
+          self.assertEquals(simulation_movement.getPortalType(),
+                  'Simulation Movement')
+          self.assertEquals(simulation_movement.getCausality(),
+                  'business_process_module/test_bp/shipping')
+
+        luxury_invoice_line = invoice_line_list[1]
+        self.assertEquals(luxury_invoice_line.getResource(),
+                'product_module/luxury')
+        self.assertAlmostEquals(luxury_invoice_line.getQuantity(),
+                1.0 * number_of_additional_sale_packing_list_lines \
+                        * number_of_sale_orders)
+        simulation_movement_list \
+                = luxury_invoice_line.getDeliveryRelatedValueList()
+        self.assertEquals(len(simulation_movement_list),
+                number_of_additional_sale_packing_list_lines \
+                        * number_of_sale_orders)
+        for simulation_movement in simulation_movement_list:
+          self.assertEquals(simulation_movement.getPortalType(),
+                  'Simulation Movement')
+          self.assertEquals(simulation_movement.getCausality(),
+                  'business_process_module/test_bp/shipping')
+
+        high_tax_invoice_line = invoice_line_list[2]
+        self.assertEquals(high_tax_invoice_line.getResource(),
+                'service_module/vat_high')
+        self.assertAlmostEquals(high_tax_invoice_line.getQuantity(),
+                1.0 * number_of_additional_sale_packing_list_lines \
+                        * number_of_sale_orders)
+        simulation_movement_list \
+                = high_tax_invoice_line.getDeliveryRelatedValueList()
+        self.assertEquals(len(simulation_movement_list),
+                number_of_additional_sale_packing_list_lines \
+                        * number_of_sale_orders)
+        for simulation_movement in simulation_movement_list:
+          self.assertEquals(simulation_movement.getPortalType(),
+                  'Simulation Movement')
+          self.assertEquals(simulation_movement.getCausality(),
+                  'business_process_module/test_bp/tax')
+
+        low_tax_invoice_line = invoice_line_list[3]
+        self.assertEquals(low_tax_invoice_line.getResource(),
+                'service_module/vat_low')
+        self.assertAlmostEquals(low_tax_invoice_line.getQuantity(),
+                1.0 * (number_of_sale_order_lines \
+                        + number_of_additional_sale_packing_list_lines) \
+                        * number_of_sale_orders)
+        simulation_movement_list \
+                = low_tax_invoice_line.getDeliveryRelatedValueList()
+        self.assertEquals(len(simulation_movement_list),
+                (number_of_sale_order_lines \
+                        + number_of_additional_sale_packing_list_lines) \
+                        * number_of_sale_orders)
+        for simulation_movement in simulation_movement_list:
+          self.assertEquals(simulation_movement.getPortalType(),
+                  'Simulation Movement')
+          self.assertEquals(simulation_movement.getCausality(),
+                  'business_process_module/test_bp/tax')
+
+    def stepCheckConvergedSalePackingLists(self, sequence=None,
+            sequence_list=None, **kw):
+      portal = self.getPortal()
+      module = portal.sale_packing_list_module
+      for packing_list in module.contentValues():
+        self.assertEquals(module.getCausalityState(), 'solved')
+
+    def stepCheckDivergedSalePackingLists(self, sequence=None,
+            sequence_list=None, **kw):
+      portal = self.getPortal()
+      module = portal.sale_packing_list_module
+      for packing_list in module.contentValues():
+        self.assertEquals(module.getCausalityState(), 'diverged')
+
+    def stepCheckSalePackingListSimulation(self, sequence=None,
+            sequence_list=None, **kw):
+      portal = self.getPortal()
+      module = portal.sale_packing_list_module
+      number_of_additional_sale_packing_list_lines \
+              = sequence.get('number_of_additional_sale_packing_list_lines')
+      destination_decision = sequence.get('destination_decision')
+      destination_administration = sequence.get('destination_administration')
+      for packing_list in module.contentValues(portal_type='Sale Packing List'):
+        self.assertEquals(packing_list.getCausalityState(), 'solved')
+        causality_list = packing_list.getCausalityRelatedValueList()
+        self.assertEquals(len(causality_list), 1)
+        applied_rule = causality_list[0]
+        self.assertEquals(applied_rule.getPortalType(), 'Applied Rule')
+        rule = applied_rule.getSpecialiseValue()
+        self.assertNotEquals(rule, None)
+        self.assertEquals(rule.getReference(),
+                'test_packing_list_root_simulation_rule')
+        self.assertEquals(applied_rule.objectCount(),
+                number_of_additional_sale_packing_list_lines)
+        for simulation_movement in applied_rule.objectValues():
+          self.assertEquals(simulation_movement.getPortalType(),
+                  'Simulation Movement')
+          delivery_list = simulation_movement.getDeliveryValueList()
+          self.assertEquals(len(delivery_list), 1)
+          delivery = delivery_list[0]
+          self.assertEquals(delivery.getPortalType(), 'Sale Packing List Line')
+          self.assertEquals(simulation_movement.getQuantity(),
+                  delivery.getQuantity())
+          self.assertEquals(simulation_movement.getResource(),
+                  delivery.getResource())
+          self.assertEquals(simulation_movement.getCausality(),
+                  'business_process_module/test_bp/shipping')
+          self.assertEquals(simulation_movement.getDestinationDecision(),
+                  destination_decision)
+          self.assertEquals(simulation_movement.getDestinationAdministration(),
+                  destination_administration)
+
+    def stepSetSolverTargetToDestinationAdministration(self, sequence=None,
+            sequence_list=None, **kw):
+      sequence.set('solver_target', 'destination_administration')
+
+    def stepAdoptPrevisionToSalePackingLists(self, sequence=None,
+            sequence_list=None, **kw):
+      solver_target = sequence.get('solver_target')
+      self.assertTrue(isinstance(solver_target, str))
+      portal = self.getPortal()
+      module = portal.sale_packing_list_module
+      for packing_list in module.contentValues(portal_type='Sale Packing List'):
+        self.assertEquals(packing_list.getCausalityState(), 'diverged')
+        packing_list.activate() \
+                .SalePackingList_adoptPrevisionForTesting(solver_target)
+      # Make sure that the same taget is not used again.
+      sequence.edit(solver_target=None)
+
+    def stepAcceptDecisionOfSalePackingLists(self, sequence=None,
+            sequence_list=None, **kw):
+      solver_target = sequence.get('solver_target')
+      self.assertTrue(isinstance(solver_target, str))
+      portal = self.getPortal()
+      module = portal.sale_packing_list_module
+      for packing_list in module.contentValues(portal_type='Sale Packing List'):
+        self.assertEquals(packing_list.getCausalityState(), 'diverged')
+        packing_list.activate() \
+                .SalePackingList_acceptDecisionForTesting(solver_target)
+      # Make sure that the same taget is not used again.
+      sequence.edit(solver_target=None)
+
+    def stepAddNewSalePackingLinesIntoSalePackingLists(self, sequence=None,
+            sequence_list=None, **kw):
+      portal = self.getPortal()
+      module = portal.sale_packing_list_module
+      number = sequence.get('number_of_additional_sale_packing_list_lines')
+      resource = sequence.get('resource')
+      for packing_list in module.contentValues(portal_type='Sale Packing List'):
+        for i in xrange(number):
+          packing_list.newContent(portal_type='Sale Packing List Line',
+                  resource=resource, quantity=1.0)
+
+    def stepStopSalePackingLists(self, sequence=None, sequence_list=None, **kw):
+      portal = self.getPortal()
+      module = portal.sale_packing_list_module
+      for packing_list in module.contentValues(portal_type='Sale Packing List'):
+        self.assertEquals(packing_list.getSimulationState(), 'confirmed')
+        packing_list.stop()
+        self.assertEquals(packing_list.getSimulationState(), 'stopped')
+
+    def stepDeliverSalePackingLists(self, sequence=None, sequence_list=None,
+            **kw):
+      portal = self.getPortal()
+      module = portal.sale_packing_list_module
+      for packing_list in module.contentValues(portal_type='Sale Packing List'):
+        self.assertEquals(packing_list.getSimulationState(), 'stopped')
+        packing_list.stop()
+        self.assertEquals(packing_list.getSimulationState(), 'delivered')
+
+    def stepTestResult(self, sequence=None, sequence_list=None, **kw):
+      measurable = sequence.get('measurable')
+      if measurable:
+        result = sequence.get('result')
+        if result:
+          for target, min_time, real_time, max_time in result:
+            self.assertTrue(min_time < real_time < max_time,
+                    '%s: %.4f < %.4f < %.4f' \
+                            % (target, min_time, real_time, max_time))
+
+    def test_01_CheckLogicAndMakeCacheHot(self):
+      """Check the correctness of the logic as well as making the cache hot.
+      """
+      message = 'Check Logic'
+      LOG('Testing... ', 0, message)
+      self._checkSimulation(number_of_sale_orders=1,
+              number_of_sale_order_lines=1,
+              number_of_additional_sale_packing_list_lines=1,
+              measurable=False)
+
+    def test_02_CheckPerformance(self):
+      """Check the performance.
+      """
+      message = 'Check Performance'
+      LOG('Testing... ', 0, message)
+      self._checkSimulation(number_of_sale_orders=10,
+              number_of_sale_order_lines=10,
+              number_of_additional_sale_packing_list_lines=10,
+              measurable=True)
+
+def test_suite():
+  suite = unittest.TestSuite()
+  suite.addTest(unittest.makeSuite(TestSimulationPerformance))
+  return suite