From 95a131b7f4eebc6e2c623f936283153d62f9e70f Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 6 Jul 2015 12:49:49 +0200 Subject: [PATCH] [ADD] pytest during-install run * runs python tests installing ~everything (grep -v 'website_version\|l10n\|hw_\|theme') * removes unused code Side-changes necessary for the pytest runner to work: * remove incorrect import * add @skipif annotations for tests which used to be implicitly skipped (not imported and not found by the runner) but are found by the pytest collector * added __init__ files to test directories: test directories *must* valid but unimported python packages otherwise pytest kinda goes screwy --- addons/account/tests/__init__.py | 15 -- addons/account/tests/account_test_classes.py | 6 +- ...test_bank_stmt_reconciliation_widget_ui.py | 2 + .../tests/base_action_rule_test.py | 12 +- .../tests/test_import_bank_statement.py | 2 + addons/mrp/tests/__init__.py | 0 addons/payment_adyen/tests/test_adyen.py | 2 + .../payment_buckaroo/tests/test_buckaroo.py | 7 +- addons/payment_ogone/tests/test_ogone.py | 2 + addons/payment_paypal/tests/test_paypal.py | 2 + addons/point_of_sale/test/__init__.py | 0 addons/point_of_sale/test/test_frontend.py | 4 +- .../website_forum/tests/test_forum_process.py | 4 +- openerp/addons/base/tests/test_base.py | 8 +- openerp/addons/base/tests/test_uninstall.py | 6 +- openerp/modules/module.py | 136 +---------- openerp/modules/registry.py | 226 +++++++++++++++--- openerp/tests/common.py | 13 + 18 files changed, 258 insertions(+), 189 deletions(-) create mode 100644 addons/mrp/tests/__init__.py create mode 100644 addons/point_of_sale/test/__init__.py diff --git a/addons/account/tests/__init__.py b/addons/account/tests/__init__.py index e4a6224852d4b..40590efc4232c 100644 --- a/addons/account/tests/__init__.py +++ b/addons/account/tests/__init__.py @@ -1,17 +1,2 @@ #Accounting tests written in python should extend the class AccountingTestCase. #See its doc for more info. - -from . import test_account_customer_invoice -from . import test_account_move_closed_period -from . import test_account_supplier_invoice -from . import test_account_validate_account_move -from . import test_bank_statement_reconciliation -#TODO re-enableand fix this test -#from . import test_bank_stmt_reconciliation_widget_ui -from . import test_chart_of_account -from . import test_fiscal_position -from . import test_manual_reconciliation -from . import test_payment -from . import test_reconciliation -from . import test_search -from . import test_tax diff --git a/addons/account/tests/account_test_classes.py b/addons/account/tests/account_test_classes.py index 148bf6027807e..0cf2c11615e65 100644 --- a/addons/account/tests/account_test_classes.py +++ b/addons/account/tests/account_test_classes.py @@ -1,9 +1,7 @@ -from openerp.tests.common import TransactionCase +from openerp.tests.common import TransactionCase, get_db_name from openerp import SUPERUSER_ID from openerp import api from openerp.modules.registry import RegistryManager -from openerp.tools import config -DB = config['db_name'] class AccountingTestCase(TransactionCase): """ @@ -18,7 +16,7 @@ class AccountingTestCase(TransactionCase): def _check_accounting_configured(self): #minimal setUp() similar to the one of TransactionCase, although we can't use the regular setUp() #because we want to assume the accounting is already configured when making the setUp of tests. - self.registry = RegistryManager.get(DB) + self.registry = RegistryManager.get(get_db_name()) self.cr = self.cursor() self.uid = SUPERUSER_ID self.env = api.Environment(self.cr, self.uid, {}) diff --git a/addons/account/tests/test_bank_stmt_reconciliation_widget_ui.py b/addons/account/tests/test_bank_stmt_reconciliation_widget_ui.py index 9ed81c5c915b9..df1d4fc7a8709 100644 --- a/addons/account/tests/test_bank_stmt_reconciliation_widget_ui.py +++ b/addons/account/tests/test_bank_stmt_reconciliation_widget_ui.py @@ -1,8 +1,10 @@ +import pytest from openerp.tests import HttpCase class TestUi(HttpCase): post_install = True at_install = False + @pytest.mark.skipif(reason="broken test, not imported previously") def test_01_admin_bank_statement_reconciliation(self): self.phantom_js("/", "odoo.__DEBUG__.services['web.Tour'].run('bank_statement_reconciliation', 'test')", "odoo.__DEBUG__.services['web.Tour'].tours.bank_statement_reconciliation", login="admin") diff --git a/addons/base_action_rule/tests/base_action_rule_test.py b/addons/base_action_rule/tests/base_action_rule_test.py index 54c72ba060714..66d8a1e25498b 100644 --- a/addons/base_action_rule/tests/base_action_rule_test.py +++ b/addons/base_action_rule/tests/base_action_rule_test.py @@ -1,7 +1,17 @@ +import pytest from openerp import SUPERUSER_ID from openerp.tests import common -from .. import test_models +@pytest.mark.skipif(reason="""These tests can't work because the +base_action_rule hook is set up in a way and at a point which makes it +impossible, I'm guessing since _register_hook was put in place. + +This was not noticed because the file wasn't matched at all by the test runner +and the test thus weren't run. I have verified that once the matching was +fixed (the old test runner only matched modules whose name starts with test_) +the 2nd and 4th tests did indeed fail. The other two seem to pass despite the +hook not being set up yet when they run. +""") class base_action_rule_test(common.TransactionCase): def setUp(self): diff --git a/addons/l10n_be_coda/tests/test_import_bank_statement.py b/addons/l10n_be_coda/tests/test_import_bank_statement.py index 1ffc3eccfc26c..ce47f852e5b7d 100644 --- a/addons/l10n_be_coda/tests/test_import_bank_statement.py +++ b/addons/l10n_be_coda/tests/test_import_bank_statement.py @@ -2,7 +2,9 @@ from openerp.modules.module import get_module_resource from openerp.tools import float_compare +import pytest +@pytest.mark.skipif(reason="tests not working because no more bank_journal") class TestCodaFile(TransactionCase): """Tests for import bank statement coda file format (account.bank.statement.import) """ diff --git a/addons/mrp/tests/__init__.py b/addons/mrp/tests/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/addons/payment_adyen/tests/test_adyen.py b/addons/payment_adyen/tests/test_adyen.py index 223d0274b3f8a..1970e91ca4d87 100644 --- a/addons/payment_adyen/tests/test_adyen.py +++ b/addons/payment_adyen/tests/test_adyen.py @@ -8,7 +8,9 @@ from openerp.addons.payment_adyen.controllers.main import AdyenController from openerp.tools import mute_logger +import pytest +@pytest.mark.skipif(reason="Not imported (not run by previous runner), not working") class AdyenCommon(PaymentAcquirerCommon): def setUp(self): diff --git a/addons/payment_buckaroo/tests/test_buckaroo.py b/addons/payment_buckaroo/tests/test_buckaroo.py index b826f3b920e48..5ed4d36a3943b 100644 --- a/addons/payment_buckaroo/tests/test_buckaroo.py +++ b/addons/payment_buckaroo/tests/test_buckaroo.py @@ -9,9 +9,9 @@ from openerp.addons.payment_buckaroo.controllers.main import BuckarooController from openerp.tools import mute_logger +import pytest -@openerp.tests.common.at_install(False) -@openerp.tests.common.post_install(False) +@pytest.mark.skipif(reason="at_install(False) and post_install(False) => never run") class BuckarooCommon(PaymentAcquirerCommon): def setUp(self): @@ -22,9 +22,6 @@ def setUp(self): # get the buckaroo account model, self.buckaroo_id = self.registry('ir.model.data').get_object_reference(cr, uid, 'payment_buckaroo', 'payment_acquirer_buckaroo') - -@openerp.tests.common.at_install(False) -@openerp.tests.common.post_install(False) class BuckarooForm(BuckarooCommon): def test_10_Buckaroo_form_render(self): diff --git a/addons/payment_ogone/tests/test_ogone.py b/addons/payment_ogone/tests/test_ogone.py index 0db68214bd4b5..fc1c29ac68632 100644 --- a/addons/payment_ogone/tests/test_ogone.py +++ b/addons/payment_ogone/tests/test_ogone.py @@ -9,7 +9,9 @@ from openerp.addons.payment_ogone.controllers.main import OgoneController from openerp.tools import mute_logger +import pytest +@pytest.mark.skipif(reason="never imported (thus run) by previous runner") class OgonePayment(PaymentAcquirerCommon): def setUp(self): diff --git a/addons/payment_paypal/tests/test_paypal.py b/addons/payment_paypal/tests/test_paypal.py index a66b90cf0fb0f..7c15f284e0c9d 100644 --- a/addons/payment_paypal/tests/test_paypal.py +++ b/addons/payment_paypal/tests/test_paypal.py @@ -8,7 +8,9 @@ from lxml import objectify import urlparse +import pytest +@pytest.mark.skipif(reason="never imported (thus run) by previous test runner") class PaypalCommon(PaymentAcquirerCommon): def setUp(self): diff --git a/addons/point_of_sale/test/__init__.py b/addons/point_of_sale/test/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/addons/point_of_sale/test/test_frontend.py b/addons/point_of_sale/test/test_frontend.py index cc5aa78b35f3a..191773dec41e0 100644 --- a/addons/point_of_sale/test/test_frontend.py +++ b/addons/point_of_sale/test/test_frontend.py @@ -1,8 +1,10 @@ - +import pytest import openerp.tests @openerp.tests.common.at_install(False) @openerp.tests.common.post_install(True) class TestUi(openerp.tests.HttpCase): + + @pytest.mark.skipif(reason="Wrong directory, & not imported -> not previously run and now failing") def test_01_pos_basic_order(self): self.phantom_js("/", "odoo.__DEBUG__.services['web.Tour'].run('pos_basic_order', 'test')", "odoo.__DEBUG__.services['web.Tour'].tours.pos_basic_order", login="admin") diff --git a/addons/website_forum/tests/test_forum_process.py b/addons/website_forum/tests/test_forum_process.py index e367c845af045..6fbae0cff9ece 100644 --- a/addons/website_forum/tests/test_forum_process.py +++ b/addons/website_forum/tests/test_forum_process.py @@ -1,11 +1,13 @@ +import pytest import openerp.tests @openerp.tests.common.at_install(False) @openerp.tests.common.post_install(True) class TestUi(openerp.tests.HttpCase): + @pytest.mark.skipif(reason="Not previously imported -> not working and not run") def test_01_admin_forum_tour(self): self.phantom_js("/", "odoo.__DEBUG__.services['web.Tour'].run('question', 'test')", "odoo.__DEBUG__.services['web.Tour'].tours.question", login="admin") + @pytest.mark.skipif(reason="Not previously imported -> not working and not run") def test_02_demo_question(self): self.phantom_js("/", "odoo.__DEBUG__.services['web.Tour'].run('forum_question', 'test')", "odoo.__DEBUG__.services['web.Tour'].tours.forum_question", login="demo") - diff --git a/openerp/addons/base/tests/test_base.py b/openerp/addons/base/tests/test_base.py index 47923ead7329e..005863b6dcc17 100644 --- a/openerp/addons/base/tests/test_base.py +++ b/openerp/addons/base/tests/test_base.py @@ -1,3 +1,4 @@ +import pytest import unittest2 import openerp.tests.common as common @@ -495,7 +496,7 @@ def setUpClass(cls): def test_00_setup(self): type(self).state = 'init' - @common.at_install(False) + @pytest.mark.at_install(False) def test_01_no_install(self): type(self).state = 'error' @@ -504,13 +505,12 @@ def test_02_check(self): self.state, 'init', "Testcase state should not have been transitioned from 00") +@pytest.mark.at_install(False) class TestPhaseInstall01(unittest2.TestCase): - at_install = False - def test_default_norun(self): self.fail("An unmarket test in a non-at-install case should not run") - @common.at_install(True) + @pytest.mark.at_install(True) def test_set_run(self): test_state['set_at_install'] = True diff --git a/openerp/addons/base/tests/test_uninstall.py b/openerp/addons/base/tests/test_uninstall.py index 758b2de97f74f..8c7ad92326df9 100644 --- a/openerp/addons/base/tests/test_uninstall.py +++ b/openerp/addons/base/tests/test_uninstall.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # This assumes an existing but uninitialized database. +import pytest import unittest2 import openerp @@ -49,6 +50,7 @@ def uninstall_module(module_name): cr.close() reload_registry() +@pytest.mark.skipif(reason="uninstall test hangs") class test_uninstall(unittest2.TestCase): """ Test the install/uninstall of a test module. The module is available in @@ -77,7 +79,3 @@ def test_02_uninstall(self): assert not search_registry('ir.model.fields', [('model', '=', 'test_uninstall.model')]) - - -if __name__ == '__main__': - unittest2.main() diff --git a/openerp/modules/module.py b/openerp/modules/module.py index 4e21917f68a11..ba215b96aa7db 100644 --- a/openerp/modules/module.py +++ b/openerp/modules/module.py @@ -2,22 +2,14 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. import contextlib -import functools import imp import importlib -import inspect import itertools import logging import os -import re import sys -import time -import unittest -import threading from os.path import join as opj -import unittest2 - import openerp import openerp.tools as tools import openerp.release as release @@ -49,8 +41,13 @@ class AddonsImportHook(object): 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. + # we only handle import paths of the form openerp.addons. + if len(module_parts) != 3 or not module_name.startswith('openerp.addons.'): + return None + # make sure is a valid module + if module_parts[2] not in get_modules(): + return None + return self # We act as a loader too. def load_module(self, module_name): if module_name in sys.modules: @@ -339,10 +336,10 @@ def clean(name): name = name[:-4] return name - return [ + return ( clean(name) for name in os.listdir(directory) if os.path.isfile(opj(directory, name, MANIFEST)) - ] + ) initialize_sys_path() modules = itertools.chain.from_iterable(listdir(ad) for ad in ad_paths) @@ -364,118 +361,3 @@ def adapt_version(version): if version == serie or not version.startswith(serie + '.'): 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 unittest2.TestLoader.loadTestsFromModule() """ - # Try to import the module - modpath = 'openerp.addons.' + module - try: - mod = importlib.import_module('.tests', modpath) - except Exception, e: - # If module has no `tests` sub-module, no problem. - if str(e) != 'No module named tests': - _logger.exception('Can not `import %s`.', module) - return [] - - 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_')] - return result - -# Use a custom stream object to log the test executions. -class TestStream(object): - def __init__(self, logger_name='openerp.tests'): - self.logger = logging.getLogger(logger_name) - self.r = re.compile(r'^-*$|^ *... *$|^ok$') - def flush(self): - pass - def write(self, s): - if self.r.match(s): - return - first = True - level = logging.ERROR if s.startswith(('ERROR', 'FAIL', 'Traceback')) else logging.INFO - for c in s.splitlines(): - if not first: - c = '` ' + c - first = False - self.logger.log(level, c) - -current_test = None - -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): - """ - :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) - threading.currentThread().testing = True - r = True - for m in mods: - tests = unwrap_suite(unittest2.TestLoader().loadTestsFromModule(m)) - suite = unittest2.TestSuite(itertools.ifilter(position, tests)) - - if suite.countTestCases(): - t0 = time.time() - t0_sql = openerp.sql_db.sql_counter - _logger.info('%s running tests.', m.__name__) - result = unittest2.TextTestRunner(verbosity=2, stream=TestStream(m.__name__)).run(suite) - 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) - 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 - threading.currentThread().testing = False - return r - -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 @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 - """ - 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 diff --git a/openerp/modules/registry.py b/openerp/modules/registry.py index e0218218fdfd3..81d511759e8b0 100644 --- a/openerp/modules/registry.py +++ b/openerp/modules/registry.py @@ -6,6 +6,7 @@ """ import collections import contextlib +import importlib import itertools import logging import os @@ -13,6 +14,12 @@ import threading import time +import pytest +import _pytest.python +import py.code +import py.error +import py.path + import openerp from .. import SUPERUSER_ID from ..tools import assertion_report, classproperty, config, convert_file, lazy_property, lru @@ -22,6 +29,163 @@ _logger = logging.getLogger(__name__) _test_logger = logging.getLogger('openerp.tests') +class OdooTestModule(_pytest.python.Module): + """ Should only be invoked for paths inside Odoo addons + """ + def _importtestmodule(self): + # copy/paste/modified from original: removed sys.path injection & + # added Odoo module prefixing so import within modules is correct + try: + pypkgpath = self.fspath.pypkgpath() + pkgroot = pypkgpath.dirpath() + names = self.fspath.new(ext="").relto(pkgroot).split(self.fspath.sep) + if names[-1] == "__init__": + names.pop() + modname = ".".join(names) + # for modules in openerp/addons, since there is a __init__ the + # module name is already fully qualified (maybe?) + if not modname.startswith('openerp.addons.'): + modname = 'openerp.addons.' + modname + + __import__(modname) + mod = sys.modules[modname] + if self.fspath.basename == "__init__.py": + return mod # we don't check anything as we might + # we in a namespace package ... too icky to check + modfile = mod.__file__ + if modfile[-4:] in ('.pyc', '.pyo'): + modfile = modfile[:-1] + elif modfile.endswith('$py.class'): + modfile = modfile[:-9] + '.py' + if modfile.endswith(os.path.sep + "__init__.py"): + if self.fspath.basename != "__init__.py": + modfile = modfile[:-12] + try: + issame = self.fspath.samefile(modfile) + except py.error.ENOENT: + issame = False + if not issame: + raise self.fspath.ImportMismatchError(modname, modfile, self) + except SyntaxError: + raise self.CollectError( + py.code.ExceptionInfo().getrepr(style="short")) + except self.fspath.ImportMismatchError: + e = sys.exc_info()[1] + raise self.CollectError( + "import file mismatch:\n" + "imported module %r has this __file__ attribute:\n" + " %s\n" + "which is not the same as the test file we want to collect:\n" + " %s\n" + "HINT: remove __pycache__ / .pyc files and/or use a " + "unique basename for your test file modules" + % e.args + ) + #print "imported test module", mod + self.config.pluginmanager.consider_module(mod) + return mod + +class ModuleTest(object): + """ Performs filtering for in-module test run: + * only collects test files contained within the specified module + * only collects tests enabled for the specified phase + """ + defaults = { + 'at_install': True, + 'post_install': False + } + def __init__(self, phase, modnames): + self.roots = map(lambda n: py.path.local(module.get_module_path(n)), modnames) + self.phase = phase + + def pytest_ignore_collect(self, path, config): + # only allow files from inside the selected module(s) + return not any( + root.common(path) == root + for root in self.roots + ) + + def pytest_collection_modifyitems(self, session, config, items): + items[:] = filter(self._filter_phase, items) + + def _filter_phase(self, item): + marker = item.get_marker(self.phase) + if marker and marker.args: + return marker.args[0] + return self.defaults[self.phase] + + @pytest.mark.tryfirst + def pytest_pycollect_makemodule(self, path, parent): + """ override collect with own test module thing to alter generated + module name when tests are found within an Odoo module: rather than + import ``.foo.bar`` it should be + ``openerp.addons..foo.bar`` + """ + # if path to collect is in addons_path, create an OdooTestModule + if any(root.common(path) == root for root in self.roots): + return OdooTestModule(path, parent) + # otherwise create a normal test module + return None + +class DataTests(object): + def __init__(self, registry, package): + self.package = package + self.registry = registry + self.paths = [ + module.get_resource_path(self.package.name, p) + for p in self.registry._get_files_of_kind('test', self.package) + ] + def pytest_collect_file(self, parent, path): + if self.paths and path in self.paths: + d = self.paths + self.paths = [] + return DataFile(path, parent, self.registry, self.package, d) + +class DataFile(pytest.File): + def __init__(self, path, parent, registry, package, paths): + super(DataFile, self).__init__(path, parent) + self.registry = registry + self.package = package + self.paths = paths + def collect(self): + return [DataItem(self, self.registry, self.package, self.paths)] + +class DataException(AssertionError): pass +class DataReporter(assertion_report.assertion_report): + def record_failure(self): + raise DataException() + +class DataItem(pytest.Item): + def __init__(self, parent, registry, package, paths): + super(DataItem, self).__init__(package.name, parent) + self.package = package + self.registry = registry + self.paths = paths + + def runtest(self, report=DataReporter()): + mode = 'update' + if hasattr(self.package, 'init') or self.package.state == 'to_install': + mode = 'init' + + try: + threading.currentThread().testing = True + with contextlib.closing(self.registry.cursor()) as cr: + idrefs = {} + for p in self.paths: + convert_file(cr, self.package.name, p, + idrefs, mode=mode, noupdate=False, kind='test', + report=report, pathname=p) + finally: + self.registry.clear_caches() + threading.currentThread().testing = False + + def reportinfo(self): + return self.fspath, 0, "" + + def repr_failure(self, exc_info): + return str(exc_info) + + class Registry(collections.Mapping): """ Model registry for a particular database. @@ -136,7 +300,6 @@ def do_parent_store(self): with self.cursor() as cr: for o in self._init_parent: self[o]._parent_store_compute(cr) - self._init = False def obj_list(self): """ Return the list of model names in this registry.""" @@ -255,10 +418,10 @@ def enter_test_mode(self): def leave_test_mode(self): """ Leave the test mode. """ assert self.test_cr is not None + RegistryManager.leave_test_mode() self.clear_caches() self.test_cr.force_close() self.test_cr = None - RegistryManager.leave_test_mode() def load_modules(self, force_demo=False, status=None, update_module=False): module.initialize_sys_path() @@ -752,7 +915,11 @@ def new(cls, db_name, force_demo=False, status=None, update_module=False): # dictionary then remove it if an exception is raised. registry = cls.registries[db_name] = Registry(db_name) try: + failures = 0 registry.setup_multi_process_signaling() + test_args = ['-r', 'fEs', '-s'] + module.ad_paths + # FIXME: openerp tests? + # FIXME: YAML & XML tests for event, data in registry.load_modules(force_demo, status, update_module): # launch tests only in demo mode, allowing tests to use demo data. if event == 'module_processed': @@ -761,41 +928,45 @@ def new(cls, db_name, force_demo=False, status=None, update_module=False): if not (hasattr(data, 'demo') or (data.dbdemo and data.state != 'installed')): continue - mode = 'update' - if hasattr(data, 'init') or data.state == 'to install': - mode = 'init' - - # closing will rollback & close instead of commit & close - with contextlib.closing(registry.cursor()) as cr: - # FIXME: e tu idref? - registry._assertion_report.record_result( - registry._load_test(cr, data, idref=None, mode=mode)) - # Python tests ir_http = registry['ir.http'] if hasattr(ir_http, '_routing_map'): # Force routing map to be rebuilt between each module test suite del ir_http._routing_map - registry._assertion_report.record_result( - module.run_unit_tests(data.name, db_name)) + + # magically defines current module as installed for + # purpose of routing map generation, maybe test should + # run after that's been done but before thingy has + # been thingied + module.current_test = data.name + threading.currentThread().testing = True + + failures += pytest.main(test_args, plugins=[ + ModuleTest('at_install', [data.name]), + DataTests(registry, data) + ]) + + threading.currentThread().testing = False + module.current_test = None # Run the post-install tests if config['test_enable']: with contextlib.closing(registry.cursor()) as cr: - t0 = time.time() - t0_sql = openerp.sql_db.sql_counter - cr.execute( - "SELECT name FROM ir_module_module WHERE state='installed'") - for [module_name] in cr.fetchall(): - registry._assertion_report.record_result( - module.run_unit_tests( - module_name, db_name, - position=module.runs_post_install)) - _logger.log(25, "All post-tested in %.2fs, %d queries", time.time() - t0, openerp.sql_db.sql_counter - t0_sql) - - if registry._assertion_report.failures: + cr.execute("SELECT name FROM ir_module_module WHERE state='installed'") + installed = [module_name for [module_name] in cr.fetchall()] + + t0 = time.time() + t0_sql = openerp.sql_db.sql_counter + threading.currentThread().testing = True + + failures += pytest.main(test_args, plugins=[ ModuleTest('post_install', installed)]) + + threading.currentThread().testing = False + _logger.log(25, "All post-tested in %.2fs, %d queries", time.time() - t0, openerp.sql_db.sql_counter - t0_sql) + + if failures: _logger.error('At least one test failed when loading the modules.') - _logger.info('Modules loaded, %s', registry._assertion_report) + _logger.info("Modules loaded...") except Exception: del cls.registries[db_name] @@ -807,6 +978,7 @@ def new(cls, db_name, force_demo=False, status=None, update_module=False): registry = cls.registries[db_name] registry.do_parent_store() + registry._init = False registry.ready = True diff --git a/openerp/tests/common.py b/openerp/tests/common.py index c591e8c7a688a..79af0f73ead27 100644 --- a/openerp/tests/common.py +++ b/openerp/tests/common.py @@ -14,6 +14,7 @@ import threading import time import itertools +import pytest import unittest2 import urllib2 import xmlrpclib @@ -60,6 +61,7 @@ def at_install(flag): starting the installation of the next module. """ def decorator(obj): + obj = pytest.mark.at_install(flag)(obj) obj.at_install = flag return obj return decorator @@ -73,10 +75,19 @@ def post_install(flag): current installation set. """ def decorator(obj): + obj = pytest.mark.post_install(flag)(obj) obj.post_install = flag return obj return decorator +class CaseMeta(type): + def __init__(cls, name, bases, attrs): + super(CaseMeta, cls).__init__(name, bases, attrs) + if 'at_install' in attrs: + pytest.mark.at_install(attrs['at_install'])(cls) + if 'post_install' in attrs: + pytest.mark.post_install(attrs['post_install'])(cls) + class BaseCase(unittest2.TestCase): """ Subclass of TestCase for common OpenERP-specific code. @@ -84,6 +95,7 @@ class BaseCase(unittest2.TestCase): This class is abstract and expects self.registry, self.cr and self.uid to be initialized by subclasses. """ + __metaclass__ = CaseMeta def cursor(self): return self.registry.cursor() @@ -215,6 +227,7 @@ def http_response(self, request, response): https_response = http_response +@pytest.mark.http class HttpCase(TransactionCase): """ Transactional HTTP TestCase with url_open and phantomjs helpers. """