Skip to content

Commit

Permalink
[ADD] pytest during-install run
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
xmo-odoo committed Jul 23, 2015
1 parent 3219863 commit 95a131b
Show file tree
Hide file tree
Showing 18 changed files with 258 additions and 189 deletions.
15 changes: 0 additions & 15 deletions addons/account/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -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
6 changes: 2 additions & 4 deletions addons/account/tests/account_test_classes.py
Original file line number Diff line number Diff line change
@@ -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):
"""
Expand All @@ -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, {})
Expand Down
Original file line number Diff line number Diff line change
@@ -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")
12 changes: 11 additions & 1 deletion addons/base_action_rule/tests/base_action_rule_test.py
Original file line number Diff line number Diff line change
@@ -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):
Expand Down
2 changes: 2 additions & 0 deletions addons/l10n_be_coda/tests/test_import_bank_statement.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
"""
Expand Down
Empty file added addons/mrp/tests/__init__.py
Empty file.
2 changes: 2 additions & 0 deletions addons/payment_adyen/tests/test_adyen.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
7 changes: 2 additions & 5 deletions addons/payment_buckaroo/tests/test_buckaroo.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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):
Expand Down
2 changes: 2 additions & 0 deletions addons/payment_ogone/tests/test_ogone.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
2 changes: 2 additions & 0 deletions addons/payment_paypal/tests/test_paypal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
Empty file.
4 changes: 3 additions & 1 deletion addons/point_of_sale/test/test_frontend.py
Original file line number Diff line number Diff line change
@@ -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")
4 changes: 3 additions & 1 deletion addons/website_forum/tests/test_forum_process.py
Original file line number Diff line number Diff line change
@@ -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")

8 changes: 4 additions & 4 deletions openerp/addons/base/tests/test_base.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import pytest
import unittest2

import openerp.tests.common as common
Expand Down Expand Up @@ -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'

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

Expand Down
6 changes: 2 additions & 4 deletions openerp/addons/base/tests/test_uninstall.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
# This assumes an existing but uninitialized database.
import pytest
import unittest2

import openerp
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()
136 changes: 9 additions & 127 deletions openerp/modules/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.<name>
if len(module_parts) != 3 or not module_name.startswith('openerp.addons.'):
return None
# make sure <name> 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:
Expand Down Expand Up @@ -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)
Expand All @@ -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
Loading

1 comment on commit 95a131b

@blaggacao
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@xmo-odoo Any chance support for pytest to be retaken any time (sooner or later)? There is excellent camptocamp's pytest-odoo, but I'm not sure how complete the support would be with all the extra odoo testing things?
/cc @guewen

Please sign in to comment.