summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--addons/web/tests/test_menu.py7
-rw-r--r--addons/web/tests/test_serving_base.py6
-rw-r--r--addons/web_editor/tests/test_converter.py5
-rw-r--r--addons/website/tests/test_converter.py10
-rw-r--r--odoo/addons/base/tests/test_base.py6
-rw-r--r--odoo/addons/base/tests/test_db_cursor.py5
-rw-r--r--odoo/addons/base/tests/test_func.py9
-rw-r--r--odoo/addons/base/tests/test_ir_sequence.py14
-rw-r--r--odoo/addons/base/tests/test_mail.py12
-rw-r--r--odoo/addons/base/tests/test_mimetypes.py5
-rw-r--r--odoo/addons/base/tests/test_misc.py9
-rw-r--r--odoo/addons/base/tests/test_osv.py7
-rw-r--r--odoo/addons/base/tests/test_tests_tags.py113
-rw-r--r--odoo/addons/base/tests/test_translate.py8
-rw-r--r--odoo/addons/base/tests/test_uninstall.py5
-rw-r--r--odoo/addons/base/tests/test_view_validation.py6
-rw-r--r--odoo/addons/test_mimetypes/tests/test_guess_mimetypes.py6
-rw-r--r--odoo/tests/common.py81
-rw-r--r--odoo/tools/config.py9
19 files changed, 195 insertions, 128 deletions
diff --git a/addons/web/tests/test_menu.py b/addons/web/tests/test_menu.py
index e608196ecd0..d60721c162a 100644
--- a/addons/web/tests/test_menu.py
+++ b/addons/web/tests/test_menu.py
@@ -1,13 +1,10 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from odoo.tests.common import tagged
-import unittest
-
+from odoo.tests.common import BaseCase
from ..controllers import main
-@tagged('standard', 'at_install')
-class ActionMungerTest(unittest.TestCase):
+class ActionMungerTest(BaseCase):
def test_actual_treeview(self):
action = {
"views": [[False, "tree"], [False, "form"],
diff --git a/addons/web/tests/test_serving_base.py b/addons/web/tests/test_serving_base.py
index b66d2d5b266..e1c6c7d5ab0 100644
--- a/addons/web/tests/test_serving_base.py
+++ b/addons/web/tests/test_serving_base.py
@@ -2,9 +2,8 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import random
-import unittest
-from odoo.tests.common import tagged
+from odoo.tests.common import BaseCase
from odoo.tools import topological_sort
@@ -14,8 +13,7 @@ def sample(population):
random.randint(0, min(len(population), 5)))
-@tagged('standard', 'at_install')
-class TestModulesLoading(unittest.TestCase):
+class TestModulesLoading(BaseCase):
def setUp(self):
self.mods = [str(i) for i in range(1000)]
diff --git a/addons/web_editor/tests/test_converter.py b/addons/web_editor/tests/test_converter.py
index bb778b07799..d2322035912 100644
--- a/addons/web_editor/tests/test_converter.py
+++ b/addons/web_editor/tests/test_converter.py
@@ -2,17 +2,16 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import textwrap
-import unittest
from lxml import etree, html
from lxml.builder import E
from odoo.tests import common
+from odoo.tests.common import BaseCase
from odoo.addons.web_editor.models.ir_qweb import html_to_text
-@common.tagged('standard', 'at_install')
-class TestHTMLToText(unittest.TestCase):
+class TestHTMLToText(BaseCase):
def test_rawstring(self):
self.assertEqual(
"foobar",
diff --git a/addons/website/tests/test_converter.py b/addons/website/tests/test_converter.py
index 0568f02d5e4..9a688c74ed9 100644
--- a/addons/website/tests/test_converter.py
+++ b/addons/website/tests/test_converter.py
@@ -1,13 +1,11 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-import unittest
from odoo.addons.http_routing.models.ir_http import slugify, unslug
-from odoo.tests.common import tagged
+from odoo.tests.common import BaseCase
-@tagged('standard', 'at_install')
-class TestUnslug(unittest.TestCase):
+class TestUnslug(BaseCase):
def test_unslug(self):
tests = {
@@ -28,9 +26,7 @@ class TestUnslug(unittest.TestCase):
for slug, expected in tests.items():
self.assertEqual(unslug(slug), expected)
-
-@tagged('standard', 'at_install')
-class TestTitleToSlug(unittest.TestCase):
+class TestTitleToSlug(BaseCase):
"""
Those tests should pass with or without python-slugify
See website/models/website.py slugify method
diff --git a/odoo/addons/base/tests/test_base.py b/odoo/addons/base/tests/test_base.py
index a823c65dc31..231f1daadd2 100644
--- a/odoo/addons/base/tests/test_base.py
+++ b/odoo/addons/base/tests/test_base.py
@@ -2,17 +2,15 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import ast
-import unittest
from odoo import SUPERUSER_ID
from odoo.exceptions import UserError, ValidationError
-from odoo.tests.common import TransactionCase, tagged
+from odoo.tests.common import TransactionCase, BaseCase
from odoo.tools import mute_logger
from odoo.tools.safe_eval import safe_eval, const_eval
-@tagged('standard', 'at_install')
-class TestSafeEval(unittest.TestCase):
+class TestSafeEval(BaseCase):
def test_const(self):
# NB: True and False are names in Python 2 not consts
expected = (1, {"a": {2.5}}, [None, u"foo"])
diff --git a/odoo/addons/base/tests/test_db_cursor.py b/odoo/addons/base/tests/test_db_cursor.py
index 839bcff6f89..b0b6a5a7071 100644
--- a/odoo/addons/base/tests/test_db_cursor.py
+++ b/odoo/addons/base/tests/test_db_cursor.py
@@ -1,11 +1,11 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-import unittest
import odoo
from odoo.sql_db import TestCursor
from odoo.tests import common
+from odoo.tests.common import BaseCase
from odoo.tools.misc import mute_logger
ADMIN_USER_ID = common.ADMIN_USER_ID
@@ -14,8 +14,7 @@ def registry():
return odoo.registry(common.get_db_name())
-@common.tagged('standard', 'at_install')
-class TestExecute(unittest.TestCase):
+class TestExecute(BaseCase):
""" Try cr.execute with wrong parameters """
@mute_logger('odoo.sql_db')
diff --git a/odoo/addons/base/tests/test_func.py b/odoo/addons/base/tests/test_func.py
index 1b630060d7f..226fc66e6ee 100644
--- a/odoo/addons/base/tests/test_func.py
+++ b/odoo/addons/base/tests/test_func.py
@@ -2,15 +2,13 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import functools
-import unittest
-from odoo.tests.common import tagged
+from odoo.tests.common import BaseCase
from odoo.tools import frozendict, pycompat
from odoo.tools.func import compose
-@tagged('standard', 'at_install')
-class TestCompose(unittest.TestCase):
+class TestCompose(BaseCase):
def test_basic(self):
str_add = compose(str, lambda a, b: a + b)
self.assertEqual(str_add(1, 2), "3")
@@ -25,8 +23,7 @@ class TestCompose(unittest.TestCase):
self.assertEqual(mul(5, 42), u"210")
-@tagged('standard', 'at_install')
-class TestFrozendict(unittest.TestCase):
+class TestFrozendict(BaseCase):
def test_frozendict_immutable(self):
""" Ensure that a frozendict is immutable. """
vals = {'name': 'Joe', 'age': 42}
diff --git a/odoo/addons/base/tests/test_ir_sequence.py b/odoo/addons/base/tests/test_ir_sequence.py
index 6c31728de35..0fc53012b48 100644
--- a/odoo/addons/base/tests/test_ir_sequence.py
+++ b/odoo/addons/base/tests/test_ir_sequence.py
@@ -2,13 +2,13 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from contextlib import contextmanager
-import unittest
import psycopg2
import psycopg2.errorcodes
import odoo
from odoo.tests import common
+from odoo.tests.common import BaseCase
ADMIN_USER_ID = common.ADMIN_USER_ID
@@ -28,8 +28,7 @@ def drop_sequence(code):
seq.unlink()
-@common.tagged('standard', 'at_install')
-class TestIrSequenceStandard(unittest.TestCase):
+class TestIrSequenceStandard(BaseCase):
""" A few tests for a 'Standard' (i.e. PostgreSQL) sequence. """
def test_ir_sequence_create(self):
@@ -67,8 +66,7 @@ class TestIrSequenceStandard(unittest.TestCase):
drop_sequence('test_sequence_type')
-@common.tagged('standard', 'at_install')
-class TestIrSequenceNoGap(unittest.TestCase):
+class TestIrSequenceNoGap(BaseCase):
""" Copy of the previous tests for a 'No gap' sequence. """
def test_ir_sequence_create_no_gap(self):
@@ -105,8 +103,7 @@ class TestIrSequenceNoGap(unittest.TestCase):
drop_sequence('test_sequence_type_2')
-@common.tagged('standard', 'at_install')
-class TestIrSequenceChangeImplementation(unittest.TestCase):
+class TestIrSequenceChangeImplementation(BaseCase):
""" Create sequence objects and change their ``implementation`` field. """
def test_ir_sequence_1_create(self):
@@ -143,8 +140,7 @@ class TestIrSequenceChangeImplementation(unittest.TestCase):
drop_sequence('test_sequence_type_4')
-@common.tagged('standard', 'at_install')
-class TestIrSequenceGenerate(unittest.TestCase):
+class TestIrSequenceGenerate(BaseCase):
""" Create sequence objects and generate some values. """
def test_ir_sequence_create(self):
diff --git a/odoo/addons/base/tests/test_mail.py b/odoo/addons/base/tests/test_mail.py
index 297a32cf67e..356bb299a24 100644
--- a/odoo/addons/base/tests/test_mail.py
+++ b/odoo/addons/base/tests/test_mail.py
@@ -1,15 +1,13 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-import unittest
-from odoo.tests.common import tagged
+from odoo.tests.common import BaseCase
from odoo.tools import html_sanitize, append_content_to_html, plaintext2html, email_split, misc
from . import test_mail_examples
-@tagged('standard', 'at_install')
-class TestSanitizer(unittest.TestCase):
+class TestSanitizer(BaseCase):
""" Test the html sanitizer that filters html to remove unwanted attributes """
def test_basic_sanitizer(self):
@@ -295,8 +293,7 @@ class TestSanitizer(unittest.TestCase):
# self.assertNotIn(ext, new_html)
-@tagged('standard', 'at_install')
-class TestHtmlTools(unittest.TestCase):
+class TestHtmlTools(BaseCase):
""" Test some of our generic utility functions about html """
def test_plaintext2html(self):
@@ -323,8 +320,7 @@ class TestHtmlTools(unittest.TestCase):
self.assertEqual(append_content_to_html(html, content, plaintext_flag, preserve_flag, container_tag), expected, 'append_content_to_html is broken')
-@tagged('standard', 'at_install')
-class TestEmailTools(unittest.TestCase):
+class TestEmailTools(BaseCase):
""" Test some of our generic utility functions for emails """
def test_email_split(self):
diff --git a/odoo/addons/base/tests/test_mimetypes.py b/odoo/addons/base/tests/test_mimetypes.py
index 3ca506cb744..e379c5474f2 100644
--- a/odoo/addons/base/tests/test_mimetypes.py
+++ b/odoo/addons/base/tests/test_mimetypes.py
@@ -1,7 +1,7 @@
import base64
import unittest
-from odoo.tests.common import tagged
+from odoo.tests.common import BaseCase
from odoo.tools.mimetypes import guess_mimetype
PNG = b'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVQI12P4//8/AAX+Av7czFnnAAAAAElFTkSuQmCC'
@@ -23,8 +23,7 @@ hbnNsYXRlKDUwLDUwKSI+CiAgICA8cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iMTUwIiBoZWlnaHQ9IjU
pyZWQ7IiAvPgogIDwvZz4KCjwvc3ZnPgo="""
-@tagged('standard', 'at_install')
-class test_guess_mimetype(unittest.TestCase):
+class test_guess_mimetype(BaseCase):
def test_default_mimetype_empty(self):
mimetype = guess_mimetype(b'')
diff --git a/odoo/addons/base/tests/test_misc.py b/odoo/addons/base/tests/test_misc.py
index 08c790387e9..d208ab11fb5 100644
--- a/odoo/addons/base/tests/test_misc.py
+++ b/odoo/addons/base/tests/test_misc.py
@@ -4,14 +4,12 @@
import datetime
from dateutil.relativedelta import relativedelta
import pytz
-import unittest
from odoo.tools import misc, date_utils
-from odoo.tests.common import TransactionCase, tagged
+from odoo.tests.common import TransactionCase, BaseCase
-@tagged('standard', 'at_install')
-class TestCountingStream(unittest.TestCase):
+class TestCountingStream(BaseCase):
def test_empty_stream(self):
s = misc.CountingStream(iter([]))
self.assertEqual(s.index, -1)
@@ -42,8 +40,7 @@ class TestCountingStream(unittest.TestCase):
self.assertEqual(s.index, 0)
-@tagged('standard', 'at_install')
-class TestDateRangeFunction(unittest.TestCase):
+class TestDateRangeFunction(BaseCase):
""" Test on date_range generator. """
def test_date_range_with_naive_datetimes(self):
diff --git a/odoo/addons/base/tests/test_osv.py b/odoo/addons/base/tests/test_osv.py
index 328124ca56c..f79dde6751a 100644
--- a/odoo/addons/base/tests/test_osv.py
+++ b/odoo/addons/base/tests/test_osv.py
@@ -1,14 +1,11 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-import unittest
-
from odoo.osv.query import Query
-from odoo.tests.common import tagged
+from odoo.tests.common import BaseCase
-@tagged('standard', 'at_install')
-class QueryTestCase(unittest.TestCase):
+class QueryTestCase(BaseCase):
def test_basic_query(self):
query = Query()
diff --git a/odoo/addons/base/tests/test_tests_tags.py b/odoo/addons/base/tests/test_tests_tags.py
index 3f2bcf21c2f..52856aadc83 100644
--- a/odoo/addons/base/tests/test_tests_tags.py
+++ b/odoo/addons/base/tests/test_tests_tags.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from odoo.tests.common import TransactionCase, tagged, TagsSelector
+from odoo.tests.common import TransactionCase, tagged, TagsSelector, BaseCase
@tagged('nodatabase')
@@ -17,7 +17,8 @@ class TestSetTags(TransactionCase):
fc = FakeClass()
self.assertTrue(hasattr(fc, 'test_tags'))
- self.assertEqual(fc.test_tags, {'at_install', 'standard', 'base'})
+ self.assertEqual(fc.test_tags, {'at_install', 'standard'})
+ self.assertEqual(fc.test_module, 'base')
def test_set_tags_not_decorated(self):
"""Test that a TransactionCase has some test_tags by default"""
@@ -28,7 +29,8 @@ class TestSetTags(TransactionCase):
fc = FakeClass()
self.assertTrue(hasattr(fc, 'test_tags'))
- self.assertEqual(fc.test_tags, {'at_install', 'standard', 'base'})
+ self.assertEqual(fc.test_tags, {'at_install', 'standard'})
+ self.assertEqual(fc.test_module, 'base')
def test_set_tags_single_tag(self):
"""Test the set_tags decorator with a single tag"""
@@ -39,7 +41,8 @@ class TestSetTags(TransactionCase):
fc = FakeClass()
- self.assertEqual(fc.test_tags, {'at_install', 'standard', 'base', 'slow'})
+ self.assertEqual(fc.test_tags, {'at_install', 'standard', 'slow'})
+ self.assertEqual(fc.test_module, 'base')
def test_set_tags_multiple_tags(self):
"""Test the set_tags decorator with multiple tags"""
@@ -50,7 +53,8 @@ class TestSetTags(TransactionCase):
fc = FakeClass()
- self.assertEqual(fc.test_tags, {'at_install', 'standard', 'base', 'slow', 'nightly'})
+ self.assertEqual(fc.test_tags, {'at_install', 'standard', 'slow', 'nightly'})
+ self.assertEqual(fc.test_module, 'base')
def test_inheritance(self):
"""Test inheritance when using the 'tagged' decorator"""
@@ -64,13 +68,15 @@ class TestSetTags(TransactionCase):
pass
fc = FakeClassB()
- self.assertEqual(fc.test_tags, {'at_install', 'standard', 'base', 'nightly'})
+ self.assertEqual(fc.test_tags, {'at_install', 'standard', 'nightly'})
+ self.assertEqual(fc.test_module, 'base')
class FakeClassC(FakeClassA):
pass
fc = FakeClassC()
- self.assertEqual(fc.test_tags, {'at_install', 'standard', 'base'})
+ self.assertEqual(fc.test_tags, {'at_install', 'standard'})
+ self.assertEqual(fc.test_module, 'base')
def test_untagging(self):
"""Test that one can remove the 'standard' tag"""
@@ -80,7 +86,8 @@ class TestSetTags(TransactionCase):
pass
fc = FakeClassA()
- self.assertEqual(fc.test_tags, {'at_install', 'base'})
+ self.assertEqual(fc.test_tags, {'at_install'})
+ self.assertEqual(fc.test_module, 'base')
@tagged('-standard', '-base', '-at_install')
class FakeClassB(TransactionCase):
@@ -104,61 +111,110 @@ class TestSelector(TransactionCase):
"""Test the parser part of the TagsSelector class"""
tags = TagsSelector('+slow')
- self.assertEqual({'slow', }, tags.include)
+ self.assertEqual({('slow', None, None, None),}, tags.include)
self.assertEqual(set(), tags.exclude)
tags = TagsSelector('+slow,nightly')
- self.assertEqual({'slow', 'nightly'}, tags.include)
+ self.assertEqual({('slow', None, None, None), ('nightly', None, None, None)}, tags.include)
self.assertEqual(set(), tags.exclude)
tags = TagsSelector('+slow,-standard')
- self.assertEqual({'slow', }, tags.include)
- self.assertEqual({'standard', }, tags.exclude)
+ self.assertEqual({('slow', None, None, None),}, tags.include)
+ self.assertEqual({('standard', None, None, None),}, tags.exclude)
# same with space after the comma
tags = TagsSelector('+slow, -standard')
- self.assertEqual({'slow', }, tags.include)
- self.assertEqual({'standard', }, tags.exclude)
+ self.assertEqual({('slow', None, None, None),}, tags.include)
+ self.assertEqual({('standard', None, None, None),}, tags.exclude)
- # same with space befaore and after the comma
+ # same with space before and after the comma
tags = TagsSelector('+slow , -standard')
- self.assertEqual({'slow', }, tags.include)
- self.assertEqual({'standard', }, tags.exclude)
+ self.assertEqual({('slow', None, None, None), }, tags.include)
+ self.assertEqual({('standard', None, None, None), }, tags.exclude)
tags = TagsSelector('+slow ,-standard,+js')
- self.assertEqual({'slow', 'js', }, tags.include)
- self.assertEqual({'standard', }, tags.exclude)
+ self.assertEqual({('slow', None, None, None),('js', None, None, None)}, tags.include)
+ self.assertEqual({('standard', None, None, None),}, tags.exclude)
+ # without +
tags = TagsSelector('slow, ')
- self.assertEqual({'slow', }, tags.include)
+ self.assertEqual({('slow', None, None, None), }, tags.include)
self.assertEqual(set(), tags.exclude)
+ # duplicates
tags = TagsSelector('+slow,-standard, slow,-standard ')
- self.assertEqual({'slow', }, tags.include)
- self.assertEqual({'standard', }, tags.exclude)
+ self.assertEqual({('slow', None, None, None), }, tags.include)
+ self.assertEqual({('standard', None, None, None), }, tags.exclude)
tags = TagsSelector('')
self.assertEqual(set(), tags.include)
self.assertEqual(set(), tags.exclude)
+ tags = TagsSelector('/module') # all standard test of a module
+ self.assertEqual({('standard', 'module', None, None), }, tags.include)
+ self.assertEqual(set(), tags.exclude)
+
+ tags = TagsSelector('*/module') # all tests of a module
+ self.assertEqual({(None, 'module', None, None), }, tags.include)
+ self.assertEqual(set(), tags.exclude)
+
+ tags = TagsSelector(':class') # all standard test of a class
+ self.assertEqual({('standard', None, 'class', None), }, tags.include)
+ self.assertEqual(set(), tags.exclude)
+
+ tags = TagsSelector('.method')
+ self.assertEqual({('standard', None, None, 'method'), }, tags.include)
+ self.assertEqual(set(), tags.exclude)
+
+ tags = TagsSelector(':class.method')
+ self.assertEqual({('standard', None, 'class', 'method'), }, tags.include)
+ self.assertEqual(set(), tags.exclude)
+
+ tags = TagsSelector('/module:class.method') # only a specific test func in a module (standard)
+ self.assertEqual({('standard', 'module', 'class', 'method'), }, tags.include)
+ self.assertEqual(set(), tags.exclude)
+
+ tags = TagsSelector('*/module:class.method') # only a specific test func in a module
+ self.assertEqual({(None, 'module', 'class', 'method'), }, tags.include)
+ self.assertEqual(set(), tags.exclude)
+
+ tags = TagsSelector('-/module:class.method') # disable a specific test func in a module
+ self.assertEqual({('standard', None, None, None), }, tags.include) # all strandard
+ self.assertEqual({(None, 'module', 'class', 'method'), }, tags.exclude) # exept the test func
+
+ tags = TagsSelector('-*/module:class.method')
+ self.assertEqual({('standard', None, None, None), }, tags.include)
+ self.assertEqual({(None, 'module', 'class', 'method'), }, tags.exclude)
+
+ tags = TagsSelector('tag/module')
+ self.assertEqual({('tag', 'module', None, None), }, tags.include)
+ self.assertEqual(set(), tags.exclude)
+
+ tags = TagsSelector('tag.method')
+ self.assertEqual({('tag', None, None, 'method'), }, tags.include)
+ self.assertEqual(set(), tags.exclude)
+
+ tags = TagsSelector('*/module,-standard') # all non standard test of a module
+ self.assertEqual({(None, 'module', None, None), }, tags.include) # all in module
+ self.assertEqual({('standard', None, None, None), }, tags.exclude) # exept standard ones
+
@tagged('nodatabase')
class TestSelectorSelection(TransactionCase):
-
def test_selector_selection(self):
"""Test check_tags use cases"""
class Test_A(TransactionCase):
pass
@tagged('stock')
- class Test_B():
+ class Test_B(BaseCase):
pass
@tagged('stock', 'slow')
- class Test_C():
+ class Test_C(BaseCase):
pass
@tagged('standard', 'slow')
- class Test_D():
+ class Test_D(BaseCase):
pass
@tagged('-at_install', 'post_install')
@@ -175,7 +231,6 @@ class TestSelectorSelection(TransactionCase):
# considered tagged standard and they are run by default if
# not explicitly deselected with '-standard' or if 'standard' is not
# selectected along with another test tag
-
# same as "--test-tags=''" parameters:
tags = TagsSelector('')
self.assertFalse(tags.check(no_tags_obj))
@@ -211,10 +266,10 @@ class TestSelectorSelection(TransactionCase):
self.assertFalse(tags.check(stock_tag_obj))
tags = TagsSelector('standard')
- self.assertFalse(tags.check(stock_tag_obj))
+ self.assertTrue(tags.check(stock_tag_obj))
tags = TagsSelector('slow,standard')
- self.assertFalse(tags.check(stock_tag_obj))
+ self.assertTrue(tags.check(stock_tag_obj))
tags = TagsSelector('slow,-standard')
self.assertFalse(tags.check(stock_tag_obj))
diff --git a/odoo/addons/base/tests/test_translate.py b/odoo/addons/base/tests/test_translate.py
index 3d989f0eaa8..43b2fd1d102 100644
--- a/odoo/addons/base/tests/test_translate.py
+++ b/odoo/addons/base/tests/test_translate.py
@@ -1,17 +1,13 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-import unittest
-
from odoo.tools import pycompat
from odoo.tools import mute_logger
from odoo.tools.translate import quote, unquote, xml_translate, html_translate
-from odoo.tests.common import TransactionCase, tagged
+from odoo.tests.common import TransactionCase, BaseCase
from psycopg2 import IntegrityError
-@tagged('standard', 'at_install')
-class TranslationToolsTestCase(unittest.TestCase):
+class TranslationToolsTestCase(BaseCase):
def assertItemsEqual(self, a, b, msg=None):
self.assertEqual(sorted(a), sorted(b), msg)
diff --git a/odoo/addons/base/tests/test_uninstall.py b/odoo/addons/base/tests/test_uninstall.py
index 041825d463d..123fbf747dd 100644
--- a/odoo/addons/base/tests/test_uninstall.py
+++ b/odoo/addons/base/tests/test_uninstall.py
@@ -8,6 +8,8 @@ import unittest
from odoo import api, registry, SUPERUSER_ID
from odoo.tests import common
+from odoo.tests.common import BaseCase
+
from odoo.modules.registry import Registry
@@ -25,8 +27,7 @@ MODULE = 'test_uninstall'
MODEL = 'test_uninstall.model'
-@common.tagged('standard', 'at_install')
-class TestUninstall(unittest.TestCase):
+class TestUninstall(BaseCase):
"""
Test the install/uninstall of a test module. The module is available in
`odoo.tests` which should be present in the addons-path.
diff --git a/odoo/addons/base/tests/test_view_validation.py b/odoo/addons/base/tests/test_view_validation.py
index 5e550414436..c253d1badea 100644
--- a/odoo/addons/base/tests/test_view_validation.py
+++ b/odoo/addons/base/tests/test_view_validation.py
@@ -3,9 +3,8 @@
from lxml import etree
import io
-import unittest
-from odoo.tests.common import tagged
+from odoo.tests.common import BaseCase
from odoo.tools.view_validation import (
valid_page_in_book, valid_att_in_form, valid_type_in_colspan,
valid_type_in_col, valid_att_in_field, valid_att_in_label,
@@ -131,8 +130,7 @@ valid_tree = etree.parse(io.BytesIO(b'''\
''')).getroot()
-@tagged('standard', 'at_install')
-class TestViewValidation(unittest.TestCase):
+class TestViewValidation(BaseCase):
""" Test the view validation code (but not the views themselves). """
def test_page_validation(self):
diff --git a/odoo/addons/test_mimetypes/tests/test_guess_mimetypes.py b/odoo/addons/test_mimetypes/tests/test_guess_mimetypes.py
index 993a3c97528..2568cd21db9 100644
--- a/odoo/addons/test_mimetypes/tests/test_guess_mimetypes.py
+++ b/odoo/addons/test_mimetypes/tests/test_guess_mimetypes.py
@@ -1,8 +1,7 @@
# -*- coding: utf-8 -*-
import os.path
-import unittest
-from odoo.tests.common import tagged
+from odoo.tests.common import BaseCase
from odoo.tools.mimetypes import guess_mimetype
def contents(extension):
@@ -14,8 +13,7 @@ def contents(extension):
return f.read()
-@tagged('standard', 'at_install')
-class TestMimeGuessing(unittest.TestCase):
+class TestMimeGuessing(BaseCase):
def test_doc(self):
self.assertEqual(
guess_mimetype(contents('doc')),
diff --git a/odoo/tests/common.py b/odoo/tests/common.py
index f7e1cb41b53..5d934c979c1 100644
--- a/odoo/tests/common.py
+++ b/odoo/tests/common.py
@@ -190,8 +190,9 @@ class MetaCase(type):
super(MetaCase, cls).__init__(name, bases, attrs)
# assign default test tags
if cls.__module__.startswith('odoo.addons.'):
- module = cls.__module__.split('.')[2]
- cls.test_tags = {'standard', 'at_install', module}
+ cls.test_tags = {'standard', 'at_install'}
+ cls.test_module = cls.__module__.split('.')[2]
+ cls.test_class = cls.__name__
class BaseCase(TreeCase, MetaCase('DummyCase', (object,), {})):
@@ -1824,7 +1825,7 @@ def _get_node(view, f, *arg):
def tagged(*tags):
"""
- A decorator to tag TestCase objects
+ A decorator to tag BaseCase objects
Tags are stored in a set that can be accessed from a 'test_tags' attribute
A tag prefixed by '-' will remove the tag e.g. to remove the 'standard' tag
By default, all Test classes from odoo.tests.common have a test_tags
@@ -1834,33 +1835,75 @@ def tagged(*tags):
def tags_decorator(obj):
include = {t for t in tags if not t.startswith('-')}
exclude = {t[1:] for t in tags if t.startswith('-')}
- obj.test_tags = (getattr(obj, 'test_tags', set()) | include) - exclude
+ obj.test_tags = (getattr(obj, 'test_tags', set()) | include) - exclude # todo remove getattr in master since we want to limmit tagged to BaseCase and always have +standard tag
return obj
return tags_decorator
class TagsSelector(object):
""" Test selector based on tags. """
+ filter_spec_re = re.compile(r'^([+-]?)(\*|\w*)(?:/(\w*))?(?::(\w*))?(?:\.(\w*))?$') # [-][tag][/module][:class][.method]
def __init__(self, spec):
""" Parse the spec to determine tags to include and exclude. """
- clean_tags = {t.strip() for t in spec.split(',') if t.strip() != ''}
- self.exclude = {t[1:] for t in clean_tags if t.startswith('-')}
- self.include = {t.replace('+', '') for t in clean_tags if not t.startswith('-')}
+ filter_specs = {t.strip() for t in spec.split(',') if t.strip()}
+ self.exclude = set()
+ self.include = set()
+
+ for filter_spec in filter_specs:
+ match = self.filter_spec_re.match(filter_spec)
+ if not match:
+ _logger.error('Invalid tag %s', filter_spec)
+ continue
+
+ sign, tag, module, klass, method = match.groups()
+ is_include = sign != '-'
+
+ if not tag and is_include:
+ # including /module:class.method implicitly requires 'standard'
+ tag = 'standard'
+ elif not tag or tag == '*':
+ # '*' indicates all tests (instead of 'standard' tests only)
+ tag = None
+ test_filter = (tag, module, klass, method)
+
+ if is_include:
+ self.include.add(test_filter)
+ else:
+ self.exclude.add(test_filter)
- def check(self, arg):
+ if self.exclude and not self.include:
+ self.include.add(('standard', None, None, None))
+
+ def check(self, test):
""" Return whether ``arg`` matches the specification: it must have at
- least one tag in ``self.include`` and none in ``self.exclude``.
+ least one tag in ``self.include`` and none in ``self.exclude`` for each tag category.
"""
- # handle the case where the Test does not inherit from TransactionCase
- tags = getattr(arg, 'test_tags', set())
- inter_no_test = self.exclude.intersection(tags)
- if inter_no_test:
- _logger.debug("Test '%s' not selected because it is tagged with : %s (exclusions: %s)", arg, inter_no_test, self.exclude)
+ if not hasattr(test, 'test_tags'): # handle the case where the Test does not inherit from BaseCase and has no test_tags
+ _logger.debug("Skipping test '%s' because no test_tag found.", test)
return False
- inter_to_test = self.include.intersection(tags)
- if not inter_to_test:
- _logger.debug("Test '%s' not selected because it was not tagged with %s", arg, self.include)
+
+ test_module = getattr(test, 'test_module', None)
+ test_class = getattr(test, 'test_class', None)
+ test_tags = test.test_tags | {test_module} # module as test_tags deprecated, keep for retrocompatibility,
+ test_method = getattr(test, '_testMethodName', None)
+
+ def _is_matching(test_filter):
+ (tag, module, klass, method) = test_filter
+ if tag and tag not in test_tags:
+ return False
+ elif module and module != test_module:
+ return False
+ elif klass and klass != test_class:
+ return False
+ elif method and test_method and method != test_method:
+ return False
+ return True
+
+ if any(_is_matching(test_filter) for test_filter in self.exclude):
return False
- _logger.debug("Test '%s' selected: tagged with %s, exclusions: %s, inclusions: %s", arg, tags, self.exclude, self.include)
- return True
+
+ if any(_is_matching(test_filter) for test_filter in self.include):
+ return True
+
+ return False
diff --git a/odoo/tools/config.py b/odoo/tools/config.py
index e9a1d388e1f..4a44415ac58 100644
--- a/odoo/tools/config.py
+++ b/odoo/tools/config.py
@@ -160,7 +160,14 @@ class configmanager(object):
dest='test_enable',
help="Enable unit tests.")
group.add_option("--test-tags", dest="test_tags",
- help="Comma separated list of tags to filter which tests to excute. Enable unit tests if set.")
+ help="""Comma separated list of spec to filter which tests to execute. Enable unit tests if set.
+ A filter spec has the format: [-][tag][/module][:class][.method]
+ The '-' specifies if we want to include or exclude tests matching this spec.
+ The tag will match tags added on a class with a @tagged decorator. By default tag value is 'standard' when not
+ given on include mode. '*' will match all tags. Tag will also match module name (deprecated, use /module)
+ The module, class, and method will respectively match the module name, test class name and test method name.
+ examples: :TestClass.test_func,/test_module,external
+ """)
parser.add_option_group(group)