| 
									
										
										
										
											2021-10-23 00:13:46 +01:00
										 |  |  | import collections.abc | 
					
						
							|  |  |  | import types | 
					
						
							|  |  |  | import unittest | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class TestExceptionGroupTypeHierarchy(unittest.TestCase): | 
					
						
							|  |  |  |     def test_exception_group_types(self): | 
					
						
							|  |  |  |         self.assertTrue(issubclass(ExceptionGroup, Exception)) | 
					
						
							|  |  |  |         self.assertTrue(issubclass(ExceptionGroup, BaseExceptionGroup)) | 
					
						
							|  |  |  |         self.assertTrue(issubclass(BaseExceptionGroup, BaseException)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_exception_is_not_generic_type(self): | 
					
						
							| 
									
										
										
										
											2022-03-05 15:59:24 +02:00
										 |  |  |         with self.assertRaisesRegex(TypeError, 'Exception'): | 
					
						
							| 
									
										
										
										
											2021-10-23 00:13:46 +01:00
										 |  |  |             Exception[OSError] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_exception_group_is_generic_type(self): | 
					
						
							|  |  |  |         E = OSError | 
					
						
							|  |  |  |         self.assertIsInstance(ExceptionGroup[E], types.GenericAlias) | 
					
						
							|  |  |  |         self.assertIsInstance(BaseExceptionGroup[E], types.GenericAlias) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class BadConstructorArgs(unittest.TestCase): | 
					
						
							|  |  |  |     def test_bad_EG_construction__too_many_args(self): | 
					
						
							| 
									
										
										
										
											2022-01-25 23:52:43 +00:00
										 |  |  |         MSG = r'BaseExceptionGroup.__new__\(\) takes exactly 2 arguments' | 
					
						
							| 
									
										
										
										
											2021-10-23 00:13:46 +01:00
										 |  |  |         with self.assertRaisesRegex(TypeError, MSG): | 
					
						
							|  |  |  |             ExceptionGroup('no errors') | 
					
						
							|  |  |  |         with self.assertRaisesRegex(TypeError, MSG): | 
					
						
							|  |  |  |             ExceptionGroup([ValueError('no msg')]) | 
					
						
							|  |  |  |         with self.assertRaisesRegex(TypeError, MSG): | 
					
						
							|  |  |  |             ExceptionGroup('eg', [ValueError('too')], [TypeError('many')]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_bad_EG_construction__bad_message(self): | 
					
						
							|  |  |  |         MSG = 'argument 1 must be str, not ' | 
					
						
							|  |  |  |         with self.assertRaisesRegex(TypeError, MSG): | 
					
						
							|  |  |  |             ExceptionGroup(ValueError(12), SyntaxError('bad syntax')) | 
					
						
							|  |  |  |         with self.assertRaisesRegex(TypeError, MSG): | 
					
						
							|  |  |  |             ExceptionGroup(None, [ValueError(12)]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_bad_EG_construction__bad_excs_sequence(self): | 
					
						
							| 
									
										
										
										
											2021-11-16 11:58:21 +00:00
										 |  |  |         MSG = r'second argument \(exceptions\) must be a sequence' | 
					
						
							| 
									
										
										
										
											2021-10-23 00:13:46 +01:00
										 |  |  |         with self.assertRaisesRegex(TypeError, MSG): | 
					
						
							|  |  |  |             ExceptionGroup('errors not sequence', {ValueError(42)}) | 
					
						
							|  |  |  |         with self.assertRaisesRegex(TypeError, MSG): | 
					
						
							|  |  |  |             ExceptionGroup("eg", None) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-16 11:58:21 +00:00
										 |  |  |         MSG = r'second argument \(exceptions\) must be a non-empty sequence' | 
					
						
							| 
									
										
										
										
											2021-10-23 00:13:46 +01:00
										 |  |  |         with self.assertRaisesRegex(ValueError, MSG): | 
					
						
							|  |  |  |             ExceptionGroup("eg", []) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_bad_EG_construction__nested_non_exceptions(self): | 
					
						
							| 
									
										
										
										
											2021-11-16 11:58:21 +00:00
										 |  |  |         MSG = (r'Item [0-9]+ of second argument \(exceptions\)' | 
					
						
							| 
									
										
										
										
											2021-10-23 00:13:46 +01:00
										 |  |  |               ' is not an exception') | 
					
						
							|  |  |  |         with self.assertRaisesRegex(ValueError, MSG): | 
					
						
							|  |  |  |             ExceptionGroup('expect instance, not type', [OSError]); | 
					
						
							|  |  |  |         with self.assertRaisesRegex(ValueError, MSG): | 
					
						
							|  |  |  |             ExceptionGroup('bad error', ["not an exception"]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class InstanceCreation(unittest.TestCase): | 
					
						
							|  |  |  |     def test_EG_wraps_Exceptions__creates_EG(self): | 
					
						
							|  |  |  |         excs = [ValueError(1), TypeError(2)] | 
					
						
							|  |  |  |         self.assertIs( | 
					
						
							|  |  |  |             type(ExceptionGroup("eg", excs)), | 
					
						
							|  |  |  |             ExceptionGroup) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_BEG_wraps_Exceptions__creates_EG(self): | 
					
						
							|  |  |  |         excs = [ValueError(1), TypeError(2)] | 
					
						
							|  |  |  |         self.assertIs( | 
					
						
							|  |  |  |             type(BaseExceptionGroup("beg", excs)), | 
					
						
							|  |  |  |             ExceptionGroup) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_EG_wraps_BaseException__raises_TypeError(self): | 
					
						
							|  |  |  |         MSG= "Cannot nest BaseExceptions in an ExceptionGroup" | 
					
						
							|  |  |  |         with self.assertRaisesRegex(TypeError, MSG): | 
					
						
							|  |  |  |             eg = ExceptionGroup("eg", [ValueError(1), KeyboardInterrupt(2)]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_BEG_wraps_BaseException__creates_BEG(self): | 
					
						
							|  |  |  |         beg = BaseExceptionGroup("beg", [ValueError(1), KeyboardInterrupt(2)]) | 
					
						
							|  |  |  |         self.assertIs(type(beg), BaseExceptionGroup) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-18 15:44:43 +00:00
										 |  |  |     def test_EG_subclass_wraps_non_base_exceptions(self): | 
					
						
							| 
									
										
										
										
											2021-10-23 00:13:46 +01:00
										 |  |  |         class MyEG(ExceptionGroup): | 
					
						
							|  |  |  |             pass | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self.assertIs( | 
					
						
							|  |  |  |             type(MyEG("eg", [ValueError(12), TypeError(42)])), | 
					
						
							|  |  |  |             MyEG) | 
					
						
							| 
									
										
										
										
											2022-11-18 15:44:43 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def test_EG_subclass_does_not_wrap_base_exceptions(self): | 
					
						
							|  |  |  |         class MyEG(ExceptionGroup): | 
					
						
							|  |  |  |             pass | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         msg = "Cannot nest BaseExceptions in 'MyEG'" | 
					
						
							|  |  |  |         with self.assertRaisesRegex(TypeError, msg): | 
					
						
							|  |  |  |             MyEG("eg", [ValueError(12), KeyboardInterrupt(42)]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_BEG_and_E_subclass_does_not_wrap_base_exceptions(self): | 
					
						
							|  |  |  |         class MyEG(BaseExceptionGroup, ValueError): | 
					
						
							|  |  |  |             pass | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         msg = "Cannot nest BaseExceptions in 'MyEG'" | 
					
						
							|  |  |  |         with self.assertRaisesRegex(TypeError, msg): | 
					
						
							|  |  |  |             MyEG("eg", [ValueError(12), KeyboardInterrupt(42)]) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-10 23:44:53 -07:00
										 |  |  |     def test_EG_and_specific_subclass_can_wrap_any_nonbase_exception(self): | 
					
						
							|  |  |  |         class MyEG(ExceptionGroup, ValueError): | 
					
						
							|  |  |  |             pass | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # The restriction is specific to Exception, not "the other base class" | 
					
						
							|  |  |  |         MyEG("eg", [ValueError(12), Exception()]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_BEG_and_specific_subclass_can_wrap_any_nonbase_exception(self): | 
					
						
							|  |  |  |         class MyEG(BaseExceptionGroup, ValueError): | 
					
						
							|  |  |  |             pass | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # The restriction is specific to Exception, not "the other base class" | 
					
						
							|  |  |  |         MyEG("eg", [ValueError(12), Exception()]) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-23 00:13:46 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def test_BEG_subclass_wraps_anything(self): | 
					
						
							|  |  |  |         class MyBEG(BaseExceptionGroup): | 
					
						
							|  |  |  |             pass | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self.assertIs( | 
					
						
							|  |  |  |             type(MyBEG("eg", [ValueError(12), TypeError(42)])), | 
					
						
							|  |  |  |             MyBEG) | 
					
						
							|  |  |  |         self.assertIs( | 
					
						
							|  |  |  |             type(MyBEG("eg", [ValueError(12), KeyboardInterrupt(42)])), | 
					
						
							|  |  |  |             MyBEG) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-22 18:28:58 +00:00
										 |  |  | class StrAndReprTests(unittest.TestCase): | 
					
						
							|  |  |  |     def test_ExceptionGroup(self): | 
					
						
							|  |  |  |         eg = BaseExceptionGroup( | 
					
						
							|  |  |  |             'flat', [ValueError(1), TypeError(2)]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self.assertEqual(str(eg), "flat (2 sub-exceptions)") | 
					
						
							|  |  |  |         self.assertEqual(repr(eg), | 
					
						
							|  |  |  |             "ExceptionGroup('flat', [ValueError(1), TypeError(2)])") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         eg = BaseExceptionGroup( | 
					
						
							|  |  |  |             'nested', [eg, ValueError(1), eg, TypeError(2)]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self.assertEqual(str(eg), "nested (4 sub-exceptions)") | 
					
						
							|  |  |  |         self.assertEqual(repr(eg), | 
					
						
							|  |  |  |             "ExceptionGroup('nested', " | 
					
						
							|  |  |  |                 "[ExceptionGroup('flat', " | 
					
						
							|  |  |  |                     "[ValueError(1), TypeError(2)]), " | 
					
						
							|  |  |  |                  "ValueError(1), " | 
					
						
							|  |  |  |                  "ExceptionGroup('flat', " | 
					
						
							|  |  |  |                     "[ValueError(1), TypeError(2)]), TypeError(2)])") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_BaseExceptionGroup(self): | 
					
						
							|  |  |  |         eg = BaseExceptionGroup( | 
					
						
							|  |  |  |             'flat', [ValueError(1), KeyboardInterrupt(2)]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self.assertEqual(str(eg), "flat (2 sub-exceptions)") | 
					
						
							|  |  |  |         self.assertEqual(repr(eg), | 
					
						
							|  |  |  |             "BaseExceptionGroup(" | 
					
						
							|  |  |  |                 "'flat', " | 
					
						
							|  |  |  |                 "[ValueError(1), KeyboardInterrupt(2)])") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         eg = BaseExceptionGroup( | 
					
						
							|  |  |  |             'nested', [eg, ValueError(1), eg]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self.assertEqual(str(eg), "nested (3 sub-exceptions)") | 
					
						
							|  |  |  |         self.assertEqual(repr(eg), | 
					
						
							|  |  |  |             "BaseExceptionGroup('nested', " | 
					
						
							|  |  |  |                 "[BaseExceptionGroup('flat', " | 
					
						
							|  |  |  |                     "[ValueError(1), KeyboardInterrupt(2)]), " | 
					
						
							|  |  |  |                 "ValueError(1), " | 
					
						
							|  |  |  |                 "BaseExceptionGroup('flat', " | 
					
						
							|  |  |  |                     "[ValueError(1), KeyboardInterrupt(2)])])") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_custom_exception(self): | 
					
						
							|  |  |  |         class MyEG(ExceptionGroup): | 
					
						
							|  |  |  |             pass | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         eg = MyEG( | 
					
						
							|  |  |  |             'flat', [ValueError(1), TypeError(2)]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self.assertEqual(str(eg), "flat (2 sub-exceptions)") | 
					
						
							|  |  |  |         self.assertEqual(repr(eg), "MyEG('flat', [ValueError(1), TypeError(2)])") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         eg = MyEG( | 
					
						
							|  |  |  |             'nested', [eg, ValueError(1), eg, TypeError(2)]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self.assertEqual(str(eg), "nested (4 sub-exceptions)") | 
					
						
							|  |  |  |         self.assertEqual(repr(eg), ( | 
					
						
							|  |  |  |                  "MyEG('nested', " | 
					
						
							|  |  |  |                      "[MyEG('flat', [ValueError(1), TypeError(2)]), " | 
					
						
							|  |  |  |                       "ValueError(1), " | 
					
						
							|  |  |  |                       "MyEG('flat', [ValueError(1), TypeError(2)]), " | 
					
						
							|  |  |  |                       "TypeError(2)])")) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-23 00:13:46 +01:00
										 |  |  | def create_simple_eg(): | 
					
						
							|  |  |  |     excs = [] | 
					
						
							|  |  |  |     try: | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             raise MemoryError("context and cause for ValueError(1)") | 
					
						
							|  |  |  |         except MemoryError as e: | 
					
						
							|  |  |  |             raise ValueError(1) from e | 
					
						
							|  |  |  |     except ValueError as e: | 
					
						
							|  |  |  |         excs.append(e) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     try: | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             raise OSError("context for TypeError") | 
					
						
							|  |  |  |         except OSError as e: | 
					
						
							|  |  |  |             raise TypeError(int) | 
					
						
							|  |  |  |     except TypeError as e: | 
					
						
							|  |  |  |         excs.append(e) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     try: | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             raise ImportError("context for ValueError(2)") | 
					
						
							|  |  |  |         except ImportError as e: | 
					
						
							|  |  |  |             raise ValueError(2) | 
					
						
							|  |  |  |     except ValueError as e: | 
					
						
							|  |  |  |         excs.append(e) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     try: | 
					
						
							|  |  |  |         raise ExceptionGroup('simple eg', excs) | 
					
						
							|  |  |  |     except ExceptionGroup as e: | 
					
						
							|  |  |  |         return e | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class ExceptionGroupFields(unittest.TestCase): | 
					
						
							|  |  |  |     def test_basics_ExceptionGroup_fields(self): | 
					
						
							|  |  |  |         eg = create_simple_eg() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # check msg | 
					
						
							|  |  |  |         self.assertEqual(eg.message, 'simple eg') | 
					
						
							|  |  |  |         self.assertEqual(eg.args[0], 'simple eg') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # check cause and context | 
					
						
							|  |  |  |         self.assertIsInstance(eg.exceptions[0], ValueError) | 
					
						
							|  |  |  |         self.assertIsInstance(eg.exceptions[0].__cause__, MemoryError) | 
					
						
							|  |  |  |         self.assertIsInstance(eg.exceptions[0].__context__, MemoryError) | 
					
						
							|  |  |  |         self.assertIsInstance(eg.exceptions[1], TypeError) | 
					
						
							|  |  |  |         self.assertIsNone(eg.exceptions[1].__cause__) | 
					
						
							|  |  |  |         self.assertIsInstance(eg.exceptions[1].__context__, OSError) | 
					
						
							|  |  |  |         self.assertIsInstance(eg.exceptions[2], ValueError) | 
					
						
							|  |  |  |         self.assertIsNone(eg.exceptions[2].__cause__) | 
					
						
							|  |  |  |         self.assertIsInstance(eg.exceptions[2].__context__, ImportError) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # check tracebacks | 
					
						
							|  |  |  |         line0 = create_simple_eg.__code__.co_firstlineno | 
					
						
							|  |  |  |         tb_linenos = [line0 + 27, | 
					
						
							|  |  |  |                       [line0 + 6, line0 + 14, line0 + 22]] | 
					
						
							|  |  |  |         self.assertEqual(eg.__traceback__.tb_lineno, tb_linenos[0]) | 
					
						
							|  |  |  |         self.assertIsNone(eg.__traceback__.tb_next) | 
					
						
							|  |  |  |         for i in range(3): | 
					
						
							|  |  |  |             tb = eg.exceptions[i].__traceback__ | 
					
						
							|  |  |  |             self.assertIsNone(tb.tb_next) | 
					
						
							|  |  |  |             self.assertEqual(tb.tb_lineno, tb_linenos[1][i]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_fields_are_readonly(self): | 
					
						
							|  |  |  |         eg = ExceptionGroup('eg', [TypeError(1), OSError(2)]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self.assertEqual(type(eg.exceptions), tuple) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         eg.message | 
					
						
							|  |  |  |         with self.assertRaises(AttributeError): | 
					
						
							|  |  |  |             eg.message = "new msg" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         eg.exceptions | 
					
						
							|  |  |  |         with self.assertRaises(AttributeError): | 
					
						
							|  |  |  |             eg.exceptions = [OSError('xyz')] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class ExceptionGroupTestBase(unittest.TestCase): | 
					
						
							|  |  |  |     def assertMatchesTemplate(self, exc, exc_type, template): | 
					
						
							|  |  |  |         """ Assert that the exception matches the template
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             A template describes the shape of exc. If exc is a | 
					
						
							|  |  |  |             leaf exception (i.e., not an exception group) then | 
					
						
							|  |  |  |             template is an exception instance that has the | 
					
						
							|  |  |  |             expected type and args value of exc. If exc is an | 
					
						
							|  |  |  |             exception group, then template is a list of the | 
					
						
							|  |  |  |             templates of its nested exceptions. | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         if exc_type is not None: | 
					
						
							|  |  |  |             self.assertIs(type(exc), exc_type) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if isinstance(exc, BaseExceptionGroup): | 
					
						
							|  |  |  |             self.assertIsInstance(template, collections.abc.Sequence) | 
					
						
							|  |  |  |             self.assertEqual(len(exc.exceptions), len(template)) | 
					
						
							|  |  |  |             for e, t in zip(exc.exceptions, template): | 
					
						
							|  |  |  |                 self.assertMatchesTemplate(e, None, t) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             self.assertIsInstance(template, BaseException) | 
					
						
							|  |  |  |             self.assertEqual(type(exc), type(template)) | 
					
						
							|  |  |  |             self.assertEqual(exc.args, template.args) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-23 19:47:47 +01:00
										 |  |  | class Predicate: | 
					
						
							|  |  |  |     def __init__(self, func): | 
					
						
							|  |  |  |         self.func = func | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __call__(self, e): | 
					
						
							|  |  |  |         return self.func(e) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def method(self, e): | 
					
						
							|  |  |  |         return self.func(e) | 
					
						
							| 
									
										
										
										
											2021-10-23 00:13:46 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | class ExceptionGroupSubgroupTests(ExceptionGroupTestBase): | 
					
						
							|  |  |  |     def setUp(self): | 
					
						
							|  |  |  |         self.eg = create_simple_eg() | 
					
						
							|  |  |  |         self.eg_template = [ValueError(1), TypeError(int), ValueError(2)] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_basics_subgroup_split__bad_arg_type(self): | 
					
						
							| 
									
										
										
										
											2023-06-23 19:47:47 +01:00
										 |  |  |         class C: | 
					
						
							|  |  |  |             pass | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-23 00:13:46 +01:00
										 |  |  |         bad_args = ["bad arg", | 
					
						
							| 
									
										
										
										
											2023-06-23 19:47:47 +01:00
										 |  |  |                     C, | 
					
						
							| 
									
										
										
										
											2021-10-23 00:13:46 +01:00
										 |  |  |                     OSError('instance not type'), | 
					
						
							| 
									
										
										
										
											2021-12-14 16:48:15 +00:00
										 |  |  |                     [OSError, TypeError], | 
					
						
							| 
									
										
										
										
											2023-06-23 19:47:47 +01:00
										 |  |  |                     (OSError, 42), | 
					
						
							|  |  |  |                    ] | 
					
						
							| 
									
										
										
										
											2021-10-23 00:13:46 +01:00
										 |  |  |         for arg in bad_args: | 
					
						
							|  |  |  |             with self.assertRaises(TypeError): | 
					
						
							|  |  |  |                 self.eg.subgroup(arg) | 
					
						
							|  |  |  |             with self.assertRaises(TypeError): | 
					
						
							|  |  |  |                 self.eg.split(arg) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_basics_subgroup_by_type__passthrough(self): | 
					
						
							|  |  |  |         eg = self.eg | 
					
						
							|  |  |  |         self.assertIs(eg, eg.subgroup(BaseException)) | 
					
						
							|  |  |  |         self.assertIs(eg, eg.subgroup(Exception)) | 
					
						
							|  |  |  |         self.assertIs(eg, eg.subgroup(BaseExceptionGroup)) | 
					
						
							|  |  |  |         self.assertIs(eg, eg.subgroup(ExceptionGroup)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_basics_subgroup_by_type__no_match(self): | 
					
						
							|  |  |  |         self.assertIsNone(self.eg.subgroup(OSError)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_basics_subgroup_by_type__match(self): | 
					
						
							|  |  |  |         eg = self.eg | 
					
						
							|  |  |  |         testcases = [ | 
					
						
							|  |  |  |             # (match_type, result_template) | 
					
						
							|  |  |  |             (ValueError, [ValueError(1), ValueError(2)]), | 
					
						
							|  |  |  |             (TypeError, [TypeError(int)]), | 
					
						
							|  |  |  |             ((ValueError, TypeError), self.eg_template)] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for match_type, template in testcases: | 
					
						
							|  |  |  |             with self.subTest(match=match_type): | 
					
						
							|  |  |  |                 subeg = eg.subgroup(match_type) | 
					
						
							|  |  |  |                 self.assertEqual(subeg.message, eg.message) | 
					
						
							|  |  |  |                 self.assertMatchesTemplate(subeg, ExceptionGroup, template) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_basics_subgroup_by_predicate__passthrough(self): | 
					
						
							| 
									
										
										
										
											2023-06-23 19:47:47 +01:00
										 |  |  |         f = lambda e: True | 
					
						
							|  |  |  |         for callable in [f, Predicate(f), Predicate(f).method]: | 
					
						
							|  |  |  |             self.assertIs(self.eg, self.eg.subgroup(callable)) | 
					
						
							| 
									
										
										
										
											2021-10-23 00:13:46 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def test_basics_subgroup_by_predicate__no_match(self): | 
					
						
							| 
									
										
										
										
											2023-06-23 19:47:47 +01:00
										 |  |  |         f = lambda e: False | 
					
						
							|  |  |  |         for callable in [f, Predicate(f), Predicate(f).method]: | 
					
						
							|  |  |  |             self.assertIsNone(self.eg.subgroup(callable)) | 
					
						
							| 
									
										
										
										
											2021-10-23 00:13:46 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def test_basics_subgroup_by_predicate__match(self): | 
					
						
							|  |  |  |         eg = self.eg | 
					
						
							|  |  |  |         testcases = [ | 
					
						
							|  |  |  |             # (match_type, result_template) | 
					
						
							|  |  |  |             (ValueError, [ValueError(1), ValueError(2)]), | 
					
						
							|  |  |  |             (TypeError, [TypeError(int)]), | 
					
						
							|  |  |  |             ((ValueError, TypeError), self.eg_template)] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for match_type, template in testcases: | 
					
						
							| 
									
										
										
										
											2023-06-23 19:47:47 +01:00
										 |  |  |             f = lambda e: isinstance(e, match_type) | 
					
						
							|  |  |  |             for callable in [f, Predicate(f), Predicate(f).method]: | 
					
						
							|  |  |  |                 with self.subTest(callable=callable): | 
					
						
							|  |  |  |                     subeg = eg.subgroup(f) | 
					
						
							|  |  |  |                     self.assertEqual(subeg.message, eg.message) | 
					
						
							|  |  |  |                     self.assertMatchesTemplate(subeg, ExceptionGroup, template) | 
					
						
							| 
									
										
										
										
											2021-10-23 00:13:46 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class ExceptionGroupSplitTests(ExceptionGroupTestBase): | 
					
						
							|  |  |  |     def setUp(self): | 
					
						
							|  |  |  |         self.eg = create_simple_eg() | 
					
						
							|  |  |  |         self.eg_template = [ValueError(1), TypeError(int), ValueError(2)] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_basics_split_by_type__passthrough(self): | 
					
						
							|  |  |  |         for E in [BaseException, Exception, | 
					
						
							|  |  |  |                   BaseExceptionGroup, ExceptionGroup]: | 
					
						
							|  |  |  |             match, rest = self.eg.split(E) | 
					
						
							|  |  |  |             self.assertMatchesTemplate( | 
					
						
							|  |  |  |                 match, ExceptionGroup, self.eg_template) | 
					
						
							|  |  |  |             self.assertIsNone(rest) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_basics_split_by_type__no_match(self): | 
					
						
							|  |  |  |         match, rest = self.eg.split(OSError) | 
					
						
							|  |  |  |         self.assertIsNone(match) | 
					
						
							|  |  |  |         self.assertMatchesTemplate( | 
					
						
							|  |  |  |             rest, ExceptionGroup, self.eg_template) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_basics_split_by_type__match(self): | 
					
						
							|  |  |  |         eg = self.eg | 
					
						
							|  |  |  |         VE = ValueError | 
					
						
							|  |  |  |         TE = TypeError | 
					
						
							|  |  |  |         testcases = [ | 
					
						
							|  |  |  |             # (matcher, match_template, rest_template) | 
					
						
							|  |  |  |             (VE, [VE(1), VE(2)], [TE(int)]), | 
					
						
							|  |  |  |             (TE, [TE(int)], [VE(1), VE(2)]), | 
					
						
							|  |  |  |             ((VE, TE), self.eg_template, None), | 
					
						
							|  |  |  |             ((OSError, VE), [VE(1), VE(2)], [TE(int)]), | 
					
						
							|  |  |  |         ] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for match_type, match_template, rest_template in testcases: | 
					
						
							|  |  |  |             match, rest = eg.split(match_type) | 
					
						
							|  |  |  |             self.assertEqual(match.message, eg.message) | 
					
						
							|  |  |  |             self.assertMatchesTemplate( | 
					
						
							|  |  |  |                 match, ExceptionGroup, match_template) | 
					
						
							|  |  |  |             if rest_template is not None: | 
					
						
							|  |  |  |                 self.assertEqual(rest.message, eg.message) | 
					
						
							|  |  |  |                 self.assertMatchesTemplate( | 
					
						
							|  |  |  |                     rest, ExceptionGroup, rest_template) | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 self.assertIsNone(rest) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_basics_split_by_predicate__passthrough(self): | 
					
						
							| 
									
										
										
										
											2023-06-23 19:47:47 +01:00
										 |  |  |         f = lambda e: True | 
					
						
							|  |  |  |         for callable in [f, Predicate(f), Predicate(f).method]: | 
					
						
							|  |  |  |             match, rest = self.eg.split(callable) | 
					
						
							|  |  |  |             self.assertMatchesTemplate(match, ExceptionGroup, self.eg_template) | 
					
						
							|  |  |  |             self.assertIsNone(rest) | 
					
						
							| 
									
										
										
										
											2021-10-23 00:13:46 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def test_basics_split_by_predicate__no_match(self): | 
					
						
							| 
									
										
										
										
											2023-06-23 19:47:47 +01:00
										 |  |  |         f = lambda e: False | 
					
						
							|  |  |  |         for callable in [f, Predicate(f), Predicate(f).method]: | 
					
						
							|  |  |  |             match, rest = self.eg.split(callable) | 
					
						
							|  |  |  |             self.assertIsNone(match) | 
					
						
							|  |  |  |             self.assertMatchesTemplate(rest, ExceptionGroup, self.eg_template) | 
					
						
							| 
									
										
										
										
											2021-10-23 00:13:46 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def test_basics_split_by_predicate__match(self): | 
					
						
							|  |  |  |         eg = self.eg | 
					
						
							|  |  |  |         VE = ValueError | 
					
						
							|  |  |  |         TE = TypeError | 
					
						
							|  |  |  |         testcases = [ | 
					
						
							|  |  |  |             # (matcher, match_template, rest_template) | 
					
						
							|  |  |  |             (VE, [VE(1), VE(2)], [TE(int)]), | 
					
						
							|  |  |  |             (TE, [TE(int)], [VE(1), VE(2)]), | 
					
						
							|  |  |  |             ((VE, TE), self.eg_template, None), | 
					
						
							|  |  |  |         ] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for match_type, match_template, rest_template in testcases: | 
					
						
							| 
									
										
										
										
											2023-06-23 19:47:47 +01:00
										 |  |  |             f = lambda e: isinstance(e, match_type) | 
					
						
							|  |  |  |             for callable in [f, Predicate(f), Predicate(f).method]: | 
					
						
							|  |  |  |                 match, rest = eg.split(callable) | 
					
						
							|  |  |  |                 self.assertEqual(match.message, eg.message) | 
					
						
							| 
									
										
										
										
											2021-10-23 00:13:46 +01:00
										 |  |  |                 self.assertMatchesTemplate( | 
					
						
							| 
									
										
										
										
											2023-06-23 19:47:47 +01:00
										 |  |  |                     match, ExceptionGroup, match_template) | 
					
						
							|  |  |  |                 if rest_template is not None: | 
					
						
							|  |  |  |                     self.assertEqual(rest.message, eg.message) | 
					
						
							|  |  |  |                     self.assertMatchesTemplate( | 
					
						
							|  |  |  |                         rest, ExceptionGroup, rest_template) | 
					
						
							| 
									
										
										
										
											2021-10-23 00:13:46 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class DeepRecursionInSplitAndSubgroup(unittest.TestCase): | 
					
						
							|  |  |  |     def make_deep_eg(self): | 
					
						
							|  |  |  |         e = TypeError(1) | 
					
						
							|  |  |  |         for i in range(2000): | 
					
						
							|  |  |  |             e = ExceptionGroup('eg', [e]) | 
					
						
							|  |  |  |         return e | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_deep_split(self): | 
					
						
							|  |  |  |         e = self.make_deep_eg() | 
					
						
							|  |  |  |         with self.assertRaises(RecursionError): | 
					
						
							|  |  |  |             e.split(TypeError) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_deep_subgroup(self): | 
					
						
							|  |  |  |         e = self.make_deep_eg() | 
					
						
							|  |  |  |         with self.assertRaises(RecursionError): | 
					
						
							|  |  |  |             e.subgroup(TypeError) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def leaf_generator(exc, tbs=None): | 
					
						
							|  |  |  |     if tbs is None: | 
					
						
							|  |  |  |         tbs = [] | 
					
						
							|  |  |  |     tbs.append(exc.__traceback__) | 
					
						
							|  |  |  |     if isinstance(exc, BaseExceptionGroup): | 
					
						
							|  |  |  |         for e in exc.exceptions: | 
					
						
							|  |  |  |             yield from leaf_generator(e, tbs) | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         # exc is a leaf exception and its traceback | 
					
						
							|  |  |  |         # is the concatenation of the traceback | 
					
						
							|  |  |  |         # segments in tbs | 
					
						
							|  |  |  |         yield exc, tbs | 
					
						
							|  |  |  |     tbs.pop() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class LeafGeneratorTest(unittest.TestCase): | 
					
						
							|  |  |  |     # The leaf_generator is mentioned in PEP 654 as a suggestion | 
					
						
							|  |  |  |     # on how to iterate over leaf nodes of an EG. Is is also | 
					
						
							|  |  |  |     # used below as a test utility. So we test it here. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_leaf_generator(self): | 
					
						
							|  |  |  |         eg = create_simple_eg() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self.assertSequenceEqual( | 
					
						
							|  |  |  |             [e for e, _ in leaf_generator(eg)], | 
					
						
							|  |  |  |             eg.exceptions) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for e, tbs in leaf_generator(eg): | 
					
						
							|  |  |  |             self.assertSequenceEqual( | 
					
						
							|  |  |  |                 tbs, [eg.__traceback__, e.__traceback__]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def create_nested_eg(): | 
					
						
							|  |  |  |     excs = [] | 
					
						
							|  |  |  |     try: | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             raise TypeError(bytes) | 
					
						
							|  |  |  |         except TypeError as e: | 
					
						
							|  |  |  |             raise ExceptionGroup("nested", [e]) | 
					
						
							|  |  |  |     except ExceptionGroup as e: | 
					
						
							|  |  |  |         excs.append(e) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     try: | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             raise MemoryError('out of memory') | 
					
						
							|  |  |  |         except MemoryError as e: | 
					
						
							|  |  |  |             raise ValueError(1) from e | 
					
						
							|  |  |  |     except ValueError as e: | 
					
						
							|  |  |  |         excs.append(e) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     try: | 
					
						
							|  |  |  |         raise ExceptionGroup("root", excs) | 
					
						
							|  |  |  |     except ExceptionGroup as eg: | 
					
						
							|  |  |  |         return eg | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class NestedExceptionGroupBasicsTest(ExceptionGroupTestBase): | 
					
						
							|  |  |  |     def test_nested_group_matches_template(self): | 
					
						
							|  |  |  |         eg = create_nested_eg() | 
					
						
							|  |  |  |         self.assertMatchesTemplate( | 
					
						
							|  |  |  |             eg, | 
					
						
							|  |  |  |             ExceptionGroup, | 
					
						
							|  |  |  |             [[TypeError(bytes)], ValueError(1)]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_nested_group_chaining(self): | 
					
						
							|  |  |  |         eg = create_nested_eg() | 
					
						
							|  |  |  |         self.assertIsInstance(eg.exceptions[1].__context__, MemoryError) | 
					
						
							|  |  |  |         self.assertIsInstance(eg.exceptions[1].__cause__, MemoryError) | 
					
						
							|  |  |  |         self.assertIsInstance(eg.exceptions[0].__context__, TypeError) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_nested_exception_group_tracebacks(self): | 
					
						
							|  |  |  |         eg = create_nested_eg() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         line0 = create_nested_eg.__code__.co_firstlineno | 
					
						
							|  |  |  |         for (tb, expected) in [ | 
					
						
							|  |  |  |             (eg.__traceback__, line0 + 19), | 
					
						
							|  |  |  |             (eg.exceptions[0].__traceback__, line0 + 6), | 
					
						
							|  |  |  |             (eg.exceptions[1].__traceback__, line0 + 14), | 
					
						
							|  |  |  |             (eg.exceptions[0].exceptions[0].__traceback__, line0 + 4), | 
					
						
							|  |  |  |         ]: | 
					
						
							|  |  |  |             self.assertEqual(tb.tb_lineno, expected) | 
					
						
							|  |  |  |             self.assertIsNone(tb.tb_next) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_iteration_full_tracebacks(self): | 
					
						
							|  |  |  |         eg = create_nested_eg() | 
					
						
							|  |  |  |         # check that iteration over leaves | 
					
						
							|  |  |  |         # produces the expected tracebacks | 
					
						
							|  |  |  |         self.assertEqual(len(list(leaf_generator(eg))), 2) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         line0 = create_nested_eg.__code__.co_firstlineno | 
					
						
							|  |  |  |         expected_tbs = [ [line0 + 19, line0 + 6, line0 + 4], | 
					
						
							|  |  |  |                          [line0 + 19, line0 + 14]] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for (i, (_, tbs)) in enumerate(leaf_generator(eg)): | 
					
						
							|  |  |  |             self.assertSequenceEqual( | 
					
						
							|  |  |  |                 [tb.tb_lineno for tb in tbs], | 
					
						
							|  |  |  |                 expected_tbs[i]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class ExceptionGroupSplitTestBase(ExceptionGroupTestBase): | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def split_exception_group(self, eg, types): | 
					
						
							|  |  |  |         """ Split an EG and do some sanity checks on the result """ | 
					
						
							|  |  |  |         self.assertIsInstance(eg, BaseExceptionGroup) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         match, rest = eg.split(types) | 
					
						
							|  |  |  |         sg = eg.subgroup(types) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if match is not None: | 
					
						
							|  |  |  |             self.assertIsInstance(match, BaseExceptionGroup) | 
					
						
							|  |  |  |             for e,_ in leaf_generator(match): | 
					
						
							|  |  |  |                 self.assertIsInstance(e, types) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             self.assertIsNotNone(sg) | 
					
						
							|  |  |  |             self.assertIsInstance(sg, BaseExceptionGroup) | 
					
						
							|  |  |  |             for e,_ in leaf_generator(sg): | 
					
						
							|  |  |  |                 self.assertIsInstance(e, types) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if rest is not None: | 
					
						
							|  |  |  |             self.assertIsInstance(rest, BaseExceptionGroup) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def leaves(exc): | 
					
						
							|  |  |  |             return [] if exc is None else [e for e,_ in leaf_generator(exc)] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # match and subgroup have the same leaves | 
					
						
							|  |  |  |         self.assertSequenceEqual(leaves(match), leaves(sg)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         match_leaves = leaves(match) | 
					
						
							|  |  |  |         rest_leaves = leaves(rest) | 
					
						
							|  |  |  |         # each leaf exception of eg is in exactly one of match and rest | 
					
						
							|  |  |  |         self.assertEqual( | 
					
						
							|  |  |  |             len(leaves(eg)), | 
					
						
							|  |  |  |             len(leaves(match)) + len(leaves(rest))) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for e in leaves(eg): | 
					
						
							|  |  |  |             self.assertNotEqual( | 
					
						
							|  |  |  |                 match and e in match_leaves, | 
					
						
							|  |  |  |                 rest and e in rest_leaves) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-21 10:12:26 +00:00
										 |  |  |         # message, cause and context, traceback and note equal to eg | 
					
						
							| 
									
										
										
										
											2021-10-23 00:13:46 +01:00
										 |  |  |         for part in [match, rest, sg]: | 
					
						
							|  |  |  |             if part is not None: | 
					
						
							|  |  |  |                 self.assertEqual(eg.message, part.message) | 
					
						
							|  |  |  |                 self.assertIs(eg.__cause__, part.__cause__) | 
					
						
							|  |  |  |                 self.assertIs(eg.__context__, part.__context__) | 
					
						
							|  |  |  |                 self.assertIs(eg.__traceback__, part.__traceback__) | 
					
						
							| 
									
										
										
										
											2022-04-16 19:59:52 +01:00
										 |  |  |                 self.assertEqual( | 
					
						
							|  |  |  |                     getattr(eg, '__notes__', None), | 
					
						
							|  |  |  |                     getattr(part, '__notes__', None)) | 
					
						
							| 
									
										
										
										
											2021-10-23 00:13:46 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         def tbs_for_leaf(leaf, eg): | 
					
						
							|  |  |  |             for e, tbs in leaf_generator(eg): | 
					
						
							|  |  |  |                 if e is leaf: | 
					
						
							|  |  |  |                     return tbs | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def tb_linenos(tbs): | 
					
						
							|  |  |  |             return [tb.tb_lineno for tb in tbs if tb] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # full tracebacks match | 
					
						
							|  |  |  |         for part in [match, rest, sg]: | 
					
						
							|  |  |  |             for e in leaves(part): | 
					
						
							|  |  |  |                 self.assertSequenceEqual( | 
					
						
							|  |  |  |                     tb_linenos(tbs_for_leaf(e, eg)), | 
					
						
							|  |  |  |                     tb_linenos(tbs_for_leaf(e, part))) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return match, rest | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class NestedExceptionGroupSplitTest(ExceptionGroupSplitTestBase): | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_split_by_type(self): | 
					
						
							|  |  |  |         class MyExceptionGroup(ExceptionGroup): | 
					
						
							|  |  |  |             pass | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def raiseVE(v): | 
					
						
							|  |  |  |             raise ValueError(v) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def raiseTE(t): | 
					
						
							|  |  |  |             raise TypeError(t) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def nested_group(): | 
					
						
							|  |  |  |             def level1(i): | 
					
						
							|  |  |  |                 excs = [] | 
					
						
							|  |  |  |                 for f, arg in [(raiseVE, i), (raiseTE, int), (raiseVE, i+1)]: | 
					
						
							|  |  |  |                     try: | 
					
						
							|  |  |  |                         f(arg) | 
					
						
							|  |  |  |                     except Exception as e: | 
					
						
							|  |  |  |                         excs.append(e) | 
					
						
							|  |  |  |                 raise ExceptionGroup('msg1', excs) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             def level2(i): | 
					
						
							|  |  |  |                 excs = [] | 
					
						
							|  |  |  |                 for f, arg in [(level1, i), (level1, i+1), (raiseVE, i+2)]: | 
					
						
							|  |  |  |                     try: | 
					
						
							|  |  |  |                         f(arg) | 
					
						
							|  |  |  |                     except Exception as e: | 
					
						
							|  |  |  |                         excs.append(e) | 
					
						
							|  |  |  |                 raise MyExceptionGroup('msg2', excs) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             def level3(i): | 
					
						
							|  |  |  |                 excs = [] | 
					
						
							|  |  |  |                 for f, arg in [(level2, i+1), (raiseVE, i+2)]: | 
					
						
							|  |  |  |                     try: | 
					
						
							|  |  |  |                         f(arg) | 
					
						
							|  |  |  |                     except Exception as e: | 
					
						
							|  |  |  |                         excs.append(e) | 
					
						
							|  |  |  |                 raise ExceptionGroup('msg3', excs) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             level3(5) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             nested_group() | 
					
						
							|  |  |  |         except ExceptionGroup as e: | 
					
						
							| 
									
										
										
										
											2022-04-16 19:59:52 +01:00
										 |  |  |             e.add_note(f"the note: {id(e)}") | 
					
						
							| 
									
										
										
										
											2021-10-23 00:13:46 +01:00
										 |  |  |             eg = e | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         eg_template = [ | 
					
						
							|  |  |  |             [ | 
					
						
							|  |  |  |                 [ValueError(6), TypeError(int), ValueError(7)], | 
					
						
							|  |  |  |                 [ValueError(7), TypeError(int), ValueError(8)], | 
					
						
							|  |  |  |                 ValueError(8), | 
					
						
							|  |  |  |             ], | 
					
						
							|  |  |  |             ValueError(7)] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         valueErrors_template = [ | 
					
						
							|  |  |  |             [ | 
					
						
							|  |  |  |                 [ValueError(6), ValueError(7)], | 
					
						
							|  |  |  |                 [ValueError(7), ValueError(8)], | 
					
						
							|  |  |  |                 ValueError(8), | 
					
						
							|  |  |  |             ], | 
					
						
							|  |  |  |             ValueError(7)] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         typeErrors_template = [[[TypeError(int)], [TypeError(int)]]] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self.assertMatchesTemplate(eg, ExceptionGroup, eg_template) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Match Nothing | 
					
						
							|  |  |  |         match, rest = self.split_exception_group(eg, SyntaxError) | 
					
						
							|  |  |  |         self.assertIsNone(match) | 
					
						
							|  |  |  |         self.assertMatchesTemplate(rest, ExceptionGroup, eg_template) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Match Everything | 
					
						
							|  |  |  |         match, rest = self.split_exception_group(eg, BaseException) | 
					
						
							|  |  |  |         self.assertMatchesTemplate(match, ExceptionGroup, eg_template) | 
					
						
							|  |  |  |         self.assertIsNone(rest) | 
					
						
							|  |  |  |         match, rest = self.split_exception_group(eg, (ValueError, TypeError)) | 
					
						
							|  |  |  |         self.assertMatchesTemplate(match, ExceptionGroup, eg_template) | 
					
						
							|  |  |  |         self.assertIsNone(rest) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Match ValueErrors | 
					
						
							|  |  |  |         match, rest = self.split_exception_group(eg, ValueError) | 
					
						
							|  |  |  |         self.assertMatchesTemplate(match, ExceptionGroup, valueErrors_template) | 
					
						
							|  |  |  |         self.assertMatchesTemplate(rest, ExceptionGroup, typeErrors_template) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Match TypeErrors | 
					
						
							|  |  |  |         match, rest = self.split_exception_group(eg, (TypeError, SyntaxError)) | 
					
						
							|  |  |  |         self.assertMatchesTemplate(match, ExceptionGroup, typeErrors_template) | 
					
						
							|  |  |  |         self.assertMatchesTemplate(rest, ExceptionGroup, valueErrors_template) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Match ExceptionGroup | 
					
						
							|  |  |  |         match, rest = eg.split(ExceptionGroup) | 
					
						
							|  |  |  |         self.assertIs(match, eg) | 
					
						
							|  |  |  |         self.assertIsNone(rest) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Match MyExceptionGroup (ExceptionGroup subclass) | 
					
						
							|  |  |  |         match, rest = eg.split(MyExceptionGroup) | 
					
						
							|  |  |  |         self.assertMatchesTemplate(match, ExceptionGroup, [eg_template[0]]) | 
					
						
							|  |  |  |         self.assertMatchesTemplate(rest, ExceptionGroup, [eg_template[1]]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_split_BaseExceptionGroup(self): | 
					
						
							|  |  |  |         def exc(ex): | 
					
						
							|  |  |  |             try: | 
					
						
							|  |  |  |                 raise ex | 
					
						
							|  |  |  |             except BaseException as e: | 
					
						
							|  |  |  |                 return e | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             raise BaseExceptionGroup( | 
					
						
							|  |  |  |                 "beg", [exc(ValueError(1)), exc(KeyboardInterrupt(2))]) | 
					
						
							|  |  |  |         except BaseExceptionGroup as e: | 
					
						
							|  |  |  |             beg = e | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Match Nothing | 
					
						
							|  |  |  |         match, rest = self.split_exception_group(beg, TypeError) | 
					
						
							|  |  |  |         self.assertIsNone(match) | 
					
						
							|  |  |  |         self.assertMatchesTemplate( | 
					
						
							|  |  |  |             rest, BaseExceptionGroup, [ValueError(1), KeyboardInterrupt(2)]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Match Everything | 
					
						
							|  |  |  |         match, rest = self.split_exception_group( | 
					
						
							|  |  |  |             beg, (ValueError, KeyboardInterrupt)) | 
					
						
							|  |  |  |         self.assertMatchesTemplate( | 
					
						
							|  |  |  |             match, BaseExceptionGroup, [ValueError(1), KeyboardInterrupt(2)]) | 
					
						
							|  |  |  |         self.assertIsNone(rest) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Match ValueErrors | 
					
						
							|  |  |  |         match, rest = self.split_exception_group(beg, ValueError) | 
					
						
							|  |  |  |         self.assertMatchesTemplate( | 
					
						
							|  |  |  |             match, ExceptionGroup, [ValueError(1)]) | 
					
						
							|  |  |  |         self.assertMatchesTemplate( | 
					
						
							|  |  |  |             rest, BaseExceptionGroup, [KeyboardInterrupt(2)]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Match KeyboardInterrupts | 
					
						
							|  |  |  |         match, rest = self.split_exception_group(beg, KeyboardInterrupt) | 
					
						
							|  |  |  |         self.assertMatchesTemplate( | 
					
						
							|  |  |  |             match, BaseExceptionGroup, [KeyboardInterrupt(2)]) | 
					
						
							|  |  |  |         self.assertMatchesTemplate( | 
					
						
							|  |  |  |             rest, ExceptionGroup, [ValueError(1)]) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-16 19:59:52 +01:00
										 |  |  |     def test_split_copies_notes(self): | 
					
						
							|  |  |  |         # make sure each exception group after a split has its own __notes__ list | 
					
						
							|  |  |  |         eg = ExceptionGroup("eg", [ValueError(1), TypeError(2)]) | 
					
						
							|  |  |  |         eg.add_note("note1") | 
					
						
							|  |  |  |         eg.add_note("note2") | 
					
						
							|  |  |  |         orig_notes = list(eg.__notes__) | 
					
						
							|  |  |  |         match, rest = eg.split(TypeError) | 
					
						
							|  |  |  |         self.assertEqual(eg.__notes__, orig_notes) | 
					
						
							|  |  |  |         self.assertEqual(match.__notes__, orig_notes) | 
					
						
							|  |  |  |         self.assertEqual(rest.__notes__, orig_notes) | 
					
						
							|  |  |  |         self.assertIsNot(eg.__notes__, match.__notes__) | 
					
						
							|  |  |  |         self.assertIsNot(eg.__notes__, rest.__notes__) | 
					
						
							|  |  |  |         self.assertIsNot(match.__notes__, rest.__notes__) | 
					
						
							|  |  |  |         eg.add_note("eg") | 
					
						
							|  |  |  |         match.add_note("match") | 
					
						
							|  |  |  |         rest.add_note("rest") | 
					
						
							|  |  |  |         self.assertEqual(eg.__notes__, orig_notes + ["eg"]) | 
					
						
							|  |  |  |         self.assertEqual(match.__notes__, orig_notes + ["match"]) | 
					
						
							|  |  |  |         self.assertEqual(rest.__notes__, orig_notes + ["rest"]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_split_does_not_copy_non_sequence_notes(self): | 
					
						
							|  |  |  |         # __notes__ should be a sequence, which is shallow copied. | 
					
						
							|  |  |  |         # If it is not a sequence, the split parts don't get any notes. | 
					
						
							|  |  |  |         eg = ExceptionGroup("eg", [ValueError(1), TypeError(2)]) | 
					
						
							|  |  |  |         eg.__notes__ = 123 | 
					
						
							|  |  |  |         match, rest = eg.split(TypeError) | 
					
						
							|  |  |  |         self.assertFalse(hasattr(match, '__notes__')) | 
					
						
							|  |  |  |         self.assertFalse(hasattr(rest, '__notes__')) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-08 19:01:47 +01:00
										 |  |  |     def test_drive_invalid_return_value(self): | 
					
						
							|  |  |  |         class MyEg(ExceptionGroup): | 
					
						
							|  |  |  |             def derive(self, excs): | 
					
						
							|  |  |  |                 return 42 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         eg = MyEg('eg', [TypeError(1), ValueError(2)]) | 
					
						
							|  |  |  |         msg = "derive must return an instance of BaseExceptionGroup" | 
					
						
							|  |  |  |         with self.assertRaisesRegex(TypeError, msg): | 
					
						
							|  |  |  |             eg.split(TypeError) | 
					
						
							|  |  |  |         with self.assertRaisesRegex(TypeError, msg): | 
					
						
							|  |  |  |             eg.subgroup(TypeError) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-23 00:13:46 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | class NestedExceptionGroupSubclassSplitTest(ExceptionGroupSplitTestBase): | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_split_ExceptionGroup_subclass_no_derive_no_new_override(self): | 
					
						
							|  |  |  |         class EG(ExceptionGroup): | 
					
						
							|  |  |  |             pass | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             try: | 
					
						
							|  |  |  |                 try: | 
					
						
							|  |  |  |                     raise TypeError(2) | 
					
						
							|  |  |  |                 except TypeError as te: | 
					
						
							|  |  |  |                     raise EG("nested", [te]) | 
					
						
							|  |  |  |             except EG as nested: | 
					
						
							|  |  |  |                 try: | 
					
						
							|  |  |  |                     raise ValueError(1) | 
					
						
							|  |  |  |                 except ValueError as ve: | 
					
						
							|  |  |  |                     raise EG("eg", [ve, nested]) | 
					
						
							|  |  |  |         except EG as e: | 
					
						
							|  |  |  |             eg = e | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self.assertMatchesTemplate(eg, EG, [ValueError(1), [TypeError(2)]]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Match Nothing | 
					
						
							|  |  |  |         match, rest = self.split_exception_group(eg, OSError) | 
					
						
							|  |  |  |         self.assertIsNone(match) | 
					
						
							|  |  |  |         self.assertMatchesTemplate( | 
					
						
							|  |  |  |             rest, ExceptionGroup, [ValueError(1), [TypeError(2)]]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Match Everything | 
					
						
							|  |  |  |         match, rest = self.split_exception_group(eg, (ValueError, TypeError)) | 
					
						
							|  |  |  |         self.assertMatchesTemplate( | 
					
						
							|  |  |  |             match, ExceptionGroup, [ValueError(1), [TypeError(2)]]) | 
					
						
							|  |  |  |         self.assertIsNone(rest) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Match ValueErrors | 
					
						
							|  |  |  |         match, rest = self.split_exception_group(eg, ValueError) | 
					
						
							|  |  |  |         self.assertMatchesTemplate(match, ExceptionGroup, [ValueError(1)]) | 
					
						
							|  |  |  |         self.assertMatchesTemplate(rest, ExceptionGroup, [[TypeError(2)]]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Match TypeErrors | 
					
						
							|  |  |  |         match, rest = self.split_exception_group(eg, TypeError) | 
					
						
							|  |  |  |         self.assertMatchesTemplate(match, ExceptionGroup, [[TypeError(2)]]) | 
					
						
							|  |  |  |         self.assertMatchesTemplate(rest, ExceptionGroup, [ValueError(1)]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_split_BaseExceptionGroup_subclass_no_derive_new_override(self): | 
					
						
							|  |  |  |         class EG(BaseExceptionGroup): | 
					
						
							|  |  |  |             def __new__(cls, message, excs, unused): | 
					
						
							|  |  |  |                 # The "unused" arg is here to show that split() doesn't call | 
					
						
							|  |  |  |                 # the actual class constructor from the default derive() | 
					
						
							|  |  |  |                 # implementation (it would fail on unused arg if so because | 
					
						
							|  |  |  |                 # it assumes the BaseExceptionGroup.__new__ signature). | 
					
						
							|  |  |  |                 return super().__new__(cls, message, excs) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             raise EG("eg", [ValueError(1), KeyboardInterrupt(2)], "unused") | 
					
						
							|  |  |  |         except EG as e: | 
					
						
							|  |  |  |             eg = e | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self.assertMatchesTemplate( | 
					
						
							|  |  |  |             eg, EG, [ValueError(1), KeyboardInterrupt(2)]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Match Nothing | 
					
						
							|  |  |  |         match, rest = self.split_exception_group(eg, OSError) | 
					
						
							|  |  |  |         self.assertIsNone(match) | 
					
						
							|  |  |  |         self.assertMatchesTemplate( | 
					
						
							|  |  |  |             rest, BaseExceptionGroup, [ValueError(1), KeyboardInterrupt(2)]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Match Everything | 
					
						
							|  |  |  |         match, rest = self.split_exception_group( | 
					
						
							|  |  |  |             eg, (ValueError, KeyboardInterrupt)) | 
					
						
							|  |  |  |         self.assertMatchesTemplate( | 
					
						
							|  |  |  |             match, BaseExceptionGroup, [ValueError(1), KeyboardInterrupt(2)]) | 
					
						
							|  |  |  |         self.assertIsNone(rest) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Match ValueErrors | 
					
						
							|  |  |  |         match, rest = self.split_exception_group(eg, ValueError) | 
					
						
							|  |  |  |         self.assertMatchesTemplate(match, ExceptionGroup, [ValueError(1)]) | 
					
						
							|  |  |  |         self.assertMatchesTemplate( | 
					
						
							|  |  |  |             rest, BaseExceptionGroup, [KeyboardInterrupt(2)]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Match KeyboardInterrupt | 
					
						
							|  |  |  |         match, rest = self.split_exception_group(eg, KeyboardInterrupt) | 
					
						
							|  |  |  |         self.assertMatchesTemplate( | 
					
						
							|  |  |  |             match, BaseExceptionGroup, [KeyboardInterrupt(2)]) | 
					
						
							|  |  |  |         self.assertMatchesTemplate(rest, ExceptionGroup, [ValueError(1)]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_split_ExceptionGroup_subclass_derive_and_new_overrides(self): | 
					
						
							|  |  |  |         class EG(ExceptionGroup): | 
					
						
							|  |  |  |             def __new__(cls, message, excs, code): | 
					
						
							|  |  |  |                 obj = super().__new__(cls, message, excs) | 
					
						
							|  |  |  |                 obj.code = code | 
					
						
							|  |  |  |                 return obj | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             def derive(self, excs): | 
					
						
							|  |  |  |                 return EG(self.message, excs, self.code) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             try: | 
					
						
							|  |  |  |                 try: | 
					
						
							|  |  |  |                     raise TypeError(2) | 
					
						
							|  |  |  |                 except TypeError as te: | 
					
						
							|  |  |  |                     raise EG("nested", [te], 101) | 
					
						
							|  |  |  |             except EG as nested: | 
					
						
							|  |  |  |                 try: | 
					
						
							|  |  |  |                     raise ValueError(1) | 
					
						
							|  |  |  |                 except ValueError as ve: | 
					
						
							|  |  |  |                     raise EG("eg", [ve, nested], 42) | 
					
						
							|  |  |  |         except EG as e: | 
					
						
							|  |  |  |             eg = e | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self.assertMatchesTemplate(eg, EG, [ValueError(1), [TypeError(2)]]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Match Nothing | 
					
						
							|  |  |  |         match, rest = self.split_exception_group(eg, OSError) | 
					
						
							|  |  |  |         self.assertIsNone(match) | 
					
						
							|  |  |  |         self.assertMatchesTemplate(rest, EG, [ValueError(1), [TypeError(2)]]) | 
					
						
							|  |  |  |         self.assertEqual(rest.code, 42) | 
					
						
							|  |  |  |         self.assertEqual(rest.exceptions[1].code, 101) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Match Everything | 
					
						
							|  |  |  |         match, rest = self.split_exception_group(eg, (ValueError, TypeError)) | 
					
						
							|  |  |  |         self.assertMatchesTemplate(match, EG, [ValueError(1), [TypeError(2)]]) | 
					
						
							|  |  |  |         self.assertEqual(match.code, 42) | 
					
						
							|  |  |  |         self.assertEqual(match.exceptions[1].code, 101) | 
					
						
							|  |  |  |         self.assertIsNone(rest) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Match ValueErrors | 
					
						
							|  |  |  |         match, rest = self.split_exception_group(eg, ValueError) | 
					
						
							|  |  |  |         self.assertMatchesTemplate(match, EG, [ValueError(1)]) | 
					
						
							|  |  |  |         self.assertEqual(match.code, 42) | 
					
						
							|  |  |  |         self.assertMatchesTemplate(rest, EG, [[TypeError(2)]]) | 
					
						
							|  |  |  |         self.assertEqual(rest.code, 42) | 
					
						
							|  |  |  |         self.assertEqual(rest.exceptions[0].code, 101) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Match TypeErrors | 
					
						
							|  |  |  |         match, rest = self.split_exception_group(eg, TypeError) | 
					
						
							|  |  |  |         self.assertMatchesTemplate(match, EG, [[TypeError(2)]]) | 
					
						
							|  |  |  |         self.assertEqual(match.code, 42) | 
					
						
							|  |  |  |         self.assertEqual(match.exceptions[0].code, 101) | 
					
						
							|  |  |  |         self.assertMatchesTemplate(rest, EG, [ValueError(1)]) | 
					
						
							|  |  |  |         self.assertEqual(rest.code, 42) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | if __name__ == '__main__': | 
					
						
							|  |  |  |     unittest.main() |