summaryrefslogtreecommitdiff
path: root/odoo/modules
diff options
context:
space:
mode:
Diffstat (limited to 'odoo/modules')
-rw-r--r--odoo/modules/loading.py8
-rw-r--r--odoo/modules/module.py223
2 files changed, 3 insertions, 228 deletions
diff --git a/odoo/modules/loading.py b/odoo/modules/loading.py
index 03c04003275..a3adfe8492b 100644
--- a/odoo/modules/loading.py
+++ b/odoo/modules/loading.py
@@ -16,10 +16,8 @@ import odoo.modules.db
import odoo.modules.graph
import odoo.modules.migration
import odoo.modules.registry
-import odoo.tools as tools
-
-from odoo import api, SUPERUSER_ID
-from odoo.modules.module import adapt_version, initialize_sys_path, load_openerp_module
+from .. import SUPERUSER_ID, api, tools
+from .module import adapt_version, initialize_sys_path, load_openerp_module
_logger = logging.getLogger(__name__)
_test_logger = logging.getLogger('odoo.tests')
@@ -271,7 +269,7 @@ def load_module_graph(cr, graph, status=None, perform_checks=True,
report.record_result(load_test(idref, mode))
# Python tests
env['ir.http']._clear_routing_map() # force routing map to be rebuilt
- report.record_result(odoo.modules.module.run_unit_tests(module_name))
+ report.record_result(odoo.tests.loader.run_unit_tests(module_name))
# tests may have reset the environment
env = api.Environment(cr, SUPERUSER_ID, {})
module = env['ir.module.module'].browse(module_id)
diff --git a/odoo/modules/module.py b/odoo/modules/module.py
index 4859de455ff..6b7065a05ad 100644
--- a/odoo/modules/module.py
+++ b/odoo/modules/module.py
@@ -4,16 +4,11 @@
import ast
import collections.abc
import importlib
-import inspect
-import itertools
import logging
import os
import pkg_resources
import re
import sys
-import time
-import unittest
-import threading
import warnings
from os.path import join as opj
@@ -419,222 +414,4 @@ def adapt_version(version):
version = '%s.%s' % (serie, version)
return version
-def get_test_modules(module):
- """ Return a list of module for the addons potentially containing tests to
- feed unittest.TestLoader.loadTestsFromModule() """
- # Try to import the module
- results = _get_tests_modules('odoo.addons', module)
-
- try:
- importlib.import_module('odoo.upgrade.%s' % module)
- except ImportError:
- pass
- else:
- results += _get_tests_modules('odoo.upgrade', module)
-
- return results
-
-def _get_tests_modules(path, module):
- modpath = '%s.%s' % (path, module)
- try:
- mod = importlib.import_module('.tests', modpath)
- except ImportError as e: # will also catch subclass ModuleNotFoundError of P3.6
- # Hide ImportErrors on `tests` sub-module, but display other exceptions
- if e.name == modpath + '.tests' and e.msg.startswith('No module named'):
- return []
- _logger.exception('Can not `import %s`.', module)
- return []
- except Exception as e:
- _logger.exception('Can not `import %s`.', module)
- return []
- if hasattr(mod, 'fast_suite') or hasattr(mod, 'checks'):
- _logger.warning(
- "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_')]
- return result
-
-
-class OdooTestResult(unittest.result.TestResult):
- """
- This class in inspired from TextTestResult (https://github.com/python/cpython/blob/master/Lib/unittest/runner.py)
- Instead of using a stream, we are using the logger,
- but replacing the "findCaller" in order to give the information we
- have based on the test object that is running.
- """
-
- def log(self, level, msg, *args, test=None, exc_info=None, extra=None, stack_info=False, caller_infos=None):
- """
- ``test`` is the running test case, ``caller_infos`` is
- (fn, lno, func, sinfo) (logger.findCaller format), see logger.log for
- the other parameters.
- """
- test = test or self
- if isinstance(test, unittest.case._SubTest) and test.test_case:
- test = test.test_case
- logger = logging.getLogger(test.__module__)
- try:
- caller_infos = caller_infos or logger.findCaller(stack_info)
- except ValueError:
- caller_infos = "(unknown file)", 0, "(unknown function)", None
- (fn, lno, func, sinfo) = caller_infos
- # using logger.log makes it difficult to spot-replace findCaller in
- # order to provide useful location information (the problematic spot
- # inside the test function), so use lower-level functions instead
- if logger.isEnabledFor(level):
- record = logger.makeRecord(logger.name, level, fn, lno, msg, args, exc_info, func, extra, sinfo)
- logger.handle(record)
-
- def getDescription(self, test):
- if isinstance(test, unittest.case._SubTest):
- return 'Subtest %s' % test._subDescription()
- if isinstance(test, unittest.TestCase):
- # since we have the module name in the logger, this will avoid to duplicate module info in log line
- # we only apply this for TestCase since we can receive error handler or other special case
- return "%s.%s" % (test.__class__.__qualname__, test._testMethodName)
- return str(test)
-
- def startTest(self, test):
- super().startTest(test)
- self.log(logging.INFO, 'Starting %s ...', self.getDescription(test), test=test)
-
- def addError(self, test, err):
- super().addError(test, err)
- self.logError("ERROR", test, err)
-
- def addFailure(self, test, err):
- super().addFailure(test, err)
- self.logError("FAIL", test, err)
-
- def addSubTest(self, test, subtest, err):
- # since addSubTest is not making a call to addFailure or addError we need to manage it too
- # https://github.com/python/cpython/blob/3.7/Lib/unittest/result.py#L136
- if err is not None:
- if issubclass(err[0], test.failureException):
- flavour = "FAIL"
- else:
- flavour = "ERROR"
- self.logError(flavour, subtest, err)
- super().addSubTest(test, subtest, err)
-
- def addSkip(self, test, reason):
- super().addSkip(test, reason)
- self.log(logging.INFO, 'skipped %s', self.getDescription(test), test=test)
-
- def addUnexpectedSuccess(self, test):
- super().addUnexpectedSuccess(test)
- self.log(logging.ERROR, 'unexpected success for %s', self.getDescription(test), test=test)
-
- def logError(self, flavour, test, error):
- err = self._exc_info_to_string(error, test)
- caller_infos = self.getErrorCallerInfo(error, test)
- self.log(logging.INFO, '=' * 70, test=test, caller_infos=caller_infos) # keep this as info !!!!!!
- self.log(logging.ERROR, "%s: %s\n%s", flavour, self.getDescription(test), err, test=test, caller_infos=caller_infos)
-
- def getErrorCallerInfo(self, error, test):
- """
- :param error: A tuple (exctype, value, tb) as returned by sys.exc_info().
- :param test: A TestCase that created this error.
- :returns: a tuple (fn, lno, func, sinfo) matching the logger findCaller format or None
- """
-
- # only test case should be executed in odoo, this is only a safe guard
- if isinstance(test, unittest.suite._ErrorHolder):
- return
- if not isinstance(test, unittest.TestCase):
- _logger.warning('%r is not a TestCase' % test)
- return
- _, _, error_traceback = error
-
- while error_traceback:
- code = error_traceback.tb_frame.f_code
- if code.co_name == test._testMethodName:
- lineno = error_traceback.tb_lineno
- filename = code.co_filename
- method = test._testMethodName
- infos = (filename, lineno, method, None)
- return infos
- error_traceback = error_traceback.tb_next
-
-
-class OdooTestRunner(object):
- """A test runner class that displays results in in logger using OdooTestResult.
- Simplified verison of TextTestRunner
- """
-
- def run(self, test):
- result = OdooTestResult()
- test(result)
- return result
-
current_test = None
-def run_unit_tests(module_name, position='at_install'):
- """
- :returns: ``True`` if all of ``module_name``'s tests succeeded, ``False``
- if any of them failed, ``None`` if no tests were ran.
- :rtype: bool | None
- """
- global current_test
- # avoid dependency hell
- from odoo.tests.common import TagsSelector, OdooSuite
- current_test = module_name
- mods = get_test_modules(module_name)
- threading.currentThread().testing = True
- config_tags = TagsSelector(tools.config['test_tags'])
- position_tag = TagsSelector(position)
- ran_tests = failures = False
- for m in mods:
- tests = unwrap_suite(unittest.TestLoader().loadTestsFromModule(m))
- suite = OdooSuite(t for t in tests if position_tag.check(t) and config_tags.check(t))
-
- if suite.countTestCases():
- t0 = time.time()
- t0_sql = odoo.sql_db.sql_counter
- _logger.info('%s running tests.', m.__name__)
- result = OdooTestRunner().run(suite)
- log_level = logging.INFO
- if time.time() - t0 > 5:
- log_level = logging.RUNBOT
- _logger.log(log_level, "%s ran %s tests in %.2fs, %s queries", m.__name__, result.testsRun, time.time() - t0, odoo.sql_db.sql_counter - t0_sql)
- ran_tests = True
- if not result.wasSuccessful():
- failures = True
- _logger.error("Module %s: %d failures, %d errors", module_name, len(result.failures), len(result.errors))
-
- current_test = None
- threading.currentThread().testing = False
-
- if failures:
- return False
- if ran_tests:
- return True
- return None
-
-def unwrap_suite(test):
- """
- 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 @unittest.skipIf with a state
- flag of some sort e.g. @unittest.skipIf(common.runstate != 'at_install'),
- but then things become weird with post_install as tests should *not* run
- by default there
- """
- 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(
- unwrap_suite(t) for t in subtests):
- yield item