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:
		 Ivan A. Melnikov
					Ivan A. Melnikov
				
			
				
					committed by
					
						 Joshua Harlow
						Joshua Harlow
					
				
			
			
				
	
			
			
			 Joshua Harlow
						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