|
5 | 5 | __email__ = "mbland at acm dot org" |
6 | 6 |
|
7 | 7 | import sys |
| 8 | +import traceback |
8 | 9 | import unittest |
9 | 10 | from collections import deque |
10 | 11 | from contextlib import _GeneratorContextManager, contextmanager, nullcontext |
@@ -79,11 +80,11 @@ def __exit__(self, *exc_info): |
79 | 80 | try: |
80 | 81 | if mgr.__exit__(*ex): |
81 | 82 | ex = (None, None, None) |
82 | | - except: |
83 | | - ex = sys.exc_info() |
| 83 | + except BaseException as e: |
| 84 | + ex = (type(e), e, e.__traceback__) |
84 | 85 | self.entered = None |
85 | 86 | if ex is not exc_info: |
86 | | - raise ex[0](ex[1]).with_traceback(ex[2]) |
| 87 | + raise ex |
87 | 88 |
|
88 | 89 |
|
89 | 90 | class MockNested(Nested): |
@@ -170,7 +171,10 @@ def __exit__(self, *args): |
170 | 171 | def shouldThrow(): |
171 | 172 | ct = EnterThrows() |
172 | 173 | self.foo = None |
173 | | - with ct as self.foo: |
| 174 | + # Ruff complains that we're redefining `self.foo` here, |
| 175 | + # but the whole point of the test is to check that `self.foo` |
| 176 | + # is *not* redefined (because `__enter__` raises) |
| 177 | + with ct as self.foo: # noqa: F811 |
174 | 178 | pass |
175 | 179 | self.assertRaises(RuntimeError, shouldThrow) |
176 | 180 | self.assertEqual(self.foo, None) |
@@ -251,7 +255,6 @@ def testInlineGeneratorBoundSyntax(self): |
251 | 255 | self.assertAfterWithGeneratorInvariantsNoError(foo) |
252 | 256 |
|
253 | 257 | def testInlineGeneratorBoundToExistingVariable(self): |
254 | | - foo = None |
255 | 258 | with mock_contextmanager_generator() as foo: |
256 | 259 | self.assertInWithGeneratorInvariants(foo) |
257 | 260 | self.assertAfterWithGeneratorInvariantsNoError(foo) |
@@ -749,5 +752,49 @@ def testEnterReturnsTuple(self): |
749 | 752 | self.assertEqual(10, b1) |
750 | 753 | self.assertEqual(20, b2) |
751 | 754 |
|
| 755 | + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'FrameSummary' object has no attribute 'end_lineno' |
| 756 | + def testExceptionLocation(self): |
| 757 | + # The location of an exception raised from |
| 758 | + # __init__, __enter__ or __exit__ of a context |
| 759 | + # manager should be just the context manager expression, |
| 760 | + # pinpointing the precise context manager in case there |
| 761 | + # is more than one. |
| 762 | + |
| 763 | + def init_raises(): |
| 764 | + try: |
| 765 | + with self.Dummy(), self.InitRaises() as cm, self.Dummy() as d: |
| 766 | + pass |
| 767 | + except Exception as e: |
| 768 | + return e |
| 769 | + |
| 770 | + def enter_raises(): |
| 771 | + try: |
| 772 | + with self.EnterRaises(), self.Dummy() as d: |
| 773 | + pass |
| 774 | + except Exception as e: |
| 775 | + return e |
| 776 | + |
| 777 | + def exit_raises(): |
| 778 | + try: |
| 779 | + with self.ExitRaises(), self.Dummy() as d: |
| 780 | + pass |
| 781 | + except Exception as e: |
| 782 | + return e |
| 783 | + |
| 784 | + for func, expected in [(init_raises, "self.InitRaises()"), |
| 785 | + (enter_raises, "self.EnterRaises()"), |
| 786 | + (exit_raises, "self.ExitRaises()"), |
| 787 | + ]: |
| 788 | + with self.subTest(func): |
| 789 | + exc = func() |
| 790 | + f = traceback.extract_tb(exc.__traceback__)[0] |
| 791 | + indent = 16 |
| 792 | + co = func.__code__ |
| 793 | + self.assertEqual(f.lineno, co.co_firstlineno + 2) |
| 794 | + self.assertEqual(f.end_lineno, co.co_firstlineno + 2) |
| 795 | + self.assertEqual(f.line[f.colno - indent : f.end_colno - indent], |
| 796 | + expected) |
| 797 | + |
| 798 | + |
752 | 799 | if __name__ == '__main__': |
753 | 800 | unittest.main() |
0 commit comments