summaryrefslogtreecommitdiff
path: root/odoo/tests
diff options
context:
space:
mode:
authorMartin Trigaux <mat@odoo.com>2019-07-16 09:14:17 +0000
committerMartin Trigaux <mat@odoo.com>2019-07-16 09:14:17 +0000
commitcf8e74a1a7d3404f57798573e8b15ce4dc5b9be4 (patch)
treebd95e9d20aff9b4f92550bffb2835a55c3698c27 /odoo/tests
parentb67a3bb403b80e98a2b66dcb8cf570d2b156fb68 (diff)
parent1bd434da34731b5467cc17fdffe6f3ed5dfea2d4 (diff)
[MERGE] Forward port of 12.0 to saas-12.2 up to 1bd434da34731b546
closes odoo/odoo#34901 Signed-off-by: Martin Trigaux (mat) <mat@odoo.com>
Diffstat (limited to 'odoo/tests')
-rw-r--r--odoo/tests/common.py105
1 files changed, 83 insertions, 22 deletions
diff --git a/odoo/tests/common.py b/odoo/tests/common.py
index a5b0c7571a2..5b7edeb4c9a 100644
--- a/odoo/tests/common.py
+++ b/odoo/tests/common.py
@@ -189,8 +189,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,), {})):
@@ -244,7 +245,10 @@ class BaseCase(TreeCase, MetaCase('DummyCase', (object,), {})):
def _assertRaises(self, exception):
""" Context manager that clears the environment upon failure. """
with super(BaseCase, self).assertRaises(exception) as cm:
- with self.env.clear_upon_failure():
+ if hasattr(self, 'env'):
+ with self.env.clear_upon_failure():
+ yield cm
+ else:
yield cm
def assertRaises(self, exception, func=None, *args, **kwargs):
@@ -333,9 +337,23 @@ class BaseCase(TreeCase, MetaCase('DummyCase', (object,), {})):
return False
return True
+ def _repr_field_value(record, field_name):
+ record_value = record[field_name]
+ field_type = record._fields[field_name].type
+ if field_type == 'monetary':
+ currency_field_name = record._fields[field_name].currency_field
+ record_currency = record[currency_field_name]
+ return record_currency and record_currency.round(record_value) or record_value
+ elif field_type in ('one2many', 'many2many'):
+ return set(record_value.ids)
+ elif field_type == 'many2one':
+ return record_value.id
+ else:
+ return record_value
+
def _format_message(records, expected_values):
''' Return a formatted representation of records/expected_values. '''
- all_records_values = records.read(list(expected_values[0].keys()), load=False)
+ all_records_values = [{key: _repr_field_value(record, key) for key in expected_values[0]} for record in records]
msg1 = '\n'.join(pprint.pformat(dic) for dic in all_records_values)
msg2 = '\n'.join(pprint.pformat(dic) for dic in expected_values)
return 'Current values:\n\n%s\n\nExpected values:\n\n%s' % (msg1, msg2)
@@ -706,6 +724,8 @@ class ChromeBrowser():
res = None
if res and res.get('id') == ready_id:
if res.get('result') == awaited_result:
+ if has_exceeded:
+ self._logger.info('The ready code tooks too much time : %s', tdiff)
return True
else:
last_bad_res = res
@@ -713,7 +733,6 @@ class ChromeBrowser():
tdiff = time.time() - start_time
if tdiff >= 2 and not has_exceeded:
has_exceeded = True
- self._logger.warning('The ready code takes too much time : %s', tdiff)
self.take_screenshot(prefix='failed_ready')
self._logger.info('Ready code last try result: %s', last_bad_res or res)
@@ -1800,7 +1819,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
@@ -1810,33 +1829,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)
- def check(self, arg):
+ if is_include:
+ self.include.add(test_filter)
+ else:
+ self.exclude.add(test_filter)
+
+ 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