blob: 62a5d677a2e7a4cc542ff67dfcd9462820b1c5b5 [file] [log] [blame]
Matthew Treinish9e26ca82016-02-23 11:43:20 -05001# Copyright 2015 Hewlett-Packard Development Company, L.P.
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
14
15import functools
16import uuid
17
Jordan Pittierc5665a62017-04-12 16:42:53 +020018from oslo_log import log as logging
Matthew Treinish9e26ca82016-02-23 11:43:20 -050019import six
20import testtools
21
Chandan Kumar7d8c2812018-02-08 14:26:56 +053022from tempest.lib import exceptions as lib_exc
23
Jordan Pittierc5665a62017-04-12 16:42:53 +020024LOG = logging.getLogger(__name__)
25
Chandan Kumar7d8c2812018-02-08 14:26:56 +053026_SUPPORTED_BUG_TYPES = {
27 'launchpad': 'https://launchpad.net/bugs/%s',
28 'storyboard': 'https://storyboard.openstack.org/#!/story/%s',
29}
30
31
32def _validate_bug_and_bug_type(bug, bug_type):
33 """Validates ``bug`` and ``bug_type`` values.
34
35 :param bug: bug number causing the test to skip (launchpad or storyboard)
36 :param bug_type: 'launchpad' or 'storyboard', default 'launchpad'
37 :raises: InvalidParam if ``bug`` is not a digit or ``bug_type`` is not
38 a valid value
39 """
40 if not bug.isdigit():
41 invalid_param = '%s must be a valid %s number' % (bug, bug_type)
42 raise lib_exc.InvalidParam(invalid_param=invalid_param)
43 if bug_type not in _SUPPORTED_BUG_TYPES:
44 invalid_param = 'bug_type "%s" must be one of: %s' % (
45 bug_type, ', '.join(_SUPPORTED_BUG_TYPES.keys()))
46 raise lib_exc.InvalidParam(invalid_param=invalid_param)
47
48
49def _get_bug_url(bug, bug_type='launchpad'):
50 """Get the bug URL based on the ``bug_type`` and ``bug``
51
52 :param bug: The launchpad/storyboard bug number causing the test
53 :param bug_type: 'launchpad' or 'storyboard', default 'launchpad'
54 :returns: Bug URL corresponding to ``bug_type`` value
55 """
56 _validate_bug_and_bug_type(bug, bug_type)
57 return _SUPPORTED_BUG_TYPES[bug_type] % bug
58
Matthew Treinish9e26ca82016-02-23 11:43:20 -050059
60def skip_because(*args, **kwargs):
61 """A decorator useful to skip tests hitting known bugs
62
Chandan Kumar7d8c2812018-02-08 14:26:56 +053063 ``bug`` must be a number and ``condition`` must be true for the test to
64 skip.
65
66 :param bug: bug number causing the test to skip (launchpad or storyboard)
67 :param bug_type: 'launchpad' or 'storyboard', default 'launchpad'
68 :param condition: optional condition to be True for the skip to have place
69 :raises: testtools.TestCase.skipException if ``condition`` is True and
70 ``bug`` is included
Matthew Treinish9e26ca82016-02-23 11:43:20 -050071 """
72 def decorator(f):
73 @functools.wraps(f)
lkuchlana2addfe2017-11-13 12:06:34 +020074 def wrapper(*func_args, **func_kwargs):
Matthew Treinish9e26ca82016-02-23 11:43:20 -050075 skip = False
Chandan Kumar7d8c2812018-02-08 14:26:56 +053076 msg = ''
Matthew Treinish9e26ca82016-02-23 11:43:20 -050077 if "condition" in kwargs:
78 if kwargs["condition"] is True:
79 skip = True
80 else:
81 skip = True
82 if "bug" in kwargs and skip is True:
Chandan Kumar7d8c2812018-02-08 14:26:56 +053083 bug = kwargs['bug']
84 bug_type = kwargs.get('bug_type', 'launchpad')
85 bug_url = _get_bug_url(bug, bug_type)
86 msg = "Skipped until bug: %s is resolved." % bug_url
Matthew Treinish9e26ca82016-02-23 11:43:20 -050087 raise testtools.TestCase.skipException(msg)
lkuchlana2addfe2017-11-13 12:06:34 +020088 return f(*func_args, **func_kwargs)
Matthew Treinish9e26ca82016-02-23 11:43:20 -050089 return wrapper
90 return decorator
91
92
Chandan Kumar7d8c2812018-02-08 14:26:56 +053093def related_bug(bug, status_code=None, bug_type='launchpad'):
94 """A decorator useful to know solutions from launchpad/storyboard reports
Jordan Pittierc5665a62017-04-12 16:42:53 +020095
Chandan Kumar7d8c2812018-02-08 14:26:56 +053096 :param bug: The launchpad/storyboard bug number causing the test bug
97 :param bug_type: 'launchpad' or 'storyboard', default 'launchpad'
98 :param status_code: The status code related to the bug report
Jordan Pittierc5665a62017-04-12 16:42:53 +020099 """
100 def decorator(f):
101 @functools.wraps(f)
lkuchlanf5c19052017-11-23 09:26:55 +0200102 def wrapper(*func_args, **func_kwargs):
Jordan Pittierc5665a62017-04-12 16:42:53 +0200103 try:
lkuchlanf5c19052017-11-23 09:26:55 +0200104 return f(*func_args, **func_kwargs)
Jordan Pittierc5665a62017-04-12 16:42:53 +0200105 except Exception as exc:
106 exc_status_code = getattr(exc, 'status_code', None)
107 if status_code is None or status_code == exc_status_code:
Chandan Kumar7d8c2812018-02-08 14:26:56 +0530108 if bug:
109 LOG.error('Hints: This test was made for the bug_type '
110 '%s. The failure could be related to '
111 '%s', bug, _get_bug_url(bug, bug_type))
Jordan Pittierc5665a62017-04-12 16:42:53 +0200112 raise exc
113 return wrapper
114 return decorator
115
116
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500117def idempotent_id(id):
118 """Stub for metadata decorator"""
119 if not isinstance(id, six.string_types):
120 raise TypeError('Test idempotent_id must be string not %s'
121 '' % type(id).__name__)
122 uuid.UUID(id)
123
124 def decorator(f):
125 f = testtools.testcase.attr('id-%s' % id)(f)
126 if f.__doc__:
127 f.__doc__ = 'Test idempotent id: %s\n%s' % (id, f.__doc__)
128 else:
129 f.__doc__ = 'Test idempotent id: %s' % id
130 return f
131 return decorator
132
133
Jordan Pittier3b46d272017-04-12 16:17:28 +0200134def attr(**kwargs):
135 """A decorator which applies the testtools attr decorator
136
137 This decorator applies the testtools.testcase.attr if it is in the list of
138 attributes to testtools we want to apply.
Matt Riedemann29999632019-02-14 14:32:20 -0500139
140 :param condition: Optional condition which if true will apply the attr. If
141 a condition is specified which is false the attr will not be applied to
142 the test function. If not specified, the attr is always applied.
Jordan Pittier3b46d272017-04-12 16:17:28 +0200143 """
144
145 def decorator(f):
Matt Riedemann29999632019-02-14 14:32:20 -0500146 # Check to see if the attr should be conditional applied.
147 if 'condition' in kwargs and not kwargs.get('condition'):
148 return f
Jordan Pittier3b46d272017-04-12 16:17:28 +0200149 if 'type' in kwargs and isinstance(kwargs['type'], str):
150 f = testtools.testcase.attr(kwargs['type'])(f)
151 elif 'type' in kwargs and isinstance(kwargs['type'], list):
152 for attr in kwargs['type']:
153 f = testtools.testcase.attr(attr)(f)
154 return f
155
156 return decorator