From cb88904763b33ce8b0e0b12a8b2956ca34052b95 Mon Sep 17 00:00:00 2001 From: Sebastien Robin <seb@nexedi.com> Date: Mon, 19 Sep 2016 20:26:11 +0000 Subject: [PATCH] performance: add performance testing for workflows and improve testPerformance for reusability - use mixin class to allow reusing common performance functions - remove usage of quiet and run_all_test --- .../test.erp5.testWorkflowPerformance.py | 88 +++++++++++++ .../test.erp5.testWorkflowPerformance.xml | 123 ++++++++++++++++++ bt5/erp5_performance_test/bt/dependency_list | 1 + .../bt/template_format_version | 1 + .../bt/template_test_id_list | 1 + bt5/erp5_performance_test/bt/title | 1 + product/ERP5Type/tests/testPerformance.py | 108 ++++++--------- 7 files changed, 257 insertions(+), 66 deletions(-) create mode 100644 bt5/erp5_performance_test/TestTemplateItem/portal_components/test.erp5.testWorkflowPerformance.py create mode 100644 bt5/erp5_performance_test/TestTemplateItem/portal_components/test.erp5.testWorkflowPerformance.xml create mode 100644 bt5/erp5_performance_test/bt/dependency_list create mode 100644 bt5/erp5_performance_test/bt/template_format_version create mode 100644 bt5/erp5_performance_test/bt/template_test_id_list create mode 100644 bt5/erp5_performance_test/bt/title diff --git a/bt5/erp5_performance_test/TestTemplateItem/portal_components/test.erp5.testWorkflowPerformance.py b/bt5/erp5_performance_test/TestTemplateItem/portal_components/test.erp5.testWorkflowPerformance.py new file mode 100644 index 0000000000..dad92dd682 --- /dev/null +++ b/bt5/erp5_performance_test/TestTemplateItem/portal_components/test.erp5.testWorkflowPerformance.py @@ -0,0 +1,88 @@ +############################################################################## +# +# Copyright (c) 2002-2016 Nexedi SA and Contributors. All Rights Reserved. +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsibility 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 +# guarantees 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +############################################################################## + +from time import time + +from Products.ERP5Type.tests.testPerformance import TestPerformanceMixin +from Products.DCWorkflow.DCWorkflow import ValidationFailed +from Testing import ZopeTestCase + +class TestWorkflowPerformance(TestPerformanceMixin): + + maxDiff = None + + def getTitle(self): + return "Workflow Performance" + + def afterSetUp(self): + super(TestWorkflowPerformance, self).afterSetUp() + self.foo_module.manage_delObjects(list(self.foo_module.objectIds())) + + def testWorkflowActionAndGetState(self): + + foo_list = [] + foo_list_append = foo_list.append + range_10 = range(10) + portal_workflow = self.portal.portal_workflow + foo_count = 100 + + for x in xrange(foo_count): + foo = self.foo_module.newContent() + foo_list_append(foo) + + self.assertEqual('draft', foo_list[0].getSimulationState()) + + start = time() + for foo in foo_list: + foo.getSimulationState() + action_list = portal_workflow.listActions(object=foo) + for x in range_10: + try: + portal_workflow.doActionFor(foo, 'dummy_failing_action') + except ValidationFailed: + pass + portal_workflow.doActionFor(foo, 'dummy_action') + portal_workflow.doActionFor(foo, 'validate_action') + foo.getSimulationState() + + end = time() + + message = "\n%s took %.4gs (%s foo(s))" % (self._testMethodName, + end - start, foo_count) + print message + ZopeTestCase._print(message) + + # some checking to make sure we tested something relevant + self.assertEqual('validated', foo.getSimulationState()) + expected_action_id_list = ['custom_action_no_dialog', 'custom_dialog_action', + 'display_status_action', 'dummy_action', + 'dummy_failing_action', 'validate_action'] + expected_action_id_list.sort() + found_action_id_list = [x['id'] for x in action_list if x['category'] == 'workflow'] + found_action_id_list.sort() + self.assertEqual(expected_action_id_list, found_action_id_list) + self.assertEqual(23, len(foo.Base_getWorkflowHistoryItemList('foo_workflow', display=0))) diff --git a/bt5/erp5_performance_test/TestTemplateItem/portal_components/test.erp5.testWorkflowPerformance.xml b/bt5/erp5_performance_test/TestTemplateItem/portal_components/test.erp5.testWorkflowPerformance.xml new file mode 100644 index 0000000000..b1bd315e6e --- /dev/null +++ b/bt5/erp5_performance_test/TestTemplateItem/portal_components/test.erp5.testWorkflowPerformance.xml @@ -0,0 +1,123 @@ +<?xml version="1.0"?> +<ZopeData> + <record id="1" aka="AAAAAAAAAAE="> + <pickle> + <global name="Test Component" module="erp5.portal_type"/> + </pickle> + <pickle> + <dictionary> + <item> + <key> <string>_recorded_property_dict</string> </key> + <value> + <persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent> + </value> + </item> + <item> + <key> <string>default_reference</string> </key> + <value> <string>testWorkflowPerformance</string> </value> + </item> + <item> + <key> <string>description</string> </key> + <value> + <none/> + </value> + </item> + <item> + <key> <string>id</string> </key> + <value> <string>test.erp5.testWorkflowPerformance</string> </value> + </item> + <item> + <key> <string>portal_type</string> </key> + <value> <string>Test Component</string> </value> + </item> + <item> + <key> <string>sid</string> </key> + <value> + <none/> + </value> + </item> + <item> + <key> <string>text_content_error_message</string> </key> + <value> + <tuple/> + </value> + </item> + <item> + <key> <string>text_content_warning_message</string> </key> + <value> + <tuple/> + </value> + </item> + <item> + <key> <string>version</string> </key> + <value> <string>erp5</string> </value> + </item> + <item> + <key> <string>workflow_history</string> </key> + <value> + <persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent> + </value> + </item> + </dictionary> + </pickle> + </record> + <record id="2" aka="AAAAAAAAAAI="> + <pickle> + <global name="PersistentMapping" module="Persistence.mapping"/> + </pickle> + <pickle> + <dictionary> + <item> + <key> <string>data</string> </key> + <value> + <dictionary/> + </value> + </item> + </dictionary> + </pickle> + </record> + <record id="3" aka="AAAAAAAAAAM="> + <pickle> + <global name="PersistentMapping" module="Persistence.mapping"/> + </pickle> + <pickle> + <dictionary> + <item> + <key> <string>data</string> </key> + <value> + <dictionary> + <item> + <key> <string>component_validation_workflow</string> </key> + <value> + <persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent> + </value> + </item> + </dictionary> + </value> + </item> + </dictionary> + </pickle> + </record> + <record id="4" aka="AAAAAAAAAAQ="> + <pickle> + <global name="WorkflowHistoryList" module="Products.ERP5Type.patches.WorkflowTool"/> + </pickle> + <pickle> + <tuple> + <none/> + <list> + <dictionary> + <item> + <key> <string>action</string> </key> + <value> <string>validate</string> </value> + </item> + <item> + <key> <string>validation_state</string> </key> + <value> <string>validated</string> </value> + </item> + </dictionary> + </list> + </tuple> + </pickle> + </record> +</ZopeData> diff --git a/bt5/erp5_performance_test/bt/dependency_list b/bt5/erp5_performance_test/bt/dependency_list new file mode 100644 index 0000000000..65d338641c --- /dev/null +++ b/bt5/erp5_performance_test/bt/dependency_list @@ -0,0 +1 @@ +erp5_ui_test \ No newline at end of file diff --git a/bt5/erp5_performance_test/bt/template_format_version b/bt5/erp5_performance_test/bt/template_format_version new file mode 100644 index 0000000000..56a6051ca2 --- /dev/null +++ b/bt5/erp5_performance_test/bt/template_format_version @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/bt5/erp5_performance_test/bt/template_test_id_list b/bt5/erp5_performance_test/bt/template_test_id_list new file mode 100644 index 0000000000..839b14f195 --- /dev/null +++ b/bt5/erp5_performance_test/bt/template_test_id_list @@ -0,0 +1 @@ +test.erp5.testWorkflowPerformance \ No newline at end of file diff --git a/bt5/erp5_performance_test/bt/title b/bt5/erp5_performance_test/bt/title new file mode 100644 index 0000000000..3402c75c89 --- /dev/null +++ b/bt5/erp5_performance_test/bt/title @@ -0,0 +1 @@ +erp5_performance_test \ No newline at end of file diff --git a/product/ERP5Type/tests/testPerformance.py b/product/ERP5Type/tests/testPerformance.py index ec0700262a..d729b09ae9 100644 --- a/product/ERP5Type/tests/testPerformance.py +++ b/product/ERP5Type/tests/testPerformance.py @@ -113,14 +113,7 @@ DO_TEST = 1 # set 1 to get profiler's result (unit_test/tests/<func_name>) PROFILE=0 -class TestPerformance(ERP5TypeTestCase, LogInterceptor): - - # Some helper methods - quiet = 0 - run_all_test = 1 - - def getTitle(self): - return "Performance" +class TestPerformanceMixin(ERP5TypeTestCase, LogInterceptor): def getBusinessTemplateList(self): """ @@ -129,12 +122,6 @@ class TestPerformance(ERP5TypeTestCase, LogInterceptor): return ('erp5_base', 'erp5_ui_test',) - def getBarModule(self): - """ - Return the bar module - """ - return self.portal['bar_module'] - def afterSetUp(self): """ Executed before each test_*. @@ -154,10 +141,35 @@ class TestPerformance(ERP5TypeTestCase, LogInterceptor): self.bar_module = self.getBarModule() self.foo_module = self.portal.foo_module + def getBarModule(self): + """ + Return the bar module + """ + return self.portal['bar_module'] + + def profile(self, func, suffix=''): + from cProfile import Profile + prof_file = '%s%s' % (func.__name__, suffix) + try: + os.unlink(prof_file) + except OSError: + pass + prof = Profile() + prof.runcall(func) + prof.dump_stats(prof_file) + def beforeTearDown(self): # Re-enable gc at teardown. gc.enable() self.abort() + +class TestPerformance(TestPerformanceMixin): + + def getTitle(self): + return "Performance" + + def beforeTearDown(self): + super(TestPerformance, self).beforeTearDown() self.bar_module.manage_delObjects(list(self.bar_module.objectIds())) self.foo_module.manage_delObjects(list(self.foo_module.objectIds())) gender = self.portal.portal_categories['gender'] @@ -165,7 +177,7 @@ class TestPerformance(ERP5TypeTestCase, LogInterceptor): gender = self.portal.portal_caches.clearAllCache() self.tic() - def checkViewBarObject(self, min, max, quiet=quiet, prefix=None): + def checkViewBarObject(self, min, max, prefix=None): # Some init to display form with some value if prefix is None: prefix = '' @@ -193,8 +205,7 @@ class TestPerformance(ERP5TypeTestCase, LogInterceptor): bar.Bar_viewPerformance() after_view = time() req_time = (after_view - before_view)/100. - if not quiet: - print "%s time to view object form %.4f < %.4f < %.4f\n" % \ + print "%s time to view object form %.4f < %.4f < %.4f\n" % \ (prefix, min, req_time, max) if PROFILE: self.profile(bar.Bar_viewPerformance) @@ -202,48 +213,21 @@ class TestPerformance(ERP5TypeTestCase, LogInterceptor): self.assertTrue(min < req_time < max, '%.4f < %.4f < %.4f' % (min, req_time, max)) - def profile(self, func, suffix=''): - from cProfile import Profile - prof_file = '%s%s' % (func.__name__, suffix) - try: - os.unlink(prof_file) - except OSError: - pass - prof = Profile() - prof.runcall(func) - prof.dump_stats(prof_file) - - def test_00_viewBarObject(self, quiet=quiet, run=run_all_test, - min=None, max=None): + def test_00_viewBarObject(self, min=None, max=None): """ Estimate average time to render object view """ - if not run : return - if not quiet: - message = 'Test form to view Bar object' - LOG('Testing... ', 0, message) + message = 'Test form to view Bar object' + LOG('Testing... ', 0, message) self.checkViewBarObject(MIN_OBJECT_VIEW, MAX_OBJECT_VIEW, prefix='objective') -# def test_00b_currentViewBarObject(self, quiet=quiet, run=run_all_test): -# """ -# Estimate average time to render object view and check with current values -# """ -# if not run : return -# if not quiet: -# message = 'Test form to view Bar object with current values' -# LOG('Testing... ', 0, message) -# self.checkViewBarObject(CURRENT_MIN_OBJECT_VIEW, CURRENT_MAX_OBJECT_VIEW, -# prefix='current') - - def test_01_viewBarModule(self, quiet=quiet, run=run_all_test): + def test_01_viewBarModule(self): """ Estimate average time to render module view """ - if not run : return - if not quiet: - message = 'Test form to view Bar module' - LOG('Testing... ', 0, message) + message = 'Test form to view Bar module' + LOG('Testing... ', 0, message) self.tic() view_result = {} tic_result = {} @@ -293,11 +277,10 @@ class TestPerformance(ERP5TypeTestCase, LogInterceptor): add_value = add_result[key] min_view = MIN_MODULE_VIEW + LISTBOX_COEF * i max_view = MAX_MODULE_VIEW + LISTBOX_COEF * i - if not quiet: - print "nb objects = %s\n\tadd = %.4f < %.4f < %.4f" %(key, MIN_OBJECT_CREATION, add_value, MAX_OBJECT_CREATION) - print "\ttic = %.4f < %.4f < %.4f" %(MIN_TIC, tic_value, MAX_TIC) - print "\tview = %.4f < %.4f < %.4f" %(min_view, module_value, max_view) - print + print "nb objects = %s\n\tadd = %.4f < %.4f < %.4f" %(key, MIN_OBJECT_CREATION, add_value, MAX_OBJECT_CREATION) + print "\ttic = %.4f < %.4f < %.4f" %(MIN_TIC, tic_value, MAX_TIC) + print "\tview = %.4f < %.4f < %.4f" %(min_view, module_value, max_view) + print i += 1 # then check results if DO_TEST: @@ -321,7 +304,7 @@ class TestPerformance(ERP5TypeTestCase, LogInterceptor): i += 1 - def test_viewProxyField(self, quiet=quiet): + def test_viewProxyField(self): # render a form with proxy fields: Foo_viewProxyField foo = self.foo_module.newContent( portal_type='Foo', @@ -341,8 +324,7 @@ class TestPerformance(ERP5TypeTestCase, LogInterceptor): after_view = time() req_time = (after_view - before_view)/100. - if not quiet: - print "time to view proxyfield form %.4f < %.4f < %.4f\n" % \ + print "time to view proxyfield form %.4f < %.4f < %.4f\n" % \ ( MIN_OBJECT_PROXYFIELD_VIEW, req_time, MAX_OBJECT_PROXYFIELD_VIEW ) @@ -356,7 +338,7 @@ class TestPerformance(ERP5TypeTestCase, LogInterceptor): req_time, MAX_OBJECT_PROXYFIELD_VIEW)) - def test_02_viewFooObjectWithManyLines(self, quiet=quiet): + def test_02_viewFooObjectWithManyLines(self): """ Estimate average time to render object view with many lines """ @@ -373,8 +355,7 @@ class TestPerformance(ERP5TypeTestCase, LogInterceptor): after_view = time() req_time = (after_view - before_view)/100. - if not quiet: - print "time to view object form with many lines %.4f < %.4f < %.4f\n" % \ + print "time to view object form with many lines %.4f < %.4f < %.4f\n" % \ ( MIN_OBJECT_MANY_LINES_VIEW, req_time, MAX_OBJECT_MANY_LINES_VIEW ) @@ -387,8 +368,3 @@ class TestPerformance(ERP5TypeTestCase, LogInterceptor): MIN_OBJECT_MANY_LINES_VIEW, req_time, MAX_OBJECT_MANY_LINES_VIEW)) - -def test_suite(): - suite = unittest.TestSuite() - suite.addTest(unittest.makeSuite(TestPerformance)) - return suite -- 2.30.9