diff --git a/openerp/addons/base/res/res_config.py b/openerp/addons/base/res/res_config.py index 4fe6ff1989e..5bbd377f29d 100644 --- a/openerp/addons/base/res/res_config.py +++ b/openerp/addons/base/res/res_config.py @@ -493,6 +493,10 @@ class res_config_settings(osv.osv_memory, res_config_module_installation_mixin): return res def execute(self, cr, uid, ids, context=None): + if context is None: + context = {} + + context = dict(context, active_test=False) if uid != SUPERUSER_ID and not self.pool['res.users'].has_group(cr, uid, 'base.group_erp_manager'): raise openerp.exceptions.AccessError(_("Only administrators can change the settings")) @@ -500,7 +504,7 @@ class res_config_settings(osv.osv_memory, res_config_module_installation_mixin): ir_module = self.pool['ir.module.module'] res_groups = self.pool['res.groups'] - classified = self._get_classified_fields(cr, uid, context) + classified = self._get_classified_fields(cr, uid, context=context) config = self.browse(cr, uid, ids[0], context) diff --git a/openerp/addons/base/res/res_users.py b/openerp/addons/base/res/res_users.py index 1668cbda3c5..b85c49abf15 100644 --- a/openerp/addons/base/res/res_users.py +++ b/openerp/addons/base/res/res_users.py @@ -586,7 +586,7 @@ class groups_implied(osv.osv): res = super(groups_implied, self).write(cr, uid, ids, values, context) if values.get('users') or values.get('implied_ids'): # add all implied groups (to all users of each group) - for g in self.browse(cr, uid, ids): + for g in self.browse(cr, uid, ids, context=context): gids = map(int, g.trans_implied_ids) vals = {'users': [(4, u.id) for u in g.users]} super(groups_implied, self).write(cr, uid, gids, vals, context) diff --git a/openerp/cli/server.py b/openerp/cli/server.py index 42d920da9aa..e7ba695f19c 100644 --- a/openerp/cli/server.py +++ b/openerp/cli/server.py @@ -127,7 +127,6 @@ def main(args): check_root_user() openerp.tools.config.parse_config(args) check_postgres_user() - openerp.netsvc.init_logger() report_configuration() config = openerp.tools.config diff --git a/openerp/http.py b/openerp/http.py index ed8b55856fb..5f37afbc116 100644 --- a/openerp/http.py +++ b/openerp/http.py @@ -287,7 +287,7 @@ class WebRequest(object): def checked_call(___dbname, *a, **kw): # The decorator can call us more than once if there is an database error. In this # case, the request cursor is unusable. Rollback transaction to create a new one. - if self._cr: + if self._cr and not openerp.tools.config['test_enable']: self._cr.rollback() return self.endpoint(*a, **kw) diff --git a/openerp/modules/loading.py b/openerp/modules/loading.py index 979917df1a2..4f44f9bd38d 100644 --- a/openerp/modules/loading.py +++ b/openerp/modules/loading.py @@ -42,6 +42,7 @@ from openerp import SUPERUSER_ID from openerp.tools.translate import _ from openerp.modules.module import initialize_sys_path, \ load_openerp_module, init_module_models, adapt_version +from module import runs_post_install _logger = logging.getLogger(__name__) _test_logger = logging.getLogger('openerp.tests') @@ -423,8 +424,13 @@ def load_modules(db, force_demo=False, status=None, update_module=False): for model in registry.models.values(): model._register_hook(cr) + # STEP 9: Run the post-install tests + cr.commit() + if openerp.tools.config['test_enable']: + cr.execute("SELECT name FROM ir_module_module WHERE state='installed'") + for module_name in cr.fetchall(): + report.record_result(openerp.modules.module.run_unit_tests(module_name[0], cr.dbname, position=runs_post_install)) finally: cr.close() - # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/modules/module.py b/openerp/modules/module.py index b0b7e0aeb94..82e86e83ce2 100644 --- a/openerp/modules/module.py +++ b/openerp/modules/module.py @@ -358,30 +358,6 @@ class TestStream(object): current_test = None -def run_unit_tests(module_name, dbname): - """ - :returns: ``True`` if all of ``module_name``'s tests succeeded, ``False`` - if any of them failed. - :rtype: bool - """ - global current_test - current_test = module_name - mods = get_test_modules(module_name) - r = True - for m in mods: - tests = unwrap_suite(unittest2.TestLoader().loadTestsFromModule(m)) - suite = unittest2.TestSuite(itertools.ifilter(runs_at_install, tests)) - _logger.info('running %s tests.', m.__name__) - - result = unittest2.TextTestRunner(verbosity=2, stream=TestStream(m.__name__)).run(suite) - - if not result.wasSuccessful(): - r = False - _logger.error("Module %s: %d failures, %d errors", - module_name, len(result.failures), len(result.errors)) - current_test = None - return r - def runs_at(test, hook, default): # by default, tests do not run post install test_runs = getattr(test, hook, default) @@ -398,6 +374,30 @@ def runs_at(test, hook, default): runs_at_install = functools.partial(runs_at, hook='at_install', default=True) runs_post_install = functools.partial(runs_at, hook='post_install', default=False) +def run_unit_tests(module_name, dbname, position=runs_at_install): + """ + :returns: ``True`` if all of ``module_name``'s tests succeeded, ``False`` + if any of them failed. + :rtype: bool + """ + global current_test + current_test = module_name + mods = get_test_modules(module_name) + r = True + for m in mods: + tests = unwrap_suite(unittest2.TestLoader().loadTestsFromModule(m)) + suite = unittest2.TestSuite(itertools.ifilter(position, tests)) + _logger.info('running %s tests.', m.__name__) + + result = unittest2.TextTestRunner(verbosity=2, stream=TestStream(m.__name__)).run(suite) + + if not result.wasSuccessful(): + r = False + _logger.error("Module %s: %d failures, %d errors", + module_name, len(result.failures), len(result.errors)) + current_test = None + return r + def unwrap_suite(test): """ Attempts to unpack testsuites (holding suites or cases) in order to diff --git a/openerp/service/http_server.py b/openerp/service/http_server.py deleted file mode 100644 index e25e75dba62..00000000000 --- a/openerp/service/http_server.py +++ /dev/null @@ -1,178 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright P. Christeas 2008-2010 -# Copyright 2010 OpenERP SA. (http://www.openerp.com) -# -# -# WARNING: This program as such is intended to be used by professional -# programmers who take the whole responsibility of assessing all potential -# consequences resulting from its eventual inadequacies and bugs -# End users who are looking for a ready-to-use solution with commercial -# guarantees and support are strongly advised to contract a Free Software -# Service Company -# -# This program is Free Software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -############################################################################### - - -""" This module offers the family of HTTP-based servers. These are not a single - class/functionality, but a set of network stack layers, implementing - extendable HTTP protocols. - - The OpenERP server defines a single instance of a HTTP server, listening at - the standard 8069, 8071 ports (well, it is 2 servers, and ports are - configurable, of course). This "single" server then uses a `MultiHTTPHandler` - to dispatch requests to the appropriate channel protocol, like the XML-RPC, - static HTTP, DAV or other. -""" - -import base64 -import posixpath -import urllib -import os -import logging - -from websrv_lib import * -import openerp.tools as tools - -try: - import fcntl -except ImportError: - fcntl = None - -try: - from ssl import SSLError -except ImportError: - class SSLError(Exception): pass - -_logger = logging.getLogger(__name__) - -# TODO delete this for 6.2, it is still needed for 6.1. -class HttpLogHandler: - """ helper class for uniform log handling - Please define self._logger at each class that is derived from this - """ - _logger = None - - def log_message(self, format, *args): - self._logger.debug(format % args) # todo: perhaps other level - - def log_error(self, format, *args): - self._logger.error(format % args) - - def log_exception(self, format, *args): - self._logger.exception(format, *args) - - def log_request(self, code='-', size='-'): - self._logger.debug('"%s" %s %s', - self.requestline, str(code), str(size)) - -class StaticHTTPHandler(HttpLogHandler, FixSendError, HttpOptions, HTTPHandler): - _logger = logging.getLogger(__name__) - - _HTTP_OPTIONS = { 'Allow': ['OPTIONS', 'GET', 'HEAD'] } - - def __init__(self,request, client_address, server): - HTTPHandler.__init__(self,request,client_address,server) - document_root = tools.config.get('static_http_document_root', False) - assert document_root, "Please specify static_http_document_root in configuration, or disable static-httpd!" - self.__basepath = document_root - - def translate_path(self, path): - """Translate a /-separated PATH to the local filename syntax. - - Components that mean special things to the local file system - (e.g. drive or directory names) are ignored. (XXX They should - probably be diagnosed.) - - """ - # abandon query parameters - path = path.split('?',1)[0] - path = path.split('#',1)[0] - path = posixpath.normpath(urllib.unquote(path)) - words = path.split('/') - words = filter(None, words) - path = self.__basepath - for word in words: - if word in (os.curdir, os.pardir): continue - path = os.path.join(path, word) - return path - -def init_static_http(): - if not tools.config.get('static_http_enable', False): - return - - document_root = tools.config.get('static_http_document_root', False) - assert document_root, "Document root must be specified explicitly to enable static HTTP service (option --static-http-document-root)" - - base_path = tools.config.get('static_http_url_prefix', '/') - - reg_http_service(base_path, StaticHTTPHandler) - - _logger.info("Registered HTTP dir %s for %s", document_root, base_path) - -import security - -class OpenERPAuthProvider(AuthProvider): - """ Require basic authentication.""" - def __init__(self,realm='OpenERP User'): - self.realm = realm - self.auth_creds = {} - self.auth_tries = 0 - self.last_auth = None - - def authenticate(self, db, user, passwd, client_address): - try: - uid = security.login(db,user,passwd) - if uid is False: - return False - return user, passwd, db, uid - except Exception,e: - _logger.debug("Fail auth: %s" % e ) - return False - - def checkRequest(self,handler,path, db=False): - auth_str = handler.headers.get('Authorization',False) - try: - if not db: - db = handler.get_db_from_path(path) - except Exception: - if path.startswith('/'): - path = path[1:] - psp= path.split('/') - if len(psp)>1: - db = psp[0] - else: - #FIXME! - _logger.info("Wrong path: %s, failing auth" %path) - raise AuthRejectedExc("Authorization failed. Wrong sub-path.") - if self.auth_creds.get(db): - return True - if auth_str and auth_str.startswith('Basic '): - auth_str=auth_str[len('Basic '):] - (user,passwd) = base64.decodestring(auth_str).split(':') - _logger.info("Found user=\"%s\", passwd=\"***\" for db=\"%s\"", user, db) - acd = self.authenticate(db,user,passwd,handler.client_address) - if acd != False: - self.auth_creds[db] = acd - self.last_auth = db - return True - if self.auth_tries > 5: - _logger.info("Failing authorization after 5 requests w/o password") - raise AuthRejectedExc("Authorization failed.") - self.auth_tries += 1 - raise AuthRequiredExc(atype='Basic', realm=self.realm) - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/service/wsgi_server.py b/openerp/service/wsgi_server.py index b31736ba998..5e72ba570b5 100644 --- a/openerp/service/wsgi_server.py +++ b/openerp/service/wsgi_server.py @@ -164,153 +164,6 @@ def wsgi_xmlrpc(environ, start_response): params, method = xmlrpclib.loads(data) return xmlrpc_return(start_response, service, method, params, string_faultcode) -def wsgi_webdav(environ, start_response): - pi = environ['PATH_INFO'] - if environ['REQUEST_METHOD'] == 'OPTIONS' and pi in ['*','/']: - return return_options(environ, start_response) - elif pi.startswith('/webdav'): - http_dir = websrv_lib.find_http_service(pi) - if http_dir: - path = pi[len(http_dir.path):] - if path.startswith('/'): - environ['PATH_INFO'] = path - else: - environ['PATH_INFO'] = '/' + path - return http_to_wsgi(http_dir)(environ, start_response) - -def return_options(environ, start_response): - # Microsoft specific header, see - # http://www.ibm.com/developerworks/rational/library/2089.html - if 'Microsoft' in environ.get('User-Agent', ''): - options = [('MS-Author-Via', 'DAV')] - else: - options = [] - options += [('DAV', '1 2'), ('Allow', 'GET HEAD PROPFIND OPTIONS REPORT')] - start_response("200 OK", [('Content-Length', str(0))] + options) - return [] - -def http_to_wsgi(http_dir): - """ - Turn a BaseHTTPRequestHandler into a WSGI entry point. - - Actually the argument is not a bare BaseHTTPRequestHandler but is wrapped - (as a class, so it needs to be instanciated) in a HTTPDir. - - This code is adapted from wbsrv_lib.MultiHTTPHandler._handle_one_foreign(). - It is a temporary solution: the HTTP sub-handlers (in particular the - document_webdav addon) have to be WSGIfied. - """ - def wsgi_handler(environ, start_response): - - headers = {} - for key, value in environ.items(): - if key.startswith('HTTP_'): - key = key[5:].replace('_', '-').title() - headers[key] = value - if key == 'CONTENT_LENGTH': - key = key.replace('_', '-').title() - headers[key] = value - if environ.get('Content-Type'): - headers['Content-Type'] = environ['Content-Type'] - - path = urllib.quote(environ.get('PATH_INFO', '')) - if environ.get('QUERY_STRING'): - path += '?' + environ['QUERY_STRING'] - - request_version = 'HTTP/1.1' # TODO - request_line = "%s %s %s\n" % (environ['REQUEST_METHOD'], path, request_version) - - class Dummy(object): - pass - - # Let's pretend we have a server to hand to the handler. - server = Dummy() - server.server_name = environ['SERVER_NAME'] - server.server_port = int(environ['SERVER_PORT']) - - # Initialize the underlying handler and associated auth. provider. - con = openerp.service.websrv_lib.noconnection(environ['wsgi.input']) - handler = http_dir.instanciate_handler(con, environ['REMOTE_ADDR'], server) - - # Populate the handler as if it is called by a regular HTTP server - # and the request is already parsed. - handler.wfile = StringIO.StringIO() - handler.rfile = environ['wsgi.input'] - handler.headers = headers - handler.command = environ['REQUEST_METHOD'] - handler.path = path - handler.request_version = request_version - handler.close_connection = 1 - handler.raw_requestline = request_line - handler.requestline = request_line - - # Handle authentication if there is an auth. provider associated to - # the handler. - if hasattr(handler, 'auth_provider'): - try: - handler.auth_provider.checkRequest(handler, path) - except websrv_lib.AuthRequiredExc, ae: - # Darwin 9.x.x webdav clients will report "HTTP/1.0" to us, while they support (and need) the - # authorisation features of HTTP/1.1 - if request_version != 'HTTP/1.1' and ('Darwin/9.' not in handler.headers.get('User-Agent', '')): - start_response("403 Forbidden", []) - return [] - start_response("401 Authorization required", [ - ('WWW-Authenticate', '%s realm="%s"' % (ae.atype,ae.realm)), - # ('Connection', 'keep-alive'), - ('Content-Type', 'text/html'), - ('Content-Length', 4), # len(self.auth_required_msg) - ]) - return ['Blah'] # self.auth_required_msg - except websrv_lib.AuthRejectedExc,e: - start_response("403 %s" % (e.args[0],), []) - return [] - - method_name = 'do_' + handler.command - - # Support the OPTIONS method even when not provided directly by the - # handler. TODO I would prefer to remove it and fix the handler if - # needed. - if not hasattr(handler, method_name): - if handler.command == 'OPTIONS': - return return_options(environ, start_response) - start_response("501 Unsupported method (%r)" % handler.command, []) - return [] - - # Finally, call the handler's method. - try: - method = getattr(handler, method_name) - method() - # The DAV handler buffers its output and provides a _flush() - # method. - getattr(handler, '_flush', lambda: None)() - response = parse_http_response(handler.wfile.getvalue()) - response_headers = response.getheaders() - body = response.read() - start_response(str(response.status) + ' ' + response.reason, response_headers) - return [body] - except (websrv_lib.AuthRejectedExc, websrv_lib.AuthRequiredExc): - raise - except Exception, e: - start_response("500 Internal error", []) - return [] - - return wsgi_handler - -def parse_http_response(s): - """ Turn a HTTP response string into a httplib.HTTPResponse object.""" - class DummySocket(StringIO.StringIO): - """ - This is used to provide a StringIO to httplib.HTTPResponse - which, instead of taking a file object, expects a socket and - uses its makefile() method. - """ - def makefile(self, *args, **kw): - return self - response = httplib.HTTPResponse(DummySocket(s)) - response.begin() - return response - # WSGI handlers registered through the register_wsgi_handler() function below. module_handlers = [] # RPC endpoints registered through the register_rpc_endpoint() function below. @@ -342,7 +195,7 @@ def application_unproxied(environ, start_response): del threading.current_thread().dbname # Try all handlers until one returns some result (i.e. not None). - wsgi_handlers = [wsgi_xmlrpc, wsgi_webdav] + wsgi_handlers = [wsgi_xmlrpc] wsgi_handlers += module_handlers for handler in wsgi_handlers: result = handler(environ, start_response) diff --git a/openerp/tests/phantomtest.js b/openerp/tests/phantomtest.js index 63a3b2e33ff..5a6a9696cc0 100644 --- a/openerp/tests/phantomtest.js +++ b/openerp/tests/phantomtest.js @@ -6,7 +6,8 @@ function waitFor (ready, callback, timeout, timeoutMessageCallback) { (function waitLoop() { if(new Date - start > timeout) { - error(timeoutMessageCallback ? timeoutMessageCallback() : "Timeout after "+timeout+" ms"); + console.log('error', timeoutMessageCallback ? timeoutMessageCallback() : "Timeout after "+timeout+" ms"); + phantom.exit(1); } else if (ready()) { callback(); } else { @@ -15,10 +16,6 @@ function waitFor (ready, callback, timeout, timeoutMessageCallback) { }()); } -function error(message) { - console.log('error', message); - phantom.exit(1); -} function PhantomTest() { var self = this; this.options = JSON.parse(phantom.args[phantom.args.length-1]); @@ -49,10 +46,12 @@ function PhantomTest() { })); msg.push('(leaf frame on top)') } - error(JSON.stringify(msg.join('\n'))); + console.log('error', JSON.stringify(msg.join('\n'))); + phantom.exit(1); }; this.page.onAlert = function(message) { - error(message); + console.log('error', message); + phantom.exit(1); }; this.page.onConsoleMessage = function(message) { console.log(message); @@ -78,7 +77,8 @@ function PhantomTest() { if(!found) { console.log('Injecting', src, 'needed for', need); if(!self.page.injectJs(src)) { - error("Cannot inject " + src); + console.log('error', "Cannot inject " + src); + phantom.exit(1); } } } @@ -88,8 +88,9 @@ function PhantomTest() { self.page.evaluate(function () { var message = ("Timeout\nhref: " + window.location.href + "\nreferrer: " + document.referrer - + "\n\n" + document.body.innerHTML).replace(/[^a-z0-9\s~!@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/]/gi, "*"); - error(message); + + "\n\n" + (document.body && document.body.innerHTML)).replace(/[^a-z0-9\s~!@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/]/gi, "*"); + console.log('error', message); + phantom.exit(1); }); }, self.timeout); @@ -108,7 +109,8 @@ function PhantomTest() { var url = self.origin + url_path; self.page.open(url, function(status) { if (status !== 'success') { - error("failed to load " + url) + console.log('error', "failed to load " + url); + phantom.exit(1); } else { console.log('loaded', url, status); // process ready @@ -119,7 +121,7 @@ function PhantomTest() { try { console.log("page.evaluate eval expr:", ready); r = !!eval(ready); - } catch(ex) { + } catch(ex) { } console.log("page.evaluate eval result:", r); return r; diff --git a/openerp/tools/config.py b/openerp/tools/config.py index 899d72f3fe4..6b21bda8b4f 100644 --- a/openerp/tools/config.py +++ b/openerp/tools/config.py @@ -152,19 +152,11 @@ class configmanager(object): parser.add_option_group(group) # WEB - # TODO move to web addons after MetaOption merge group = optparse.OptionGroup(parser, "Web interface Configuration") group.add_option("--db-filter", dest="dbfilter", default='.*', help="Filter listed database", metavar="REGEXP") parser.add_option_group(group) - # Static HTTP - group = optparse.OptionGroup(parser, "Static HTTP service") - group.add_option("--static-http-enable", dest="static_http_enable", action="store_true", my_default=False, help="enable static HTTP service for serving plain HTML files") - group.add_option("--static-http-document-root", dest="static_http_document_root", help="specify the directory containing your static HTML files (e.g '/var/www/')") - group.add_option("--static-http-url-prefix", dest="static_http_url_prefix", help="specify the URL root prefix where you want web browsers to access your static HTML files (e.g '/')") - parser.add_option_group(group) - # Testing Group group = optparse.OptionGroup(parser, "Testing Configuration") group.add_option("--test-file", dest="test_file", my_default=False, @@ -313,9 +305,10 @@ class configmanager(object): self.options[option.dest] = option.my_default self.casts[option.dest] = option - self.parse_config(None, False) + # generate default config + self._parse_config() - def parse_config(self, args=None, complete=True): + def parse_config(self, args=None): """ Parse the configuration file (if any) and the command-line arguments. @@ -329,10 +322,12 @@ class configmanager(object): Typical usage of this method: openerp.tools.config.parse_config(sys.argv[1:]) - - :param complete: this is a hack used in __init__(), leave it to True. - """ + self._parse_config(args) + openerp.netsvc.init_logger() + openerp.modules.module.initialize_sys_path() + + def _parse_config(self, args=None): if args is None: args = [] opt, args = self.parser.parse_args(args) @@ -391,7 +386,6 @@ class configmanager(object): 'db_maxconn', 'import_partial', 'addons_path', 'xmlrpc', 'syslog', 'without_demo', 'timezone', 'xmlrpcs_interface', 'xmlrpcs_port', 'xmlrpcs', - 'static_http_enable', 'static_http_document_root', 'static_http_url_prefix', 'secure_cert_file', 'secure_pkey_file', 'dbfilter', 'log_handler', 'log_level', 'log_db' ] @@ -503,8 +497,6 @@ class configmanager(object): openerp.conf.server_wide_modules = map(lambda m: m.strip(), opt.server_wide_modules.split(',')) else: openerp.conf.server_wide_modules = ['web','web_kanban'] - if complete: - openerp.modules.module.initialize_sys_path() def _generate_pgpassfile(self): """