backport stable
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Sat, 09 Oct 2010 00:05:49 +0200
changeset 6425 8d7c2fd2ac66
parent 6423 c560f8d9faee (current diff)
parent 6424 f443a2b8a5c7 (diff)
child 6426 541659c39f6a
backport stable
devtools/__init__.py
devtools/testlib.py
entity.py
web/webconfig.py
--- a/devtools/__init__.py	Fri Oct 08 17:07:46 2010 +0200
+++ b/devtools/__init__.py	Sat Oct 09 00:05:49 2010 +0200
@@ -179,7 +179,7 @@
         # threads
         return True
 
-
+# XXX merge with BaseApptestConfiguration ?
 class ApptestConfiguration(BaseApptestConfiguration):
 
     def __init__(self, appid, log_threshold=logging.CRITICAL, sourcefile=None):
@@ -187,6 +187,7 @@
         self.init_repository = sourcefile is None
         self.sourcefile = sourcefile
 
+
 class RealDatabaseConfiguration(ApptestConfiguration):
     """configuration class for tests to run on a real database.
 
@@ -209,6 +210,7 @@
     db_require_setup = False    # skip init_db / reset_db steps
     read_instance_schema = True # read schema from database
 
+
 # test database handling #######################################################
 
 def init_test_database(config=None, configdir='data'):
@@ -231,7 +233,6 @@
         install_sqlite_patch(repo.querier)
     return repo, cnx
 
-
 def reset_test_database(config):
     """init a test database for a specific driver"""
     if not config.db_require_setup:
@@ -331,7 +332,6 @@
         dbfile = config.sources()['system']['db-name']
         shutil.copy(dbfile, '%s-template' % dbfile)
 
-
 def install_sqlite_patch(querier):
     """This patch hotfixes the following sqlite bug :
        - http://www.sqlite.org/cvstrac/tktview?tn=1327,33
--- a/devtools/cwwindmill.py	Fri Oct 08 17:07:46 2010 +0200
+++ b/devtools/cwwindmill.py	Sat Oct 09 00:05:49 2010 +0200
@@ -25,6 +25,7 @@
 
 
 import os, os.path as osp
+from logging import getLogger, ERROR
 import sys
 
 # imported by default to simplify further import statements
@@ -32,6 +33,7 @@
 
 import windmill
 from windmill.dep import functest
+from windmill.bin.admin_lib import configure_global_settings, setup, teardown
 
 from cubicweb.devtools.httptest import CubicWebServerTC
 
@@ -44,46 +46,81 @@
 unittestreporter = UnitTestReporter()
 functest.reports.register_reporter(unittestreporter)
 
-class WindmillUnitTestCase(TestCase):
+
+# Windmill use case are written with no anonymous user
+from cubicweb.devtools.httptest import CubicWebServerConfig
+CubicWebServerConfig.anonymous_logged = False
+
+class CubicWebWindmillUseCase(CubicWebServerTC):
+    """basic class for Windmill use case tests
+
+    If you want to change cubicweb test server parameters, define a new
+    :class:`CubicWebServerConfig` and override the :var:`configcls`
+    attribute:
+
+        configcls = CubicWebServerConfig
+
+    From Windmill configuration:
+
+    .. attribute:: browser
+        identification string (firefox|ie|safari|chrome) (firefox by default)
+    .. attribute :: edit_test
+        load and edit test for debugging (False by default)
+    .. attribute:: test_dir (optional)
+        testing file path or directory (windmill directory under your unit case
+        file by default)
+
+    Examples:
+
+        browser = 'firefox'
+        test_dir = osp.join(__file__, 'windmill')
+        edit_test = False
+
+    If you prefer, you can put here the use cases recorded by windmill GUI
+    (services transformer) instead of the windmill sub-directory
+    You can change `test_dir` as following:
+
+        test_dir = __file__
+
+    Instead of toggle `edit_test` value, try `pytest -i`
+    """
+    browser = 'firefox'
+    edit_test = "-i" in sys.argv # detection for pytest invocation
+
+    def _test_dir(self):
+        """access to class attribute if possible or make assumption
+        of expected directory"""
+        try:
+            return getattr(self, 'test_dir')
+        except AttributeError:
+            if os.path.basename(sys.argv[0]) == "pytest":
+                test_dir = os.getcwd()
+            else:
+                import inspect
+                test_dir = os.path.dirname(inspect.stack()[-1][1])
+            return osp.join(test_dir, 'windmill')
+
     def setUp(self):
+        # Start CubicWeb session before running the server to populate self.vreg
+        CubicWebServerTC.setUp(self)
+        # XXX reduce log output (should be done in a cleaner way)
+        # windmill fu** up our logging configuration
+        for logkey in ('windmill', 'logilab', 'cubicweb'):
+            getLogger(logkey).setLevel(ERROR)
+        self.test_dir = self._test_dir()
+        msg = "provide a valid 'test_dir' as the given test file/dir (current: %s)"
+        assert os.path.exists(self.test_dir), (msg % self.test_dir)
+        # windmill setup
         windmill.stdout, windmill.stdin = sys.stdout, sys.stdin
-        from windmill.bin.admin_lib import configure_global_settings, setup
         configure_global_settings()
-        windmill.settings['TEST_URL'] = self.test_url
+        windmill.settings['TEST_URL'] = self.config['base-url']
         if hasattr(self,"windmill_settings"):
             for (setting,value) in self.windmill_settings.iteritems():
                 windmill.settings[setting] = value
         self.windmill_shell_objects = setup()
 
     def tearDown(self):
-        from windmill.bin.admin_lib import teardown
         teardown(self.windmill_shell_objects)
-
-
-class CubicWebWindmillUseCase(CubicWebServerTC, WindmillUnitTestCase):
-    """basic class for Windmill use case tests
-
-    :param browser: browser identification string (firefox|ie|safari|chrome) (firefox by default)
-    :param test_dir: testing file path or directory (./windmill by default)
-    :param edit_test: load and edit test for debugging (False by default)
-    """
-    browser = 'firefox'
-    test_dir = osp.join(os.getcwd(), 'windmill')
-    edit_test = "-i" in sys.argv # detection for pytest invocation
-
-    def setUp(self):
-        # reduce log output
-        from logging import getLogger, ERROR
-        getLogger('cubicweb').setLevel(ERROR)
-        getLogger('logilab').setLevel(ERROR)
-        getLogger('windmill').setLevel(ERROR)
-        # Start CubicWeb session before running the server to populate self.vreg
-        CubicWebServerTC.setUp(self)
-        assert os.path.exists(self.test_dir), "provide 'test_dir' as the given test file/dir"
-        WindmillUnitTestCase.setUp(self)
-
-    def tearDown(self):
-        WindmillUnitTestCase.tearDown(self)
         CubicWebServerTC.tearDown(self)
 
     def testWindmill(self):
@@ -91,8 +128,8 @@
             # see windmill.bin.admin_options.Firebug
             windmill.settings['INSTALL_FIREBUG'] = 'firebug'
             windmill.settings.setdefault('MOZILLA_PLUGINS', []).extend(
-                '/usr/share/mozilla-extensions/',
-                '/usr/share/xul-ext/')
+                ['/usr/share/mozilla-extensions/',
+                 '/usr/share/xul-ext/'])
         controller = self.windmill_shell_objects['start_' + self.browser]()
         self.windmill_shell_objects['do_test'](self.test_dir,
                                                load=self.edit_test,
--- a/devtools/httptest.py	Fri Oct 08 17:07:46 2010 +0200
+++ b/devtools/httptest.py	Sat Oct 09 00:05:49 2010 +0200
@@ -25,11 +25,13 @@
 import threading
 import socket
 import httplib
+from urlparse import urlparse
 
 from twisted.internet import reactor, error
 
 from cubicweb.etwist.server import run
 from cubicweb.devtools.testlib import CubicWebTC
+from cubicweb.devtools import ApptestConfiguration
 
 
 def get_available_port(ports_scan):
@@ -43,6 +45,8 @@
     :type ports_range: list
     :param ports_range: range of ports to test
     :rtype: int
+
+    .. see:: :func:`test.test_support.bind_port`
     """
     for port in ports_scan:
         try:
@@ -56,40 +60,52 @@
     raise RuntimeError('get_available_port([ports_range]) cannot find an available port')
 
 
-class CubicWebServerTC(CubicWebTC):
-    """basic class for running test server
+class CubicWebServerConfig(ApptestConfiguration):
+    """basic configuration class for configuring test server
 
     :param ports_range: range of http ports to test (range(7000, 8000) by default)
     :type ports_range: iterable
     :param anonymous_logged: is anonymous user logged by default ? (True by default)
     :type anonymous_logged: bool
-    :param test_url: base url used by server
-    :param test_host: server host
-    :param test_port: server port
+    :param port: server port (optional, used to force value)
+    :type port: int
 
     The first port found as available in `ports_range` will be used to launch
     the test server
     """
-    ports_range = range(7000, 8000)
     # anonymous is logged by default in cubicweb test cases
     anonymous_logged = True
-    test_host='127.0.0.1'
+    ports_range = range(7000, 8000)
+
+    def default_base_url(self):
+        port = self['port'] or get_available_port(self.ports_range)
+        self.global_set_option('port', port) # force rewrite here
+        return 'http://127.0.0.1:%d/' % self['port']
+
+    def pyro_enabled(self):
+        return False
 
+    def load_configuration(self):
+        super(CubicWebServerConfig, self).load_configuration()
+        self.global_set_option('base-url', self.default_base_url())
+        if not self.anonymous_logged:
+            self.global_set_option('anonymous-user', None)
+        else:
+            self.global_set_option('anonymous-user', 'anon')
+            self.global_set_option('anonymous-password', 'anon')
+        self.global_set_option('force-html-content-type', True)
+        # no undo support in tests
+        self.global_set_option('undo-support', '')
 
 
-    @property
-    def test_url(self):
-        return 'http://%s:%d/' % (self.test_host, self.test_port)
+class CubicWebServerTC(CubicWebTC):
+    """class for running test server
 
-    def init_server(self):
-        self.test_port = get_available_port(self.ports_range)
-        self.config['port'] = self.test_port
-        self.config['base-url'] = self.test_url
-        self.config['force-html-content-type'] = True
-        self.config['pyro-server'] = False
+    :cvar: :ref:`CubicWebServerConfig` class
+    """
+    configcls = CubicWebServerConfig
 
     def start_server(self):
-        self.config.pyro_enabled = lambda : False
         # use a semaphore to avoid starting test while the http server isn't
         # fully initilialized
         semaphore = threading.Semaphore(0)
@@ -103,15 +119,16 @@
         t = threading.Thread(target=safe_run, name='cubicweb_test_web_server',
                              args=(self.config, self.vreg, True))
         self.web_thread = t
-        if not self.anonymous_logged:
-                self.config.global_set_option('anonymous-user', None)
         t.start()
         semaphore.acquire()
         if not self.web_thread.isAlive():
             # XXX race condition with actual thread death
             raise RuntimeError('Could not start the web server')
         #pre init utils connection
-        self._web_test_cnx = httplib.HTTPConnection(self.test_host, self.test_port)
+        parseurl = urlparse(self.config['base-url'])
+        assert parseurl.port == self.config['port']
+        self._web_test_cnx = httplib.HTTPConnection(parseurl.hostname,
+                                                    parseurl.port)
         self._ident_cookie = None
 
     def stop_server(self, timeout=15):
@@ -169,7 +186,6 @@
 
     def setUp(self):
         CubicWebTC.setUp(self)
-        self.init_server()
         self.start_server()
 
     def tearDown(self):
@@ -179,4 +195,3 @@
             # Server could be launched manually
             print err
         CubicWebTC.tearDown(self)
-
--- a/devtools/qunit.py	Fri Oct 08 17:07:46 2010 +0200
+++ b/devtools/qunit.py	Sat Oct 09 00:05:49 2010 +0200
@@ -106,8 +106,6 @@
            return osp.abspath(osp.join(dirname,path))
         return path
 
-
-
     def test_javascripts(self):
         for args in self.all_js_tests:
             test_file = self.abspath(args[0])
@@ -130,12 +128,11 @@
         for data in data_files:
             assert osp.exists(data), data
 
-
         # generate html test file
         jquery_dir = 'file://' + self.config.locate_resource('jquery.js')[0]
         html_test_file = NamedTemporaryFile(suffix='.html')
         html_test_file.write(make_qunit_html(test_file, depends,
-                             server_data=(self.test_host, self.test_port),
+                             base_url=self.config['base-url'],
                              web_data_path=jquery_dir))
         html_test_file.flush()
         # copying data file
@@ -210,21 +207,19 @@
         self._log_stack.append('%s: %s' % (result, message))
 
 
-
 def cw_path(*paths):
   return file_path(osp.join(cubicweb.CW_SOFTWARE_ROOT, *paths))
 
 def file_path(path):
     return 'file://' + osp.abspath(path)
 
-def build_js_script( host, port):
+def build_js_script(host):
     return """
     var host = '%s';
-    var port = '%s';
 
     QUnit.moduleStart = function (name) {
       jQuery.ajax({
-                  url: 'http://'+host+':'+port+'/qunit_result',
+                  url: host+'/qunit_result',
                  data: {"event": "module_start",
                         "name": name},
                  async: false});
@@ -232,7 +227,7 @@
 
     QUnit.testDone = function (name, failures, total) {
       jQuery.ajax({
-                  url: 'http://'+host+':'+port+'/qunit_result',
+                  url: host+'/qunit_result',
                  data: {"event": "test_done",
                         "name": name,
                         "failures": failures,
@@ -242,7 +237,7 @@
 
     QUnit.done = function (failures, total) {
       jQuery.ajax({
-                   url: 'http://'+host+':'+port+'/qunit_result',
+                   url: host+'/qunit_result',
                    data: {"event": "done",
                           "failures": failures,
                           "total":total},
@@ -252,15 +247,15 @@
 
     QUnit.log = function (result, message) {
       jQuery.ajax({
-                   url: 'http://'+host+':'+port+'/qunit_result',
+                   url: host+'/qunit_result',
                    data: {"event": "log",
                           "result": result,
                           "message": message},
                    async: false});
     }
-    """ % (host, port)
+    """ % host
 
-def make_qunit_html(test_file, depends=(), server_data=None,
+def make_qunit_html(test_file, depends=(), base_url=None,
                     web_data_path=cw_path('web', 'data')):
     """"""
     data = {
@@ -276,11 +271,10 @@
     <script src="%(web_test)s/cwmock.js" type="text/javascript"></script>
     <script src="%(web_test)s/qunit.js" type="text/javascript"></script>'''
     % data]
-    if server_data is not None:
-        host, port = server_data
+    if base_url is not None:
         html.append('<!-- result report tools -->')
         html.append('<script type="text/javascript">')
-        html.append(build_js_script(host, port))
+        html.append(build_js_script(base_url))
         html.append('</script>')
     html.append('<!-- Test script dependencies (tested code for example) -->')
 
@@ -303,9 +297,5 @@
 
 
 
-
-
-
-
 if __name__ == '__main__':
     unittest_main()
--- a/devtools/repotest.py	Fri Oct 08 17:07:46 2010 +0200
+++ b/devtools/repotest.py	Sat Oct 09 00:05:49 2010 +0200
@@ -158,7 +158,10 @@
         ExecutionPlan._check_permissions = _dummy_check_permissions
         rqlannotation._select_principal = _select_principal
         if self.backend is not None:
-            dbhelper = get_db_helper(self.backend)
+            try:
+                dbhelper = get_db_helper(self.backend)
+            except ImportError, ex:
+                self.skipTest(str(ex))
             self.o = SQLGenerator(self.schema, dbhelper)
 
     def tearDown(self):
--- a/devtools/test/unittest_httptest.py	Fri Oct 08 17:07:46 2010 +0200
+++ b/devtools/test/unittest_httptest.py	Sat Oct 09 00:05:49 2010 +0200
@@ -1,8 +1,7 @@
-from logilab.common.testlib import TestCase, unittest_main, tag
-from cubicweb.devtools.httptest import CubicWebServerTC
+import httplib
 
-import httplib
-from os import path as osp
+from cubicweb.devtools.httptest import CubicWebServerTC
+from cubicweb.devtools.httptest import CubicWebServerConfig
 
 
 class TwistedCWAnonTC(CubicWebServerTC):
@@ -17,15 +16,16 @@
         response = self.web_get()
         self.assertEqual(response.status, httplib.OK)
 
-
     def test_base_url(self):
-        if self.test_url not in self.web_get().read():
+        if self.config['base-url'] not in self.web_get().read():
             self.fail('no mention of base url in retrieved page')
 
 
 class TwistedCWIdentTC(CubicWebServerTC):
 
-    anonymous_logged = False
+    def setUp(self):
+        CubicWebServerConfig.anonymous_logged = False
+        CubicWebServerTC.setUp(self)
 
     def test_response_denied(self):
         response = self.web_get()
@@ -48,4 +48,5 @@
 
 
 if __name__ == '__main__':
+    from logilab.common.testlib import unittest_main
     unittest_main()
--- a/devtools/testlib.py	Fri Oct 08 17:07:46 2010 +0200
+++ b/devtools/testlib.py	Sat Oct 09 00:05:49 2010 +0200
@@ -71,7 +71,6 @@
         after = before
     return center - before <= line_no <= center + after
 
-
 def unprotected_entities(schema, strict=False):
     """returned a set of each non final entity type, excluding "system" entities
     (eg CWGroup, CWUser...)
@@ -82,7 +81,6 @@
         protected_entities = yams.schema.BASE_TYPES.union(SYSTEM_ENTITIES)
     return set(schema.entities()) - protected_entities
 
-
 def refresh_repo(repo, resetschema=False, resetvreg=False):
     for pool in repo.pools:
         pool.close(True)
@@ -144,6 +142,7 @@
 
 cwconfig.SMTP = MockSMTP
 
+
 class TestCaseConnectionProxy(object):
     """thin wrapper around `cubicweb.dbapi.Connection` context-manager
     used in CubicWebTC (cf. `cubicweb.devtools.testlib.CubicWebTC.login` method)
@@ -192,8 +191,9 @@
 
     @classproperty
     def config(cls):
-        """return the configuration object. Configuration is cached on the test
-        class.
+        """return the configuration object
+
+        Configuration is cached on the test class.
         """
         try:
             return cls.__dict__['_config']
@@ -204,7 +204,12 @@
 
     @classmethod
     def init_config(cls, config):
-        """configuration initialization hooks. You may want to override this."""
+        """configuration initialization hooks.
+
+        You may only want to override here the configuraton logic.
+
+        Otherwise, consider to use a different :class:`ApptestConfiguration`
+        defined in the `configcls` class attribute"""
         source = config.sources()['system']
         cls.admlogin = unicode(source['db-user'])
         cls.admpassword = source['db-password']
@@ -226,7 +231,7 @@
         config.global_set_option('default-dest-addrs', send_to)
         config.global_set_option('sender-name', 'cubicweb-test')
         config.global_set_option('sender-addr', 'cubicweb-test@logilab.fr')
-        config.global_set_option('base-url', BASE_URL)
+        
         # web resources
         try:
             config.global_set_option('embed-allowed', re.compile('.*'))
--- a/doc/book/en/tutorials/tools/windmill.rst	Fri Oct 08 17:07:46 2010 +0200
+++ b/doc/book/en/tutorials/tools/windmill.rst	Sat Oct 09 00:05:49 2010 +0200
@@ -100,6 +100,23 @@
 Integrate Windmill tests into CubicWeb
 ======================================
 
+Set environment
+---------------
+
+You have to create a new unit test file and a `windmill` directory and copy all
+your windmill use case into it.
+
+.. sourcecode:: python
+
+    # test_windmill.py
+
+    # Run all scenarii found in windmill directory
+    from cubicweb.devtools.cwwindmill import (CubicWebWindmillUseCase,
+                                              unittest_main)
+
+    if __name__ == '__main__':
+        unittest_main()
+
 Run your tests
 --------------
 
@@ -114,9 +131,34 @@
 to run test instance server on localhost. In the general case, You've no need
 to change anything.
 
-Check the :class:`cubicweb.devtools.cwwindmill.CubicWebServerTC` class for server
-parameters and :class:`cubicweb.devtools.cwwindmill.CubicWebWindmillUseCase` for
-Windmill configuration.
+Check :class:`cubicweb.devtools.cwwindmill.CubicWebWindmillUseCase` for
+Windmill configuration. You can edit windmill settings with following class attributes:
+
+* browser
+  identification string (firefox|ie|safari|chrome) (firefox by default)
+* test_dir
+  testing file path or directory (windmill directory under your unit case
+  file by default)
+* edit_test
+  load and edit test for debugging (False by default)
+
+Examples:
+
+    browser = 'firefox'
+    test_dir = osp.join(__file__, 'windmill')
+    edit_test = False
+
+If you want to change cubicweb test server parameters, you can check class
+variables from :class:`CubicWebServerConfig` or inherit it with overriding the
+:var:`configcls` attribute in :class:`CubicWebServerTC` ::
+
+.. sourcecode:: python
+
+    class OtherCubicWebServerConfig(CubicWebServerConfig):
+        port = 9999
+
+    class NewCubicWebServerTC(CubicWebServerTC):
+        configcls = OtherCubicWebServerConfig
 
 For instance, CubicWeb framework windmill tests can be manually run by::
 
--- a/entity.py	Fri Oct 08 17:07:46 2010 +0200
+++ b/entity.py	Sat Oct 09 00:05:49 2010 +0200
@@ -1108,7 +1108,7 @@
 
     def __get__(self, eobj, eclass):
         if eobj is None:
-            raise AttributeError('%s cannot be only be accessed from instances'
+            raise AttributeError('%s can only be accessed from instances'
                                  % self._rtype)
         return eobj.related(self._rtype, self._role, entities=True)
 
--- a/web/test/test_windmill.py	Fri Oct 08 17:07:46 2010 +0200
+++ b/web/test/test_windmill.py	Sat Oct 09 00:05:49 2010 +0200
@@ -1,48 +1,6 @@
-import os, os.path as osp
-
-from cubicweb.devtools import cwwindmill
-
-
-class CubicWebWindmillUseCase(cwwindmill.CubicWebWindmillUseCase):
-    """class for windmill use case tests
-
-    From test server parameters:
-
-    :params ports_range: range of http ports to test (range(7000, 8000) by default)
-    :type ports_range: iterable
-    :param anonymous_logged: is anonymous user logged by default ?
-    :type anonymous_logged: bool
-
-    The first port found as available in `ports_range` will be used to launch
-    the test server
-
-    Instead of toggle `edit_test` value, try `pytest -i`
-
-    From Windmill configuration:
-
-    :param browser: browser identification string (firefox|ie|safari|chrome) (firefox by default)
-    :param test_dir: testing file path or directory (./windmill by default)
-    :param edit_test: load and edit test for debugging (False by default)
-    """
-    #ports_range = range(7000, 8000)
-    anonymous_logged = False
-    #browser = 'firefox'
-    #test_dir = osp.join(os.getcwd(), 'windmill')
-    #edit_test = False
-
-    # If you prefer, you can put here the use cases recorded by windmill GUI
-    # (services transformer) instead of the windmill sub-directory
-    # You can change `test_dir` as following:
-    #test_dir = __file__
-
-
-from windmill.authoring import WindmillTestClient
-def test_usecase():
-    client = WindmillTestClient(__name__)
-    import pdb; pdb.set_trace()
-    client.open(url=u'/')
-#    ...
-
+# Run all scenarii found in windmill directory
+from cubicweb.devtools.cwwindmill import (CubicWebWindmillUseCase,
+                                          unittest_main)
 
 if __name__ == '__main__':
-    cwwindmill.unittest_main()
+    unittest_main()
--- a/web/views/wdoc.py	Fri Oct 08 17:07:46 2010 +0200
+++ b/web/views/wdoc.py	Sat Oct 09 00:05:49 2010 +0200
@@ -62,9 +62,11 @@
         snode = index[section.attrib['insertbefore']]
         node = snode.parent
         idx = node.getchildren().index(snode)
-    else:
+    elif 'appendto' in section.attrib:
         node = index[section.attrib['appendto']]
         idx = None
+    else:
+        node, idx = None, None
     return node, idx
 
 def build_toc(config):
@@ -79,6 +81,8 @@
         toc = parse(fpath).getroot()
         for section in toc:
             node, idx = get_insertion_point(section, index)
+            if node is None:
+                continue
             if idx is None:
                 node.append(section)
             else:
--- a/web/webconfig.py	Fri Oct 08 17:07:46 2010 +0200
+++ b/web/webconfig.py	Sat Oct 09 00:05:49 2010 +0200
@@ -202,8 +202,6 @@
           'help': 'use cubicweb.old.css instead of 3.9 cubicweb.css',
           'group': 'web', 'level': 2,
           }),
-
-
         ))
 
     def fckeditor_installed(self):