|
3 | 3 | import sys |
4 | 4 | from textwrap import dedent, indent |
5 | 5 | import threading |
| 6 | +import traceback |
6 | 7 | import types |
7 | 8 | import unittest |
8 | 9 |
|
@@ -1667,6 +1668,55 @@ def test_set___main___attrs(self): |
1667 | 1668 | self.assertEqual(rc, 0) |
1668 | 1669 |
|
1669 | 1670 |
|
| 1671 | +class CaptureExceptionTests(unittest.TestCase): |
| 1672 | + |
| 1673 | + # Prevent crashes with incompatible TracebackException.format(). |
| 1674 | + # Regression test for https://github.com/python/cpython/issues/143377. |
| 1675 | + |
| 1676 | + def capture_with_formatter(self, exc, formatter): |
| 1677 | + with support.swap_attr(traceback.TracebackException, "format", formatter): |
| 1678 | + return _interpreters.capture_exception(exc) |
| 1679 | + |
| 1680 | + def test_capture_exception(self): |
| 1681 | + captured = _interpreters.capture_exception(ValueError("hello")) |
| 1682 | + |
| 1683 | + self.assertEqual(captured.type.__name__, "ValueError") |
| 1684 | + self.assertEqual(captured.type.__qualname__, "ValueError") |
| 1685 | + self.assertEqual(captured.type.__module__, "builtins") |
| 1686 | + |
| 1687 | + self.assertEqual(captured.msg, "hello") |
| 1688 | + self.assertEqual(captured.formatted, "ValueError: hello") |
| 1689 | + |
| 1690 | + def test_capture_exception_custom_format(self): |
| 1691 | + exc = ValueError("good bye!") |
| 1692 | + formatter = lambda self: ["hello\n", "world\n"] |
| 1693 | + captured = self.capture_with_formatter(exc, formatter) |
| 1694 | + self.assertEqual(captured.msg, "good bye!") |
| 1695 | + self.assertEqual(captured.formatted, "ValueError: good bye!") |
| 1696 | + self.assertEqual(captured.errdisplay, "hello\nworld") |
| 1697 | + |
| 1698 | + @support.subTests("exc_lines", ([], ["x-no-nl"], ["x-no-nl", "y-no-nl"])) |
| 1699 | + def test_capture_exception_invalid_format(self, exc_lines): |
| 1700 | + formatter = lambda self: exc_lines |
| 1701 | + captured = self.capture_with_formatter(ValueError(), formatter) |
| 1702 | + self.assertEqual(captured.msg, "") |
| 1703 | + self.assertEqual(captured.formatted, "ValueError: ") |
| 1704 | + self.assertEqual(captured.errdisplay, "".join(exc_lines)) |
| 1705 | + |
| 1706 | + @unittest.skipUnless( |
| 1707 | + support.Py_DEBUG, |
| 1708 | + "printing subinterpreter unraisable exceptions requires DEBUG build", |
| 1709 | + ) |
| 1710 | + def test_capture_exception_unraisable_exception(self): |
| 1711 | + formatter = lambda self: 1 |
| 1712 | + with support.catch_unraisable_exception() as cm: |
| 1713 | + captured = self.capture_with_formatter(ValueError(), formatter) |
| 1714 | + self.assertFalse(hasattr(captured, "errdisplay")) |
| 1715 | + self.assertEqual(cm.unraisable.exc_type, TypeError) |
| 1716 | + self.assertEqual(str(cm.unraisable.exc_value), |
| 1717 | + "can only join an iterable") |
| 1718 | + |
| 1719 | + |
1670 | 1720 | if __name__ == '__main__': |
1671 | 1721 | # Test needs to be a package, so we can do relative imports. |
1672 | 1722 | unittest.main() |
0 commit comments