merge stable
authorAlexandre Fayolle <alexandre.fayolle@logilab.fr>
Wed, 01 Sep 2010 15:56:21 +0200
branchstable
changeset 6171 8af7d755f05f
parent 6170 394eebfeea8a (current diff)
parent 6168 a52ea49434c8 (diff)
child 6172 9987f5525e20
child 6179 2506ab5d82d7
merge
--- a/devtools/__init__.py	Thu Aug 26 15:05:03 2010 +0200
+++ b/devtools/__init__.py	Wed Sep 01 15:56:21 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 15:05:03 2010 +0200
+++ b/devtools/testlib.py	Wed Sep 01 15:56:21 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 15:05:03 2010 +0200
+++ b/doc/book/en/devrepo/testing.rst	Wed Sep 01 15:56:21 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 15:05:03 2010 +0200
+++ b/hooks/syncschema.py	Wed Sep 01 15:56:21 2010 +0200
@@ -521,10 +521,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 15:05:03 2010 +0200
+++ b/server/test/data/migratedapp/schema.py	Wed Sep 01 15:56:21 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 15:05:03 2010 +0200
+++ b/server/test/unittest_migractions.py	Wed Sep 01 15:56:21 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 15:05:03 2010 +0200
+++ b/test/unittest_entity.py	Wed Sep 01 15:56:21 2010 +0200
@@ -329,7 +329,7 @@
         e['content'] = 'du *texte*'
         e['content_format'] = 'text/plain'
         self.assertEquals(e.printable_value('content'),
-                          '<p>\ndu *texte*\n</p>')
+                          '<p>\ndu *texte*<br/>\n</p>')
         e['title'] = 'zou'
         e['content'] = '''\
 a title