diff options
| author | Xavier-Do <xdo@odoo.com> | 2019-07-01 15:32:45 +0000 |
|---|---|---|
| committer | Xavier-Do <xdo@odoo.com> | 2019-07-11 09:03:14 +0000 |
| commit | 95b4f2ab4b5698ab3a28c9c35ac8da6fb6def983 (patch) | |
| tree | f863a0f5f2933556f59c7e2aa4bcafdfed9029f5 | |
| parent | b8f4ad585e9eedb3bb71559e5948dfacc9dfb3a5 (diff) | |
[IMP] core: improve test_tags
Add the support to be able to specify exlicitely module, class and method name in test_tags
Before this commit, there is no distinction between module tag and test tag. Meaning
that --test-tag module_name will replace the default +standard tag and execute all test
of the module.
This commit tries to keep the initial behaviour, while addind new features, like explicit
modules tag with /module_name, as well as class (:class) and method (.method)
Some usage examples:
--test_tags /module will execute all standard test of module
--test_tags :class will execute all standard test with class name 'class'
--test_tags .method will execute all standard test with method name 'method'
--test_tags external/module will execute all external test of module
--test_tags */module will execute all tests of module,
--test_tags */module,-standard will execute all non standard tests of module,
--test_tags -/website:TestUiTranslate.test_admin_tour_rte_translator will disable only rte translator test
closes odoo/odoo#34756
Signed-off-by: Raphael Collet (rco) <rco@openerp.com>
| -rw-r--r-- | addons/web/tests/test_menu.py | 7 | ||||
| -rw-r--r-- | addons/web/tests/test_serving_base.py | 6 | ||||
| -rw-r--r-- | addons/web_editor/tests/test_converter.py | 5 | ||||
| -rw-r--r-- | addons/website/tests/test_converter.py | 10 | ||||
| -rw-r--r-- | odoo/addons/base/tests/test_base.py | 6 | ||||
| -rw-r--r-- | odoo/addons/base/tests/test_db_cursor.py | 5 | ||||
| -rw-r--r-- | odoo/addons/base/tests/test_func.py | 9 | ||||
| -rw-r--r-- | odoo/addons/base/tests/test_ir_sequence.py | 14 | ||||
| -rw-r--r-- | odoo/addons/base/tests/test_mail.py | 12 | ||||
| -rw-r--r-- | odoo/addons/base/tests/test_mimetypes.py | 5 | ||||
| -rw-r--r-- | odoo/addons/base/tests/test_misc.py | 9 | ||||
| -rw-r--r-- | odoo/addons/base/tests/test_osv.py | 7 | ||||
| -rw-r--r-- | odoo/addons/base/tests/test_tests_tags.py | 113 | ||||
| -rw-r--r-- | odoo/addons/base/tests/test_translate.py | 8 | ||||
| -rw-r--r-- | odoo/addons/base/tests/test_uninstall.py | 5 | ||||
| -rw-r--r-- | odoo/addons/base/tests/test_view_validation.py | 6 | ||||
| -rw-r--r-- | odoo/addons/test_mimetypes/tests/test_guess_mimetypes.py | 6 | ||||
| -rw-r--r-- | odoo/tests/common.py | 81 | ||||
| -rw-r--r-- | odoo/tools/config.py | 9 |
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) |
