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