backport stable
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Mon, 06 Sep 2010 15:03:11 +0200
changeset 6173 eb386e473044
parent 6162 76bd320c5ace (current diff)
parent 6172 9987f5525e20 (diff)
child 6174 1a6ae4ef1716
backport stable
dataimport.py
devtools/testlib.py
hooks/syncschema.py
test/unittest_entity.py
web/uicfg.py
--- a/dataimport.py	Thu Aug 26 11:45:57 2010 +0200
+++ b/dataimport.py	Mon Sep 06 15:03:11 2010 +0200
@@ -576,7 +576,9 @@
         if self.commitevery is None:
             return self.get_data(datakey)
         else:
-            return callfunc_every(self.commitevery, self.store.commit, self.get_data(datakey))
+            return callfunc_every(self.store.commit,
+                                  self.commitevery,
+                                  self.get_data(datakey))
 
 
 
--- a/devtools/__init__.py	Thu Aug 26 11:45:57 2010 +0200
+++ b/devtools/__init__.py	Mon Sep 06 15:03:11 2010 +0200
@@ -96,6 +96,7 @@
     set_language = False
     read_instance_schema = False
     init_repository = True
+    db_require_setup = True
     options = cwconfig.merge_options(ServerConfiguration.options + (
         ('anonymous-user',
          {'type' : 'string',
@@ -195,6 +196,27 @@
         self.init_repository = sourcefile is None
         self.sourcefile = sourcefile
 
+class RealDatabaseConfiguration(ApptestConfiguration):
+    """configuration class for tests to run on a real database.
+
+    The intialization is done by specifying a source file path.
+
+    Important note: init_test_database / reset_test_database steps are
+    skipped. It's thus up to the test developer to implement setUp/tearDown
+    accordingly.
+
+    Example usage::
+
+      class MyTests(CubicWebTC):
+          _config = RealDatabseConfiguration('myapp',
+                                             sourcefile='/path/to/sources')
+          def test_something(self):
+              rset = self.execute('Any X WHERE X is CWUser')
+              self.view('foaf', rset)
+
+    """
+    db_require_setup = False    # skip init_db / reset_db steps
+    read_instance_schema = True # read schema from database
 
 # test database handling #######################################################
 
@@ -204,12 +226,13 @@
     config = config or TestServerConfiguration(configdir)
     sources = config.sources()
     driver = sources['system']['db-driver']
-    if driver == 'sqlite':
-        init_test_database_sqlite(config)
-    elif driver == 'postgres':
-        init_test_database_postgres(config)
-    else:
-        raise ValueError('no initialization function for driver %r' % driver)
+    if config.db_require_setup:
+        if driver == 'sqlite':
+            init_test_database_sqlite(config)
+        elif driver == 'postgres':
+            init_test_database_postgres(config)
+        else:
+            raise ValueError('no initialization function for driver %r' % driver)
     config._cubes = None # avoid assertion error
     repo, cnx = in_memory_cnx(config, unicode(sources['admin']['login']),
                               password=sources['admin']['password'] or 'xxx')
@@ -220,6 +243,8 @@
 
 def reset_test_database(config):
     """init a test database for a specific driver"""
+    if not config.db_require_setup:
+        return
     driver = config.sources()['system']['db-driver']
     if driver == 'sqlite':
         reset_test_database_sqlite(config)
--- a/devtools/testlib.py	Thu Aug 26 11:45:57 2010 +0200
+++ b/devtools/testlib.py	Mon Sep 06 15:03:11 2010 +0200
@@ -25,6 +25,7 @@
 import sys
 import re
 from urllib import unquote
+import urlparse
 from math import log
 from contextlib import contextmanager
 from warnings import warn
@@ -142,6 +143,29 @@
 
 cwconfig.SMTP = MockSMTP
 
+class TestCaseConnectionProxy(object):
+    """thin wrapper around `cubicweb.dbapi.Connection` context-manager
+    used in CubicWebTC (cf. `cubicweb.devtools.testlib.CubicWebTC.login` method)
+
+    It just proxies to the default connection context manager but
+    restores the original connection on exit.
+    """
+    def __init__(self, testcase, cnx):
+        self.testcase = testcase
+        self.cnx = cnx
+
+    def __getattr__(self, attrname):
+        return getattr(self.cnx, attrname)
+
+    def __enter__(self):
+        return self.cnx.__enter__()
+
+    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 ###############
 
@@ -331,7 +355,7 @@
             self._cnxs.append(self.cnx)
         if login == self.vreg.config.anonymous_user()[0]:
             self.cnx.anonymous_connection = True
-        return self.cnx
+        return TestCaseConnectionProxy(self, self.cnx)
 
     def restore_connection(self):
         if not self.cnx is self._orig_cnx[0]:
@@ -528,6 +552,30 @@
             raise
         return result
 
+    def req_from_url(self, url):
+        """parses `url` and builds the corresponding CW-web request
+
+        req.form will be setup using the url's query string
+        """
+        req = self.request()
+        if isinstance(url, unicode):
+            url = url.encode(req.encoding) # req.setup_params() expects encoded strings
+        querystring = urlparse.urlparse(url)[-2]
+        params = urlparse.parse_qs(querystring)
+        req.setup_params(params)
+        return req
+
+    def url_publish(self, url):
+        """takes `url`, uses application's app_resolver to find the
+        appropriate controller, and publishes the result.
+
+        This should pretty much correspond to what occurs in a real CW server
+        except the apache-rewriter component is not called.
+        """
+        req = self.req_from_url(url)
+        ctrlid, rset = self.app.url_resolver.process(req, req.relative_path(False))
+        return self.ctrl_publish(req, ctrlid)
+
     def expect_redirect(self, callback, req):
         """call the given callback with req as argument, expecting to get a
         Redirect exception
--- a/doc/book/en/devrepo/testing.rst	Thu Aug 26 11:45:57 2010 +0200
+++ b/doc/book/en/devrepo/testing.rst	Mon Sep 06 15:03:11 2010 +0200
@@ -212,6 +212,37 @@
   auto_populate cannot guess by itself; these must yield resultsets
   against which views may be selected.
 
+Testing on a real-life database
+-------------------------------
+
+The ``CubicWebTC`` class uses the `cubicweb.devtools.ApptestConfiguration`
+configuration class to setup its testing environment (database driver,
+user password, application home, and so on). The `cubicweb.devtools`
+module also provides a `RealDatabaseConfiguration`
+class that will read a regular cubicweb sources file to fetch all
+this information but will also prevent the database to be initalized
+and reset between tests.
+
+For a test class to use a specific configuration, you have to set
+the `_config` class attribute on the class as in:
+
+.. sourcecode:: python
+
+    from cubicweb.devtools import RealDatabaseConfiguration
+    from cubicweb.devtools.testlib import CubicWebTC
+
+    class BlogRealDatabaseTC(CubicWebTC):
+        _config = RealDatabaseConfiguration('blog',
+                                            sourcefile='/path/to/realdb_sources')
+
+        def test_blog_rss(self):
+	    req = self.request()
+	    rset = req.execute('Any B ORDERBY D DESC WHERE B is BlogEntry, '
+	                       'B created_by U, U login "logilab", B creation_date D')
+            self.view('rss', rset)
+
+
+
 Testing with other cubes
 ------------------------
 
--- a/hooks/syncschema.py	Thu Aug 26 11:45:57 2010 +0200
+++ b/hooks/syncschema.py	Mon Sep 06 15:03:11 2010 +0200
@@ -519,10 +519,16 @@
             insert_rdef_on_subclasses(session, eschema, rschema, rdefdef,
                                       {'composite': entity.composite})
         else:
+            if rschema.symmetric:
+                # for symmetric relations, rdefs will store relation definitions
+                # in both ways (i.e. (subj -> obj) and (obj -> subj))
+                relation_already_defined = len(rschema.rdefs) > 2
+            else:
+                relation_already_defined = len(rschema.rdefs) > 1
             # need to create the relation if no relation definition in the
             # schema and if it has not been added during other event of the same
             # transaction
-            if not (len(rschema.rdefs) > 1 or
+            if not (relation_already_defined or
                     rtype in session.transaction_data.get('createdtables', ())):
                 rschema = schema.rschema(rtype)
                 # create the necessary table
--- a/server/test/data/migratedapp/schema.py	Thu Aug 26 11:45:57 2010 +0200
+++ b/server/test/data/migratedapp/schema.py	Mon Sep 06 15:03:11 2010 +0200
@@ -138,6 +138,9 @@
     cp   = String(maxsize=12)
     ville= String(maxsize=32)
 
+class same_as(RelationDefinition):
+    subject = ('Societe',)
+    object = 'ExternalUri'
 
 class evaluee(RelationDefinition):
     subject = ('Personne', 'CWUser', 'Societe')
--- a/server/test/unittest_migractions.py	Thu Aug 26 11:45:57 2010 +0200
+++ b/server/test/unittest_migractions.py	Mon Sep 06 15:03:11 2010 +0200
@@ -545,5 +545,15 @@
         self.assertEquals(self.schema['Note'].specializes(), None)
         self.assertEquals(self.schema['Text'].specializes(), None)
 
+
+    def test_add_symmetric_relation_type(self):
+        same_as_sql = self.mh.sqlexec("SELECT sql FROM sqlite_master WHERE type='table' "
+                                      "and name='same_as_relation'")
+        self.failIf(same_as_sql)
+        self.mh.cmd_add_relation_type('same_as')
+        same_as_sql = self.mh.sqlexec("SELECT sql FROM sqlite_master WHERE type='table' "
+                                      "and name='same_as_relation'")
+        self.failUnless(same_as_sql)
+
 if __name__ == '__main__':
     unittest_main()
--- a/test/unittest_entity.py	Thu Aug 26 11:45:57 2010 +0200
+++ b/test/unittest_entity.py	Mon Sep 06 15:03:11 2010 +0200
@@ -16,9 +16,7 @@
 #
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-"""unit tests for cubicweb.web.views.entities module
-
-"""
+"""unit tests for cubicweb.web.views.entities module"""
 
 from datetime import datetime
 
@@ -329,7 +327,7 @@
         e.cw_attr_cache['content'] = 'du *texte*'
         e.cw_attr_cache['content_format'] = 'text/plain'
         self.assertEquals(e.printable_value('content'),
-                          '<p>\ndu *texte*\n</p>')
+                          '<p>\ndu *texte*<br/>\n</p>')
         e.cw_attr_cache['title'] = 'zou'
         e.cw_attr_cache['content'] = '''\
 a title
--- a/web/uicfg.py	Thu Aug 26 11:45:57 2010 +0200
+++ b/web/uicfg.py	Mon Sep 06 15:03:11 2010 +0200
@@ -407,7 +407,7 @@
 
     def tag_relation(self, key, tag):
         for tagkey in tag.iterkeys():
-            assert tagkey in self._keys
+            assert tagkey in self._keys, 'tag %r not in accepted tags: %r' % (tag, self._keys)
         return super(_ReleditTags, self).tag_relation(key, tag)
 
 reledit_ctrl = _ReleditTags('reledit')