From 278ed718e9805edf088642ba10d3b7c4e5716c31 Mon Sep 17 00:00:00 2001 From: Antony Lesuisse Date: Sun, 9 Feb 2014 01:40:05 +0100 Subject: [PATCH] new test execution engine remove deprecated zipfile support add preload_registry option when server is running allow registries to be used in contruction in test mode add a rollback test case for http tests add a phantomjs helper bzr revid: al@openerp.com-20140209004005-p5pwym4sqc23vw5b --- openerp/cli/server.py | 32 ++--- openerp/http.py | 7 +- openerp/modules/__init__.py | 12 +- openerp/modules/loading.py | 15 +- openerp/modules/module.py | 271 ++++++++---------------------------- openerp/modules/registry.py | 26 +++- openerp/service/model.py | 2 +- openerp/service/server.py | 43 +++++- openerp/tests/common.py | 123 ++++++++++++---- openerpcommand/run_tests.py | 2 + 10 files changed, 240 insertions(+), 293 deletions(-) diff --git a/openerp/cli/server.py b/openerp/cli/server.py index ee2e90bccbd..09557a7399f 100644 --- a/openerp/cli/server.py +++ b/openerp/cli/server.py @@ -89,16 +89,6 @@ def setup_pid_file(): pidtext = "%d" % (os.getpid()) fd.write(pidtext) -def preload_registry(dbname): - """ Preload a registry, and start the cron.""" - try: - update_module = True if openerp.tools.config['init'] or openerp.tools.config['update'] else False - registry = openerp.modules.registry.RegistryManager.new(dbname, update_module=update_module) - except Exception: - _logger.exception('Failed to initialize database `%s`.', dbname) - return False - return registry._assertion_report.failures == 0 - def run_test_file(dbname, test_file): """ Preload a registry, possibly run a test file, and start the cron.""" try: @@ -172,23 +162,17 @@ def main(args): if config['workers']: openerp.multi_process = True - # preload registries, needed for -u --stop_after_init - rc = 0 + preload = [] if config['db_name']: - for dbname in config['db_name'].split(','): - if not preload_registry(dbname): - rc += 1 + preload = config['db_name'].split(',') - if not config["stop_after_init"]: - setup_pid_file() - openerp.service.server.start() - if config['pidfile']: - os.unlink(config['pidfile']) - else: - sys.exit(rc) + stop = config["stop_after_init"] - _logger.info('OpenERP server is running, waiting for connections...') - quit_on_signals() + setup_pid_file() + rc = openerp.service.server.start(preload=preload, stop=stop) + if config['pidfile']: + os.unlink(config['pidfile']) + sys.exit(rc) class Server(Command): def run(self, args): diff --git a/openerp/http.py b/openerp/http.py index c8c1bed24eb..0b7fba85e64 100644 --- a/openerp/http.py +++ b/openerp/http.py @@ -168,7 +168,10 @@ class WebRequest(object): """ # some magic to lazy create the cr if not self._cr: - self._cr = self.registry.db.cursor() + if openerp.tools.config['test_enable'] and self.session_id in openerp.tests.common.HTTP_SESSION: + self._cr = openerp.tests.common.HTTP_SESSION[self.session_id] + else: + self._cr = self.registry.db.cursor() return self._cr def __enter__(self): @@ -177,7 +180,7 @@ class WebRequest(object): def __exit__(self, exc_type, exc_value, traceback): _request_stack.pop() - if self._cr: + if self._cr and not (openerp.tools.config['test_enable'] and self.session_id in openerp.tests.common.HTTP_SESSION): if exc_type is None: self._cr.commit() self._cr.close() diff --git a/openerp/modules/__init__.py b/openerp/modules/__init__.py index feaf014f568..55eeefaaa19 100644 --- a/openerp/modules/__init__.py +++ b/openerp/modules/__init__.py @@ -26,16 +26,10 @@ from . import db, graph, loading, migration, module, registry -# TODO temporarily expose those things -from openerp.modules.module import \ - get_modules, get_modules_with_version, \ - load_information_from_description_file, \ - get_module_resource, zip_directory, \ - get_module_path, initialize_sys_path, \ - load_openerp_module, init_module_models, \ - adapt_version - from openerp.modules.loading import load_modules +from openerp.modules.module import get_modules, get_modules_with_version, \ + load_information_from_description_file, get_module_resource, get_module_path, \ + initialize_sys_path, load_openerp_module, init_module_models, adapt_version # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/modules/loading.py b/openerp/modules/loading.py index 4e157c51d20..7ca0a6785d6 100644 --- a/openerp/modules/loading.py +++ b/openerp/modules/loading.py @@ -182,18 +182,12 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules= migrations.migrate_module(package, 'post') if has_demo: - # launch tests only in demo mode, as most tests will depend - # on demo data. Other tests can be added into the regular - # 'data' section, but should probably not alter the data, - # as there is no rollback. + # launch tests only in demo mode, allowing tests to use demo data. if tools.config.options['test_enable']: + # Yamel test report.record_result(load_test(module_name, idref, mode)) - - # Run the `fast_suite` and `checks` tests given by the module. - if module_name == 'base': - # Also run the core tests after the database is created. - report.record_result(openerp.modules.module.run_unit_tests('openerp')) - report.record_result(openerp.modules.module.run_unit_tests(module_name)) + # Python tests + report.record_result(openerp.modules.module.run_unit_tests(module_name, cr.dbname)) processed_modules.append(package.name) @@ -247,7 +241,6 @@ def load_marked_modules(cr, graph, states, force, progressdict, report, loaded_m if not processed: break return processed_modules - def load_modules(db, force_demo=False, status=None, update_module=False): # TODO status['progress'] reporting is broken: used twice (and reset each # time to zero) in load_module_graph, not fine-grained enough. diff --git a/openerp/modules/module.py b/openerp/modules/module.py index cf3ff61805e..e3b8e10cc97 100644 --- a/openerp/modules/module.py +++ b/openerp/modules/module.py @@ -20,32 +20,31 @@ # ############################################################################## +import base64 import imp import itertools +import logging import os -from os.path import join as opj +import re import sys import types -import zipimport - -import openerp.tools as tools -import openerp.tools.osutil as osutil -from openerp.tools.safe_eval import safe_eval as eval - -import zipfile -import openerp.release as release - -import re -import base64 -from zipfile import PyZipFile, ZIP_DEFLATED from cStringIO import StringIO +from os.path import join as opj -import logging +import unittest2 + +import openerp +import openerp.tools as tools +import openerp.release as release +from openerp.tools.safe_eval import safe_eval as eval _logger = logging.getLogger(__name__) _test_logger = logging.getLogger('openerp.tests') +# addons path ','.joined _ad = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'addons') # default addons path (base) + +# addons path as a list ad_paths = [] # Modules already loaded @@ -120,7 +119,6 @@ def get_module_path(module, downloaded=False, display_warning=True): _logger.warning('module %s: module not found', module) return False - def get_module_filetree(module, dir='.'): path = get_module_path(module) if not path: @@ -132,12 +130,7 @@ def get_module_filetree(module, dir='.'): if dir.startswith('..') or (dir and dir[0] == '/'): raise Exception('Cannot access file outside the module') - if not os.path.isdir(path): - # zipmodule - zip = zipfile.ZipFile(path + ".zip") - files = ['/'.join(f.split('/')[1:]) for f in zip.namelist()] - else: - files = osutil.listdir(path, True) + files = openerp.tools.osutil.listdir(path, True) tree = {} for f in files: @@ -154,68 +147,6 @@ def get_module_filetree(module, dir='.'): return tree -def zip_directory(directory, b64enc=True, src=True): - """Compress a directory - - @param directory: The directory to compress - @param base64enc: if True the function will encode the zip file with base64 - @param src: Integrate the source files - - @return: a string containing the zip file - """ - - RE_exclude = re.compile('(?:^\..+\.swp$)|(?:\.py[oc]$)|(?:\.bak$)|(?:\.~.~$)', re.I) - - def _zippy(archive, path, src=True): - path = os.path.abspath(path) - base = os.path.basename(path) - for f in osutil.listdir(path, True): - bf = os.path.basename(f) - if not RE_exclude.search(bf) and (src or bf == '__openerp__.py' or not bf.endswith('.py')): - archive.write(os.path.join(path, f), os.path.join(base, f)) - - archname = StringIO() - archive = PyZipFile(archname, "w", ZIP_DEFLATED) - - # for Python 2.5, ZipFile.write() still expects 8-bit strings (2.6 converts to utf-8) - directory = tools.ustr(directory).encode('utf-8') - - archive.writepy(directory) - _zippy(archive, directory, src=src) - archive.close() - archive_data = archname.getvalue() - archname.close() - - if b64enc: - return base64.encodestring(archive_data) - - return archive_data - -def get_module_as_zip(modulename, b64enc=True, src=True): - """Generate a module as zip file with the source or not and can do a base64 encoding - - @param modulename: The module name - @param b64enc: if True the function will encode the zip file with base64 - @param src: Integrate the source files - - @return: a stream to store in a file-like object - """ - - ap = get_module_path(str(modulename)) - if not ap: - raise Exception('Unable to find path for module %s' % modulename) - - ap = ap.encode('utf8') - if os.path.isfile(ap + '.zip'): - val = file(ap + '.zip', 'rb').read() - if b64enc: - val = base64.encodestring(val) - else: - val = zip_directory(ap, b64enc, src) - - return val - - def get_module_resource(module, *args): """Return the full path of a resource of the given module. @@ -235,12 +166,6 @@ def get_module_resource(module, *args): # the module is a directory - ignore zip behavior if os.path.exists(resource_path): return resource_path - elif zipfile.is_zipfile(mod_path + '.zip'): - zip = zipfile.ZipFile( mod_path + ".zip") - files = ['/'.join(f.split('/')[1:]) for f in zip.namelist()] - resource_path = '/'.join(args) - if resource_path in files: - return opj(mod_path, resource_path) return False def get_module_icon(module): @@ -258,7 +183,7 @@ def load_information_from_description_file(module): mod_path = get_module_path(module) if terp_file: info = {} - if os.path.isfile(terp_file) or zipfile.is_zipfile(mod_path+'.zip'): + if os.path.isfile(terp_file): # default values for descriptor info = { 'application': False, @@ -300,7 +225,6 @@ def load_information_from_description_file(module): _logger.debug('module %s: no __openerp__.py file found.', module) return {} - def init_module_models(cr, module_name, obj_list): """ Initialize a list of models. @@ -342,12 +266,7 @@ def load_openerp_module(module_name): initialize_sys_path() try: mod_path = get_module_path(module_name) - zip_mod_path = '' if not mod_path else mod_path + '.zip' - if not os.path.isfile(zip_mod_path): - __import__('openerp.addons.' + module_name) - else: - zimp = zipimport.zipimporter(zip_mod_path) - zimp.load_module(module_name) + __import__('openerp.addons.' + module_name) # Call the module's post-load hook. This can done before any model or # data has been initialized. This is ok as the post-load hook is for @@ -357,8 +276,7 @@ def load_openerp_module(module_name): getattr(sys.modules['openerp.addons.' + module_name], info['post_load'])() except Exception, e: - mt = isinstance(e, zipimport.ZipImportError) and 'zip ' or '' - msg = "Couldn't load %smodule %s" % (mt, module_name) + msg = "Couldn't load module %s" % (module_name) _logger.critical(msg) _logger.critical(e) raise @@ -378,7 +296,7 @@ def get_modules(): def is_really_module(name): manifest_name = opj(dir, name, '__openerp__.py') zipfile_name = opj(dir, name) - return os.path.isfile(manifest_name) or zipfile.is_zipfile(zipfile_name) + return os.path.isfile(manifest_name) return map(clean, filter(is_really_module, os.listdir(dir))) plist = [] @@ -387,7 +305,6 @@ def get_modules(): plist.extend(listdir(ad)) return list(set(plist)) - def get_modules_with_version(): modules = get_modules() res = dict.fromkeys(modules, adapt_version('1.0')) @@ -405,126 +322,58 @@ def adapt_version(version): version = '%s.%s' % (serie, version) return version - -def get_test_modules(module, submodule, explode): - """ - Return a list of submodules containing tests. - `submodule` can be: - - None - - the name of a submodule - - '__fast_suite__' - - '__sanity_checks__' - """ - # Turn command-line module, submodule into importable names. - if module is None: - pass - elif module == 'openerp': - module = 'openerp.tests' - else: - module = 'openerp.addons.' + module + '.tests' - +def get_test_modules(module): + """ Return a list of module for the addons potentialy containing tests to + feed unittest2.TestLoader.loadTestsFromModule() """ # Try to import the module + module = 'openerp.addons.' + module + '.tests' try: - __import__(module) + m = __import__(module) except Exception, e: - if explode: - print 'Can not `import %s`.' % module - import logging - logging.exception('') - sys.exit(1) - else: - if str(e) == 'No module named tests': - # It seems the module has no `tests` sub-module, no problem. - pass - else: - _logger.exception('Can not `import %s`.', module) - return [] + # If module has no `tests` sub-module, no problem. + if str(e) != 'No module named tests': + _logger.exception('Can not `import %s`.', module) + return [] - # Discover available test sub-modules. - m = sys.modules[module] - submodule_names = sorted([x for x in dir(m) \ - if x.startswith('test_') and \ - isinstance(getattr(m, x), types.ModuleType)]) - submodules = [getattr(m, x) for x in submodule_names] + # include submodules too + result = [] + for name in sys.modules: + if name.startswith(module) and sys.modules[name]: + result.append(sys.modules[name]) + return result - def show_submodules_and_exit(): - if submodule_names: - print 'Available submodules are:' - for x in submodule_names: - print ' ', x - sys.exit(1) +# Use a custom stream object to log the test executions. +class TestStream(object): + def __init__(self): + self.r = re.compile(r'^-*$|^ *... *$|^ok$') + def flush(self): + pass + def write(self, s): + if self.r.match(s): + return + first = True + for c in s.split('\n'): + if not first: + c = '` ' + c + first = False + _test_logger.info(c) - if submodule is None: - # Use auto-discovered sub-modules. - ms = submodules - elif submodule == '__fast_suite__': - # Obtain the explicit test sub-modules list. - ms = getattr(sys.modules[module], 'fast_suite', None) - # `suite` was used before the 6.1 release instead of `fast_suite`. - ms = ms if ms else getattr(sys.modules[module], 'suite', None) - if ms is None: - if explode: - print 'The module `%s` has no defined test suite.' % (module,) - show_submodules_and_exit() - else: - ms = [] - elif submodule == '__sanity_checks__': - ms = getattr(sys.modules[module], 'checks', None) - if ms is None: - if explode: - print 'The module `%s` has no defined sanity checks.' % (module,) - show_submodules_and_exit() - else: - ms = [] - else: - # Pick the command-line-specified test sub-module. - m = getattr(sys.modules[module], submodule, None) - ms = [m] - - if m is None: - if explode: - print 'The module `%s` has no submodule named `%s`.' % \ - (module, submodule) - show_submodules_and_exit() - else: - ms = [] - - return ms - -def run_unit_tests(module_name): +def run_unit_tests(module_name, dbname): """ Return True or False if some tests were found and succeeded or failed. Return None if no test was found. """ - import unittest2 - ms = get_test_modules(module_name, '__fast_suite__', explode=False) - # TODO: No need to try again if the above call failed because of e.g. a syntax error. - ms.extend(get_test_modules(module_name, '__sanity_checks__', explode=False)) - suite = unittest2.TestSuite() - for m in ms: - suite.addTests(unittest2.TestLoader().loadTestsFromModule(m)) - if ms: - _test_logger.info('module %s: executing %s `fast_suite` and/or `checks` sub-modules', module_name, len(ms)) - # Use a custom stream object to log the test executions. - class MyStream(object): - def __init__(self): - self.r = re.compile(r'^-*$|^ *... *$|^ok$') - def flush(self): - pass - def write(self, s): - if self.r.match(s): - return - first = True - for c in s.split('\n'): - if not first: - c = '` ' + c - first = False - _test_logger.info(c) - result = unittest2.TextTestRunner(verbosity=2, stream=MyStream()).run(suite) - if result.wasSuccessful(): - return True - else: + mods = get_test_modules(module_name) + r = True + for m in mods: + suite = unittest2.TestSuite() + for t in unittest2.TestLoader().loadTestsFromModule(m): + suite.addTest(t) + _logger.log(logging.INFO, 'module %s: running test %s.', module_name, m.__name__) + result = unittest2.TextTestRunner(verbosity=2, stream=TestStream()).run(suite) + if not result.wasSuccessful(): + r = False _logger.error('module %s: at least one error occurred in a test', module_name) - return False + return r # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/modules/registry.py b/openerp/modules/registry.py index 7dcf02020fd..a277d4f6402 100644 --- a/openerp/modules/registry.py +++ b/openerp/modules/registry.py @@ -193,6 +193,30 @@ class Registry(Mapping): finally: cr.close() +class TestRLock(object): + def __init__(self): + self._lock = threading.RLock() + def acquire(self): + if openerp.tools.config['test_enable']: + return + return self._lock.acquire() + def release(self): + if openerp.tools.config['test_enable']: + return + return self._lock.release() + def __enter__(self): + self.acquire() + def __exit__(self, type, value, traceback): + self.release() + +# def __enter__(self, *l, **kw): +# if openerp.tools.config['test_enable']: +# return +# return super(TestRlock, self).__enter__(*l, **kw) +# def __exit__(self, *l, **kw): +# if openerp.tools.config['test_enable']: +# return +# return super(TestRlock, self).__exit__(*l, **kw) class RegistryManager(object): """ Model registries manager. @@ -204,7 +228,7 @@ class RegistryManager(object): # Mapping between db name and model registry. # Accessed through the methods below. registries = {} - registries_lock = threading.RLock() + registries_lock = TestRLock() @classmethod def get(cls, db_name, force_demo=False, status=None, update_module=False): diff --git a/openerp/service/model.py b/openerp/service/model.py index d65cbd77333..a637e234295 100644 --- a/openerp/service/model.py +++ b/openerp/service/model.py @@ -108,7 +108,7 @@ def check(f): tries = 0 while True: try: - if openerp.registry(dbname)._init: + if openerp.registry(dbname)._init and not openerp.tools.config['test_enable']: raise openerp.exceptions.Warning('Currently, this database is not fully loaded and can not be used.') return f(dbname, *args, **kwargs) except OperationalError, e: diff --git a/openerp/service/server.py b/openerp/service/server.py index 6a558a24e91..4f05e17e52e 100644 --- a/openerp/service/server.py +++ b/openerp/service/server.py @@ -306,7 +306,7 @@ class ThreadedServer(CommonServer): openerp.modules.registry.RegistryManager.delete_all() logging.shutdown() - def run(self): + def run(self, preload=None, stop=False): """ Start the http server and the cron thread then wait for a signal. The first SIGINT or SIGTERM signal will initiate a graceful shutdown while @@ -314,6 +314,13 @@ class ThreadedServer(CommonServer): """ self.start() + preload_registries(preload) + + if stop: + self.stop() + return + + # Wait for a first signal to be handled. (time.sleep will be interrupted # by the signal handler.) The try/except is for the win32 case. try: @@ -362,7 +369,7 @@ class GeventServer(CommonServer): self.httpd.stop() gevent.shutdown() - def run(self): + def run(self, preload, stop): self.start() self.stop() @@ -569,8 +576,15 @@ class PreforkServer(CommonServer): self.worker_kill(pid, signal.SIGTERM) self.socket.close() - def run(self): + def run(self, preload, stop): self.start() + + preload_registries(preload) + + if stop: + self.stop() + return + _logger.debug("Multiprocess starting") while 1: try: @@ -587,7 +601,7 @@ class PreforkServer(CommonServer): except Exception,e: _logger.exception(e) self.stop(False) - sys.exit(-1) + return -1 class Worker(object): """ Workers """ @@ -809,7 +823,21 @@ def _reexec(updated_modules=None): args.insert(0, exe) os.execv(sys.executable, args) -def start(): +def preload_registries(dbnames): + """ Preload a registries.""" + dbnames = dbnames or [] + for dbname in dbnames: + try: + update_module = openerp.tools.config['init'] or openerp.tools.config['update'] + registry = openerp.modules.registry.RegistryManager.new(dbname, update_module=update_module) + #if config['test_enable']: + # openerp.modules.module.run_http_test(config['db_name']) + #if registry._assertion_report.failures != 0: + except Exception: + _logger.exception('Failed to initialize database `%s`.', dbname) + return + +def start(preload=None, stop=False): """ Start the openerp http server and cron processor. """ global server @@ -825,7 +853,7 @@ def start(): autoreload = AutoReload(server) autoreload.run() - server.run() + rc = server.run(preload, stop) # like the legend of the phoenix, all ends with beginnings if getattr(openerp, 'phoenix', False): @@ -833,7 +861,8 @@ def start(): if config['auto_reload']: modules = autoreload.modules.keys() _reexec(modules) - sys.exit(0) + + return rc if rc else 0 def restart(): """ Restart the server diff --git a/openerp/tests/common.py b/openerp/tests/common.py index 6d47bc92379..775066f6c96 100644 --- a/openerp/tests/common.py +++ b/openerp/tests/common.py @@ -110,37 +110,106 @@ class SingleTransactionCase(BaseCase): cls.cr.close() -class RpcCase(unittest2.TestCase): - """ - Subclass of TestCase with a few XML-RPC proxies. +class HttpCase(SingleTransactionCase): + """ Transactionnal HTTP TestCase with a phantomjs helper. """ def __init__(self, methodName='runTest'): - super(RpcCase, self).__init__(methodName) - - class A(object): - pass - self.proxy = A() - - # Use the old (pre 6.1) API. - self.proxy.url_60 = url_60 = 'http://%s:%d/xmlrpc/' % (HOST, PORT) - self.proxy.common_60 = xmlrpclib.ServerProxy(url_60 + 'common') - self.proxy.db_60 = xmlrpclib.ServerProxy(url_60 + 'db') - self.proxy.object_60 = xmlrpclib.ServerProxy(url_60 + 'object') - #self.proxy.edi_60 = xmlrpclib.ServerProxy(url_60 + 'edi') - - # Use the new (8) API. - self.proxy.url_8 = url_8 = 'http://%s:%d/xmlrpc/2/' % (HOST, PORT) - self.proxy.common_8 = xmlrpclib.ServerProxy(url_8 + 'common') - self.proxy.db_8 = xmlrpclib.ServerProxy(url_8 + 'db') - self.proxy.object_8 = xmlrpclib.ServerProxy(url_8 + 'object') + super(HttpCase, self).__init__(methodName) + # v8 api with correct xmlrpc exception handling. + self.xmlrpc_url = url_8 = 'http://%s:%d/xmlrpc/2/' % (HOST, PORT) + self.xmlrpc_common = xmlrpclib.ServerProxy(url_8 + 'common') + self.xmlrpc_db = xmlrpclib.ServerProxy(url_8 + 'db') + self.xmlrpc_object = xmlrpclib.ServerProxy(url_8 + 'object') @classmethod - def generate_database_name(cls): - if hasattr(cls, '_database_id'): - cls._database_id += 1 - else: - cls._database_id = 0 - return '_fresh_name_' + str(cls._database_id) + '_' + def setUpClass(cls): + super(HttpCase, cls).setUpClass() + cls.session_id = uuid.uuid4().hex + HTTP_SESSION[cls.session_id] = cls.cr + + @classmethod + def tearDownClass(cls): + del HTTP_SESSION[cls.session_id] + super(HttpCase, cls).tearDownClass() + + def phantomjs(self, jsfile, timeout=30, options=None): + """ Phantomjs Test protocol. + + Use console.log in phantomjs to output test results evrey line must be + a one line JSON message using the following format: + + - for a success: { "event": "success", "message": "Log message" } + - for an error: { "event": "error", "message": "Short error description" } + + if a non json parsable line is received the helper will raise an + exception, the output buffer will be printed and phantom will be + killed + + """ + self.timeout = timeout + self.options = { + 'timeout' : timeout, + 'port': PORT, + 'db': DB, + 'user': ADMIN_USER, + 'password': ADMIN_PASSWORD, + 'session_id': self.session_id, + } + if options: + self.options.update(options) + + self.ignore_filters = [ + # Ignore phantomjs warnings + "*** WARNING:", + # Fixes an issue with PhantomJS 1.9.2 on OS X 10.9 (Mavericks) + # cf. https://github.com/ariya/phantomjs/issues/11418 + "CoreText performance note", + ] + + cmd = ['phantomjs', jsfile, json.dumps(self.options)] + _logger.info('executing %s', cmd) + try: + phantom = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + except OSError: + _logger.info("phantomjs not found, test %s skipped", jsfile) + + try: + t0 = time.time() + buf = '' + while 1: + if time.time() > t0 + self.timeout: + raise Exception("Phantom js timeout (%ss)" % self.timeout) + + ready, _, _ = select.select([phantom.stdout], [], [], 0.5) + if ready: + s = phantom.stdout.read(4096) + if s: + buf += s + else: + break + + # process lines + if '\n' in buf: + line, buf = buf.split('\n', 1) + if line not in self.ignore_filters: + try: + line_json = json.loads(line) + if line_json.get('event') == 'success': + _logger.info(line_json.get('message','ok')) + continue + elif line_json.get('event') == 'error': + err = line_json.get('message','error') + _logger.info(err) + else: + err = line + buf + except ValueError: + err = line + buf + raise Exception(err) + finally: + # kill phantomjs if phantom.exit() wasn't called in the test + if phantom.poll() is None: + phantom.terminate() + # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerpcommand/run_tests.py b/openerpcommand/run_tests.py index 7e23f3edee3..0e48ed9a556 100644 --- a/openerpcommand/run_tests.py +++ b/openerpcommand/run_tests.py @@ -96,6 +96,8 @@ def get_test_modules(module, submodule, explode): return ms def run(args): + print "Please use openerp-server --test-enable -d -i modulename" + sys.exit(0) import unittest2 import openerp