Merge "Added storyboard integration to tempest.lib decorators"
This commit is contained in:
commit
fc8ef3aeeb
@ -0,0 +1,17 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Add a new parameter called ``bug_type`` to
|
||||
``tempest.lib.decorators.related_bug`` and
|
||||
``tempest.lib.decorators.skip_because`` decorators, which accepts
|
||||
2 values:
|
||||
|
||||
* launchpad
|
||||
* storyboard
|
||||
|
||||
This offers the possibility of tracking bugs related to tests using
|
||||
launchpad or storyboard references. The default value is launchpad
|
||||
for backward compatibility.
|
||||
|
||||
Passing in a non-digit ``bug`` value to either decorator will raise
|
||||
a ``InvalidParam`` exception (previously ``ValueError``).
|
@ -19,39 +19,83 @@ from oslo_log import log as logging
|
||||
import six
|
||||
import testtools
|
||||
|
||||
from tempest.lib import exceptions as lib_exc
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
_SUPPORTED_BUG_TYPES = {
|
||||
'launchpad': 'https://launchpad.net/bugs/%s',
|
||||
'storyboard': 'https://storyboard.openstack.org/#!/story/%s',
|
||||
}
|
||||
|
||||
|
||||
def _validate_bug_and_bug_type(bug, bug_type):
|
||||
"""Validates ``bug`` and ``bug_type`` values.
|
||||
|
||||
:param bug: bug number causing the test to skip (launchpad or storyboard)
|
||||
:param bug_type: 'launchpad' or 'storyboard', default 'launchpad'
|
||||
:raises: InvalidParam if ``bug`` is not a digit or ``bug_type`` is not
|
||||
a valid value
|
||||
"""
|
||||
if not bug.isdigit():
|
||||
invalid_param = '%s must be a valid %s number' % (bug, bug_type)
|
||||
raise lib_exc.InvalidParam(invalid_param=invalid_param)
|
||||
if bug_type not in _SUPPORTED_BUG_TYPES:
|
||||
invalid_param = 'bug_type "%s" must be one of: %s' % (
|
||||
bug_type, ', '.join(_SUPPORTED_BUG_TYPES.keys()))
|
||||
raise lib_exc.InvalidParam(invalid_param=invalid_param)
|
||||
|
||||
|
||||
def _get_bug_url(bug, bug_type='launchpad'):
|
||||
"""Get the bug URL based on the ``bug_type`` and ``bug``
|
||||
|
||||
:param bug: The launchpad/storyboard bug number causing the test
|
||||
:param bug_type: 'launchpad' or 'storyboard', default 'launchpad'
|
||||
:returns: Bug URL corresponding to ``bug_type`` value
|
||||
"""
|
||||
_validate_bug_and_bug_type(bug, bug_type)
|
||||
return _SUPPORTED_BUG_TYPES[bug_type] % bug
|
||||
|
||||
|
||||
def skip_because(*args, **kwargs):
|
||||
"""A decorator useful to skip tests hitting known bugs
|
||||
|
||||
@param bug: bug number causing the test to skip
|
||||
@param condition: optional condition to be True for the skip to have place
|
||||
``bug`` must be a number and ``condition`` must be true for the test to
|
||||
skip.
|
||||
|
||||
:param bug: bug number causing the test to skip (launchpad or storyboard)
|
||||
:param bug_type: 'launchpad' or 'storyboard', default 'launchpad'
|
||||
:param condition: optional condition to be True for the skip to have place
|
||||
:raises: testtools.TestCase.skipException if ``condition`` is True and
|
||||
``bug`` is included
|
||||
"""
|
||||
def decorator(f):
|
||||
@functools.wraps(f)
|
||||
def wrapper(*func_args, **func_kwargs):
|
||||
skip = False
|
||||
msg = ''
|
||||
if "condition" in kwargs:
|
||||
if kwargs["condition"] is True:
|
||||
skip = True
|
||||
else:
|
||||
skip = True
|
||||
if "bug" in kwargs and skip is True:
|
||||
if not kwargs['bug'].isdigit():
|
||||
raise ValueError('bug must be a valid bug number')
|
||||
msg = "Skipped until Bug: %s is resolved." % kwargs["bug"]
|
||||
bug = kwargs['bug']
|
||||
bug_type = kwargs.get('bug_type', 'launchpad')
|
||||
bug_url = _get_bug_url(bug, bug_type)
|
||||
msg = "Skipped until bug: %s is resolved." % bug_url
|
||||
raise testtools.TestCase.skipException(msg)
|
||||
return f(*func_args, **func_kwargs)
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
|
||||
def related_bug(bug, status_code=None):
|
||||
"""A decorator useful to know solutions from launchpad bug reports
|
||||
def related_bug(bug, status_code=None, bug_type='launchpad'):
|
||||
"""A decorator useful to know solutions from launchpad/storyboard reports
|
||||
|
||||
@param bug: The launchpad bug number causing the test
|
||||
@param status_code: The status code related to the bug report
|
||||
:param bug: The launchpad/storyboard bug number causing the test bug
|
||||
:param bug_type: 'launchpad' or 'storyboard', default 'launchpad'
|
||||
:param status_code: The status code related to the bug report
|
||||
"""
|
||||
def decorator(f):
|
||||
@functools.wraps(f)
|
||||
@ -61,9 +105,10 @@ def related_bug(bug, status_code=None):
|
||||
except Exception as exc:
|
||||
exc_status_code = getattr(exc, 'status_code', None)
|
||||
if status_code is None or status_code == exc_status_code:
|
||||
LOG.error('Hints: This test was made for the bug %s. '
|
||||
'The failure could be related to '
|
||||
'https://launchpad.net/bugs/%s', bug, bug)
|
||||
if bug:
|
||||
LOG.error('Hints: This test was made for the bug_type '
|
||||
'%s. The failure could be related to '
|
||||
'%s', bug, _get_bug_url(bug, bug_type))
|
||||
raise exc
|
||||
return wrapper
|
||||
return decorator
|
||||
|
@ -19,6 +19,7 @@ import testtools
|
||||
from tempest.lib import base as test
|
||||
from tempest.lib.common.utils import data_utils
|
||||
from tempest.lib import decorators
|
||||
from tempest.lib import exceptions as lib_exc
|
||||
from tempest.tests import base
|
||||
|
||||
|
||||
@ -62,21 +63,40 @@ class TestSkipBecauseDecorator(base.TestCase):
|
||||
|
||||
t = TestFoo('test_bar')
|
||||
if expected_to_skip:
|
||||
self.assertRaises(testtools.TestCase.skipException, t.test_bar)
|
||||
e = self.assertRaises(testtools.TestCase.skipException, t.test_bar)
|
||||
bug = decorator_args['bug']
|
||||
bug_type = decorator_args.get('bug_type', 'launchpad')
|
||||
self.assertRegex(
|
||||
str(e),
|
||||
r'Skipped until bug\: %s.*' % decorators._get_bug_url(
|
||||
bug, bug_type)
|
||||
)
|
||||
else:
|
||||
# assert that test_bar returned 0
|
||||
self.assertEqual(TestFoo('test_bar').test_bar(), 0)
|
||||
|
||||
def test_skip_because_bug(self):
|
||||
def test_skip_because_launchpad_bug(self):
|
||||
self._test_skip_because_helper(bug='12345')
|
||||
|
||||
def test_skip_because_bug_and_condition_true(self):
|
||||
def test_skip_because_launchpad_bug_and_condition_true(self):
|
||||
self._test_skip_because_helper(bug='12348', condition=True)
|
||||
|
||||
def test_skip_because_bug_and_condition_false(self):
|
||||
def test_skip_because_launchpad_bug_and_condition_false(self):
|
||||
self._test_skip_because_helper(expected_to_skip=False,
|
||||
bug='12349', condition=False)
|
||||
|
||||
def test_skip_because_storyboard_bug(self):
|
||||
self._test_skip_because_helper(bug='1992', bug_type='storyboard')
|
||||
|
||||
def test_skip_because_storyboard_bug_and_condition_true(self):
|
||||
self._test_skip_because_helper(bug='1992', bug_type='storyboard',
|
||||
condition=True)
|
||||
|
||||
def test_skip_because_storyboard_bug_and_condition_false(self):
|
||||
self._test_skip_because_helper(expected_to_skip=False,
|
||||
bug='1992', bug_type='storyboard',
|
||||
condition=False)
|
||||
|
||||
def test_skip_because_bug_without_bug_never_skips(self):
|
||||
"""Never skip without a bug parameter."""
|
||||
self._test_skip_because_helper(expected_to_skip=False,
|
||||
@ -84,8 +104,8 @@ class TestSkipBecauseDecorator(base.TestCase):
|
||||
self._test_skip_because_helper(expected_to_skip=False)
|
||||
|
||||
def test_skip_because_invalid_bug_number(self):
|
||||
"""Raise ValueError if with an invalid bug number"""
|
||||
self.assertRaises(ValueError, self._test_skip_because_helper,
|
||||
"""Raise InvalidParam if with an invalid bug number"""
|
||||
self.assertRaises(lib_exc.InvalidParam, self._test_skip_because_helper,
|
||||
bug='critical_bug')
|
||||
|
||||
|
||||
@ -126,6 +146,13 @@ class TestIdempotentIdDecorator(base.TestCase):
|
||||
|
||||
|
||||
class TestRelatedBugDecorator(base.TestCase):
|
||||
|
||||
def _get_my_exception(self):
|
||||
class MyException(Exception):
|
||||
def __init__(self, status_code):
|
||||
self.status_code = status_code
|
||||
return MyException
|
||||
|
||||
def test_relatedbug_when_no_exception(self):
|
||||
f = mock.Mock()
|
||||
sentinel = object()
|
||||
@ -137,10 +164,9 @@ class TestRelatedBugDecorator(base.TestCase):
|
||||
test_foo(sentinel)
|
||||
f.assert_called_once_with(sentinel)
|
||||
|
||||
def test_relatedbug_when_exception(self):
|
||||
class MyException(Exception):
|
||||
def __init__(self, status_code):
|
||||
self.status_code = status_code
|
||||
def test_relatedbug_when_exception_with_launchpad_bug_type(self):
|
||||
"""Validate related_bug decorator with bug_type == 'launchpad'"""
|
||||
MyException = self._get_my_exception()
|
||||
|
||||
def f(self):
|
||||
raise MyException(status_code=500)
|
||||
@ -152,4 +178,53 @@ class TestRelatedBugDecorator(base.TestCase):
|
||||
with mock.patch.object(decorators.LOG, 'error') as m_error:
|
||||
self.assertRaises(MyException, test_foo, object())
|
||||
|
||||
m_error.assert_called_once_with(mock.ANY, '1234', '1234')
|
||||
m_error.assert_called_once_with(
|
||||
mock.ANY, '1234', 'https://launchpad.net/bugs/1234')
|
||||
|
||||
def test_relatedbug_when_exception_with_storyboard_bug_type(self):
|
||||
"""Validate related_bug decorator with bug_type == 'storyboard'"""
|
||||
MyException = self._get_my_exception()
|
||||
|
||||
def f(self):
|
||||
raise MyException(status_code=500)
|
||||
|
||||
@decorators.related_bug(bug="1234", status_code=500,
|
||||
bug_type='storyboard')
|
||||
def test_foo(self):
|
||||
f(self)
|
||||
|
||||
with mock.patch.object(decorators.LOG, 'error') as m_error:
|
||||
self.assertRaises(MyException, test_foo, object())
|
||||
|
||||
m_error.assert_called_once_with(
|
||||
mock.ANY, '1234', 'https://storyboard.openstack.org/#!/story/1234')
|
||||
|
||||
def test_relatedbug_when_exception_invalid_bug_type(self):
|
||||
"""Check related_bug decorator raises exc when bug_type is not valid"""
|
||||
MyException = self._get_my_exception()
|
||||
|
||||
def f(self):
|
||||
raise MyException(status_code=500)
|
||||
|
||||
@decorators.related_bug(bug="1234", status_code=500,
|
||||
bug_type=mock.sentinel.invalid)
|
||||
def test_foo(self):
|
||||
f(self)
|
||||
|
||||
with mock.patch.object(decorators.LOG, 'error'):
|
||||
self.assertRaises(lib_exc.InvalidParam, test_foo, object())
|
||||
|
||||
def test_relatedbug_when_exception_invalid_bug_number(self):
|
||||
"""Check related_bug decorator raises exc when bug_number != digit"""
|
||||
MyException = self._get_my_exception()
|
||||
|
||||
def f(self):
|
||||
raise MyException(status_code=500)
|
||||
|
||||
@decorators.related_bug(bug="not a digit", status_code=500,
|
||||
bug_type='launchpad')
|
||||
def test_foo(self):
|
||||
f(self)
|
||||
|
||||
with mock.patch.object(decorators.LOG, 'error'):
|
||||
self.assertRaises(lib_exc.InvalidParam, test_foo, object())
|
||||
|
Loading…
x
Reference in New Issue
Block a user