# HG changeset patch # User Julien Jehannet # Date 1286544531 -7200 # Node ID f443a2b8a5c76dbb1387b81ead9fc133326149a0 # Parent 4c14be06e5576511e30ac3ae853620aaaac0f8dc [devtools] refactor http server initialization in a much saner way Use a new ApptestConfiguration to group server paramater. Fix base-url mangling between main site and data ressources. diff -r 4c14be06e557 -r f443a2b8a5c7 devtools/__init__.py --- a/devtools/__init__.py Fri Oct 08 13:19:07 2010 +0200 +++ b/devtools/__init__.py Fri Oct 08 15:28:51 2010 +0200 @@ -190,7 +190,7 @@ # threads return True - +# XXX merge with BaseApptestConfiguration ? class ApptestConfiguration(BaseApptestConfiguration): def __init__(self, appid, log_threshold=logging.CRITICAL, sourcefile=None): @@ -198,6 +198,7 @@ self.init_repository = sourcefile is None self.sourcefile = sourcefile + class RealDatabaseConfiguration(ApptestConfiguration): """configuration class for tests to run on a real database. @@ -220,6 +221,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'): @@ -242,7 +244,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: @@ -342,7 +343,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 diff -r 4c14be06e557 -r f443a2b8a5c7 devtools/cwwindmill.py --- a/devtools/cwwindmill.py Fri Oct 08 13:19:07 2010 +0200 +++ b/devtools/cwwindmill.py Fri Oct 08 15:28:51 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, diff -r 4c14be06e557 -r f443a2b8a5c7 devtools/httptest.py --- a/devtools/httptest.py Fri Oct 08 13:19:07 2010 +0200 +++ b/devtools/httptest.py Fri Oct 08 15:28:51 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) - diff -r 4c14be06e557 -r f443a2b8a5c7 devtools/qunit.py --- a/devtools/qunit.py Fri Oct 08 13:19:07 2010 +0200 +++ b/devtools/qunit.py Fri Oct 08 15:28:51 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 @@ ''' % data] - if server_data is not None: - host, port = server_data + if base_url is not None: html.append('') html.append('') html.append('') @@ -303,9 +297,5 @@ - - - - if __name__ == '__main__': unittest_main() diff -r 4c14be06e557 -r f443a2b8a5c7 devtools/test/unittest_httptest.py --- a/devtools/test/unittest_httptest.py Fri Oct 08 13:19:07 2010 +0200 +++ b/devtools/test/unittest_httptest.py Fri Oct 08 15:28:51 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() diff -r 4c14be06e557 -r f443a2b8a5c7 devtools/testlib.py --- a/devtools/testlib.py Fri Oct 08 13:19:07 2010 +0200 +++ b/devtools/testlib.py Fri Oct 08 15:28:51 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('.*')) diff -r 4c14be06e557 -r f443a2b8a5c7 doc/book/en/tutorials/tools/windmill.rst --- a/doc/book/en/tutorials/tools/windmill.rst Fri Oct 08 13:19:07 2010 +0200 +++ b/doc/book/en/tutorials/tools/windmill.rst Fri Oct 08 15:28:51 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:: diff -r 4c14be06e557 -r f443a2b8a5c7 web/test/test_windmill.py --- a/web/test/test_windmill.py Fri Oct 08 13:19:07 2010 +0200 +++ b/web/test/test_windmill.py Fri Oct 08 15:28:51 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() diff -r 4c14be06e557 -r f443a2b8a5c7 web/webconfig.py --- a/web/webconfig.py Fri Oct 08 13:19:07 2010 +0200 +++ b/web/webconfig.py Fri Oct 08 15:28:51 2010 +0200 @@ -213,8 +213,6 @@ 'help': 'use cubicweb.old.css instead of 3.9 cubicweb.css', 'group': 'web', 'level': 2, }), - - )) def fckeditor_installed(self):