Skip to content

Commit

Permalink
Change string representation of marshalled exception type (#391)
Browse files Browse the repository at this point in the history
* Fix marshal_exception not to use global state

* Change the class representation in marshal_exception

* Fix up existing tests

* Update traits_futures/tests/test_exception_handling.py
  • Loading branch information
mdickinson committed Jul 12, 2021
1 parent 28cb7e9 commit 6b1967a
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 7 deletions.
30 changes: 29 additions & 1 deletion traits_futures/exception_handling.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,34 @@
import traceback


def _qualified_type_name(class_):
"""
Compute a descriptive string representing a class, including
a module name where relevant.
Example outputs are "RuntimeError" for the built-in RuntimeError
exception, or "struct.error" for the struct module exception class.
Parameters
----------
class_ : type
Returns
-------
class_name : str
"""
# We're being extra conservative here and allowing for the possibility that
# the class doesn't have __module__ and/or __qualname__ attributes. This
# function is called during exception handling, so we want to minimise the
# possibility that it raises a new exception.
class_module = getattr(class_, "__module__", "<unknown>")
class_qualname = getattr(class_, "__qualname__", "<unknown>")
if class_module == "builtins":
return f"{class_qualname}"
else:
return f"{class_module}.{class_qualname}"


def marshal_exception(exception):
"""
Turn exception details into something that can be safely
Expand All @@ -31,7 +59,7 @@ def marshal_exception(exception):
formatted traceback.
"""
return (
str(type(exception)),
_qualified_type_name(type(exception)),
str(exception),
"".join(
traceback.format_exception(
Expand Down
2 changes: 1 addition & 1 deletion traits_futures/tests/background_call_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ def assertNoResult(self, future):
future.result

def assertException(self, future, exc_type):
self.assertEqual(future.exception[0], str(exc_type))
self.assertIn(exc_type.__name__, future.exception[0])

def assertNoException(self, future):
with self.assertRaises(AttributeError):
Expand Down
2 changes: 1 addition & 1 deletion traits_futures/tests/background_iteration_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,7 @@ def assertNoResult(self, future):
future.result

def assertException(self, future, exc_type):
self.assertEqual(future.exception[0], str(exc_type))
self.assertIn(exc_type.__name__, future.exception[0])

def assertNoException(self, future):
with self.assertRaises(AttributeError):
Expand Down
2 changes: 1 addition & 1 deletion traits_futures/tests/background_progress_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -332,4 +332,4 @@ def assertNoException(self, future):
future.exception

def assertException(self, future, exc_type):
self.assertEqual(future.exception[0], str(exc_type))
self.assertIn(exc_type.__name__, future.exception[0])
45 changes: 42 additions & 3 deletions traits_futures/tests/test_exception_handling.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
from traits_futures.exception_handling import marshal_exception


class CustomException(Exception):
"""Custom exception for testing purposes."""


class TestExceptionHandling(unittest.TestCase):
def test_marshal_exception(self):
try:
Expand All @@ -25,7 +29,7 @@ def test_marshal_exception(self):
self.assertIsInstance(exc_value, str)
self.assertIsInstance(exc_traceback, str)

self.assertEqual(exc_type, str(RuntimeError))
self.assertEqual(exc_type, "RuntimeError")
self.assertIn("something went wrong", exc_value)
self.assertIn("test_marshal_exception", exc_traceback)

Expand All @@ -41,7 +45,7 @@ def test_marshal_exception_with_unicode_message(self):
self.assertIsInstance(exc_value, str)
self.assertIsInstance(exc_traceback, str)

self.assertEqual(exc_type, str(ValueError))
self.assertEqual(exc_type, "ValueError")
self.assertIn(message, exc_value)
self.assertIn("test_marshal_exception", exc_traceback)

Expand All @@ -59,6 +63,41 @@ def test_marshal_exception_works_outside_except(self):
self.assertIsInstance(exc_value, str)
self.assertIsInstance(exc_traceback, str)

self.assertEqual(exc_type, str(RuntimeError))
self.assertEqual(exc_type, "RuntimeError")
self.assertIn("something went wrong", exc_value)
self.assertIn("test_marshal_exception", exc_traceback)

def test_marshal_exception_non_builtin(self):
message = "printer on fire"
try:
raise CustomException(message)
except BaseException as exception:
marshalled = marshal_exception(exception)

exc_type, exc_value, exc_traceback = marshalled
self.assertIsInstance(exc_type, str)
self.assertIsInstance(exc_value, str)
self.assertIsInstance(exc_traceback, str)

self.assertEqual(
exc_type,
f"{__name__}.CustomException",
)
self.assertIn(message, exc_value)
self.assertIn("test_marshal_exception", exc_traceback)

def test_marshal_exception_nested_exception(self):
class NestedException(Exception):
pass

try:
raise NestedException()
except BaseException as exception:
marshalled = marshal_exception(exception)

exc_type, exc_value, exc_traceback = marshalled
self.assertEqual(
exc_type,
f"{__name__}.TestExceptionHandling."
"test_marshal_exception_nested_exception.<locals>.NestedException",
)

0 comments on commit 6b1967a

Please sign in to comment.