# HG changeset patch # User Alexandre Fayolle # Date 1283349381 -7200 # Node ID 8af7d755f05fc516e45ef272e9fe165a409faeaf # Parent 394eebfeea8a39af8555ffb699c62d92a5b83cf2# Parent a52ea49434c8325f8e9d747aab4591fa074913ab merge diff -r 394eebfeea8a -r 8af7d755f05f devtools/__init__.py --- 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) diff -r 394eebfeea8a -r 8af7d755f05f devtools/testlib.py --- 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 diff -r 394eebfeea8a -r 8af7d755f05f doc/book/en/devrepo/testing.rst --- 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 ------------------------ diff -r 394eebfeea8a -r 8af7d755f05f hooks/syncschema.py --- 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 diff -r 394eebfeea8a -r 8af7d755f05f server/test/data/migratedapp/schema.py --- 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') diff -r 394eebfeea8a -r 8af7d755f05f server/test/unittest_migractions.py --- 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() diff -r 394eebfeea8a -r 8af7d755f05f test/unittest_entity.py --- 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'), - '

\ndu *texte*\n

') + '

\ndu *texte*
\n

') e['title'] = 'zou' e['content'] = '''\ a title