[MERGE] upstream

bzr revid: fme@openerp.com-20140326152307-d0rn3yuzel11457g
This commit is contained in:
Fabien Meghazi 2014-03-26 16:23:07 +01:00
commit 2eea637227
10 changed files with 61 additions and 383 deletions

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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:

View File

@ -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

View File

@ -1,178 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright P. Christeas <p_christ@hol.gr> 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:

View File

@ -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)

View File

@ -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;

View File

@ -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):
"""