2011-05-11 17:24:48 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
##############################################################################
|
|
|
|
#
|
|
|
|
# OpenERP, Open Source Management Solution
|
|
|
|
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
|
2014-01-15 18:03:13 +00:00
|
|
|
# Copyright (C) 2010-2014 OpenERP s.a. (<http://openerp.com>).
|
2011-05-11 17:24:48 +00:00
|
|
|
#
|
|
|
|
# This program is free software: you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU Affero General Public License as
|
|
|
|
# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
#
|
|
|
|
##############################################################################
|
|
|
|
|
2014-02-18 09:54:52 +00:00
|
|
|
import functools
|
2012-03-01 12:16:11 +00:00
|
|
|
import imp
|
2015-01-15 12:28:25 +00:00
|
|
|
import importlib
|
|
|
|
import inspect
|
2011-05-11 17:24:48 +00:00
|
|
|
import itertools
|
2014-02-09 00:40:05 +00:00
|
|
|
import logging
|
2012-03-01 12:16:11 +00:00
|
|
|
import os
|
2014-02-09 00:40:05 +00:00
|
|
|
import re
|
2012-03-01 12:16:11 +00:00
|
|
|
import sys
|
2014-06-29 12:42:27 +00:00
|
|
|
import time
|
2014-02-17 15:58:30 +00:00
|
|
|
import unittest
|
2015-01-16 10:57:23 +00:00
|
|
|
import threading
|
2014-02-09 00:40:05 +00:00
|
|
|
from os.path import join as opj
|
2011-05-11 17:24:48 +00:00
|
|
|
|
2014-02-09 00:40:05 +00:00
|
|
|
import unittest2
|
2011-05-11 17:24:48 +00:00
|
|
|
|
2014-02-09 00:40:05 +00:00
|
|
|
import openerp
|
|
|
|
import openerp.tools as tools
|
2011-05-11 17:24:48 +00:00
|
|
|
import openerp.release as release
|
2014-02-09 00:40:05 +00:00
|
|
|
from openerp.tools.safe_eval import safe_eval as eval
|
2011-05-11 17:24:48 +00:00
|
|
|
|
2014-05-29 16:23:48 +00:00
|
|
|
MANIFEST = '__openerp__.py'
|
2014-08-13 08:57:53 +00:00
|
|
|
README = ['README.rst', 'README.md', 'README.txt']
|
2014-05-29 16:23:48 +00:00
|
|
|
|
2012-01-30 11:31:16 +00:00
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
2014-02-09 00:40:05 +00:00
|
|
|
# addons path as a list
|
2014-07-07 13:32:25 +00:00
|
|
|
ad_paths = []
|
2014-05-30 16:23:15 +00:00
|
|
|
hooked = False
|
2011-05-11 17:24:48 +00:00
|
|
|
|
|
|
|
# Modules already loaded
|
|
|
|
loaded = []
|
|
|
|
|
2012-01-09 12:52:43 +00:00
|
|
|
class AddonsImportHook(object):
|
|
|
|
"""
|
|
|
|
Import hook to load OpenERP addons from multiple paths.
|
|
|
|
|
|
|
|
OpenERP implements its own import-hook to load its addons. OpenERP
|
|
|
|
addons are Python modules. Originally, they were each living in their
|
|
|
|
own top-level namespace, e.g. the sale module, or the hr module. For
|
|
|
|
backward compatibility, `import <module>` is still supported. Now they
|
|
|
|
are living in `openerp.addons`. The good way to import such modules is
|
|
|
|
thus `import openerp.addons.module`.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def find_module(self, module_name, package_path):
|
|
|
|
module_parts = module_name.split('.')
|
|
|
|
if len(module_parts) == 3 and module_name.startswith('openerp.addons.'):
|
|
|
|
return self # We act as a loader too.
|
|
|
|
|
|
|
|
def load_module(self, module_name):
|
2014-02-14 14:35:49 +00:00
|
|
|
if module_name in sys.modules:
|
|
|
|
return sys.modules[module_name]
|
2012-01-09 12:52:43 +00:00
|
|
|
|
2014-02-14 14:35:49 +00:00
|
|
|
_1, _2, module_part = module_name.split('.')
|
2012-01-09 12:52:43 +00:00
|
|
|
# Note: we don't support circular import.
|
|
|
|
f, path, descr = imp.find_module(module_part, ad_paths)
|
2012-01-24 10:20:24 +00:00
|
|
|
mod = imp.load_module('openerp.addons.' + module_part, f, path, descr)
|
2012-01-09 12:52:43 +00:00
|
|
|
sys.modules['openerp.addons.' + module_part] = mod
|
|
|
|
return mod
|
|
|
|
|
2011-05-11 17:24:48 +00:00
|
|
|
def initialize_sys_path():
|
2012-01-10 09:27:30 +00:00
|
|
|
"""
|
|
|
|
Setup an import-hook to be able to import OpenERP addons from the different
|
|
|
|
addons paths.
|
2011-09-02 13:31:36 +00:00
|
|
|
|
2012-01-10 09:27:30 +00:00
|
|
|
This ensures something like ``import crm`` (or even
|
|
|
|
``import openerp.addons.crm``) works even if the addons are not in the
|
|
|
|
PYTHONPATH.
|
2011-09-02 13:31:36 +00:00
|
|
|
"""
|
2011-05-11 17:24:48 +00:00
|
|
|
global ad_paths
|
2014-05-30 16:23:15 +00:00
|
|
|
global hooked
|
2011-05-11 17:24:48 +00:00
|
|
|
|
2014-07-07 13:32:25 +00:00
|
|
|
dd = tools.config.addons_data_dir
|
|
|
|
if dd not in ad_paths:
|
|
|
|
ad_paths.append(dd)
|
|
|
|
|
2014-05-30 16:23:15 +00:00
|
|
|
for ad in tools.config['addons_path'].split(','):
|
|
|
|
ad = os.path.abspath(tools.ustr(ad.strip()))
|
|
|
|
if ad not in ad_paths:
|
|
|
|
ad_paths.append(ad)
|
2014-01-15 18:03:13 +00:00
|
|
|
|
|
|
|
# add base module path
|
|
|
|
base_path = os.path.abspath(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'addons'))
|
2014-05-30 16:23:15 +00:00
|
|
|
if base_path not in ad_paths:
|
|
|
|
ad_paths.append(base_path)
|
2014-01-15 18:03:13 +00:00
|
|
|
|
2014-05-30 16:23:15 +00:00
|
|
|
if not hooked:
|
|
|
|
sys.meta_path.append(AddonsImportHook())
|
|
|
|
hooked = True
|
2011-05-11 17:24:48 +00:00
|
|
|
|
2012-01-09 10:16:47 +00:00
|
|
|
def get_module_path(module, downloaded=False, display_warning=True):
|
2011-05-11 17:24:48 +00:00
|
|
|
"""Return the path of the given module.
|
|
|
|
|
|
|
|
Search the addons paths and return the first path where the given
|
|
|
|
module is found. If downloaded is True, return the default addons
|
|
|
|
path if nothing else is found.
|
|
|
|
|
|
|
|
"""
|
|
|
|
initialize_sys_path()
|
|
|
|
for adp in ad_paths:
|
|
|
|
if os.path.exists(opj(adp, module)) or os.path.exists(opj(adp, '%s.zip' % module)):
|
|
|
|
return opj(adp, module)
|
|
|
|
|
|
|
|
if downloaded:
|
2014-01-15 18:03:13 +00:00
|
|
|
return opj(tools.config.addons_data_dir, module)
|
2012-01-09 10:16:47 +00:00
|
|
|
if display_warning:
|
2012-01-24 12:42:52 +00:00
|
|
|
_logger.warning('module %s: module not found', module)
|
2011-05-11 17:24:48 +00:00
|
|
|
return False
|
|
|
|
|
|
|
|
def get_module_filetree(module, dir='.'):
|
|
|
|
path = get_module_path(module)
|
|
|
|
if not path:
|
|
|
|
return False
|
|
|
|
|
|
|
|
dir = os.path.normpath(dir)
|
|
|
|
if dir == '.':
|
|
|
|
dir = ''
|
|
|
|
if dir.startswith('..') or (dir and dir[0] == '/'):
|
|
|
|
raise Exception('Cannot access file outside the module')
|
|
|
|
|
2014-02-09 00:40:05 +00:00
|
|
|
files = openerp.tools.osutil.listdir(path, True)
|
2011-05-11 17:24:48 +00:00
|
|
|
|
|
|
|
tree = {}
|
|
|
|
for f in files:
|
|
|
|
if not f.startswith(dir):
|
|
|
|
continue
|
|
|
|
|
|
|
|
if dir:
|
|
|
|
f = f[len(dir)+int(not dir.endswith('/')):]
|
|
|
|
lst = f.split(os.sep)
|
|
|
|
current = tree
|
|
|
|
while len(lst) != 1:
|
|
|
|
current = current.setdefault(lst.pop(0), {})
|
|
|
|
current[lst.pop(0)] = None
|
|
|
|
|
|
|
|
return tree
|
|
|
|
|
|
|
|
def get_module_resource(module, *args):
|
|
|
|
"""Return the full path of a resource of the given module.
|
|
|
|
|
2012-02-08 17:39:32 +00:00
|
|
|
:param module: module name
|
|
|
|
:param list(str) args: resource path components within module
|
2011-05-11 17:24:48 +00:00
|
|
|
|
2012-02-08 17:39:32 +00:00
|
|
|
:rtype: str
|
|
|
|
:return: absolute path to the resource
|
2011-05-11 17:24:48 +00:00
|
|
|
|
|
|
|
TODO name it get_resource_path
|
|
|
|
TODO make it available inside on osv object (self.get_resource_path)
|
|
|
|
"""
|
2012-02-08 17:39:32 +00:00
|
|
|
mod_path = get_module_path(module)
|
|
|
|
if not mod_path: return False
|
|
|
|
resource_path = opj(mod_path, *args)
|
|
|
|
if os.path.isdir(mod_path):
|
|
|
|
# the module is a directory - ignore zip behavior
|
|
|
|
if os.path.exists(resource_path):
|
|
|
|
return resource_path
|
2011-05-11 17:24:48 +00:00
|
|
|
return False
|
|
|
|
|
2011-12-09 12:28:03 +00:00
|
|
|
def get_module_icon(module):
|
2013-06-28 15:07:55 +00:00
|
|
|
iconpath = ['static', 'description', 'icon.png']
|
2011-12-09 12:28:03 +00:00
|
|
|
if get_module_resource(module, *iconpath):
|
|
|
|
return ('/' + module + '/') + '/'.join(iconpath)
|
|
|
|
return '/base/' + '/'.join(iconpath)
|
2011-05-11 17:24:48 +00:00
|
|
|
|
2014-05-29 16:23:48 +00:00
|
|
|
def get_module_root(path):
|
|
|
|
"""
|
|
|
|
Get closest module's root begining from path
|
|
|
|
|
|
|
|
# Given:
|
|
|
|
# /foo/bar/module_dir/static/src/...
|
|
|
|
|
|
|
|
get_module_root('/foo/bar/module_dir/static/')
|
|
|
|
# returns '/foo/bar/module_dir'
|
|
|
|
|
|
|
|
get_module_root('/foo/bar/module_dir/')
|
|
|
|
# returns '/foo/bar/module_dir'
|
|
|
|
|
|
|
|
get_module_root('/foo/bar')
|
|
|
|
# returns None
|
|
|
|
|
|
|
|
@param path: Path from which the lookup should start
|
|
|
|
|
|
|
|
@return: Module root path or None if not found
|
|
|
|
"""
|
|
|
|
while not os.path.exists(os.path.join(path, MANIFEST)):
|
|
|
|
new_path = os.path.abspath(os.path.join(path, os.pardir))
|
|
|
|
if path == new_path:
|
|
|
|
return None
|
|
|
|
path = new_path
|
|
|
|
return path
|
|
|
|
|
2014-03-19 08:57:08 +00:00
|
|
|
def load_information_from_description_file(module, mod_path=None):
|
2011-05-11 17:24:48 +00:00
|
|
|
"""
|
|
|
|
:param module: The name of the module (sale, purchase, ...)
|
2014-03-19 08:57:08 +00:00
|
|
|
:param mod_path: Physical path of module, if not providedThe name of the module (sale, purchase, ...)
|
2011-05-11 17:24:48 +00:00
|
|
|
"""
|
|
|
|
|
2014-03-19 08:57:08 +00:00
|
|
|
if not mod_path:
|
|
|
|
mod_path = get_module_path(module)
|
2014-05-29 16:23:48 +00:00
|
|
|
terp_file = mod_path and opj(mod_path, MANIFEST) or False
|
2011-05-11 17:24:48 +00:00
|
|
|
if terp_file:
|
|
|
|
info = {}
|
2014-02-09 00:40:05 +00:00
|
|
|
if os.path.isfile(terp_file):
|
2011-12-09 11:53:16 +00:00
|
|
|
# default values for descriptor
|
|
|
|
info = {
|
|
|
|
'application': False,
|
|
|
|
'author': '',
|
2012-01-30 21:04:29 +00:00
|
|
|
'auto_install': False,
|
2011-12-09 11:53:16 +00:00
|
|
|
'category': 'Uncategorized',
|
|
|
|
'depends': [],
|
|
|
|
'description': '',
|
2011-12-09 12:28:03 +00:00
|
|
|
'icon': get_module_icon(module),
|
2011-12-09 11:53:16 +00:00
|
|
|
'installable': True,
|
|
|
|
'license': 'AGPL-3',
|
|
|
|
'post_load': None,
|
2012-08-24 14:23:23 +00:00
|
|
|
'version': '1.0',
|
2011-12-09 11:53:16 +00:00
|
|
|
'web': False,
|
|
|
|
'website': '',
|
2012-01-07 04:17:45 +00:00
|
|
|
'sequence': 100,
|
2012-07-17 08:50:28 +00:00
|
|
|
'summary': '',
|
2011-12-09 11:53:16 +00:00
|
|
|
}
|
|
|
|
info.update(itertools.izip(
|
|
|
|
'depends data demo test init_xml update_xml demo_xml'.split(),
|
|
|
|
iter(list, None)))
|
|
|
|
|
2012-02-02 15:45:32 +00:00
|
|
|
f = tools.file_open(terp_file)
|
|
|
|
try:
|
|
|
|
info.update(eval(f.read()))
|
|
|
|
finally:
|
|
|
|
f.close()
|
2011-12-09 12:02:07 +00:00
|
|
|
|
2014-08-13 08:57:53 +00:00
|
|
|
if not info.get('description'):
|
|
|
|
readme_path = [opj(mod_path, x) for x in README
|
|
|
|
if os.path.isfile(opj(mod_path, x))]
|
|
|
|
if readme_path:
|
|
|
|
readme_text = tools.file_open(readme_path[0]).read()
|
|
|
|
info['description'] = readme_text
|
|
|
|
|
2012-01-30 11:31:16 +00:00
|
|
|
if 'active' in info:
|
2012-01-30 21:04:29 +00:00
|
|
|
# 'active' has been renamed 'auto_install'
|
2012-01-30 11:31:16 +00:00
|
|
|
info['auto_install'] = info['active']
|
|
|
|
|
2012-08-24 14:23:23 +00:00
|
|
|
info['version'] = adapt_version(info['version'])
|
2011-05-11 17:24:48 +00:00
|
|
|
return info
|
|
|
|
|
|
|
|
#TODO: refactor the logger in this file to follow the logging guidelines
|
|
|
|
# for 6.0
|
2014-05-29 16:23:48 +00:00
|
|
|
_logger.debug('module %s: no %s file found.', module, MANIFEST)
|
2011-05-11 17:24:48 +00:00
|
|
|
return {}
|
|
|
|
|
|
|
|
def init_module_models(cr, module_name, obj_list):
|
|
|
|
""" Initialize a list of models.
|
|
|
|
|
|
|
|
Call _auto_init and init on each model to create or update the
|
|
|
|
database tables supporting the models.
|
|
|
|
|
|
|
|
TODO better explanation of _auto_init and init.
|
|
|
|
|
|
|
|
"""
|
2012-01-24 12:42:52 +00:00
|
|
|
_logger.info('module %s: creating or updating database tables', module_name)
|
2011-05-11 17:24:48 +00:00
|
|
|
todo = []
|
|
|
|
for obj in obj_list:
|
2011-06-10 14:05:21 +00:00
|
|
|
result = obj._auto_init(cr, {'module': module_name})
|
2011-05-11 17:24:48 +00:00
|
|
|
if result:
|
|
|
|
todo += result
|
|
|
|
if hasattr(obj, 'init'):
|
|
|
|
obj.init(cr)
|
|
|
|
cr.commit()
|
2011-06-10 14:05:21 +00:00
|
|
|
for obj in obj_list:
|
|
|
|
obj._auto_end(cr, {'module': module_name})
|
|
|
|
cr.commit()
|
2014-07-06 14:44:26 +00:00
|
|
|
todo.sort(key=lambda x: x[0])
|
2011-05-11 17:24:48 +00:00
|
|
|
for t in todo:
|
|
|
|
t[1](cr, *t[2])
|
|
|
|
cr.commit()
|
|
|
|
|
2012-02-09 15:27:32 +00:00
|
|
|
def load_openerp_module(module_name):
|
|
|
|
""" Load an OpenERP module, if not already loaded.
|
2011-05-11 17:24:48 +00:00
|
|
|
|
2011-09-02 13:31:36 +00:00
|
|
|
This loads the module and register all of its models, thanks to either
|
|
|
|
the MetaModel metaclass, or the explicit instantiation of the model.
|
2012-02-09 15:27:32 +00:00
|
|
|
This is also used to load server-wide module (i.e. it is also used
|
|
|
|
when there is no model to register).
|
2011-05-11 17:24:48 +00:00
|
|
|
"""
|
|
|
|
global loaded
|
2012-02-09 15:27:32 +00:00
|
|
|
if module_name in loaded:
|
2011-05-11 17:24:48 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
initialize_sys_path()
|
|
|
|
try:
|
2012-02-09 15:27:32 +00:00
|
|
|
mod_path = get_module_path(module_name)
|
2014-02-09 00:40:05 +00:00
|
|
|
__import__('openerp.addons.' + module_name)
|
2012-02-09 15:27:32 +00:00
|
|
|
|
|
|
|
# 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
|
|
|
|
# server-wide (instead of registry-specific) functionalities.
|
|
|
|
info = load_information_from_description_file(module_name)
|
|
|
|
if info['post_load']:
|
|
|
|
getattr(sys.modules['openerp.addons.' + module_name], info['post_load'])()
|
|
|
|
|
2011-05-11 17:24:48 +00:00
|
|
|
except Exception, e:
|
2014-02-09 00:40:05 +00:00
|
|
|
msg = "Couldn't load module %s" % (module_name)
|
2012-02-09 15:27:32 +00:00
|
|
|
_logger.critical(msg)
|
|
|
|
_logger.critical(e)
|
2011-05-11 17:24:48 +00:00
|
|
|
raise
|
|
|
|
else:
|
2012-02-09 15:27:32 +00:00
|
|
|
loaded.append(module_name)
|
2011-05-11 17:24:48 +00:00
|
|
|
|
|
|
|
def get_modules():
|
|
|
|
"""Returns the list of module names
|
|
|
|
"""
|
|
|
|
def listdir(dir):
|
|
|
|
def clean(name):
|
|
|
|
name = os.path.basename(name)
|
|
|
|
if name[-4:] == '.zip':
|
|
|
|
name = name[:-4]
|
|
|
|
return name
|
|
|
|
|
|
|
|
def is_really_module(name):
|
2014-05-29 16:23:48 +00:00
|
|
|
manifest_name = opj(dir, name, MANIFEST)
|
2012-12-09 02:46:18 +00:00
|
|
|
zipfile_name = opj(dir, name)
|
2014-02-09 00:40:05 +00:00
|
|
|
return os.path.isfile(manifest_name)
|
2011-05-11 17:24:48 +00:00
|
|
|
return map(clean, filter(is_really_module, os.listdir(dir)))
|
|
|
|
|
|
|
|
plist = []
|
|
|
|
initialize_sys_path()
|
|
|
|
for ad in ad_paths:
|
|
|
|
plist.extend(listdir(ad))
|
|
|
|
return list(set(plist))
|
|
|
|
|
|
|
|
def get_modules_with_version():
|
|
|
|
modules = get_modules()
|
2012-08-24 14:23:23 +00:00
|
|
|
res = dict.fromkeys(modules, adapt_version('1.0'))
|
2011-05-11 17:24:48 +00:00
|
|
|
for module in modules:
|
|
|
|
try:
|
|
|
|
info = load_information_from_description_file(module)
|
2012-08-24 14:23:23 +00:00
|
|
|
res[module] = info['version']
|
|
|
|
except Exception:
|
2011-05-11 17:24:48 +00:00
|
|
|
continue
|
|
|
|
return res
|
|
|
|
|
2012-08-24 14:23:23 +00:00
|
|
|
def adapt_version(version):
|
|
|
|
serie = release.major_version
|
|
|
|
if version == serie or not version.startswith(serie + '.'):
|
|
|
|
version = '%s.%s' % (serie, version)
|
|
|
|
return version
|
|
|
|
|
2014-02-09 00:40:05 +00:00
|
|
|
def get_test_modules(module):
|
2015-01-15 12:28:25 +00:00
|
|
|
""" Return a list of module for the addons potentially containing tests to
|
2014-02-09 14:19:12 +00:00
|
|
|
feed unittest2.TestLoader.loadTestsFromModule() """
|
2012-03-01 12:16:11 +00:00
|
|
|
# Try to import the module
|
2015-01-15 12:28:25 +00:00
|
|
|
modpath = 'openerp.addons.' + module
|
2012-03-01 12:16:11 +00:00
|
|
|
try:
|
2015-01-15 12:28:25 +00:00
|
|
|
mod = importlib.import_module('.tests', modpath)
|
2012-03-01 12:16:11 +00:00
|
|
|
except Exception, e:
|
2014-02-09 14:19:12 +00:00
|
|
|
# If module has no `tests` sub-module, no problem.
|
|
|
|
if str(e) != 'No module named tests':
|
|
|
|
_logger.exception('Can not `import %s`.', module)
|
|
|
|
return []
|
|
|
|
|
2015-01-15 12:28:25 +00:00
|
|
|
if hasattr(mod, 'fast_suite') or hasattr(mod, 'checks'):
|
|
|
|
_logger.warn(
|
|
|
|
"Found deprecated fast_suite or checks attribute in test module "
|
|
|
|
"%s. These have no effect in or after version 8.0.",
|
|
|
|
mod.__name__)
|
|
|
|
|
|
|
|
result = [mod_obj for name, mod_obj in inspect.getmembers(mod, inspect.ismodule)
|
|
|
|
if name.startswith('test_')]
|
2014-02-09 14:19:12 +00:00
|
|
|
return result
|
2014-02-09 00:40:05 +00:00
|
|
|
|
|
|
|
# Use a custom stream object to log the test executions.
|
|
|
|
class TestStream(object):
|
2014-03-17 02:13:17 +00:00
|
|
|
def __init__(self, logger_name='openerp.tests'):
|
|
|
|
self.logger = logging.getLogger(logger_name)
|
2014-02-09 00:40:05 +00:00
|
|
|
self.r = re.compile(r'^-*$|^ *... *$|^ok$')
|
|
|
|
def flush(self):
|
|
|
|
pass
|
|
|
|
def write(self, s):
|
|
|
|
if self.r.match(s):
|
|
|
|
return
|
|
|
|
first = True
|
2014-06-22 09:31:07 +00:00
|
|
|
level = logging.ERROR if s.startswith(('ERROR', 'FAIL', 'Traceback')) else logging.INFO
|
|
|
|
for c in s.splitlines():
|
2014-02-09 00:40:05 +00:00
|
|
|
if not first:
|
|
|
|
c = '` ' + c
|
|
|
|
first = False
|
2014-06-22 09:31:07 +00:00
|
|
|
self.logger.log(level, c)
|
2014-02-09 00:40:05 +00:00
|
|
|
|
2014-02-17 00:51:37 +00:00
|
|
|
current_test = None
|
|
|
|
|
2014-03-18 12:22:50 +00:00
|
|
|
def runs_at(test, hook, default):
|
|
|
|
# by default, tests do not run post install
|
|
|
|
test_runs = getattr(test, hook, default)
|
|
|
|
|
|
|
|
# for a test suite, we're done
|
|
|
|
if not isinstance(test, unittest.TestCase):
|
|
|
|
return test_runs
|
|
|
|
|
|
|
|
# otherwise check the current test method to see it's been set to a
|
|
|
|
# different state
|
|
|
|
method = getattr(test, test._testMethodName)
|
|
|
|
return getattr(method, hook, test_runs)
|
|
|
|
|
|
|
|
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):
|
2012-03-02 11:02:27 +00:00
|
|
|
"""
|
2014-02-18 09:56:32 +00:00
|
|
|
:returns: ``True`` if all of ``module_name``'s tests succeeded, ``False``
|
|
|
|
if any of them failed.
|
|
|
|
:rtype: bool
|
2012-03-02 11:02:27 +00:00
|
|
|
"""
|
2014-02-17 00:51:37 +00:00
|
|
|
global current_test
|
|
|
|
current_test = module_name
|
2014-02-09 00:40:05 +00:00
|
|
|
mods = get_test_modules(module_name)
|
2015-01-16 10:57:23 +00:00
|
|
|
threading.currentThread().testing = True
|
2014-02-09 00:40:05 +00:00
|
|
|
r = True
|
|
|
|
for m in mods:
|
2014-02-17 15:58:30 +00:00
|
|
|
tests = unwrap_suite(unittest2.TestLoader().loadTestsFromModule(m))
|
2014-03-18 12:22:50 +00:00
|
|
|
suite = unittest2.TestSuite(itertools.ifilter(position, tests))
|
2014-02-14 14:52:57 +00:00
|
|
|
|
2014-06-29 12:42:27 +00:00
|
|
|
if suite.countTestCases():
|
2014-07-01 19:57:55 +00:00
|
|
|
t0 = time.time()
|
|
|
|
t0_sql = openerp.sql_db.sql_counter
|
2014-06-29 12:42:27 +00:00
|
|
|
_logger.info('%s running tests.', m.__name__)
|
|
|
|
result = unittest2.TextTestRunner(verbosity=2, stream=TestStream(m.__name__)).run(suite)
|
2014-07-01 19:57:55 +00:00
|
|
|
if time.time() - t0 > 5:
|
|
|
|
_logger.log(25, "%s tested in %.2fs, %s queries", m.__name__, time.time() - t0, openerp.sql_db.sql_counter - t0_sql)
|
2014-06-29 12:42:27 +00:00
|
|
|
if not result.wasSuccessful():
|
|
|
|
r = False
|
|
|
|
_logger.error("Module %s: %d failures, %d errors", module_name, len(result.failures), len(result.errors))
|
2014-02-18 10:18:47 +00:00
|
|
|
|
2014-02-17 00:51:37 +00:00
|
|
|
current_test = None
|
2015-01-16 10:57:23 +00:00
|
|
|
threading.currentThread().testing = False
|
2014-02-09 00:40:05 +00:00
|
|
|
return r
|
2011-05-11 17:24:48 +00:00
|
|
|
|
2014-02-17 15:58:30 +00:00
|
|
|
def unwrap_suite(test):
|
2012-03-02 11:02:27 +00:00
|
|
|
"""
|
2014-02-18 09:54:52 +00:00
|
|
|
Attempts to unpack testsuites (holding suites or cases) in order to
|
|
|
|
generate a single stream of terminals (either test cases or customized
|
|
|
|
test suites). These can then be checked for run/skip attributes
|
|
|
|
individually.
|
|
|
|
|
|
|
|
An alternative would be to use a variant of @unittest2.skipIf with a state
|
|
|
|
flag of some sort e.g. @unittest2.skipIf(common.runstate != 'at_install'),
|
|
|
|
but then things become weird with post_install as tests should *not* run
|
|
|
|
by default there
|
|
|
|
"""
|
2014-02-17 15:58:30 +00:00
|
|
|
if isinstance(test, unittest.TestCase):
|
|
|
|
yield test
|
|
|
|
return
|
|
|
|
|
|
|
|
subtests = list(test)
|
|
|
|
# custom test suite (no test cases)
|
|
|
|
if not len(subtests):
|
|
|
|
yield test
|
|
|
|
return
|
|
|
|
|
|
|
|
for item in itertools.chain.from_iterable(
|
|
|
|
itertools.imap(unwrap_suite, subtests)):
|
|
|
|
yield item
|
2011-05-11 17:24:48 +00:00
|
|
|
|
|
|
|
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|