PARTIAL: Using the repoapi in test
authorPierre-Yves David <pierre-yves.david@logilab.fr>
Tue, 25 Jun 2013 10:59:01 +0200
changeset 9070 4a803380f718
parent 9069 aff871b58ba0
child 9071 46885bfa4150
PARTIAL: Using the repoapi in test Test now use the repoapi.ClientConnection to access the repo. This is very big change. The current method to access the repo are kept for backward compatibility. The new methods will be introduced much later. The TestCase keep a ClientConnection To an admin session for the whole test. This changeset does not makes all tests pass without the next one that change set htt without the next one that change the http stack
devtools/fake.py
devtools/testlib.py
server/test/unittest_session.py
--- a/devtools/fake.py	Tue Jun 25 14:45:16 2013 +0200
+++ b/devtools/fake.py	Tue Jun 25 10:59:01 2013 +0200
@@ -24,7 +24,7 @@
 
 from cubicweb.req import RequestSessionBase
 from cubicweb.cwvreg import CWRegistryStore
-from cubicweb.web.request import CubicWebRequestBase
+from cubicweb.web.request import ConnectionCubicWebRequestBase
 
 from cubicweb.devtools import BASE_URL, BaseApptestConfiguration
 
@@ -53,7 +53,7 @@
         return {'system': {'db-driver': 'sqlite'}}
 
 
-class FakeRequest(CubicWebRequestBase):
+class FakeRequest(ConnectionCubicWebRequestBase):
     """test implementation of an cubicweb request object"""
 
     def __init__(self, *args, **kwargs):
--- a/devtools/testlib.py	Tue Jun 25 14:45:16 2013 +0200
+++ b/devtools/testlib.py	Tue Jun 25 10:59:01 2013 +0200
@@ -40,7 +40,7 @@
 from logilab.common.shellutils import getlogin
 
 from cubicweb import ValidationError, NoSelectableObject, AuthenticationError
-from cubicweb import cwconfig, dbapi, devtools, web, server
+from cubicweb import cwconfig, dbapi, devtools, web, server, repoapi
 from cubicweb.utils import json
 from cubicweb.sobjects import notification
 from cubicweb.web import Redirect, application
@@ -146,13 +146,13 @@
         return getattr(self.cnx, attrname)
 
     def __enter__(self):
-        return self.cnx.__enter__()
+        # already open
+        return self.cnx
 
     def __exit__(self, exctype, exc, tb):
         try:
             return self.cnx.__exit__(exctype, exc, tb)
         finally:
-            self.cnx.close()
             self.testcase.restore_connection()
 
 # base class for cubicweb tests requiring a full cw environments ###############
@@ -181,57 +181,87 @@
                   # stay on connection for leak detection purpose
 
     def __init__(self, *args, **kwargs):
-        self._cnx = None  # current connection
+        self._admin_session = None
+        self._admin_clt_cnx = None
+        self._current_session = None
+        self._current_clt_cnx = None
         self.repo = None
-        self.websession = None
         super(CubicWebTC, self).__init__(*args, **kwargs)
 
     # repository connection handling ###########################################
 
-    # Too much complicated stuff. the class doesn't need to bear the repo anymore
     def set_cnx(self, cnx):
-        self._cnxs.add(cnx)
-        self._cnx = cnx
+        # XXX we want to deprecate this
+        assert getattr(cnx, '_session', None) is not None
+        if cnx is self._admin_clt_cnx:
+            self._pop_custom_cnx()
+        else:
+            self._cnxs.add(cnx) # register the cns to make sure it is removed
+            self._current_session = cnx._session
+            self._current_clt_cnx = cnx
 
     @property
     def cnx(self):
-        return self._cnx
+        # XXX we want to deprecate this
+        clt_cnx = self._current_clt_cnx
+        if clt_cnx is None:
+            clt_cnx = self._admin_clt_cnx
+        return clt_cnx
 
     def _close_cnx(self):
+        """ensure that all cnx used by a test have been closed"""
         for cnx in list(self._cnxs):
-            if not cnx._closed:
+            if cnx._open and not cnx._session.closed:
                 cnx.rollback()
                 cnx.close()
             self._cnxs.remove(cnx)
 
     @property
     def session(self):
-        """return current server side session (using default manager account)"""
-        session = self.repo._sessions[self.cnx.sessionid]
+        """return current server side session"""
+        # XXX We want to use a srv_connection instead and deprecate this
+        # property
+        session = self._current_session
+        if session is None:
+            session = self._admin_session
+            session.set_cnx(self._admin_clt_cnx._cnxid)
         session.set_cnxset()
         return session
 
+    @property
+    def websession(self):
+        return self.session
+
+    @property
+    def adminsession(self):
+        """return current server side session (using default manager account)"""
+        return self._admin_session
+
     def login(self, login, **kwargs):
         """return a connection for the given login/password"""
+        __ = kwargs.pop('autoclose', True) # not used anymore
         if login == self.admlogin:
-            self.restore_connection()
             # definitly don't want autoclose when used as a context manager
-            return self.cnx
-        autoclose = kwargs.pop('autoclose', True)
-        if not kwargs:
-            kwargs['password'] = str(login)
-        self.set_cnx(dbapi._repo_connect(self.repo, unicode(login), **kwargs))
-        self.websession = dbapi.DBAPISession(self.cnx)
-        if autoclose:
-            return TestCaseConnectionProxy(self, self.cnx)
-        return self.cnx
+            clt_cnx = repoapi.ClientConnection(self._admin_session)
+        else:
+            if not kwargs:
+                kwargs['password'] = str(login)
+            clt_cnx = repoapi.connect(self.repo, login, **kwargs)
+        self.set_cnx(clt_cnx)
+        clt_cnx.__enter__()
+        return TestCaseConnectionProxy(self, clt_cnx)
 
     def restore_connection(self):
-        if not self.cnx is self._orig_cnx[0]:
-            if not self.cnx._closed:
-                self.cnx.close()
-        cnx, self.websession = self._orig_cnx
-        self.set_cnx(cnx)
+        self._pop_custom_cnx()
+
+    def _pop_custom_cnx(self):
+        if self._current_clt_cnx is not None:
+            if self._current_clt_cnx._open:
+                self._current_clt_cnx.close()
+            if not  self._current_session.closed:
+                self.repo.close(self._current_session.id)
+            self._current_clt_cnx = None
+            self._current_session = None
 
     #XXX this doesn't need to a be classmethod anymore
     def _init_repo(self):
@@ -243,19 +273,36 @@
         db_handler = devtools.get_test_db_handler(self.config)
         db_handler.build_db_cache(self.test_db_id, self.pre_setup_database)
 
-        self.repo, cnx = db_handler.get_repo_and_cnx(self.test_db_id)
+        db_handler.restore_database(self.test_db_id)
+        self.repo = db_handler.get_repo(startup=True)
+        # get an admin session (without actual login)
+        sources = db_handler.config.sources()
+        login = unicode(sources['admin']['login'])
+        with self.repo.internal_session() as session:
+            rset = session.execute('CWUser U WHERE U login %(u)s', {'u': login})
+            user = rset.get_entity(0, 0)
+            user.groups
+            user.properties
+            from cubicweb.server.session import Session
+            self._admin_session = Session(user, self.repo)
+            self.repo._sessions[self._admin_session.id] =  self._admin_session
+            self._admin_session.user._cw = self._admin_session
+        self._admin_clt_cnx = repoapi.ClientConnection(self._admin_session)
+
         # no direct assignation to cls.cnx anymore.
         # cnx is now an instance property that use a class protected attributes.
-        self.set_cnx(cnx)
-        self.websession = dbapi.DBAPISession(cnx, self.admlogin)
-        self._orig_cnx = (cnx, self.websession)
+        self._cnxs.add(self._admin_clt_cnx)
+        self._admin_clt_cnx.__enter__()
         self.config.repository = lambda x=None: self.repo
 
     # db api ##################################################################
 
     @nocoverage
     def cursor(self, req=None):
-        return self.cnx.cursor(req or self.request())
+        if req is not None:
+            return req.cnx
+        else:
+            return self.cnx
 
     @nocoverage
     def execute(self, rql, args=None, eidkey=None, req=None):
@@ -287,19 +334,12 @@
     requestcls = fake.FakeRequest
     def request(self, rollbackfirst=False, url=None, headers={}, **kwargs):
         """return a web ui request"""
+        if rollbackfirst:
+            self.cnx.rollback()
         req = self.requestcls(self.vreg, url=url, headers=headers, form=kwargs)
-        if rollbackfirst:
-            self.websession.cnx.rollback()
-        req.set_session(self.websession)
+        req.set_cnx(self.cnx)
         return req
 
-    @property
-    def adminsession(self):
-        """return current server side session (using default manager account)"""
-        return self.repo._sessions[self._orig_cnx[0].sessionid]
-
-
-
     # server side db api #######################################################
 
     def sexecute(self, rql, args=None, eid_key=None):
@@ -409,6 +449,14 @@
 
     def tearDown(self):
         # XXX hack until logilab.common.testlib is fixed
+        if self._admin_clt_cnx is not None:
+            if self._admin_clt_cnx._open:
+                self._admin_clt_cnx.close()
+            self._admin_clt_cnx = None
+        if self._admin_session is not None:
+            if not self._admin_session.closed:
+                self.repo.close(self._admin_session.id)
+            self._admin_session = None
         while self._cleanups:
             cleanup, args, kwargs = self._cleanups.pop(-1)
             cleanup(*args, **kwargs)
@@ -437,8 +485,7 @@
     def user(self, req=None):
         """return the application schema"""
         if req is None:
-            req = self.request()
-            return self.cnx.user(req)
+            return self.request().user
         else:
             return req.user
 
--- a/server/test/unittest_session.py	Tue Jun 25 14:45:16 2013 +0200
+++ b/server/test/unittest_session.py	Tue Jun 25 10:59:01 2013 +0200
@@ -30,6 +30,12 @@
 
     def test_hooks_control(self):
         session = self.session
+        # this test check the "old" behavior of session with automatic connection management
+        # close the default cnx, we do nto want it to interfer with the test
+        self.cnx.close()
+        # open a dedicated one
+        session.set_cnx('Some-random-cnx-unrelated-to-the-default-one')
+        # go test go
         self.assertEqual(HOOKS_ALLOW_ALL, session.hooks_mode)
         self.assertEqual(set(), session.disabled_hook_categories)
         self.assertEqual(set(), session.enabled_hook_categories)