Be really careful with non-ascii data in exceptions/failures
When exception message is Python 2 str with non-ascii symbols, six.text_type(exc) raises UnicodeError. This change handles this case gracefully by calling str() instead of string.text_type() on such exceptions when retrieving messages for exceptions/failures. Closes-bug: 1276053 Change-Id: I2eb7318a7a5cd5dd687390a65abc0a45bd47de40
This commit is contained in:
committed by
Joshua Harlow
parent
de0398c457
commit
ec908db1fc
@@ -16,6 +16,8 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import six
|
||||
|
||||
|
||||
class TaskFlowException(Exception):
|
||||
"""Base class for exceptions emitted from this library."""
|
||||
@@ -136,4 +138,16 @@ class WrappedFailure(TaskFlowException):
|
||||
return None
|
||||
|
||||
def __str__(self):
|
||||
return 'WrappedFailure: %s' % [str(cause) for cause in self._causes]
|
||||
causes = [exception_message(cause) for cause in self._causes]
|
||||
return 'WrappedFailure: %s' % causes
|
||||
|
||||
|
||||
def exception_message(exc):
|
||||
"""Return the string representation of exception."""
|
||||
# NOTE(imelnikov): Dealing with non-ascii data in python is difficult:
|
||||
# https://bugs.launchpad.net/taskflow/+bug/1275895
|
||||
# https://bugs.launchpad.net/taskflow/+bug/1276053
|
||||
try:
|
||||
return six.text_type(exc)
|
||||
except UnicodeError:
|
||||
return str(exc)
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import six
|
||||
|
||||
from taskflow import exceptions
|
||||
from taskflow import test
|
||||
@@ -225,3 +226,49 @@ class WrappedFailureTestCase(test.TestCase):
|
||||
|
||||
wf = exceptions.WrappedFailure([fail_obj, f3])
|
||||
self.assertEqual(list(wf), [f1, f2, f3])
|
||||
|
||||
|
||||
class NonAsciiExceptionsTestCase(test.TestCase):
|
||||
|
||||
def test_exception_with_non_ascii_str(self):
|
||||
bad_string = chr(200)
|
||||
fail = misc.Failure.from_exception(ValueError(bad_string))
|
||||
self.assertEqual(fail.exception_str, bad_string)
|
||||
self.assertEqual(str(fail), 'Failure: ValueError: %s' % bad_string)
|
||||
|
||||
def test_exception_non_ascii_unicode(self):
|
||||
hi_ru = u'привет'
|
||||
fail = misc.Failure.from_exception(ValueError(hi_ru))
|
||||
self.assertEqual(fail.exception_str, hi_ru)
|
||||
self.assertIsInstance(fail.exception_str, six.text_type)
|
||||
self.assertEqual(six.text_type(fail),
|
||||
u'Failure: ValueError: %s' % hi_ru)
|
||||
|
||||
def test_wrapped_failure_non_ascii_unicode(self):
|
||||
hi_cn = u'嗨'
|
||||
fail = ValueError(hi_cn)
|
||||
self.assertEqual(hi_cn, exceptions.exception_message(fail))
|
||||
fail = misc.Failure.from_exception(fail)
|
||||
wrapped_fail = exceptions.WrappedFailure([fail])
|
||||
if six.PY2:
|
||||
# Python 2.x will unicode escape it, while python 3.3+ will not,
|
||||
# so we sadly have to differentiate between these two...
|
||||
expected_result = (u"WrappedFailure: "
|
||||
"[u'Failure: ValueError: %s']"
|
||||
% (hi_cn.encode("unicode-escape")))
|
||||
else:
|
||||
expected_result = (u"WrappedFailure: "
|
||||
"['Failure: ValueError: %s']" % (hi_cn))
|
||||
self.assertEqual(expected_result, six.text_type(wrapped_fail))
|
||||
|
||||
def test_failure_equality_with_non_ascii_str(self):
|
||||
bad_string = chr(200)
|
||||
fail = misc.Failure.from_exception(ValueError(bad_string))
|
||||
copied = fail.copy()
|
||||
self.assertEqual(fail, copied)
|
||||
|
||||
def test_failure_equality_non_ascii_unicode(self):
|
||||
hi_ru = u'привет'
|
||||
fail = misc.Failure.from_exception(ValueError(hi_ru))
|
||||
copied = fail.copy()
|
||||
self.assertEqual(fail, copied)
|
||||
|
||||
@@ -31,7 +31,7 @@ import traceback
|
||||
|
||||
import six
|
||||
|
||||
from taskflow import exceptions
|
||||
from taskflow import exceptions as exc
|
||||
from taskflow.openstack.common import jsonutils
|
||||
from taskflow.utils import reflection
|
||||
|
||||
@@ -136,8 +136,8 @@ def item_from(container, index, name=None):
|
||||
# unsubscriptable type is being requested (type error).
|
||||
if name is None:
|
||||
name = index
|
||||
raise exceptions.NotFound("Unable to find %r in container %s"
|
||||
% (name, container))
|
||||
raise exc.NotFound("Unable to find %r in container %s"
|
||||
% (name, container))
|
||||
|
||||
|
||||
def get_duplicate_keys(iterable, key=None):
|
||||
@@ -444,7 +444,7 @@ def are_equal_exc_info_tuples(ei1, ei2):
|
||||
if ei1[0] is not ei2[0]:
|
||||
return False
|
||||
if not all((type(ei1[1]) == type(ei2[1]),
|
||||
six.text_type(ei1[1]) == six.text_type(ei2[1]),
|
||||
exc.exception_message(ei1[1]) == exc.exception_message(ei2[1]),
|
||||
repr(ei1[1]) == repr(ei2[1]))):
|
||||
return False
|
||||
if ei1[2] == ei2[2]:
|
||||
@@ -470,7 +470,7 @@ class Failure(object):
|
||||
reflection.get_all_class_names(exc_info[0], up_to=Exception))
|
||||
if not self._exc_type_names:
|
||||
raise TypeError('Invalid exception type: %r' % exc_info[0])
|
||||
self._exception_str = six.text_type(self._exc_info[1])
|
||||
self._exception_str = exc.exception_message(self._exc_info[1])
|
||||
self._traceback_str = ''.join(
|
||||
traceback.format_tb(self._exc_info[2]))
|
||||
else:
|
||||
@@ -557,14 +557,14 @@ class Failure(object):
|
||||
if len(failures) == 1:
|
||||
failures[0].reraise()
|
||||
elif len(failures) > 1:
|
||||
raise exceptions.WrappedFailure(failures)
|
||||
raise exc.WrappedFailure(failures)
|
||||
|
||||
def reraise(self):
|
||||
"""Re-raise captured exception."""
|
||||
if self._exc_info:
|
||||
six.reraise(*self._exc_info)
|
||||
else:
|
||||
raise exceptions.WrappedFailure([self])
|
||||
raise exc.WrappedFailure([self])
|
||||
|
||||
def check(self, *exc_classes):
|
||||
"""Check if any of exc_classes caused the failure.
|
||||
|
||||
Reference in New Issue
Block a user