From c5665a6cc75b5140227942dfe7ec994f021d8ba5 Mon Sep 17 00:00:00 2001 From: Jordan Pittier Date: Wed, 12 Apr 2017 16:42:53 +0200 Subject: [PATCH] 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 --- ...bug-decorator-to-lib-dbfd5c543bbb2805.yaml | 6 ++++ tempest/api/compute/admin/test_servers.py | 3 +- .../compute/admin/test_volumes_negative.py | 2 +- .../compute/servers/test_servers_negative.py | 4 +-- .../volumes/test_attach_volume_negative.py | 2 +- tempest/lib/decorators.py | 25 +++++++++++++++ tempest/test.py | 27 +++------------- tempest/tests/lib/test_decorators.py | 31 +++++++++++++++++++ 8 files changed, 72 insertions(+), 28 deletions(-) create mode 100644 releasenotes/notes/move-related_bug-decorator-to-lib-dbfd5c543bbb2805.yaml diff --git a/releasenotes/notes/move-related_bug-decorator-to-lib-dbfd5c543bbb2805.yaml b/releasenotes/notes/move-related_bug-decorator-to-lib-dbfd5c543bbb2805.yaml new file mode 100644 index 0000000000..8c420c82c7 --- /dev/null +++ b/releasenotes/notes/move-related_bug-decorator-to-lib-dbfd5c543bbb2805.yaml @@ -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. diff --git a/tempest/api/compute/admin/test_servers.py b/tempest/api/compute/admin/test_servers.py index 4360586573..aff61bf475 100644 --- a/tempest/api/compute/admin/test_servers.py +++ b/tempest/api/compute/admin/test_servers.py @@ -18,7 +18,6 @@ from tempest.common import fixed_network from tempest.common import waiters from tempest.lib.common.utils import data_utils from tempest.lib import decorators -from tempest import test class ServersAdminTestJSON(base.BaseV2ComputeAdminTest): @@ -93,7 +92,7 @@ class ServersAdminTestJSON(base.BaseV2ComputeAdminTest): self.assertIn(self.s1_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') def test_list_servers_by_admin_with_specified_tenant(self): # In nova v2, tenant_id is ignored unless all_tenants is specified diff --git a/tempest/api/compute/admin/test_volumes_negative.py b/tempest/api/compute/admin/test_volumes_negative.py index 06b089385a..7ebd07451f 100644 --- a/tempest/api/compute/admin/test_volumes_negative.py +++ b/tempest/api/compute/admin/test_volumes_negative.py @@ -46,7 +46,7 @@ class VolumesAdminNegativeTest(base.BaseV2ComputeAdminTest): self.server['id'], nonexistent_volume, volumeId=volume['id']) - @test.related_bug('1629110', status_code=400) + @decorators.related_bug('1629110', status_code=400) @test.attr(type=['negative']) @decorators.idempotent_id('7dcac15a-b107-46d3-a5f6-cb863f4e454a') def test_update_attached_volume_with_nonexistent_volume_in_body(self): diff --git a/tempest/api/compute/servers/test_servers_negative.py b/tempest/api/compute/servers/test_servers_negative.py index c6b3b402be..40a42893ff 100644 --- a/tempest/api/compute/servers/test_servers_negative.py +++ b/tempest/api/compute/servers/test_servers_negative.py @@ -178,7 +178,7 @@ class ServersNegativeTestJSON(base.BaseV2ComputeTest): self.client.rebuild_server, server['id'], self.image_ref) - @test.related_bug('1660878', status_code=409) + @decorators.related_bug('1660878', status_code=409) @test.attr(type=['negative']) @decorators.idempotent_id('581a397d-5eab-486f-9cf9-1014bbd4c984') def test_reboot_deleted_server(self): @@ -219,7 +219,7 @@ class ServersNegativeTestJSON(base.BaseV2ComputeTest): name=server_name) @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') def test_create_server_invalid_bdm_in_2nd_dict(self): volume = self.create_volume() diff --git a/tempest/api/compute/volumes/test_attach_volume_negative.py b/tempest/api/compute/volumes/test_attach_volume_negative.py index c01769045a..c178a8790e 100644 --- a/tempest/api/compute/volumes/test_attach_volume_negative.py +++ b/tempest/api/compute/volumes/test_attach_volume_negative.py @@ -31,7 +31,7 @@ class AttachVolumeNegativeTest(base.BaseV2ComputeTest): raise cls.skipException(skip_msg) @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') def test_delete_attached_volume(self): server = self.create_test_server(wait_until='ACTIVE') diff --git a/tempest/lib/decorators.py b/tempest/lib/decorators.py index 92f96985f6..c2ee2120c4 100644 --- a/tempest/lib/decorators.py +++ b/tempest/lib/decorators.py @@ -16,9 +16,12 @@ import functools import uuid import debtcollector.removals +from oslo_log import log as logging import six import testtools +LOG = logging.getLogger(__name__) + def skip_because(*args, **kwargs): """A decorator useful to skip tests hitting known bugs @@ -45,6 +48,28 @@ def skip_because(*args, **kwargs): 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): """Stub for metadata decorator""" if not isinstance(id, six.string_types): diff --git a/tempest/test.py b/tempest/test.py index 70421fd1c5..f2a8bd62d8 100644 --- a/tempest/test.py +++ b/tempest/test.py @@ -45,6 +45,11 @@ idempotent_id = debtcollector.moves.moved_function( version='Mitaka', removal_version='?') +related_bug = debtcollector.moves.moved_function( + decorators.related_bug, 'related_bug', __name__, + version='Pike', removal_version='?') + + def attr(**kwargs): """A decorator which applies the testtools attr decorator @@ -143,28 +148,6 @@ def is_extension_enabled(extension_name, service): 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): """Check the list of enabled compute scheduler filters from config. diff --git a/tempest/tests/lib/test_decorators.py b/tempest/tests/lib/test_decorators.py index f3a4e9c039..ea38e08460 100644 --- a/tempest/tests/lib/test_decorators.py +++ b/tempest/tests/lib/test_decorators.py @@ -13,6 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. +import mock import testtools 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): 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')