python (3.12.0)

(root)/
lib/
python3.12/
test/
test_exception_group.py
       1  import collections.abc
       2  import types
       3  import unittest
       4  from test.support import C_RECURSION_LIMIT
       5  
       6  class ESC[4;38;5;81mTestExceptionGroupTypeHierarchy(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
       7      def test_exception_group_types(self):
       8          self.assertTrue(issubclass(ExceptionGroup, Exception))
       9          self.assertTrue(issubclass(ExceptionGroup, BaseExceptionGroup))
      10          self.assertTrue(issubclass(BaseExceptionGroup, BaseException))
      11  
      12      def test_exception_is_not_generic_type(self):
      13          with self.assertRaisesRegex(TypeError, 'Exception'):
      14              Exception[OSError]
      15  
      16      def test_exception_group_is_generic_type(self):
      17          E = OSError
      18          self.assertIsInstance(ExceptionGroup[E], types.GenericAlias)
      19          self.assertIsInstance(BaseExceptionGroup[E], types.GenericAlias)
      20  
      21  
      22  class ESC[4;38;5;81mBadConstructorArgs(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
      23      def test_bad_EG_construction__too_many_args(self):
      24          MSG = r'BaseExceptionGroup.__new__\(\) takes exactly 2 arguments'
      25          with self.assertRaisesRegex(TypeError, MSG):
      26              ExceptionGroup('no errors')
      27          with self.assertRaisesRegex(TypeError, MSG):
      28              ExceptionGroup([ValueError('no msg')])
      29          with self.assertRaisesRegex(TypeError, MSG):
      30              ExceptionGroup('eg', [ValueError('too')], [TypeError('many')])
      31  
      32      def test_bad_EG_construction__bad_message(self):
      33          MSG = 'argument 1 must be str, not '
      34          with self.assertRaisesRegex(TypeError, MSG):
      35              ExceptionGroup(ValueError(12), SyntaxError('bad syntax'))
      36          with self.assertRaisesRegex(TypeError, MSG):
      37              ExceptionGroup(None, [ValueError(12)])
      38  
      39      def test_bad_EG_construction__bad_excs_sequence(self):
      40          MSG = r'second argument \(exceptions\) must be a sequence'
      41          with self.assertRaisesRegex(TypeError, MSG):
      42              ExceptionGroup('errors not sequence', {ValueError(42)})
      43          with self.assertRaisesRegex(TypeError, MSG):
      44              ExceptionGroup("eg", None)
      45  
      46          MSG = r'second argument \(exceptions\) must be a non-empty sequence'
      47          with self.assertRaisesRegex(ValueError, MSG):
      48              ExceptionGroup("eg", [])
      49  
      50      def test_bad_EG_construction__nested_non_exceptions(self):
      51          MSG = (r'Item [0-9]+ of second argument \(exceptions\)'
      52                ' is not an exception')
      53          with self.assertRaisesRegex(ValueError, MSG):
      54              ExceptionGroup('expect instance, not type', [OSError]);
      55          with self.assertRaisesRegex(ValueError, MSG):
      56              ExceptionGroup('bad error', ["not an exception"])
      57  
      58  
      59  class ESC[4;38;5;81mInstanceCreation(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
      60      def test_EG_wraps_Exceptions__creates_EG(self):
      61          excs = [ValueError(1), TypeError(2)]
      62          self.assertIs(
      63              type(ExceptionGroup("eg", excs)),
      64              ExceptionGroup)
      65  
      66      def test_BEG_wraps_Exceptions__creates_EG(self):
      67          excs = [ValueError(1), TypeError(2)]
      68          self.assertIs(
      69              type(BaseExceptionGroup("beg", excs)),
      70              ExceptionGroup)
      71  
      72      def test_EG_wraps_BaseException__raises_TypeError(self):
      73          MSG= "Cannot nest BaseExceptions in an ExceptionGroup"
      74          with self.assertRaisesRegex(TypeError, MSG):
      75              eg = ExceptionGroup("eg", [ValueError(1), KeyboardInterrupt(2)])
      76  
      77      def test_BEG_wraps_BaseException__creates_BEG(self):
      78          beg = BaseExceptionGroup("beg", [ValueError(1), KeyboardInterrupt(2)])
      79          self.assertIs(type(beg), BaseExceptionGroup)
      80  
      81      def test_EG_subclass_wraps_non_base_exceptions(self):
      82          class ESC[4;38;5;81mMyEG(ESC[4;38;5;149mExceptionGroup):
      83              pass
      84  
      85          self.assertIs(
      86              type(MyEG("eg", [ValueError(12), TypeError(42)])),
      87              MyEG)
      88  
      89      def test_EG_subclass_does_not_wrap_base_exceptions(self):
      90          class ESC[4;38;5;81mMyEG(ESC[4;38;5;149mExceptionGroup):
      91              pass
      92  
      93          msg = "Cannot nest BaseExceptions in 'MyEG'"
      94          with self.assertRaisesRegex(TypeError, msg):
      95              MyEG("eg", [ValueError(12), KeyboardInterrupt(42)])
      96  
      97      def test_BEG_and_E_subclass_does_not_wrap_base_exceptions(self):
      98          class ESC[4;38;5;81mMyEG(ESC[4;38;5;149mBaseExceptionGroup, ESC[4;38;5;149mValueError):
      99              pass
     100  
     101          msg = "Cannot nest BaseExceptions in 'MyEG'"
     102          with self.assertRaisesRegex(TypeError, msg):
     103              MyEG("eg", [ValueError(12), KeyboardInterrupt(42)])
     104  
     105      def test_EG_and_specific_subclass_can_wrap_any_nonbase_exception(self):
     106          class ESC[4;38;5;81mMyEG(ESC[4;38;5;149mExceptionGroup, ESC[4;38;5;149mValueError):
     107              pass
     108  
     109          # The restriction is specific to Exception, not "the other base class"
     110          MyEG("eg", [ValueError(12), Exception()])
     111  
     112      def test_BEG_and_specific_subclass_can_wrap_any_nonbase_exception(self):
     113          class ESC[4;38;5;81mMyEG(ESC[4;38;5;149mBaseExceptionGroup, ESC[4;38;5;149mValueError):
     114              pass
     115  
     116          # The restriction is specific to Exception, not "the other base class"
     117          MyEG("eg", [ValueError(12), Exception()])
     118  
     119  
     120      def test_BEG_subclass_wraps_anything(self):
     121          class ESC[4;38;5;81mMyBEG(ESC[4;38;5;149mBaseExceptionGroup):
     122              pass
     123  
     124          self.assertIs(
     125              type(MyBEG("eg", [ValueError(12), TypeError(42)])),
     126              MyBEG)
     127          self.assertIs(
     128              type(MyBEG("eg", [ValueError(12), KeyboardInterrupt(42)])),
     129              MyBEG)
     130  
     131  
     132  class ESC[4;38;5;81mStrAndReprTests(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
     133      def test_ExceptionGroup(self):
     134          eg = BaseExceptionGroup(
     135              'flat', [ValueError(1), TypeError(2)])
     136  
     137          self.assertEqual(str(eg), "flat (2 sub-exceptions)")
     138          self.assertEqual(repr(eg),
     139              "ExceptionGroup('flat', [ValueError(1), TypeError(2)])")
     140  
     141          eg = BaseExceptionGroup(
     142              'nested', [eg, ValueError(1), eg, TypeError(2)])
     143  
     144          self.assertEqual(str(eg), "nested (4 sub-exceptions)")
     145          self.assertEqual(repr(eg),
     146              "ExceptionGroup('nested', "
     147                  "[ExceptionGroup('flat', "
     148                      "[ValueError(1), TypeError(2)]), "
     149                   "ValueError(1), "
     150                   "ExceptionGroup('flat', "
     151                      "[ValueError(1), TypeError(2)]), TypeError(2)])")
     152  
     153      def test_BaseExceptionGroup(self):
     154          eg = BaseExceptionGroup(
     155              'flat', [ValueError(1), KeyboardInterrupt(2)])
     156  
     157          self.assertEqual(str(eg), "flat (2 sub-exceptions)")
     158          self.assertEqual(repr(eg),
     159              "BaseExceptionGroup("
     160                  "'flat', "
     161                  "[ValueError(1), KeyboardInterrupt(2)])")
     162  
     163          eg = BaseExceptionGroup(
     164              'nested', [eg, ValueError(1), eg])
     165  
     166          self.assertEqual(str(eg), "nested (3 sub-exceptions)")
     167          self.assertEqual(repr(eg),
     168              "BaseExceptionGroup('nested', "
     169                  "[BaseExceptionGroup('flat', "
     170                      "[ValueError(1), KeyboardInterrupt(2)]), "
     171                  "ValueError(1), "
     172                  "BaseExceptionGroup('flat', "
     173                      "[ValueError(1), KeyboardInterrupt(2)])])")
     174  
     175      def test_custom_exception(self):
     176          class ESC[4;38;5;81mMyEG(ESC[4;38;5;149mExceptionGroup):
     177              pass
     178  
     179          eg = MyEG(
     180              'flat', [ValueError(1), TypeError(2)])
     181  
     182          self.assertEqual(str(eg), "flat (2 sub-exceptions)")
     183          self.assertEqual(repr(eg), "MyEG('flat', [ValueError(1), TypeError(2)])")
     184  
     185          eg = MyEG(
     186              'nested', [eg, ValueError(1), eg, TypeError(2)])
     187  
     188          self.assertEqual(str(eg), "nested (4 sub-exceptions)")
     189          self.assertEqual(repr(eg), (
     190                   "MyEG('nested', "
     191                       "[MyEG('flat', [ValueError(1), TypeError(2)]), "
     192                        "ValueError(1), "
     193                        "MyEG('flat', [ValueError(1), TypeError(2)]), "
     194                        "TypeError(2)])"))
     195  
     196  
     197  def create_simple_eg():
     198      excs = []
     199      try:
     200          try:
     201              raise MemoryError("context and cause for ValueError(1)")
     202          except MemoryError as e:
     203              raise ValueError(1) from e
     204      except ValueError as e:
     205          excs.append(e)
     206  
     207      try:
     208          try:
     209              raise OSError("context for TypeError")
     210          except OSError as e:
     211              raise TypeError(int)
     212      except TypeError as e:
     213          excs.append(e)
     214  
     215      try:
     216          try:
     217              raise ImportError("context for ValueError(2)")
     218          except ImportError as e:
     219              raise ValueError(2)
     220      except ValueError as e:
     221          excs.append(e)
     222  
     223      try:
     224          raise ExceptionGroup('simple eg', excs)
     225      except ExceptionGroup as e:
     226          return e
     227  
     228  
     229  class ESC[4;38;5;81mExceptionGroupFields(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
     230      def test_basics_ExceptionGroup_fields(self):
     231          eg = create_simple_eg()
     232  
     233          # check msg
     234          self.assertEqual(eg.message, 'simple eg')
     235          self.assertEqual(eg.args[0], 'simple eg')
     236  
     237          # check cause and context
     238          self.assertIsInstance(eg.exceptions[0], ValueError)
     239          self.assertIsInstance(eg.exceptions[0].__cause__, MemoryError)
     240          self.assertIsInstance(eg.exceptions[0].__context__, MemoryError)
     241          self.assertIsInstance(eg.exceptions[1], TypeError)
     242          self.assertIsNone(eg.exceptions[1].__cause__)
     243          self.assertIsInstance(eg.exceptions[1].__context__, OSError)
     244          self.assertIsInstance(eg.exceptions[2], ValueError)
     245          self.assertIsNone(eg.exceptions[2].__cause__)
     246          self.assertIsInstance(eg.exceptions[2].__context__, ImportError)
     247  
     248          # check tracebacks
     249          line0 = create_simple_eg.__code__.co_firstlineno
     250          tb_linenos = [line0 + 27,
     251                        [line0 + 6, line0 + 14, line0 + 22]]
     252          self.assertEqual(eg.__traceback__.tb_lineno, tb_linenos[0])
     253          self.assertIsNone(eg.__traceback__.tb_next)
     254          for i in range(3):
     255              tb = eg.exceptions[i].__traceback__
     256              self.assertIsNone(tb.tb_next)
     257              self.assertEqual(tb.tb_lineno, tb_linenos[1][i])
     258  
     259      def test_fields_are_readonly(self):
     260          eg = ExceptionGroup('eg', [TypeError(1), OSError(2)])
     261  
     262          self.assertEqual(type(eg.exceptions), tuple)
     263  
     264          eg.message
     265          with self.assertRaises(AttributeError):
     266              eg.message = "new msg"
     267  
     268          eg.exceptions
     269          with self.assertRaises(AttributeError):
     270              eg.exceptions = [OSError('xyz')]
     271  
     272  
     273  class ESC[4;38;5;81mExceptionGroupTestBase(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
     274      def assertMatchesTemplate(self, exc, exc_type, template):
     275          """ Assert that the exception matches the template
     276  
     277              A template describes the shape of exc. If exc is a
     278              leaf exception (i.e., not an exception group) then
     279              template is an exception instance that has the
     280              expected type and args value of exc. If exc is an
     281              exception group, then template is a list of the
     282              templates of its nested exceptions.
     283          """
     284          if exc_type is not None:
     285              self.assertIs(type(exc), exc_type)
     286  
     287          if isinstance(exc, BaseExceptionGroup):
     288              self.assertIsInstance(template, collections.abc.Sequence)
     289              self.assertEqual(len(exc.exceptions), len(template))
     290              for e, t in zip(exc.exceptions, template):
     291                  self.assertMatchesTemplate(e, None, t)
     292          else:
     293              self.assertIsInstance(template, BaseException)
     294              self.assertEqual(type(exc), type(template))
     295              self.assertEqual(exc.args, template.args)
     296  
     297  
     298  class ESC[4;38;5;81mExceptionGroupSubgroupTests(ESC[4;38;5;149mExceptionGroupTestBase):
     299      def setUp(self):
     300          self.eg = create_simple_eg()
     301          self.eg_template = [ValueError(1), TypeError(int), ValueError(2)]
     302  
     303      def test_basics_subgroup_split__bad_arg_type(self):
     304          bad_args = ["bad arg",
     305                      OSError('instance not type'),
     306                      [OSError, TypeError],
     307                      (OSError, 42)]
     308          for arg in bad_args:
     309              with self.assertRaises(TypeError):
     310                  self.eg.subgroup(arg)
     311              with self.assertRaises(TypeError):
     312                  self.eg.split(arg)
     313  
     314      def test_basics_subgroup_by_type__passthrough(self):
     315          eg = self.eg
     316          self.assertIs(eg, eg.subgroup(BaseException))
     317          self.assertIs(eg, eg.subgroup(Exception))
     318          self.assertIs(eg, eg.subgroup(BaseExceptionGroup))
     319          self.assertIs(eg, eg.subgroup(ExceptionGroup))
     320  
     321      def test_basics_subgroup_by_type__no_match(self):
     322          self.assertIsNone(self.eg.subgroup(OSError))
     323  
     324      def test_basics_subgroup_by_type__match(self):
     325          eg = self.eg
     326          testcases = [
     327              # (match_type, result_template)
     328              (ValueError, [ValueError(1), ValueError(2)]),
     329              (TypeError, [TypeError(int)]),
     330              ((ValueError, TypeError), self.eg_template)]
     331  
     332          for match_type, template in testcases:
     333              with self.subTest(match=match_type):
     334                  subeg = eg.subgroup(match_type)
     335                  self.assertEqual(subeg.message, eg.message)
     336                  self.assertMatchesTemplate(subeg, ExceptionGroup, template)
     337  
     338      def test_basics_subgroup_by_predicate__passthrough(self):
     339          self.assertIs(self.eg, self.eg.subgroup(lambda e: True))
     340  
     341      def test_basics_subgroup_by_predicate__no_match(self):
     342          self.assertIsNone(self.eg.subgroup(lambda e: False))
     343  
     344      def test_basics_subgroup_by_predicate__match(self):
     345          eg = self.eg
     346          testcases = [
     347              # (match_type, result_template)
     348              (ValueError, [ValueError(1), ValueError(2)]),
     349              (TypeError, [TypeError(int)]),
     350              ((ValueError, TypeError), self.eg_template)]
     351  
     352          for match_type, template in testcases:
     353              subeg = eg.subgroup(lambda e: isinstance(e, match_type))
     354              self.assertEqual(subeg.message, eg.message)
     355              self.assertMatchesTemplate(subeg, ExceptionGroup, template)
     356  
     357  
     358  class ESC[4;38;5;81mExceptionGroupSplitTests(ESC[4;38;5;149mExceptionGroupTestBase):
     359      def setUp(self):
     360          self.eg = create_simple_eg()
     361          self.eg_template = [ValueError(1), TypeError(int), ValueError(2)]
     362  
     363      def test_basics_split_by_type__passthrough(self):
     364          for E in [BaseException, Exception,
     365                    BaseExceptionGroup, ExceptionGroup]:
     366              match, rest = self.eg.split(E)
     367              self.assertMatchesTemplate(
     368                  match, ExceptionGroup, self.eg_template)
     369              self.assertIsNone(rest)
     370  
     371      def test_basics_split_by_type__no_match(self):
     372          match, rest = self.eg.split(OSError)
     373          self.assertIsNone(match)
     374          self.assertMatchesTemplate(
     375              rest, ExceptionGroup, self.eg_template)
     376  
     377      def test_basics_split_by_type__match(self):
     378          eg = self.eg
     379          VE = ValueError
     380          TE = TypeError
     381          testcases = [
     382              # (matcher, match_template, rest_template)
     383              (VE, [VE(1), VE(2)], [TE(int)]),
     384              (TE, [TE(int)], [VE(1), VE(2)]),
     385              ((VE, TE), self.eg_template, None),
     386              ((OSError, VE), [VE(1), VE(2)], [TE(int)]),
     387          ]
     388  
     389          for match_type, match_template, rest_template in testcases:
     390              match, rest = eg.split(match_type)
     391              self.assertEqual(match.message, eg.message)
     392              self.assertMatchesTemplate(
     393                  match, ExceptionGroup, match_template)
     394              if rest_template is not None:
     395                  self.assertEqual(rest.message, eg.message)
     396                  self.assertMatchesTemplate(
     397                      rest, ExceptionGroup, rest_template)
     398              else:
     399                  self.assertIsNone(rest)
     400  
     401      def test_basics_split_by_predicate__passthrough(self):
     402          match, rest = self.eg.split(lambda e: True)
     403          self.assertMatchesTemplate(match, ExceptionGroup, self.eg_template)
     404          self.assertIsNone(rest)
     405  
     406      def test_basics_split_by_predicate__no_match(self):
     407          match, rest = self.eg.split(lambda e: False)
     408          self.assertIsNone(match)
     409          self.assertMatchesTemplate(rest, ExceptionGroup, self.eg_template)
     410  
     411      def test_basics_split_by_predicate__match(self):
     412          eg = self.eg
     413          VE = ValueError
     414          TE = TypeError
     415          testcases = [
     416              # (matcher, match_template, rest_template)
     417              (VE, [VE(1), VE(2)], [TE(int)]),
     418              (TE, [TE(int)], [VE(1), VE(2)]),
     419              ((VE, TE), self.eg_template, None),
     420          ]
     421  
     422          for match_type, match_template, rest_template in testcases:
     423              match, rest = eg.split(lambda e: isinstance(e, match_type))
     424              self.assertEqual(match.message, eg.message)
     425              self.assertMatchesTemplate(
     426                  match, ExceptionGroup, match_template)
     427              if rest_template is not None:
     428                  self.assertEqual(rest.message, eg.message)
     429                  self.assertMatchesTemplate(
     430                      rest, ExceptionGroup, rest_template)
     431  
     432  
     433  class ESC[4;38;5;81mDeepRecursionInSplitAndSubgroup(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
     434      def make_deep_eg(self):
     435          e = TypeError(1)
     436          for i in range(C_RECURSION_LIMIT + 1):
     437              e = ExceptionGroup('eg', [e])
     438          return e
     439  
     440      def test_deep_split(self):
     441          e = self.make_deep_eg()
     442          with self.assertRaises(RecursionError):
     443              e.split(TypeError)
     444  
     445      def test_deep_subgroup(self):
     446          e = self.make_deep_eg()
     447          with self.assertRaises(RecursionError):
     448              e.subgroup(TypeError)
     449  
     450  
     451  def leaf_generator(exc, tbs=None):
     452      if tbs is None:
     453          tbs = []
     454      tbs.append(exc.__traceback__)
     455      if isinstance(exc, BaseExceptionGroup):
     456          for e in exc.exceptions:
     457              yield from leaf_generator(e, tbs)
     458      else:
     459          # exc is a leaf exception and its traceback
     460          # is the concatenation of the traceback
     461          # segments in tbs
     462          yield exc, tbs
     463      tbs.pop()
     464  
     465  
     466  class ESC[4;38;5;81mLeafGeneratorTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
     467      # The leaf_generator is mentioned in PEP 654 as a suggestion
     468      # on how to iterate over leaf nodes of an EG. Is is also
     469      # used below as a test utility. So we test it here.
     470  
     471      def test_leaf_generator(self):
     472          eg = create_simple_eg()
     473  
     474          self.assertSequenceEqual(
     475              [e for e, _ in leaf_generator(eg)],
     476              eg.exceptions)
     477  
     478          for e, tbs in leaf_generator(eg):
     479              self.assertSequenceEqual(
     480                  tbs, [eg.__traceback__, e.__traceback__])
     481  
     482  
     483  def create_nested_eg():
     484      excs = []
     485      try:
     486          try:
     487              raise TypeError(bytes)
     488          except TypeError as e:
     489              raise ExceptionGroup("nested", [e])
     490      except ExceptionGroup as e:
     491          excs.append(e)
     492  
     493      try:
     494          try:
     495              raise MemoryError('out of memory')
     496          except MemoryError as e:
     497              raise ValueError(1) from e
     498      except ValueError as e:
     499          excs.append(e)
     500  
     501      try:
     502          raise ExceptionGroup("root", excs)
     503      except ExceptionGroup as eg:
     504          return eg
     505  
     506  
     507  class ESC[4;38;5;81mNestedExceptionGroupBasicsTest(ESC[4;38;5;149mExceptionGroupTestBase):
     508      def test_nested_group_matches_template(self):
     509          eg = create_nested_eg()
     510          self.assertMatchesTemplate(
     511              eg,
     512              ExceptionGroup,
     513              [[TypeError(bytes)], ValueError(1)])
     514  
     515      def test_nested_group_chaining(self):
     516          eg = create_nested_eg()
     517          self.assertIsInstance(eg.exceptions[1].__context__, MemoryError)
     518          self.assertIsInstance(eg.exceptions[1].__cause__, MemoryError)
     519          self.assertIsInstance(eg.exceptions[0].__context__, TypeError)
     520  
     521      def test_nested_exception_group_tracebacks(self):
     522          eg = create_nested_eg()
     523  
     524          line0 = create_nested_eg.__code__.co_firstlineno
     525          for (tb, expected) in [
     526              (eg.__traceback__, line0 + 19),
     527              (eg.exceptions[0].__traceback__, line0 + 6),
     528              (eg.exceptions[1].__traceback__, line0 + 14),
     529              (eg.exceptions[0].exceptions[0].__traceback__, line0 + 4),
     530          ]:
     531              self.assertEqual(tb.tb_lineno, expected)
     532              self.assertIsNone(tb.tb_next)
     533  
     534      def test_iteration_full_tracebacks(self):
     535          eg = create_nested_eg()
     536          # check that iteration over leaves
     537          # produces the expected tracebacks
     538          self.assertEqual(len(list(leaf_generator(eg))), 2)
     539  
     540          line0 = create_nested_eg.__code__.co_firstlineno
     541          expected_tbs = [ [line0 + 19, line0 + 6, line0 + 4],
     542                           [line0 + 19, line0 + 14]]
     543  
     544          for (i, (_, tbs)) in enumerate(leaf_generator(eg)):
     545              self.assertSequenceEqual(
     546                  [tb.tb_lineno for tb in tbs],
     547                  expected_tbs[i])
     548  
     549  
     550  class ESC[4;38;5;81mExceptionGroupSplitTestBase(ESC[4;38;5;149mExceptionGroupTestBase):
     551  
     552      def split_exception_group(self, eg, types):
     553          """ Split an EG and do some sanity checks on the result """
     554          self.assertIsInstance(eg, BaseExceptionGroup)
     555  
     556          match, rest = eg.split(types)
     557          sg = eg.subgroup(types)
     558  
     559          if match is not None:
     560              self.assertIsInstance(match, BaseExceptionGroup)
     561              for e,_ in leaf_generator(match):
     562                  self.assertIsInstance(e, types)
     563  
     564              self.assertIsNotNone(sg)
     565              self.assertIsInstance(sg, BaseExceptionGroup)
     566              for e,_ in leaf_generator(sg):
     567                  self.assertIsInstance(e, types)
     568  
     569          if rest is not None:
     570              self.assertIsInstance(rest, BaseExceptionGroup)
     571  
     572          def leaves(exc):
     573              return [] if exc is None else [e for e,_ in leaf_generator(exc)]
     574  
     575          # match and subgroup have the same leaves
     576          self.assertSequenceEqual(leaves(match), leaves(sg))
     577  
     578          match_leaves = leaves(match)
     579          rest_leaves = leaves(rest)
     580          # each leaf exception of eg is in exactly one of match and rest
     581          self.assertEqual(
     582              len(leaves(eg)),
     583              len(leaves(match)) + len(leaves(rest)))
     584  
     585          for e in leaves(eg):
     586              self.assertNotEqual(
     587                  match and e in match_leaves,
     588                  rest and e in rest_leaves)
     589  
     590          # message, cause and context, traceback and note equal to eg
     591          for part in [match, rest, sg]:
     592              if part is not None:
     593                  self.assertEqual(eg.message, part.message)
     594                  self.assertIs(eg.__cause__, part.__cause__)
     595                  self.assertIs(eg.__context__, part.__context__)
     596                  self.assertIs(eg.__traceback__, part.__traceback__)
     597                  self.assertEqual(
     598                      getattr(eg, '__notes__', None),
     599                      getattr(part, '__notes__', None))
     600  
     601          def tbs_for_leaf(leaf, eg):
     602              for e, tbs in leaf_generator(eg):
     603                  if e is leaf:
     604                      return tbs
     605  
     606          def tb_linenos(tbs):
     607              return [tb.tb_lineno for tb in tbs if tb]
     608  
     609          # full tracebacks match
     610          for part in [match, rest, sg]:
     611              for e in leaves(part):
     612                  self.assertSequenceEqual(
     613                      tb_linenos(tbs_for_leaf(e, eg)),
     614                      tb_linenos(tbs_for_leaf(e, part)))
     615  
     616          return match, rest
     617  
     618  
     619  class ESC[4;38;5;81mNestedExceptionGroupSplitTest(ESC[4;38;5;149mExceptionGroupSplitTestBase):
     620  
     621      def test_split_by_type(self):
     622          class ESC[4;38;5;81mMyExceptionGroup(ESC[4;38;5;149mExceptionGroup):
     623              pass
     624  
     625          def raiseVE(v):
     626              raise ValueError(v)
     627  
     628          def raiseTE(t):
     629              raise TypeError(t)
     630  
     631          def nested_group():
     632              def level1(i):
     633                  excs = []
     634                  for f, arg in [(raiseVE, i), (raiseTE, int), (raiseVE, i+1)]:
     635                      try:
     636                          f(arg)
     637                      except Exception as e:
     638                          excs.append(e)
     639                  raise ExceptionGroup('msg1', excs)
     640  
     641              def level2(i):
     642                  excs = []
     643                  for f, arg in [(level1, i), (level1, i+1), (raiseVE, i+2)]:
     644                      try:
     645                          f(arg)
     646                      except Exception as e:
     647                          excs.append(e)
     648                  raise MyExceptionGroup('msg2', excs)
     649  
     650              def level3(i):
     651                  excs = []
     652                  for f, arg in [(level2, i+1), (raiseVE, i+2)]:
     653                      try:
     654                          f(arg)
     655                      except Exception as e:
     656                          excs.append(e)
     657                  raise ExceptionGroup('msg3', excs)
     658  
     659              level3(5)
     660  
     661          try:
     662              nested_group()
     663          except ExceptionGroup as e:
     664              e.add_note(f"the note: {id(e)}")
     665              eg = e
     666  
     667          eg_template = [
     668              [
     669                  [ValueError(6), TypeError(int), ValueError(7)],
     670                  [ValueError(7), TypeError(int), ValueError(8)],
     671                  ValueError(8),
     672              ],
     673              ValueError(7)]
     674  
     675          valueErrors_template = [
     676              [
     677                  [ValueError(6), ValueError(7)],
     678                  [ValueError(7), ValueError(8)],
     679                  ValueError(8),
     680              ],
     681              ValueError(7)]
     682  
     683          typeErrors_template = [[[TypeError(int)], [TypeError(int)]]]
     684  
     685          self.assertMatchesTemplate(eg, ExceptionGroup, eg_template)
     686  
     687          # Match Nothing
     688          match, rest = self.split_exception_group(eg, SyntaxError)
     689          self.assertIsNone(match)
     690          self.assertMatchesTemplate(rest, ExceptionGroup, eg_template)
     691  
     692          # Match Everything
     693          match, rest = self.split_exception_group(eg, BaseException)
     694          self.assertMatchesTemplate(match, ExceptionGroup, eg_template)
     695          self.assertIsNone(rest)
     696          match, rest = self.split_exception_group(eg, (ValueError, TypeError))
     697          self.assertMatchesTemplate(match, ExceptionGroup, eg_template)
     698          self.assertIsNone(rest)
     699  
     700          # Match ValueErrors
     701          match, rest = self.split_exception_group(eg, ValueError)
     702          self.assertMatchesTemplate(match, ExceptionGroup, valueErrors_template)
     703          self.assertMatchesTemplate(rest, ExceptionGroup, typeErrors_template)
     704  
     705          # Match TypeErrors
     706          match, rest = self.split_exception_group(eg, (TypeError, SyntaxError))
     707          self.assertMatchesTemplate(match, ExceptionGroup, typeErrors_template)
     708          self.assertMatchesTemplate(rest, ExceptionGroup, valueErrors_template)
     709  
     710          # Match ExceptionGroup
     711          match, rest = eg.split(ExceptionGroup)
     712          self.assertIs(match, eg)
     713          self.assertIsNone(rest)
     714  
     715          # Match MyExceptionGroup (ExceptionGroup subclass)
     716          match, rest = eg.split(MyExceptionGroup)
     717          self.assertMatchesTemplate(match, ExceptionGroup, [eg_template[0]])
     718          self.assertMatchesTemplate(rest, ExceptionGroup, [eg_template[1]])
     719  
     720      def test_split_BaseExceptionGroup(self):
     721          def exc(ex):
     722              try:
     723                  raise ex
     724              except BaseException as e:
     725                  return e
     726  
     727          try:
     728              raise BaseExceptionGroup(
     729                  "beg", [exc(ValueError(1)), exc(KeyboardInterrupt(2))])
     730          except BaseExceptionGroup as e:
     731              beg = e
     732  
     733          # Match Nothing
     734          match, rest = self.split_exception_group(beg, TypeError)
     735          self.assertIsNone(match)
     736          self.assertMatchesTemplate(
     737              rest, BaseExceptionGroup, [ValueError(1), KeyboardInterrupt(2)])
     738  
     739          # Match Everything
     740          match, rest = self.split_exception_group(
     741              beg, (ValueError, KeyboardInterrupt))
     742          self.assertMatchesTemplate(
     743              match, BaseExceptionGroup, [ValueError(1), KeyboardInterrupt(2)])
     744          self.assertIsNone(rest)
     745  
     746          # Match ValueErrors
     747          match, rest = self.split_exception_group(beg, ValueError)
     748          self.assertMatchesTemplate(
     749              match, ExceptionGroup, [ValueError(1)])
     750          self.assertMatchesTemplate(
     751              rest, BaseExceptionGroup, [KeyboardInterrupt(2)])
     752  
     753          # Match KeyboardInterrupts
     754          match, rest = self.split_exception_group(beg, KeyboardInterrupt)
     755          self.assertMatchesTemplate(
     756              match, BaseExceptionGroup, [KeyboardInterrupt(2)])
     757          self.assertMatchesTemplate(
     758              rest, ExceptionGroup, [ValueError(1)])
     759  
     760      def test_split_copies_notes(self):
     761          # make sure each exception group after a split has its own __notes__ list
     762          eg = ExceptionGroup("eg", [ValueError(1), TypeError(2)])
     763          eg.add_note("note1")
     764          eg.add_note("note2")
     765          orig_notes = list(eg.__notes__)
     766          match, rest = eg.split(TypeError)
     767          self.assertEqual(eg.__notes__, orig_notes)
     768          self.assertEqual(match.__notes__, orig_notes)
     769          self.assertEqual(rest.__notes__, orig_notes)
     770          self.assertIsNot(eg.__notes__, match.__notes__)
     771          self.assertIsNot(eg.__notes__, rest.__notes__)
     772          self.assertIsNot(match.__notes__, rest.__notes__)
     773          eg.add_note("eg")
     774          match.add_note("match")
     775          rest.add_note("rest")
     776          self.assertEqual(eg.__notes__, orig_notes + ["eg"])
     777          self.assertEqual(match.__notes__, orig_notes + ["match"])
     778          self.assertEqual(rest.__notes__, orig_notes + ["rest"])
     779  
     780      def test_split_does_not_copy_non_sequence_notes(self):
     781          # __notes__ should be a sequence, which is shallow copied.
     782          # If it is not a sequence, the split parts don't get any notes.
     783          eg = ExceptionGroup("eg", [ValueError(1), TypeError(2)])
     784          eg.__notes__ = 123
     785          match, rest = eg.split(TypeError)
     786          self.assertFalse(hasattr(match, '__notes__'))
     787          self.assertFalse(hasattr(rest, '__notes__'))
     788  
     789      def test_drive_invalid_return_value(self):
     790          class ESC[4;38;5;81mMyEg(ESC[4;38;5;149mExceptionGroup):
     791              def derive(self, excs):
     792                  return 42
     793  
     794          eg = MyEg('eg', [TypeError(1), ValueError(2)])
     795          msg = "derive must return an instance of BaseExceptionGroup"
     796          with self.assertRaisesRegex(TypeError, msg):
     797              eg.split(TypeError)
     798          with self.assertRaisesRegex(TypeError, msg):
     799              eg.subgroup(TypeError)
     800  
     801  
     802  class ESC[4;38;5;81mNestedExceptionGroupSubclassSplitTest(ESC[4;38;5;149mExceptionGroupSplitTestBase):
     803  
     804      def test_split_ExceptionGroup_subclass_no_derive_no_new_override(self):
     805          class ESC[4;38;5;81mEG(ESC[4;38;5;149mExceptionGroup):
     806              pass
     807  
     808          try:
     809              try:
     810                  try:
     811                      raise TypeError(2)
     812                  except TypeError as te:
     813                      raise EG("nested", [te])
     814              except EG as nested:
     815                  try:
     816                      raise ValueError(1)
     817                  except ValueError as ve:
     818                      raise EG("eg", [ve, nested])
     819          except EG as e:
     820              eg = e
     821  
     822          self.assertMatchesTemplate(eg, EG, [ValueError(1), [TypeError(2)]])
     823  
     824          # Match Nothing
     825          match, rest = self.split_exception_group(eg, OSError)
     826          self.assertIsNone(match)
     827          self.assertMatchesTemplate(
     828              rest, ExceptionGroup, [ValueError(1), [TypeError(2)]])
     829  
     830          # Match Everything
     831          match, rest = self.split_exception_group(eg, (ValueError, TypeError))
     832          self.assertMatchesTemplate(
     833              match, ExceptionGroup, [ValueError(1), [TypeError(2)]])
     834          self.assertIsNone(rest)
     835  
     836          # Match ValueErrors
     837          match, rest = self.split_exception_group(eg, ValueError)
     838          self.assertMatchesTemplate(match, ExceptionGroup, [ValueError(1)])
     839          self.assertMatchesTemplate(rest, ExceptionGroup, [[TypeError(2)]])
     840  
     841          # Match TypeErrors
     842          match, rest = self.split_exception_group(eg, TypeError)
     843          self.assertMatchesTemplate(match, ExceptionGroup, [[TypeError(2)]])
     844          self.assertMatchesTemplate(rest, ExceptionGroup, [ValueError(1)])
     845  
     846      def test_split_BaseExceptionGroup_subclass_no_derive_new_override(self):
     847          class ESC[4;38;5;81mEG(ESC[4;38;5;149mBaseExceptionGroup):
     848              def __new__(cls, message, excs, unused):
     849                  # The "unused" arg is here to show that split() doesn't call
     850                  # the actual class constructor from the default derive()
     851                  # implementation (it would fail on unused arg if so because
     852                  # it assumes the BaseExceptionGroup.__new__ signature).
     853                  return super().__new__(cls, message, excs)
     854  
     855          try:
     856              raise EG("eg", [ValueError(1), KeyboardInterrupt(2)], "unused")
     857          except EG as e:
     858              eg = e
     859  
     860          self.assertMatchesTemplate(
     861              eg, EG, [ValueError(1), KeyboardInterrupt(2)])
     862  
     863          # Match Nothing
     864          match, rest = self.split_exception_group(eg, OSError)
     865          self.assertIsNone(match)
     866          self.assertMatchesTemplate(
     867              rest, BaseExceptionGroup, [ValueError(1), KeyboardInterrupt(2)])
     868  
     869          # Match Everything
     870          match, rest = self.split_exception_group(
     871              eg, (ValueError, KeyboardInterrupt))
     872          self.assertMatchesTemplate(
     873              match, BaseExceptionGroup, [ValueError(1), KeyboardInterrupt(2)])
     874          self.assertIsNone(rest)
     875  
     876          # Match ValueErrors
     877          match, rest = self.split_exception_group(eg, ValueError)
     878          self.assertMatchesTemplate(match, ExceptionGroup, [ValueError(1)])
     879          self.assertMatchesTemplate(
     880              rest, BaseExceptionGroup, [KeyboardInterrupt(2)])
     881  
     882          # Match KeyboardInterrupt
     883          match, rest = self.split_exception_group(eg, KeyboardInterrupt)
     884          self.assertMatchesTemplate(
     885              match, BaseExceptionGroup, [KeyboardInterrupt(2)])
     886          self.assertMatchesTemplate(rest, ExceptionGroup, [ValueError(1)])
     887  
     888      def test_split_ExceptionGroup_subclass_derive_and_new_overrides(self):
     889          class ESC[4;38;5;81mEG(ESC[4;38;5;149mExceptionGroup):
     890              def __new__(cls, message, excs, code):
     891                  obj = super().__new__(cls, message, excs)
     892                  obj.code = code
     893                  return obj
     894  
     895              def derive(self, excs):
     896                  return EG(self.message, excs, self.code)
     897  
     898          try:
     899              try:
     900                  try:
     901                      raise TypeError(2)
     902                  except TypeError as te:
     903                      raise EG("nested", [te], 101)
     904              except EG as nested:
     905                  try:
     906                      raise ValueError(1)
     907                  except ValueError as ve:
     908                      raise EG("eg", [ve, nested], 42)
     909          except EG as e:
     910              eg = e
     911  
     912          self.assertMatchesTemplate(eg, EG, [ValueError(1), [TypeError(2)]])
     913  
     914          # Match Nothing
     915          match, rest = self.split_exception_group(eg, OSError)
     916          self.assertIsNone(match)
     917          self.assertMatchesTemplate(rest, EG, [ValueError(1), [TypeError(2)]])
     918          self.assertEqual(rest.code, 42)
     919          self.assertEqual(rest.exceptions[1].code, 101)
     920  
     921          # Match Everything
     922          match, rest = self.split_exception_group(eg, (ValueError, TypeError))
     923          self.assertMatchesTemplate(match, EG, [ValueError(1), [TypeError(2)]])
     924          self.assertEqual(match.code, 42)
     925          self.assertEqual(match.exceptions[1].code, 101)
     926          self.assertIsNone(rest)
     927  
     928          # Match ValueErrors
     929          match, rest = self.split_exception_group(eg, ValueError)
     930          self.assertMatchesTemplate(match, EG, [ValueError(1)])
     931          self.assertEqual(match.code, 42)
     932          self.assertMatchesTemplate(rest, EG, [[TypeError(2)]])
     933          self.assertEqual(rest.code, 42)
     934          self.assertEqual(rest.exceptions[0].code, 101)
     935  
     936          # Match TypeErrors
     937          match, rest = self.split_exception_group(eg, TypeError)
     938          self.assertMatchesTemplate(match, EG, [[TypeError(2)]])
     939          self.assertEqual(match.code, 42)
     940          self.assertEqual(match.exceptions[0].code, 101)
     941          self.assertMatchesTemplate(rest, EG, [ValueError(1)])
     942          self.assertEqual(rest.code, 42)
     943  
     944  
     945  if __name__ == '__main__':
     946      unittest.main()