Fix handling of MultipleException

Change-Id: I187c364db9e41c437e9d6dbcbc6df7bfadba43bb
This commit is contained in:
Federico Ressi 2019-10-30 13:55:49 +01:00
parent d41a755991
commit 5fe0a7d610
4 changed files with 136 additions and 14 deletions

View File

@ -33,6 +33,8 @@ fail = _asserts.fail
TobikoException = _exception.TobikoException
check_valid_type = _exception.check_valid_type
exc_info = _exception.exc_info
handle_multiple_exceptions = _exception.handle_multiple_exceptions
list_exc_infos = _exception.list_exc_infos
is_fixture = _fixture.is_fixture
get_fixture = _fixture.get_fixture

View File

@ -13,11 +13,13 @@
# under the License.
from __future__ import absolute_import
import contextlib
import collections
import sys
from oslo_log import log
import six
import testtools
LOG = log.getLogger(__name__)
@ -107,3 +109,36 @@ def exc_info(reraise=True):
info = ExceptionInfo(*sys.exc_info())
info.reraise_on_exit = reraise
return info
@contextlib.contextmanager
def handle_multiple_exceptions():
try:
yield
except testtools.MultipleExceptions as exc:
exc_infos = list_exc_infos()
if exc_infos:
for info in exc_infos[1:]:
LOG.exception("Unhandled exception:", exc_info=info)
six.reraise(*exc_infos[0])
else:
LOG.debug('empty MultipleExceptions: %s', str(exc))
def list_exc_infos(exc_info=None):
# pylint: disable=redefined-outer-name
exc_info = exc_info or sys.exc_info()
result = []
if exc_info[0]:
visited = set()
visiting = [exc_info]
while visiting:
exc_info = visiting.pop()
_, exc, _ = exc_info
if exc not in visited:
visited.add(exc)
if isinstance(exc, testtools.MultipleExceptions):
visiting.extend(reversed(exc.args))
else:
result.append(exc_info)
return result

View File

@ -23,6 +23,7 @@ import testtools
import tobiko
from tobiko.common import _detail
from tobiko.common import _exception
LOG = log.getLogger(__name__)
@ -98,33 +99,24 @@ def remove_fixture(obj, manager=None):
def setup_fixture(obj, manager=None):
'''Get registered fixture and setup it up'''
fixture = get_fixture(obj, manager=manager)
try:
with _exception.handle_multiple_exceptions():
fixture.setUp()
except testtools.MultipleExceptions as ex:
for exc_info in ex.args[1:]:
LOG.exception("Error setting up fixture %r",
fixture.fixture_name, exc_info=exc_info)
six.reraise(*ex.args[0])
return fixture
def reset_fixture(obj, manager=None):
'''Get registered fixture and setup it up'''
'''Get registered fixture and reset it'''
fixture = get_fixture(obj, manager=manager)
try:
with _exception.handle_multiple_exceptions():
fixture.reset()
except testtools.MultipleExceptions as ex:
for exc_info in ex.args[1:]:
LOG.exception("Error reseting fixture %r",
fixture.fixture_name, exc_info=exc_info)
six.reraise(*ex.args[0])
return fixture
def cleanup_fixture(obj, manager=None):
'''Get registered fixture and clean it up'''
fixture = get_fixture(obj, manager=manager)
fixture.cleanUp()
with _exception.handle_multiple_exceptions():
fixture.cleanUp()
return fixture

View File

@ -17,6 +17,7 @@ import sys
import tobiko
from tobiko.tests import unit
import testtools
class SomeException(tobiko.TobikoException):
@ -122,3 +123,95 @@ class TestExcInfo(unit.TobikoUnitTest):
reraised = self.assertRaises(RuntimeError, exc_info.reraise)
self.assertIs(exc_value, reraised)
class TestListExcInfo(unit.TobikoUnitTest):
def test_list_exc_info(self):
result = tobiko.list_exc_infos()
self.assertEqual([], result)
def test_list_exc_info_with_info(self):
error = make_exception(RuntimeError, 'error')
result = tobiko.list_exc_infos(exc_info=error)
self.assertEqual([error], result)
def test_list_exc_info_handling_exception(self):
try:
raise RuntimeError('error')
except RuntimeError:
result = tobiko.list_exc_infos()
self.assertEqual([sys.exc_info()], result)
def test_list_exc_info_handling_empty_multiple_exceptions(self):
error = make_exception(testtools.MultipleExceptions)
result = tobiko.list_exc_infos(exc_info=error)
self.assertEqual([], result)
def test_list_exc_info_handling_multiple_exceptions(self):
a = make_exception(RuntimeError, 'a')
b = make_exception(ValueError, 'b')
c = make_exception(TypeError, 'c')
multi = make_exception(testtools.MultipleExceptions, a, b, c)
result = tobiko.list_exc_infos(exc_info=multi)
self.assertEqual([a, b, c], result)
def test_list_exc_info_handling_nested_multiple_exceptions(self):
a = make_exception(RuntimeError, 'a')
b = make_exception(ValueError, 'b')
c = make_exception(TypeError, 'c')
d = make_exception(IndexError, 'd')
inner = make_exception(testtools.MultipleExceptions, b, c)
multi = make_exception(testtools.MultipleExceptions, a, inner, d)
result = tobiko.list_exc_infos(exc_info=multi)
self.assertEqual([a, b, c, d], result)
class TestHandleMultipleExceptions(unit.TobikoUnitTest):
def test_handle_multiple_exceptions(self):
with tobiko.handle_multiple_exceptions():
pass
def test_handle_multiple_exceptions_with_exception(self):
def run():
with tobiko.handle_multiple_exceptions():
raise RuntimeError('error')
self.assertRaises(RuntimeError, run)
def test_handle_multiple_exceptions_with_empty_multiple_exception(self):
with tobiko.handle_multiple_exceptions():
raise testtools.MultipleExceptions()
def test_handle_multiple_exceptions_with_multiple_exceptions(self):
a = make_exception(TypeError, 'a')
b = make_exception(ValueError, 'b')
c = make_exception(RuntimeError, 'c')
def run():
with tobiko.handle_multiple_exceptions():
raise testtools.MultipleExceptions(a, b, c)
ex = self.assertRaises(TypeError, run)
self.assertEqual(a[1], ex)
def test_handle_multiple_exceptions_with_nested_multiple_exceptions(self):
a = make_exception(RuntimeError, 'a')
b = make_exception(ValueError, 'b')
c = make_exception(TypeError, 'c')
d = make_exception(IndexError, 'd')
inner = make_exception(testtools.MultipleExceptions, b, c)
def run():
with tobiko.handle_multiple_exceptions():
raise testtools.MultipleExceptions(a, inner, d)
ex = self.assertRaises(RuntimeError, run)
self.assertEqual(a[1], ex)
def make_exception(cls, *args, **kwargs):
try:
raise cls(*args, **kwargs)
except cls:
return sys.exc_info()