[Erp5-dev] [Erp5-report] r28043 - /erp5/trunk/products/ERP5Type/tests/

Julien Muchembled jm at nexedi.com
Thu Jul 9 14:38:57 CEST 2009


Hello,


Commit 28043 improves the way --load and --save are working so that they can be combined.
A saved unit test instance is composed of 3 parts:
* a Data.fs
* a MySQL dump
* static files (Constraint.bak, Document.bak, Extensions.bak, PropertySheet.bak)

Now:
* When --save is not set, the above files are not touched. Otherwise, everything is saved to these files at the end (-> persistent mode).
* When --load is not set, the above files are ignored and a new instance is created. Otherwise, the instance is restored from them:
  - Data.fs is opened in RO (DemoStorage) or RW mode depending on the presence of --save.
  - MySQL dump is loaded (if missing, the site is automatically reindexed)
  - static files are copied to normal folders (= without .bak)

To keep backward compatibility, no test is run if --save if used without --load.

So a new way to run unit test is possible: using both --load and --save.
It runs the unit test on an existing instance (--load) but will save everything again at the end. This is mostly useful for 2 things:
* Upgrade business templates of an existing instance rather than recreating everything:
  ex: --load --save --update_only=erp5_mrp --run_only=dummy
* Build a normal instance from unit tests, with useful data:
  ex: 1. --save
      2. --load --save testFoo.py
      3. --load --save testBar.py
      4. the instance contains data created by testFoo.py and testBar.py

I doubt someone already used erp5_force_data_fs because it was almost unusable. I removed it. Instead, use --load and --save at the same time.

WARNING:
Before this commit, --data_fs_path was ignored if --load or -save was used.
It isn't anymore and in particular, --save will modify the Data.fs specified by --data_fs_path


The commit also fixes the order in which products and static files are loaded. This removes hacks over hacks that didn't work well anyway.


2 very small other improvements:
* A ZServer is started automatically at the beginning of each test. The port is random, and printed.
* DeadlockDebugger is loaded if present.


Julien

nobody at svn.erp5.org a écrit :
> Author: jm
> Date: Thu Jul  9 14:32:09 2009
> New Revision: 28043
> 
> URL: http://svn.erp5.org?rev=28043&view=rev
> Log:
> Unit tests:
> * Slightly change purpose of some options, so that --load and --save can be combined:
>   * --load reuses an existing unit test instance.
>   * --save runs in persistent mode. Tests are skipped if --load is unset.
> * --data_fs_path just allows to specify a Data.fs path other than the default one. Before this commit, the option was used without --load or --save. To get the previous behaviour, it must be used with --load. Note that it is now possible to use it with --save, what overwrites or modifies it.
> * if --load is used and MySQL dump is missing, --recreate_catalog is automatically set.
> * Drop now useless 'erp5_force_data_fs' environment variable.
> * Import Products and static files in the correct order (= same as a normal instance).
> * Enable DeadlockDebugger and start ZServer.
> 
> Modified:
>     erp5/trunk/products/ERP5Type/tests/ERP5TypeTestCase.py
>     erp5/trunk/products/ERP5Type/tests/custom_zodb.py
>     erp5/trunk/products/ERP5Type/tests/runFunctionalTest.py
>     erp5/trunk/products/ERP5Type/tests/runUnitTest.py
> 
> Modified: erp5/trunk/products/ERP5Type/tests/ERP5TypeTestCase.py
> URL: http://svn.erp5.org/erp5/trunk/products/ERP5Type/tests/ERP5TypeTestCase.py?rev=28043&r1=28042&r2=28043&view=diff
> ==============================================================================
> --- erp5/trunk/products/ERP5Type/tests/ERP5TypeTestCase.py [utf8] (original)
> +++ erp5/trunk/products/ERP5Type/tests/ERP5TypeTestCase.py [utf8] Thu Jul  9 14:32:09 2009
> @@ -34,17 +34,7 @@
>  import transaction
>  from Testing import ZopeTestCase
>  from Testing.ZopeTestCase.PortalTestCase import PortalTestCase, user_name
> -from Products.ERP5Type.tests.utils import getMySQLArguments
>  from Products.CMFCore.utils import getToolByName
> -from Products.ERP5Type.Utils import getLocalPropertySheetList, \
> -                                    removeLocalPropertySheet, \
> -                                    importLocalPropertySheet
> -from Products.ERP5Type.Utils import getLocalDocumentList, \
> -                                    removeLocalDocument, \
> -                                    importLocalDocument
> -from Products.ERP5Type.Utils import getLocalConstraintList, \
> -                                    removeLocalConstraint, \
> -                                    importLocalConstraint
>  from Products.DCWorkflow.DCWorkflow import ValidationFailed
>  from Products.ERP5Type.Base import _aq_reset
>  from zLOG import LOG, DEBUG
> @@ -53,6 +43,9 @@
>  install_product_quiet = 1
>  # Quiet messages when installing business templates
>  install_bt5_quiet = 0
> +
> +import OFS.Application
> +OFS.Application.import_products()
>  
>  # Std Zope Products
>  ZopeTestCase.installProduct('ExtFile', quiet=install_product_quiet)
> @@ -161,10 +154,6 @@
>       or os.path.isdir(os.path.join(product_dir, product_name, 'Constraint')) \
>       or os.path.isdir(os.path.join(product_dir, product_name, 'Tool')):
>      ZopeTestCase.installProduct(product_name, quiet=install_product_quiet)
> -
> -# Install Document types (circumvent different init order in ZopeTestCase)
> -from Products.ERP5Type.InitGenerator import initializeProductDocumentRegistry
> -initializeProductDocumentRegistry()
>  
>  from AccessControl.SecurityManagement import newSecurityManager, noSecurityManager
>  
> @@ -226,7 +215,8 @@
>      """
>  
>      def dummy_test(self):
> -      ZopeTestCase._print('All tests are skipped with --save option.')
> +      ZopeTestCase._print('All tests are skipped when --save option is passed '
> +                          'with --update_business_templates or without --load')
>  
>      def getRevision(self):
>        try:
> @@ -328,7 +318,7 @@
>        erp5_catalog_storage = os.environ.get('erp5_catalog_storage',
>                                              'erp5_mysql_innodb_catalog')
>        update_business_templates = os.environ.get('update_business_templates') is not None
> -      erp5_load_data_fs = os.environ.get('erp5_load_data_fs') is not None
> +      erp5_load_data_fs = int(os.environ.get('erp5_load_data_fs', 0))
>        if update_business_templates and erp5_load_data_fs:
>          update_only = os.environ.get('update_only', None)
>          template_list = (erp5_catalog_storage, 'erp5_core', 'erp5_xhtml_style') + tuple(template_list)
> @@ -393,7 +383,6 @@
>        global current_app
>        current_app = self.app
>        self._updateConnectionStrings()
> -      self._recreateCatalog()
>  
>      def afterSetUp(self):
>        '''Called after setUp() has completed. This is
> @@ -431,13 +420,12 @@
>        """Clear activities and catalog and recatalog everything.
>        Test runner can set `erp5_tests_recreate_catalog` environnement variable,
>        in that case we have to clear catalog. """
> -      portal = self.getPortal()
>        if int(os.environ.get('erp5_tests_recreate_catalog', 0)):
>          try:
> -          self.login()
>            _start = time.time()
>            if not quiet:
>              ZopeTestCase._print('\nRecreating catalog ... ')
> +          portal = self.getPortal()
>            portal.portal_activities.manageClearActivities()
>            portal.portal_catalog.manage_catalogClear()
>            transaction.commit()
> @@ -448,7 +436,6 @@
>              ZopeTestCase._print('done (%.3fs)\n' % (time.time() - _start,))
>          finally:
>            os.environ['erp5_tests_recreate_catalog'] = '0'
> -          noSecurityManager()
>  
>      # Utility methods specific to ERP5Type
>      def getTemplateTool(self):
> @@ -698,25 +685,23 @@
>                  ZopeTestCase._print('done (%.3fs)\n' % (time.time() - _start))
>                # Release locks
>                transaction.commit()
> -
> -            if os.environ.get('erp5_load_data_fs'):
> -              # Import local PropertySheets, Documents
> -              for id_ in getLocalPropertySheetList():
> -                importLocalPropertySheet(id_)
> -              for id_ in getLocalDocumentList():
> -                importLocalDocument(id_)
> -              for id_ in getLocalConstraintList():
> -                importLocalConstraint(id_)
> -            else:
> -              # Remove all local PropertySheets, Documents
> -              for id_ in getLocalPropertySheetList():
> -                removeLocalPropertySheet(id_)
> -              for id_ in getLocalDocumentList():
> -                removeLocalDocument(id_)
> -              for id_ in getLocalConstraintList():
> -                removeLocalConstraint(id_)
> +            self.portal = portal
> +            portal_activities = getattr(portal, 'portal_activities', None)
> +
> +            if len(setup_done) == 1: # make sure it is run only once
> +              try:
> +                from Products import DeadlockDebugger
> +              except ImportError:
> +                pass
> +              from Testing.ZopeTestCase.utils import startZServer
> +              ZopeTestCase._print('Running ZServer on port %i\n'
> +                                  % startZServer()[1])
> +              if portal_activities is not None:
> +                portal_activities.distributingNode = portal_activities.getCurrentNode()
> +                portal_activities._nodes = portal_activities.distributingNode,
>  
>              self._updateConnectionStrings()
> +            self._recreateCatalog()
>  
>              update_business_templates = os.environ.get('update_business_templates') is not None
>              BusinessTemplate_getModifiedObject = aq_base(getattr(portal, 'BusinessTemplate_getModifiedObject', None))
> @@ -762,7 +747,6 @@
>                  start = time.time()
>                # setUpOnce method may use self.app and self.portal
>                self.app = app
> -              self.portal = portal
>                setup_once()
>                if not quiet:
>                  ZopeTestCase._print('done (%.3fs)\n' % (time.time() - start))
> @@ -775,7 +759,6 @@
>  
>              transaction.commit()
>  
> -            portal_activities = getattr(portal, 'portal_activities', None)
>              if portal_activities is not None:
>                if not quiet:
>                  ZopeTestCase._print('Executing pending activities ... ')
> @@ -804,33 +787,6 @@
>              # Reset aq dynamic, so all unit tests will start again
>              _aq_reset()
>  
> -            if os.environ.get('erp5_save_data_fs'):
> -              # Quit the test in order to get a clean site
> -              if not quiet:
> -                ZopeTestCase._print('done (%.3fs)\n' % (time.time()-_start,))
> -                ZopeTestCase._print('Data.fs created\n')
> -              transaction.commit()
> -              ZopeTestCase.close(app)
> -              instance_home = os.environ['INSTANCE_HOME']
> -              # The output of mysqldump needs to merge many lines at a time
> -              # for performance reasons (merging lines is at most 10 times
> -              # faster, so this produce somewhat not nice to read sql
> -              command = 'mysqldump %s > %s/dump.sql' \
> -                          % (getMySQLArguments(), instance_home)
> -              if not quiet:
> -                ZopeTestCase._print('Dumping MySQL database with %s... ' \
> -                                      % command)
> -              os.system(command)
> -              if not quiet:
> -                ZopeTestCase._print('done\n')
> -              if not quiet:
> -                ZopeTestCase._print('Dumping static files... ')
> -              for dir in ('Constraint', 'Document', 'PropertySheet', 'Extensions'):
> -                os.system('rm -rf %s/%s.bak' % (instance_home, dir))
> -                os.system('cp -ar %s/%s %s/%s.bak' % (instance_home, dir, instance_home, dir))
> -              if not quiet:
> -                ZopeTestCase._print('done\n')
> -
>              # Log out
>              if not quiet:
>                ZopeTestCase._print('Logout ... \n')
> @@ -844,18 +800,6 @@
>            else:
>              transaction.commit()
>              ZopeTestCase.close(app)
> -
> -        if os.environ.get('erp5_load_data_fs'):
> -          # Import local PropertySheets, Documents
> -          # when loading an environnement
> -          for id_ in getLocalPropertySheetList():
> -            importLocalPropertySheet(id_)
> -          for id_ in getLocalDocumentList():
> -            importLocalDocument(id_)
> -          for id_ in getLocalConstraintList():
> -            importLocalConstraint(id_)
> -          _aq_reset()
> -
>        except:
>          f = StringIO()
>          traceback.print_exc(file=f)
> 
> Modified: erp5/trunk/products/ERP5Type/tests/custom_zodb.py
> URL: http://svn.erp5.org/erp5/trunk/products/ERP5Type/tests/custom_zodb.py?rev=28043&r1=28042&r2=28043&view=diff
> ==============================================================================
> --- erp5/trunk/products/ERP5Type/tests/custom_zodb.py [utf8] (original)
> +++ erp5/trunk/products/ERP5Type/tests/custom_zodb.py [utf8] Thu Jul  9 14:32:09 2009
> @@ -7,18 +7,19 @@
>  from Products.ERP5Type.tests.utils import getMySQLArguments
>  
>  instance_home = os.environ.get('INSTANCE_HOME')
> -data_fs_path = os.environ.get('erp5_tests_data_fs_path')
> -new_data_fs_path = os.path.join(instance_home, 'Data.fs')
> +data_fs_path = os.environ.get('erp5_tests_data_fs_path',
> +                              os.path.join(instance_home, 'Data.fs'))
> +load = int(os.environ.get('erp5_load_data_fs', 0))
> +save = int(os.environ.get('erp5_save_data_fs', 0))
>  
> -if os.environ.get('erp5_load_data_fs'):
> -  if os.environ.get('erp5_force_data_fs'):
> -    Storage = FileStorage(new_data_fs_path)
> +if load:
> +  dump_sql = os.path.join(instance_home, 'dump.sql')
> +  if os.path.exists(dump_sql):
> +    print("Restoring MySQL database ... ")
> +    ret = os.system("mysql %s < %s" % (getMySQLArguments(), dump_sql))
> +    assert not ret
>    else:
> -    Storage = DemoStorage(base=FileStorage(new_data_fs_path), quota=(1<<20))
> -  print("Restoring MySQL database ... ")
> -  ret = os.system("mysql %s < %s/dump.sql" % (
> -                getMySQLArguments(), instance_home))
> -  assert ret == 0
> +    os.environ['erp5_tests_recreate_catalog'] = '1'
>    print("Restoring static files ... ")
>    for dir in ('Constraint', 'Document', 'PropertySheet', 'Extensions'):
>      if os.path.exists(os.path.join(instance_home, '%s.bak' % dir)):
> @@ -26,7 +27,7 @@
>        shutil.rmtree(full_path)
>        shutil.copytree(os.path.join(instance_home, '%s.bak' % dir),
>                        full_path, symlinks=True)
> -elif os.environ.get('erp5_save_data_fs'):
> +else:
>    print("Cleaning static files ... ")
>    for dir in ('Constraint', 'Document', 'PropertySheet', 'Extensions'):
>      full_path = os.path.join(instance_home, dir)
> @@ -34,10 +35,12 @@
>        assert os.path.isdir(full_path)
>        for f in glob.glob('%s/*' % full_path):
>          os.unlink(f)
> -  if os.path.exists(new_data_fs_path):
> -    os.remove(new_data_fs_path)
> -  Storage = FileStorage(new_data_fs_path)
> -elif data_fs_path:
> +  if save and os.path.exists(data_fs_path):
> +    os.remove(data_fs_path)
> +
> +if save:
> +  Storage = FileStorage(data_fs_path)
> +elif load:
>    Storage = DemoStorage(base=FileStorage(data_fs_path), quota=(1<<20))
>  else:
>    Storage = DemoStorage(quota=(1<<20))
> 
> Modified: erp5/trunk/products/ERP5Type/tests/runFunctionalTest.py
> URL: http://svn.erp5.org/erp5/trunk/products/ERP5Type/tests/runFunctionalTest.py?rev=28043&r1=28042&r2=28043&view=diff
> ==============================================================================
> --- erp5/trunk/products/ERP5Type/tests/runFunctionalTest.py [utf8] (original)
> +++ erp5/trunk/products/ERP5Type/tests/runFunctionalTest.py [utf8] Thu Jul  9 14:32:09 2009
> @@ -135,7 +135,7 @@
>          os.kill(firefox_pid, signal.SIGTERM)
>  
>  def startZope():
> -  os.environ['erp5_force_data_fs'] = "1"
> +  os.environ['erp5_save_data_fs'] = "1"
>    os.system('%s/bin/zopectl start' % instance_home)
>    sleep(2) # ad hoc
>  
> 
> Modified: erp5/trunk/products/ERP5Type/tests/runUnitTest.py
> URL: http://svn.erp5.org/erp5/trunk/products/ERP5Type/tests/runUnitTest.py?rev=28043&r1=28042&r2=28043&view=diff
> ==============================================================================
> --- erp5/trunk/products/ERP5Type/tests/runUnitTest.py [utf8] (original)
> +++ erp5/trunk/products/ERP5Type/tests/runUnitTest.py [utf8] Thu Jul  9 14:32:09 2009
> @@ -5,11 +5,10 @@
>  import re
>  import getopt
>  import unittest
> -
> -WIN = False
> -if os.name == 'nt':
> -  import shutil
> -  WIN = True
> +import shutil
> +import errno
> +
> +WIN = os.name == 'nt'
>  
>  
>  __doc__ = """%(program)s: unit test runner for the ERP5 Project
> @@ -23,20 +22,20 @@
>    --portal_id=STRING         force id of the portal. Useful when using
>                               --data_fs_path to run tests on an existing
>                               Data.fs
> -  --data_fs_path=STRING      Path to the original Data.fs to run tests on an
> -                             existing environment. The Data.fs is opened read
> -                             only
> +  --data_fs_path=STRING      Use the given path for the Data.fs
>    --bt5_path                 Path to the Business Templates. Default is
>                               INSTANCE_HOME/bt5.
>    --recreate_catalog=0 or 1  recreate the content of the sql catalog. Default
>                               is to recreate, unless using --data_fs_path
> -  --save                     add erp5 sites and business templates in Data.fs
> -                             and exit without invoking any tests
> -  --load                     load Data.fs and skip adding erp5 sites and
> -                             business templates
> +  --save                     Run unit tests in persistent mode (if unset,
> +                             existing Data.fs, dump.sql and *.bak static
> +                             folders are not modified). Tests are skipped
> +                             if business templates are updated
> +                             or if --load is unset.
> +  --load                     Reuse existing instance (created with --save).
>    --erp5_sql_connection_string=STRING
>                               ZSQL Connection string for erp5_sql_connection, by
> -                             default, it will use "test test"                            
> +                             default, it will use "test test"
>    --cmf_activity_sql_connection_string=STRING
>                               ZSQL Connection string for
>                               cmf_activity_sql_connection (if unset, defaults to
> @@ -45,7 +44,7 @@
>                               ZSQL Connection string for
>                               erp5_sql_deferred_connection (if unset, defaults
>                               to erp5_sql_connection_string)
> -  --email_from_address=STRING 
> +  --email_from_address=STRING
>                               Initialise the email_from_address property of the
>                               portal, by defaults, CMFActivity failures are sent
>                               on localhost from this address, to this address
> @@ -58,7 +57,7 @@
>                               Run only specified test methods delimited with
>                               commas (e.g. testFoo,testBar). This can be regular
>                               expressions.
> -  -D                         
> +  -D
>                               Invoke debugger on errors / failures.
>    --update_business_templates
>                               Update all business templates prior to runing
> @@ -174,10 +173,6 @@
>  class ERP5TypeTestLoader(unittest.TestLoader):
>    """Load test cases from the name passed on the command line.
>    """
> -  def __init__(self, save=0):
> -    if save:
> -      self.testMethodPrefix = 'dummy_test'
> -
>    def loadTestsFromName(self, name, module=None):
>      """This method is here for compatibility with old style arguments.
>      - It is possible to have the .py prefix for the test file
> @@ -249,7 +244,10 @@
>    else:
>      products_home = os.path.join(instance_home, 'Products')
>  
> -  from Testing import ZopeTestCase
> +  import OFS.Application
> +  import_products = OFS.Application.import_products
> +  from Testing import ZopeTestCase # This will import custom_zodb.py
> +  OFS.Application.import_products = import_products
>  
>    try:
>      # On Zope 2.8, ZopeTestCase does not have any logging facility.
> @@ -299,29 +297,31 @@
>    # it is then possible to run the debugger by "import pdb; pdb.set_trace()"
>    sys.path.insert(0, tests_framework_home)
>  
> -  save = 0
> -  # pass save=1 to test loader to skip all tests in save mode
> -  # and monkeypatch PortalTestCase.setUp to skip beforeSetUp and afterSetUp.
> -  # Also patch unittest.makeSuite, as it's used in test_suite function in 
> -  # test cases.
> -  if os.environ.get('erp5_save_data_fs'):
> +  test_loader = ERP5TypeTestLoader()
> +
> +  save = int(os.environ.get('erp5_save_data_fs', 0))
> +  dummy_test = save and (int(os.environ.get('update_business_templates', 0))
> +                         or not int(os.environ.get('erp5_load_data_fs', 0)))
> +  if dummy_test:
> +    # Skip all tests in save mode and monkeypatch PortalTestCase.setUp
> +    # to skip beforeSetUp and afterSetUp. Also patch unittest.makeSuite,
> +    # as it's used in test_suite function in test cases.
>      from Products.ERP5Type.tests.ERP5TypeTestCase import \
>                    dummy_makeSuite, dummy_setUp, dummy_tearDown
> -    save = 1
>      from Testing.ZopeTestCase.PortalTestCase import PortalTestCase
>      unittest.makeSuite = dummy_makeSuite
>      PortalTestCase.setUp = dummy_setUp
>      PortalTestCase.tearDown = dummy_tearDown
> -  
> -  suite = ERP5TypeTestLoader(save=save).loadTestsFromNames(test_list)
> +    test_loader.testMethodPrefix = 'dummy_test'
> +
> +  suite = test_loader.loadTestsFromNames(test_list)
>  
>    # Hack the profiler to run only specified test methods, and wrap results when
>    # running in debug mode. We also monkeypatch unittest.TestCase for tests that
>    # does not use ERP5TypeTestCase
> -  run_only = os.environ.get('run_only', '')
> -  if not save:
> -    test_method_list = run_only.split(',')
> -    
> +  if not dummy_test:
> +    test_method_list = os.environ.get('run_only', '').split(',')
> +
>      def wrapped_run(run_orig):
>        # wrap the method that run the test to run test method only if its name
>        # matches the run_only spec and to provide post mortem debugging facility
> @@ -341,11 +341,33 @@
>      from unittest import TestCase
>      TestCase.__call__ = wrapped_run(TestCase.__call__)
>  
> -
> -
>    # change current directory to the test home, to create zLOG.log in this dir.
>    os.chdir(tests_home)
> -  return TestRunner(verbosity=verbosity).run(suite)
> +  result = TestRunner(verbosity=verbosity).run(suite)
> +
> +  if save:
> +    from Products.ERP5Type.tests.utils import getMySQLArguments
> +    # The output of mysqldump needs to merge many lines at a time
> +    # for performance reasons (merging lines is at most 10 times
> +    # faster, so this produce somewhat not nice to read sql
> +    command = 'mysqldump %s > %s' % (getMySQLArguments(),
> +                                     os.path.join(instance_home, 'dump.sql'))
> +    if verbosity:
> +      print('Dumping MySQL database with %s... ' % command)
> +    os.system(command)
> +    if verbosity:
> +      print('Dumping static files... ')
> +    for static_dir in 'Constraint', 'Document', 'Extensions', 'PropertySheet':
> +      static_dir = os.path.join(instance_home, static_dir)
> +      try:
> +        shutil.rmtree(static_dir + '.bak')
> +      except OSError, e:
> +        if e.errno != errno.ENOENT:
> +          raise
> +      shutil.copytree(static_dir, static_dir + '.bak', symlinks=True)
> +
> +  return result
> +
>  
>  def usage(stream, msg=None):
>    if msg:
> @@ -379,9 +401,7 @@
>    os.environ["erp5_tests_recreate_catalog"] = "0"
>    verbosity = 1
>    debug = 0
> -  load = False
> -  save = False
> -  
> +
>    for opt, arg in opts:
>      if opt in ("-v", "--verbose"):
>        os.environ['VERBOSE'] = "1"
> @@ -418,10 +438,8 @@
>        os.environ["email_from_address"] = arg
>      elif opt == "--save":
>        os.environ["erp5_save_data_fs"] = "1"
> -      save = True
>      elif opt == "--load":
>        os.environ["erp5_load_data_fs"] = "1"
> -      load = True
>      elif opt == "--erp5_catalog_storage":
>        os.environ["erp5_catalog_storage"] = arg
>      elif opt == "--run_only":
> @@ -432,9 +450,6 @@
>      elif opt == "--update_business_templates":
>        os.environ["update_business_templates"] = "1"
>  
> -  if load and save:
> -    os.environ["erp5_force_data_fs"] = "1"
> -
>    test_list = args
>    if not test_list:
>      print "No test to run, exiting immediately."
> 
> _______________________________________________
> Erp5-report mailing list
> Erp5-report at erp5.org
> http://mail.nexedi.com/mailman/listinfo/erp5-report




More information about the Erp5-dev mailing list