devtools/testlib.py
changeset 9070 4a803380f718
parent 9044 cfec5cc46008
child 9071 46885bfa4150
equal deleted inserted replaced
9069:aff871b58ba0 9070:4a803380f718
    38 from logilab.common.decorators import cached, classproperty, clear_cache, iclassmethod
    38 from logilab.common.decorators import cached, classproperty, clear_cache, iclassmethod
    39 from logilab.common.deprecation import deprecated, class_deprecated
    39 from logilab.common.deprecation import deprecated, class_deprecated
    40 from logilab.common.shellutils import getlogin
    40 from logilab.common.shellutils import getlogin
    41 
    41 
    42 from cubicweb import ValidationError, NoSelectableObject, AuthenticationError
    42 from cubicweb import ValidationError, NoSelectableObject, AuthenticationError
    43 from cubicweb import cwconfig, dbapi, devtools, web, server
    43 from cubicweb import cwconfig, dbapi, devtools, web, server, repoapi
    44 from cubicweb.utils import json
    44 from cubicweb.utils import json
    45 from cubicweb.sobjects import notification
    45 from cubicweb.sobjects import notification
    46 from cubicweb.web import Redirect, application
    46 from cubicweb.web import Redirect, application
    47 from cubicweb.server.hook import SendMailOp
    47 from cubicweb.server.hook import SendMailOp
    48 from cubicweb.devtools import SYSTEM_ENTITIES, SYSTEM_RELATIONS, VIEW_VALIDATORS
    48 from cubicweb.devtools import SYSTEM_ENTITIES, SYSTEM_RELATIONS, VIEW_VALIDATORS
   144 
   144 
   145     def __getattr__(self, attrname):
   145     def __getattr__(self, attrname):
   146         return getattr(self.cnx, attrname)
   146         return getattr(self.cnx, attrname)
   147 
   147 
   148     def __enter__(self):
   148     def __enter__(self):
   149         return self.cnx.__enter__()
   149         # already open
       
   150         return self.cnx
   150 
   151 
   151     def __exit__(self, exctype, exc, tb):
   152     def __exit__(self, exctype, exc, tb):
   152         try:
   153         try:
   153             return self.cnx.__exit__(exctype, exc, tb)
   154             return self.cnx.__exit__(exctype, exc, tb)
   154         finally:
   155         finally:
   155             self.cnx.close()
       
   156             self.testcase.restore_connection()
   156             self.testcase.restore_connection()
   157 
   157 
   158 # base class for cubicweb tests requiring a full cw environments ###############
   158 # base class for cubicweb tests requiring a full cw environments ###############
   159 
   159 
   160 class CubicWebTC(TestCase):
   160 class CubicWebTC(TestCase):
   179     test_db_id = DEFAULT_EMPTY_DB_ID
   179     test_db_id = DEFAULT_EMPTY_DB_ID
   180     _cnxs = set() # establised connection
   180     _cnxs = set() # establised connection
   181                   # stay on connection for leak detection purpose
   181                   # stay on connection for leak detection purpose
   182 
   182 
   183     def __init__(self, *args, **kwargs):
   183     def __init__(self, *args, **kwargs):
   184         self._cnx = None  # current connection
   184         self._admin_session = None
       
   185         self._admin_clt_cnx = None
       
   186         self._current_session = None
       
   187         self._current_clt_cnx = None
   185         self.repo = None
   188         self.repo = None
   186         self.websession = None
       
   187         super(CubicWebTC, self).__init__(*args, **kwargs)
   189         super(CubicWebTC, self).__init__(*args, **kwargs)
   188 
   190 
   189     # repository connection handling ###########################################
   191     # repository connection handling ###########################################
   190 
   192 
   191     # Too much complicated stuff. the class doesn't need to bear the repo anymore
       
   192     def set_cnx(self, cnx):
   193     def set_cnx(self, cnx):
   193         self._cnxs.add(cnx)
   194         # XXX we want to deprecate this
   194         self._cnx = cnx
   195         assert getattr(cnx, '_session', None) is not None
       
   196         if cnx is self._admin_clt_cnx:
       
   197             self._pop_custom_cnx()
       
   198         else:
       
   199             self._cnxs.add(cnx) # register the cns to make sure it is removed
       
   200             self._current_session = cnx._session
       
   201             self._current_clt_cnx = cnx
   195 
   202 
   196     @property
   203     @property
   197     def cnx(self):
   204     def cnx(self):
   198         return self._cnx
   205         # XXX we want to deprecate this
       
   206         clt_cnx = self._current_clt_cnx
       
   207         if clt_cnx is None:
       
   208             clt_cnx = self._admin_clt_cnx
       
   209         return clt_cnx
   199 
   210 
   200     def _close_cnx(self):
   211     def _close_cnx(self):
       
   212         """ensure that all cnx used by a test have been closed"""
   201         for cnx in list(self._cnxs):
   213         for cnx in list(self._cnxs):
   202             if not cnx._closed:
   214             if cnx._open and not cnx._session.closed:
   203                 cnx.rollback()
   215                 cnx.rollback()
   204                 cnx.close()
   216                 cnx.close()
   205             self._cnxs.remove(cnx)
   217             self._cnxs.remove(cnx)
   206 
   218 
   207     @property
   219     @property
   208     def session(self):
   220     def session(self):
   209         """return current server side session (using default manager account)"""
   221         """return current server side session"""
   210         session = self.repo._sessions[self.cnx.sessionid]
   222         # XXX We want to use a srv_connection instead and deprecate this
       
   223         # property
       
   224         session = self._current_session
       
   225         if session is None:
       
   226             session = self._admin_session
       
   227             session.set_cnx(self._admin_clt_cnx._cnxid)
   211         session.set_cnxset()
   228         session.set_cnxset()
   212         return session
   229         return session
   213 
   230 
       
   231     @property
       
   232     def websession(self):
       
   233         return self.session
       
   234 
       
   235     @property
       
   236     def adminsession(self):
       
   237         """return current server side session (using default manager account)"""
       
   238         return self._admin_session
       
   239 
   214     def login(self, login, **kwargs):
   240     def login(self, login, **kwargs):
   215         """return a connection for the given login/password"""
   241         """return a connection for the given login/password"""
       
   242         __ = kwargs.pop('autoclose', True) # not used anymore
   216         if login == self.admlogin:
   243         if login == self.admlogin:
   217             self.restore_connection()
       
   218             # definitly don't want autoclose when used as a context manager
   244             # definitly don't want autoclose when used as a context manager
   219             return self.cnx
   245             clt_cnx = repoapi.ClientConnection(self._admin_session)
   220         autoclose = kwargs.pop('autoclose', True)
   246         else:
   221         if not kwargs:
   247             if not kwargs:
   222             kwargs['password'] = str(login)
   248                 kwargs['password'] = str(login)
   223         self.set_cnx(dbapi._repo_connect(self.repo, unicode(login), **kwargs))
   249             clt_cnx = repoapi.connect(self.repo, login, **kwargs)
   224         self.websession = dbapi.DBAPISession(self.cnx)
   250         self.set_cnx(clt_cnx)
   225         if autoclose:
   251         clt_cnx.__enter__()
   226             return TestCaseConnectionProxy(self, self.cnx)
   252         return TestCaseConnectionProxy(self, clt_cnx)
   227         return self.cnx
       
   228 
   253 
   229     def restore_connection(self):
   254     def restore_connection(self):
   230         if not self.cnx is self._orig_cnx[0]:
   255         self._pop_custom_cnx()
   231             if not self.cnx._closed:
   256 
   232                 self.cnx.close()
   257     def _pop_custom_cnx(self):
   233         cnx, self.websession = self._orig_cnx
   258         if self._current_clt_cnx is not None:
   234         self.set_cnx(cnx)
   259             if self._current_clt_cnx._open:
       
   260                 self._current_clt_cnx.close()
       
   261             if not  self._current_session.closed:
       
   262                 self.repo.close(self._current_session.id)
       
   263             self._current_clt_cnx = None
       
   264             self._current_session = None
   235 
   265 
   236     #XXX this doesn't need to a be classmethod anymore
   266     #XXX this doesn't need to a be classmethod anymore
   237     def _init_repo(self):
   267     def _init_repo(self):
   238         """init the repository and connection to it.
   268         """init the repository and connection to it.
   239         """
   269         """
   241         self.init_config(self.config)
   271         self.init_config(self.config)
   242         # get or restore and working db.
   272         # get or restore and working db.
   243         db_handler = devtools.get_test_db_handler(self.config)
   273         db_handler = devtools.get_test_db_handler(self.config)
   244         db_handler.build_db_cache(self.test_db_id, self.pre_setup_database)
   274         db_handler.build_db_cache(self.test_db_id, self.pre_setup_database)
   245 
   275 
   246         self.repo, cnx = db_handler.get_repo_and_cnx(self.test_db_id)
   276         db_handler.restore_database(self.test_db_id)
       
   277         self.repo = db_handler.get_repo(startup=True)
       
   278         # get an admin session (without actual login)
       
   279         sources = db_handler.config.sources()
       
   280         login = unicode(sources['admin']['login'])
       
   281         with self.repo.internal_session() as session:
       
   282             rset = session.execute('CWUser U WHERE U login %(u)s', {'u': login})
       
   283             user = rset.get_entity(0, 0)
       
   284             user.groups
       
   285             user.properties
       
   286             from cubicweb.server.session import Session
       
   287             self._admin_session = Session(user, self.repo)
       
   288             self.repo._sessions[self._admin_session.id] =  self._admin_session
       
   289             self._admin_session.user._cw = self._admin_session
       
   290         self._admin_clt_cnx = repoapi.ClientConnection(self._admin_session)
       
   291 
   247         # no direct assignation to cls.cnx anymore.
   292         # no direct assignation to cls.cnx anymore.
   248         # cnx is now an instance property that use a class protected attributes.
   293         # cnx is now an instance property that use a class protected attributes.
   249         self.set_cnx(cnx)
   294         self._cnxs.add(self._admin_clt_cnx)
   250         self.websession = dbapi.DBAPISession(cnx, self.admlogin)
   295         self._admin_clt_cnx.__enter__()
   251         self._orig_cnx = (cnx, self.websession)
       
   252         self.config.repository = lambda x=None: self.repo
   296         self.config.repository = lambda x=None: self.repo
   253 
   297 
   254     # db api ##################################################################
   298     # db api ##################################################################
   255 
   299 
   256     @nocoverage
   300     @nocoverage
   257     def cursor(self, req=None):
   301     def cursor(self, req=None):
   258         return self.cnx.cursor(req or self.request())
   302         if req is not None:
       
   303             return req.cnx
       
   304         else:
       
   305             return self.cnx
   259 
   306 
   260     @nocoverage
   307     @nocoverage
   261     def execute(self, rql, args=None, eidkey=None, req=None):
   308     def execute(self, rql, args=None, eidkey=None, req=None):
   262         """executes <rql>, builds a resultset, and returns a couple (rset, req)
   309         """executes <rql>, builds a resultset, and returns a couple (rset, req)
   263         where req is a FakeRequest
   310         where req is a FakeRequest
   285             self.session.set_cnxset() # ensure cnxset still set after commit
   332             self.session.set_cnxset() # ensure cnxset still set after commit
   286 
   333 
   287     requestcls = fake.FakeRequest
   334     requestcls = fake.FakeRequest
   288     def request(self, rollbackfirst=False, url=None, headers={}, **kwargs):
   335     def request(self, rollbackfirst=False, url=None, headers={}, **kwargs):
   289         """return a web ui request"""
   336         """return a web ui request"""
       
   337         if rollbackfirst:
       
   338             self.cnx.rollback()
   290         req = self.requestcls(self.vreg, url=url, headers=headers, form=kwargs)
   339         req = self.requestcls(self.vreg, url=url, headers=headers, form=kwargs)
   291         if rollbackfirst:
   340         req.set_cnx(self.cnx)
   292             self.websession.cnx.rollback()
       
   293         req.set_session(self.websession)
       
   294         return req
   341         return req
   295 
       
   296     @property
       
   297     def adminsession(self):
       
   298         """return current server side session (using default manager account)"""
       
   299         return self.repo._sessions[self._orig_cnx[0].sessionid]
       
   300 
       
   301 
       
   302 
   342 
   303     # server side db api #######################################################
   343     # server side db api #######################################################
   304 
   344 
   305     def sexecute(self, rql, args=None, eid_key=None):
   345     def sexecute(self, rql, args=None, eid_key=None):
   306         if eid_key is not None:
   346         if eid_key is not None:
   407         self.commit()
   447         self.commit()
   408         MAILBOX[:] = [] # reset mailbox
   448         MAILBOX[:] = [] # reset mailbox
   409 
   449 
   410     def tearDown(self):
   450     def tearDown(self):
   411         # XXX hack until logilab.common.testlib is fixed
   451         # XXX hack until logilab.common.testlib is fixed
       
   452         if self._admin_clt_cnx is not None:
       
   453             if self._admin_clt_cnx._open:
       
   454                 self._admin_clt_cnx.close()
       
   455             self._admin_clt_cnx = None
       
   456         if self._admin_session is not None:
       
   457             if not self._admin_session.closed:
       
   458                 self.repo.close(self._admin_session.id)
       
   459             self._admin_session = None
   412         while self._cleanups:
   460         while self._cleanups:
   413             cleanup, args, kwargs = self._cleanups.pop(-1)
   461             cleanup, args, kwargs = self._cleanups.pop(-1)
   414             cleanup(*args, **kwargs)
   462             cleanup(*args, **kwargs)
   415 
   463 
   416     def _patch_SendMailOp(self):
   464     def _patch_SendMailOp(self):
   435     # user / session management ###############################################
   483     # user / session management ###############################################
   436 
   484 
   437     def user(self, req=None):
   485     def user(self, req=None):
   438         """return the application schema"""
   486         """return the application schema"""
   439         if req is None:
   487         if req is None:
   440             req = self.request()
   488             return self.request().user
   441             return self.cnx.user(req)
       
   442         else:
   489         else:
   443             return req.user
   490             return req.user
   444 
   491 
   445     @iclassmethod # XXX turn into a class method
   492     @iclassmethod # XXX turn into a class method
   446     def create_user(self, req, login=None, groups=('users',), password=None,
   493     def create_user(self, req, login=None, groups=('users',), password=None,