devtools/__init__.py
brancholdstable
changeset 6665 90f2f20367bc
parent 6593 0fd8792c9c8a
child 6594 e10468a23291
child 6729 1a423eaee782
equal deleted inserted replaced
6018:f4d1d5d9ccbb 6665:90f2f20367bc
    13 # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
    13 # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
    14 # details.
    14 # details.
    15 #
    15 #
    16 # You should have received a copy of the GNU Lesser General Public License along
    16 # You should have received a copy of the GNU Lesser General Public License along
    17 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
    17 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
    18 """Test tools for cubicweb
    18 """Test tools for cubicweb"""
    19 
    19 
    20 """
       
    21 __docformat__ = "restructuredtext en"
    20 __docformat__ = "restructuredtext en"
    22 
    21 
    23 import os
    22 import os
       
    23 import sys
    24 import logging
    24 import logging
    25 from datetime import timedelta
    25 from datetime import timedelta
    26 from os.path import (abspath, join, exists, basename, dirname, normpath, split,
    26 from os.path import (abspath, join, exists, basename, dirname, normpath, split,
    27                      isfile, isabs)
    27                      isfile, isabs)
    28 
    28 
    93 class TestServerConfiguration(ServerConfiguration):
    93 class TestServerConfiguration(ServerConfiguration):
    94     mode = 'test'
    94     mode = 'test'
    95     set_language = False
    95     set_language = False
    96     read_instance_schema = False
    96     read_instance_schema = False
    97     init_repository = True
    97     init_repository = True
       
    98     db_require_setup = True
    98     options = cwconfig.merge_options(ServerConfiguration.options + (
    99     options = cwconfig.merge_options(ServerConfiguration.options + (
    99         ('anonymous-user',
   100         ('anonymous-user',
   100          {'type' : 'string',
   101          {'type' : 'string',
   101           'default': None,
   102           'default': None,
   102           'help': 'login of the CubicWeb user account to use for anonymous user (if you want to allow anonymous)',
   103           'help': 'login of the CubicWeb user account to use for anonymous user (if you want to allow anonymous)',
   108           'help': 'password of the CubicWeb user account matching login',
   109           'help': 'password of the CubicWeb user account matching login',
   109           'group': 'main', 'level': 1,
   110           'group': 'main', 'level': 1,
   110           }),
   111           }),
   111         ))
   112         ))
   112 
   113 
   113     def __init__(self, appid, log_threshold=logging.CRITICAL+10):
   114     def __init__(self, appid, apphome=None, log_threshold=logging.CRITICAL+10):
       
   115         # must be set before calling parent __init__
       
   116         if apphome is None:
       
   117             if exists(appid):
       
   118                 apphome = abspath(appid)
       
   119             else: # cube test
       
   120                 apphome = abspath('..')
       
   121         self._apphome = apphome
   114         ServerConfiguration.__init__(self, appid)
   122         ServerConfiguration.__init__(self, appid)
   115         self.init_log(log_threshold, force=True)
   123         self.init_log(log_threshold, force=True)
   116         # need this, usually triggered by cubicweb-ctl
   124         # need this, usually triggered by cubicweb-ctl
   117         self.load_cwctl_plugins()
   125         self.load_cwctl_plugins()
   118 
   126 
   119     anonymous_user = TwistedConfiguration.anonymous_user.im_func
   127     anonymous_user = TwistedConfiguration.anonymous_user.im_func
   120 
   128 
   121     @property
   129     @property
   122     def apphome(self):
   130     def apphome(self):
   123         if exists(self.appid):
   131         return self._apphome
   124             return abspath(self.appid)
       
   125         # cube test
       
   126         return abspath('..')
       
   127     appdatahome = apphome
   132     appdatahome = apphome
   128 
   133 
   129     def load_configuration(self):
   134     def load_configuration(self):
   130         super(TestServerConfiguration, self).load_configuration()
   135         super(TestServerConfiguration, self).load_configuration()
   131         self.global_set_option('anonymous-user', 'anon')
   136         self.global_set_option('anonymous-user', 'anon')
   134         self.global_set_option('undo-support', '')
   139         self.global_set_option('undo-support', '')
   135 
   140 
   136     def main_config_file(self):
   141     def main_config_file(self):
   137         """return instance's control configuration file"""
   142         """return instance's control configuration file"""
   138         return join(self.apphome, '%s.conf' % self.name)
   143         return join(self.apphome, '%s.conf' % self.name)
   139 
       
   140     def instance_md5_version(self):
       
   141         return ''
       
   142 
   144 
   143     def bootstrap_cubes(self):
   145     def bootstrap_cubes(self):
   144         try:
   146         try:
   145             super(TestServerConfiguration, self).bootstrap_cubes()
   147             super(TestServerConfiguration, self).bootstrap_cubes()
   146         except IOError:
   148         except IOError:
   168         sources = super(TestServerConfiguration, self).sources()
   170         sources = super(TestServerConfiguration, self).sources()
   169         if not sources:
   171         if not sources:
   170             sources = DEFAULT_SOURCES
   172             sources = DEFAULT_SOURCES
   171         return sources
   173         return sources
   172 
   174 
       
   175     # web config methods needed here for cases when we use this config as a web
       
   176     # config
       
   177 
       
   178     def instance_md5_version(self):
       
   179         return ''
       
   180 
       
   181     def default_base_url(self):
       
   182         return BASE_URL
       
   183 
   173 
   184 
   174 class BaseApptestConfiguration(TestServerConfiguration, TwistedConfiguration):
   185 class BaseApptestConfiguration(TestServerConfiguration, TwistedConfiguration):
   175     repo_method = 'inmemory'
   186     repo_method = 'inmemory'
   176     options = cwconfig.merge_options(TestServerConfiguration.options
   187     options = cwconfig.merge_options(TestServerConfiguration.options
   177                                      + TwistedConfiguration.options)
   188                                      + TwistedConfiguration.options)
   179     cube_appobject_path = TestServerConfiguration.cube_appobject_path | TwistedConfiguration.cube_appobject_path
   190     cube_appobject_path = TestServerConfiguration.cube_appobject_path | TwistedConfiguration.cube_appobject_path
   180 
   191 
   181     def available_languages(self, *args):
   192     def available_languages(self, *args):
   182         return ('en', 'fr', 'de')
   193         return ('en', 'fr', 'de')
   183 
   194 
   184     def ext_resources_file(self):
       
   185         """return instance's external resources file"""
       
   186         return join(self.apphome, 'data', 'external_resources')
       
   187 
       
   188     def pyro_enabled(self):
   195     def pyro_enabled(self):
   189         # but export PYRO_MULTITHREAD=0 or you get problems with sqlite and threads
   196         # but export PYRO_MULTITHREAD=0 or you get problems with sqlite and
       
   197         # threads
   190         return True
   198         return True
   191 
   199 
   192 
   200 # XXX merge with BaseApptestConfiguration ?
   193 class ApptestConfiguration(BaseApptestConfiguration):
   201 class ApptestConfiguration(BaseApptestConfiguration):
   194 
   202 
   195     def __init__(self, appid, log_threshold=logging.CRITICAL, sourcefile=None):
   203     def __init__(self, appid, apphome=None,
   196         BaseApptestConfiguration.__init__(self, appid, log_threshold=log_threshold)
   204                  log_threshold=logging.CRITICAL, sourcefile=None):
       
   205         BaseApptestConfiguration.__init__(self, appid, apphome,
       
   206                                           log_threshold=log_threshold)
   197         self.init_repository = sourcefile is None
   207         self.init_repository = sourcefile is None
   198         self.sourcefile = sourcefile
   208         self.sourcefile = sourcefile
       
   209 
       
   210 
       
   211 class RealDatabaseConfiguration(ApptestConfiguration):
       
   212     """configuration class for tests to run on a real database.
       
   213 
       
   214     The intialization is done by specifying a source file path.
       
   215 
       
   216     Important note: init_test_database / reset_test_database steps are
       
   217     skipped. It's thus up to the test developer to implement setUp/tearDown
       
   218     accordingly.
       
   219 
       
   220     Example usage::
       
   221 
       
   222       class MyTests(CubicWebTC):
       
   223           _config = RealDatabseConfiguration('myapp',
       
   224                                              sourcefile='/path/to/sources')
       
   225           def test_something(self):
       
   226               rset = self.execute('Any X WHERE X is CWUser')
       
   227               self.view('foaf', rset)
       
   228 
       
   229     """
       
   230     db_require_setup = False    # skip init_db / reset_db steps
       
   231     read_instance_schema = True # read schema from database
   199 
   232 
   200 
   233 
   201 # test database handling #######################################################
   234 # test database handling #######################################################
   202 
   235 
   203 def init_test_database(config=None, configdir='data'):
   236 def init_test_database(config=None, configdir='data'):
   204     """init a test database for a specific driver"""
   237     """init a test database for a specific driver"""
   205     from cubicweb.dbapi import in_memory_cnx
   238     from cubicweb.dbapi import in_memory_cnx
   206     config = config or TestServerConfiguration(configdir)
   239     config = config or TestServerConfiguration(configdir)
   207     sources = config.sources()
   240     sources = config.sources()
   208     driver = sources['system']['db-driver']
   241     driver = sources['system']['db-driver']
   209     if driver == 'sqlite':
   242     if config.db_require_setup:
   210         init_test_database_sqlite(config)
   243         if driver == 'sqlite':
   211     elif driver == 'postgres':
   244             init_test_database_sqlite(config)
   212         init_test_database_postgres(config)
   245         elif driver == 'postgres':
   213     elif driver == 'sqlserver2005':
   246             init_test_database_postgres(config)
   214         init_test_database_sqlserver2005(config)
   247         else:
   215     else:
   248             raise ValueError('no initialization function for driver %r' % driver)
   216         raise ValueError('no initialization function for driver %r' % driver)
       
   217     config._cubes = None # avoid assertion error
   249     config._cubes = None # avoid assertion error
   218     repo, cnx = in_memory_cnx(config, unicode(sources['admin']['login']),
   250     repo, cnx = in_memory_cnx(config, unicode(sources['admin']['login']),
   219                               password=sources['admin']['password'] or 'xxx')
   251                               password=sources['admin']['password'] or 'xxx')
   220     if driver == 'sqlite':
   252     if driver == 'sqlite':
   221         install_sqlite_patch(repo.querier)
   253         install_sqlite_patch(repo.querier)
   222     return repo, cnx
   254     return repo, cnx
   223 
   255 
   224 
       
   225 def reset_test_database(config):
   256 def reset_test_database(config):
   226     """init a test database for a specific driver"""
   257     """init a test database for a specific driver"""
       
   258     if not config.db_require_setup:
       
   259         return
   227     driver = config.sources()['system']['db-driver']
   260     driver = config.sources()['system']['db-driver']
   228     if driver == 'sqlite':
   261     if driver == 'sqlite':
   229         reset_test_database_sqlite(config)
   262         reset_test_database_sqlite(config)
   230     elif driver in ('sqlserver2005', 'postgres'):
   263     elif driver == 'postgres':
   231         # XXX do something with dump/restore ?
   264         init_test_database_postgres(config)
   232         print 'resetting the database is not done for', driver
       
   233         print 'you should handle it manually'
       
   234     else:
   265     else:
   235         raise ValueError('no reset function for driver %r' % driver)
   266         raise ValueError('no reset function for driver %r' % driver)
   236 
   267 
   237 
   268 
   238 ### postgres test database handling ############################################
   269 ### postgres test database handling ############################################
   239 
   270 
   240 def init_test_database_postgres(config):
   271 def init_test_database_postgres(config):
   241     """initialize a fresh postgresql databse used for testing purpose"""
   272     """initialize a fresh postgresql databse used for testing purpose"""
   242     if config.init_repository:
   273     from logilab.database import get_db_helper
   243         from cubicweb.server import init_repository
   274     from cubicweb.server import init_repository
   244         init_repository(config, interactive=False, drop=True)
   275     from cubicweb.server.serverctl import (createdb, system_source_cnx,
   245 
   276                                            _db_sys_cnx)
   246 ### sqlserver2005 test database handling ############################################
   277     source = config.sources()['system']
       
   278     dbname = source['db-name']
       
   279     templdbname = dbname + '_template'
       
   280     helper = get_db_helper('postgres')
       
   281     # connect on the dbms system base to create our base
       
   282     dbcnx = _db_sys_cnx(source, 'CREATE DATABASE and / or USER', verbose=0)
       
   283     cursor = dbcnx.cursor()
       
   284     try:
       
   285         if dbname in helper.list_databases(cursor):
       
   286             cursor.execute('DROP DATABASE %s' % dbname)
       
   287         if not templdbname in helper.list_databases(cursor):
       
   288             source['db-name'] = templdbname
       
   289             createdb(helper, source, dbcnx, cursor)
       
   290             dbcnx.commit()
       
   291             cnx = system_source_cnx(source, special_privs='LANGUAGE C', verbose=0)
       
   292             templcursor = cnx.cursor()
       
   293             # XXX factorize with db-create code
       
   294             helper.init_fti_extensions(templcursor)
       
   295             # install plpythonu/plpgsql language if not installed by the cube
       
   296             langs = sys.platform == 'win32' and ('plpgsql',) or ('plpythonu', 'plpgsql')
       
   297             for extlang in langs:
       
   298                 helper.create_language(templcursor, extlang)
       
   299             cnx.commit()
       
   300             templcursor.close()
       
   301             cnx.close()
       
   302             init_repository(config, interactive=False)
       
   303             source['db-name'] = dbname
       
   304     except:
       
   305         dbcnx.rollback()
       
   306         # XXX drop template
       
   307         raise
       
   308     createdb(helper, source, dbcnx, cursor, template=templdbname)
       
   309     dbcnx.commit()
       
   310     dbcnx.close()
       
   311 
       
   312 ### sqlserver2005 test database handling #######################################
   247 
   313 
   248 def init_test_database_sqlserver2005(config):
   314 def init_test_database_sqlserver2005(config):
   249     """initialize a fresh sqlserver databse used for testing purpose"""
   315     """initialize a fresh sqlserver databse used for testing purpose"""
   250     if config.init_repository:
   316     if config.init_repository:
   251         from cubicweb.server import init_repository
   317         from cubicweb.server import init_repository
   283         import shutil
   349         import shutil
   284         from cubicweb.server import init_repository
   350         from cubicweb.server import init_repository
   285         init_repository(config, interactive=False)
   351         init_repository(config, interactive=False)
   286         dbfile = config.sources()['system']['db-name']
   352         dbfile = config.sources()['system']['db-name']
   287         shutil.copy(dbfile, '%s-template' % dbfile)
   353         shutil.copy(dbfile, '%s-template' % dbfile)
   288 
       
   289 
   354 
   290 def install_sqlite_patch(querier):
   355 def install_sqlite_patch(querier):
   291     """This patch hotfixes the following sqlite bug :
   356     """This patch hotfixes the following sqlite bug :
   292        - http://www.sqlite.org/cvstrac/tktview?tn=1327,33
   357        - http://www.sqlite.org/cvstrac/tktview?tn=1327,33
   293        (some dates are returned as strings rather thant date objects)
   358        (some dates are returned as strings rather thant date objects)