Move the related_bug decorator from test.py to tempest/lib

I think it's a good idea to move all utility decorators into
tempest/lib/decorators.py. This patch does that for the `related_bug`
decorator.

Change-Id: I846d575e41f4dddfd5642b7750e988f75a717e7d
This commit is contained in:
Jordan Pittier 2017-04-12 16:42:53 +02:00
parent 0d93900ba6
commit c5665a6cc7
8 changed files with 72 additions and 28 deletions

View File

@ -0,0 +1,6 @@
---
features:
- |
A new ``related_bug`` decorator has been added to
``tempest.lib.decorators``. Use it to decorate and tag a test that was
added in relation to a launchpad bug report.

View File

@ -18,7 +18,6 @@ from tempest.common import fixed_network
from tempest.common import waiters from tempest.common import waiters
from tempest.lib.common.utils import data_utils from tempest.lib.common.utils import data_utils
from tempest.lib import decorators from tempest.lib import decorators
from tempest import test
class ServersAdminTestJSON(base.BaseV2ComputeAdminTest): class ServersAdminTestJSON(base.BaseV2ComputeAdminTest):
@ -93,7 +92,7 @@ class ServersAdminTestJSON(base.BaseV2ComputeAdminTest):
self.assertIn(self.s1_name, servers_name) self.assertIn(self.s1_name, servers_name)
self.assertIn(self.s2_name, servers_name) self.assertIn(self.s2_name, servers_name)
@test.related_bug('1659811') @decorators.related_bug('1659811')
@decorators.idempotent_id('7e5d6b8f-454a-4ba1-8ae2-da857af8338b') @decorators.idempotent_id('7e5d6b8f-454a-4ba1-8ae2-da857af8338b')
def test_list_servers_by_admin_with_specified_tenant(self): def test_list_servers_by_admin_with_specified_tenant(self):
# In nova v2, tenant_id is ignored unless all_tenants is specified # In nova v2, tenant_id is ignored unless all_tenants is specified

View File

@ -46,7 +46,7 @@ class VolumesAdminNegativeTest(base.BaseV2ComputeAdminTest):
self.server['id'], nonexistent_volume, self.server['id'], nonexistent_volume,
volumeId=volume['id']) volumeId=volume['id'])
@test.related_bug('1629110', status_code=400) @decorators.related_bug('1629110', status_code=400)
@test.attr(type=['negative']) @test.attr(type=['negative'])
@decorators.idempotent_id('7dcac15a-b107-46d3-a5f6-cb863f4e454a') @decorators.idempotent_id('7dcac15a-b107-46d3-a5f6-cb863f4e454a')
def test_update_attached_volume_with_nonexistent_volume_in_body(self): def test_update_attached_volume_with_nonexistent_volume_in_body(self):

View File

@ -178,7 +178,7 @@ class ServersNegativeTestJSON(base.BaseV2ComputeTest):
self.client.rebuild_server, self.client.rebuild_server,
server['id'], self.image_ref) server['id'], self.image_ref)
@test.related_bug('1660878', status_code=409) @decorators.related_bug('1660878', status_code=409)
@test.attr(type=['negative']) @test.attr(type=['negative'])
@decorators.idempotent_id('581a397d-5eab-486f-9cf9-1014bbd4c984') @decorators.idempotent_id('581a397d-5eab-486f-9cf9-1014bbd4c984')
def test_reboot_deleted_server(self): def test_reboot_deleted_server(self):
@ -219,7 +219,7 @@ class ServersNegativeTestJSON(base.BaseV2ComputeTest):
name=server_name) name=server_name)
@test.attr(type=['negative']) @test.attr(type=['negative'])
@test.related_bug('1651064', status_code=500) @decorators.related_bug('1651064', status_code=500)
@decorators.idempotent_id('12146ac1-d7df-4928-ad25-b1f99e5286cd') @decorators.idempotent_id('12146ac1-d7df-4928-ad25-b1f99e5286cd')
def test_create_server_invalid_bdm_in_2nd_dict(self): def test_create_server_invalid_bdm_in_2nd_dict(self):
volume = self.create_volume() volume = self.create_volume()

View File

@ -31,7 +31,7 @@ class AttachVolumeNegativeTest(base.BaseV2ComputeTest):
raise cls.skipException(skip_msg) raise cls.skipException(skip_msg)
@test.attr(type=['negative']) @test.attr(type=['negative'])
@test.related_bug('1630783', status_code=500) @decorators.related_bug('1630783', status_code=500)
@decorators.idempotent_id('a313b5cd-fbd0-49cc-94de-870e99f763c7') @decorators.idempotent_id('a313b5cd-fbd0-49cc-94de-870e99f763c7')
def test_delete_attached_volume(self): def test_delete_attached_volume(self):
server = self.create_test_server(wait_until='ACTIVE') server = self.create_test_server(wait_until='ACTIVE')

View File

@ -16,9 +16,12 @@ import functools
import uuid import uuid
import debtcollector.removals import debtcollector.removals
from oslo_log import log as logging
import six import six
import testtools import testtools
LOG = logging.getLogger(__name__)
def skip_because(*args, **kwargs): def skip_because(*args, **kwargs):
"""A decorator useful to skip tests hitting known bugs """A decorator useful to skip tests hitting known bugs
@ -45,6 +48,28 @@ def skip_because(*args, **kwargs):
return decorator return decorator
def related_bug(bug, status_code=None):
"""A decorator useful to know solutions from launchpad bug reports
@param bug: The launchpad bug number causing the test
@param status_code: The status code related to the bug report
"""
def decorator(f):
@functools.wraps(f)
def wrapper(self, *func_args, **func_kwargs):
try:
return f(self, *func_args, **func_kwargs)
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)
raise exc
return wrapper
return decorator
def idempotent_id(id): def idempotent_id(id):
"""Stub for metadata decorator""" """Stub for metadata decorator"""
if not isinstance(id, six.string_types): if not isinstance(id, six.string_types):

View File

@ -45,6 +45,11 @@ idempotent_id = debtcollector.moves.moved_function(
version='Mitaka', removal_version='?') version='Mitaka', removal_version='?')
related_bug = debtcollector.moves.moved_function(
decorators.related_bug, 'related_bug', __name__,
version='Pike', removal_version='?')
def attr(**kwargs): def attr(**kwargs):
"""A decorator which applies the testtools attr decorator """A decorator which applies the testtools attr decorator
@ -143,28 +148,6 @@ def is_extension_enabled(extension_name, service):
return False return False
def related_bug(bug, status_code=None):
"""A decorator useful to know solutions from launchpad bug reports
@param bug: The launchpad bug number causing the test
@param status_code: The status code related to the bug report
"""
def decorator(f):
@functools.wraps(f)
def wrapper(self, *func_args, **func_kwargs):
try:
return f(self, *func_args, **func_kwargs)
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)
raise exc
return wrapper
return decorator
def is_scheduler_filter_enabled(filter_name): def is_scheduler_filter_enabled(filter_name):
"""Check the list of enabled compute scheduler filters from config. """Check the list of enabled compute scheduler filters from config.

View File

@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import mock
import testtools import testtools
from tempest.lib import base as test from tempest.lib import base as test
@ -123,3 +124,33 @@ class TestSkipUnlessAttrDecorator(base.TestCase):
def test_no_skip_for_attr_exist_and_true(self): def test_no_skip_for_attr_exist_and_true(self):
self._test_skip_unless_attr('expected_attr', expected_to_skip=False) self._test_skip_unless_attr('expected_attr', expected_to_skip=False)
class TestRelatedBugDecorator(base.TestCase):
def test_relatedbug_when_no_exception(self):
f = mock.Mock()
sentinel = object()
@decorators.related_bug(bug="1234", status_code=500)
def test_foo(self):
f(self)
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 f(self):
raise MyException(status_code=500)
@decorators.related_bug(bug="1234", status_code=500)
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', '1234')