the restriction is specific to exception not the other base class the restriction is specific to exception not the other base class check msg check cause and context check tracebacks 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 matchtype resulttemplate matchtype resulttemplate matcher matchtemplate resttemplate matcher matchtemplate resttemplate exc is a leaf exception and its traceback is the concatenation of the traceback segments in tbs the leafgenerator 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 check that iteration over leaves produces the expected tracebacks split an eg and do some sanity checks on the result self assertisinstanceeg baseexceptiongroup match rest eg splittypes sg eg subgrouptypes if match is not none self assertisinstancematch baseexceptiongroup for e in leafgeneratormatch self assertisinstancee types self assertisnotnonesg self assertisinstancesg baseexceptiongroup for e in leafgeneratorsg self assertisinstancee types if rest is not none self assertisinstancerest baseexceptiongroup def leavesexc return if exc is none else e for e in leafgeneratorexc match and subgroup have the same leaves self assertsequenceequalleavesmatch leavessg matchleaves leavesmatch restleaves leavesrest each leaf exception of eg is in exactly one of match and rest self assertequal lenleaveseg lenleavesmatch lenleavesrest for e in leaveseg self assertnotequal match and e in matchleaves rest and e in restleaves message cause and context traceback and note equal to eg for part in match rest sg if part is not none self assertequaleg message part message self assertiseg cause part cause self assertiseg context part context self assertiseg traceback part traceback self assertequal getattreg notes none getattrpart notes none def tbsforleafleaf eg for e tbs in leafgeneratoreg if e is leaf return tbs def tblinenostbs return tb tblineno for tb in tbs if tb full tracebacks match for part in match rest sg for e in leavespart self assertsequenceequal tblinenostbsforleafe eg tblinenostbsforleafe part return match rest class nestedexceptiongroupsplittestexceptiongroupsplittestbase def testsplitbytypeself class myexceptiongroupexceptiongroup pass def raisevev raise valueerrorv def raisetet raise typeerrort def nestedgroup def level1i excs for f arg in raiseve i raisete int raiseve i1 try farg except exception as e excs appende raise exceptiongroup msg1 excs def level2i excs for f arg in level1 i level1 i1 raiseve i2 try farg except exception as e excs appende raise myexceptiongroup msg2 excs def level3i excs for f arg in level2 i1 raiseve i2 try farg except exception as e excs appende raise exceptiongroup msg3 excs level35 try nestedgroup except exceptiongroup as e e addnotefthe note ide eg e egtemplate valueerror6 typeerrorint valueerror7 valueerror7 typeerrorint valueerror8 valueerror8 valueerror7 valueerrorstemplate valueerror6 valueerror7 valueerror7 valueerror8 valueerror8 valueerror7 typeerrorstemplate typeerrorint typeerrorint self assertmatchestemplateeg exceptiongroup egtemplate match nothing match rest self splitexceptiongroupeg syntaxerror self assertisnonematch self assertmatchestemplaterest exceptiongroup egtemplate match everything match rest self splitexceptiongroupeg baseexception self assertmatchestemplatematch exceptiongroup egtemplate self assertisnonerest match rest self splitexceptiongroupeg valueerror typeerror self assertmatchestemplatematch exceptiongroup egtemplate self assertisnonerest match valueerrors match rest self splitexceptiongroupeg valueerror self assertmatchestemplatematch exceptiongroup valueerrorstemplate self assertmatchestemplaterest exceptiongroup typeerrorstemplate match typeerrors match rest self splitexceptiongroupeg typeerror syntaxerror self assertmatchestemplatematch exceptiongroup typeerrorstemplate self assertmatchestemplaterest exceptiongroup valueerrorstemplate match exceptiongroup match rest eg splitexceptiongroup self assertismatch eg self assertisnonerest match myexceptiongroup exceptiongroup subclass match rest eg splitmyexceptiongroup self assertmatchestemplatematch exceptiongroup egtemplate0 self assertmatchestemplaterest exceptiongroup egtemplate1 def testsplitbaseexceptiongroupself def excex try raise ex except baseexception as e return e try raise baseexceptiongroup beg excvalueerror1 exckeyboardinterrupt2 except baseexceptiongroup as e beg e match nothing match rest self splitexceptiongroupbeg typeerror self assertisnonematch self assertmatchestemplate rest baseexceptiongroup valueerror1 keyboardinterrupt2 match everything match rest self splitexceptiongroup beg valueerror keyboardinterrupt self assertmatchestemplate match baseexceptiongroup valueerror1 keyboardinterrupt2 self assertisnonerest match valueerrors match rest self splitexceptiongroupbeg valueerror self assertmatchestemplate match exceptiongroup valueerror1 self assertmatchestemplate rest baseexceptiongroup keyboardinterrupt2 match keyboardinterrupts match rest self splitexceptiongroupbeg keyboardinterrupt self assertmatchestemplate match baseexceptiongroup keyboardinterrupt2 self assertmatchestemplate rest exceptiongroup valueerror1 def testsplitcopiesnotesself make sure each exception group after a split has its own notes list eg exceptiongroupeg valueerror1 typeerror2 eg addnotenote1 eg addnotenote2 orignotes listeg notes match rest eg splittypeerror self assertequaleg notes orignotes self assertequalmatch notes orignotes self assertequalrest notes orignotes self assertisnoteg notes match notes self assertisnoteg notes rest notes self assertisnotmatch notes rest notes eg addnoteeg match addnotematch rest addnoterest self assertequaleg notes orignotes eg self assertequalmatch notes orignotes match self assertequalrest notes orignotes rest def testsplitdoesnotcopynonsequencenotesself notes should be a sequence which is shallow copied if it is not a sequence the split parts don t get any notes eg exceptiongroupeg valueerror1 typeerror2 eg notes 123 match rest eg splittypeerror self assertfalsehasattrmatch notes self assertfalsehasattrrest notes def testdriveinvalidreturnvalueself class myegexceptiongroup def deriveself excs return 42 eg myeg eg typeerror1 valueerror2 msg derive must return an instance of baseexceptiongroup with self assertraisesregextypeerror msg eg splittypeerror with self assertraisesregextypeerror msg eg subgrouptypeerror class nestedexceptiongroupsubclasssplittestexceptiongroupsplittestbase def testsplitexceptiongroupsubclassnoderivenonewoverrideself class egexceptiongroup pass try try try raise typeerror2 except typeerror as te raise egnested te except eg as nested try raise valueerror1 except valueerror as ve raise egeg ve nested except eg as e eg e self assertmatchestemplateeg eg valueerror1 typeerror2 match nothing match rest self splitexceptiongroupeg oserror self assertisnonematch self assertmatchestemplate rest exceptiongroup valueerror1 typeerror2 match everything match rest self splitexceptiongroupeg valueerror typeerror self assertmatchestemplate match exceptiongroup valueerror1 typeerror2 self assertisnonerest match valueerrors match rest self splitexceptiongroupeg valueerror self assertmatchestemplatematch exceptiongroup valueerror1 self assertmatchestemplaterest exceptiongroup typeerror2 match typeerrors match rest self splitexceptiongroupeg typeerror self assertmatchestemplatematch exceptiongroup typeerror2 self assertmatchestemplaterest exceptiongroup valueerror1 def testsplitbaseexceptiongroupsubclassnoderivenewoverrideself class egbaseexceptiongroup def newcls 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 newcls message excs try raise egeg valueerror1 keyboardinterrupt2 unused except eg as e eg e self assertmatchestemplate eg eg valueerror1 keyboardinterrupt2 match nothing match rest self splitexceptiongroupeg oserror self assertisnonematch self assertmatchestemplate rest baseexceptiongroup valueerror1 keyboardinterrupt2 match everything match rest self splitexceptiongroup eg valueerror keyboardinterrupt self assertmatchestemplate match baseexceptiongroup valueerror1 keyboardinterrupt2 self assertisnonerest match valueerrors match rest self splitexceptiongroupeg valueerror self assertmatchestemplatematch exceptiongroup valueerror1 self assertmatchestemplate rest baseexceptiongroup keyboardinterrupt2 match keyboardinterrupt match rest self splitexceptiongroupeg keyboardinterrupt self assertmatchestemplate match baseexceptiongroup keyboardinterrupt2 self assertmatchestemplaterest exceptiongroup valueerror1 def testsplitexceptiongroupsubclassderiveandnewoverridesself class egexceptiongroup def newcls message excs code obj super newcls message excs obj code code return obj def deriveself excs return egself message excs self code try try try raise typeerror2 except typeerror as te raise egnested te 101 except eg as nested try raise valueerror1 except valueerror as ve raise egeg ve nested 42 except eg as e eg e self assertmatchestemplateeg eg valueerror1 typeerror2 match nothing match rest self splitexceptiongroupeg oserror self assertisnonematch self assertmatchestemplaterest eg valueerror1 typeerror2 self assertequalrest code 42 self assertequalrest exceptions1 code 101 match everything match rest self splitexceptiongroupeg valueerror typeerror self assertmatchestemplatematch eg valueerror1 typeerror2 self assertequalmatch code 42 self assertequalmatch exceptions1 code 101 self assertisnonerest match valueerrors match rest self splitexceptiongroupeg valueerror self assertmatchestemplatematch eg valueerror1 self assertequalmatch code 42 self assertmatchestemplaterest eg typeerror2 self assertequalrest code 42 self assertequalrest exceptions0 code 101 match typeerrors match rest self splitexceptiongroupeg typeerror self assertmatchestemplatematch eg typeerror2 self assertequalmatch code 42 self assertequalmatch exceptions0 code 101 self assertmatchestemplaterest eg valueerror1 self assertequalrest code 42 if name main unittest main the restriction is specific to exception not the other base class the restriction is specific to exception not the other base class check msg check cause and context check tracebacks 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 match_type result_template match_type result_template matcher match_template rest_template matcher match_template rest_template exc is a leaf exception and its traceback is the concatenation of the traceback segments in tbs 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 check that iteration over leaves produces the expected tracebacks split an eg and do some sanity checks on the result match and subgroup have the same leaves each leaf exception of eg is in exactly one of match and rest message cause and context traceback and note equal to eg full tracebacks match match nothing match everything match valueerrors match typeerrors match exceptiongroup match myexceptiongroup exceptiongroup subclass match nothing match everything match valueerrors match keyboardinterrupts make sure each exception group after a split has its own __notes__ list __notes__ should be a sequence which is shallow copied if it is not a sequence the split parts don t get any notes match nothing match everything match valueerrors match typeerrors 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 match nothing match everything match valueerrors match keyboardinterrupt match nothing match everything match valueerrors match typeerrors
import collections.abc
import types
import unittest
from test.support import Py_C_RECURSION_LIMIT

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):
        with self.assertRaisesRegex(TypeError, 'Exception'):
            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):
        MSG = r'BaseExceptionGroup.__new__\(\) takes exactly 2 arguments'
        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):
        MSG = r'second argument \(exceptions\) must be a sequence'
        with self.assertRaisesRegex(TypeError, MSG):
            ExceptionGroup('errors not sequence', {ValueError(42)})
        with self.assertRaisesRegex(TypeError, MSG):
            ExceptionGroup("eg", None)

        MSG = r'second argument \(exceptions\) must be a non-empty sequence'
        with self.assertRaisesRegex(ValueError, MSG):
            ExceptionGroup("eg", [])

    def test_bad_EG_construction__nested_non_exceptions(self):
        MSG = (r'Item [0-9]+ of second argument \(exceptions\)'
              ' 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)

    def test_EG_subclass_wraps_non_base_exceptions(self):
        class MyEG(ExceptionGroup):
            pass

        self.assertIs(
            type(MyEG("eg", [ValueError(12), TypeError(42)])),
            MyEG)

    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)])

    def test_EG_and_specific_subclass_can_wrap_any_nonbase_exception(self):
        class MyEG(ExceptionGroup, ValueError):
            pass

        
        MyEG("eg", [ValueError(12), Exception()])

    def test_BEG_and_specific_subclass_can_wrap_any_nonbase_exception(self):
        class MyEG(BaseExceptionGroup, ValueError):
            pass

        
        MyEG("eg", [ValueError(12), Exception()])


    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)


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)])"))


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()

        
        self.assertEqual(eg.message, 'simple eg')
        self.assertEqual(eg.args[0], 'simple eg')

        
        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)

        
        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):
        
        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)

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)

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):
        class C:
            pass

        bad_args = ["bad arg",
                    C,
                    OSError('instance not type'),
                    [OSError, TypeError],
                    (OSError, 42),
                   ]
        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 = [
            
            (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):
        f = lambda e: True
        for callable in [f, Predicate(f), Predicate(f).method]:
            self.assertIs(self.eg, self.eg.subgroup(callable))

    def test_basics_subgroup_by_predicate__no_match(self):
        f = lambda e: False
        for callable in [f, Predicate(f), Predicate(f).method]:
            self.assertIsNone(self.eg.subgroup(callable))

    def test_basics_subgroup_by_predicate__match(self):
        eg = self.eg
        testcases = [
            
            (ValueError, [ValueError(1), ValueError(2)]),
            (TypeError, [TypeError(int)]),
            ((ValueError, TypeError), self.eg_template)]

        for match_type, template in testcases:
            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)


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 = [
            
            (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):
        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)

    def test_basics_split_by_predicate__no_match(self):
        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)

    def test_basics_split_by_predicate__match(self):
        eg = self.eg
        VE = ValueError
        TE = TypeError
        testcases = [
            
            (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:
            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)
                self.assertMatchesTemplate(
                    match, ExceptionGroup, match_template)
                if rest_template is not None:
                    self.assertEqual(rest.message, eg.message)
                    self.assertMatchesTemplate(
                        rest, ExceptionGroup, rest_template)


class DeepRecursionInSplitAndSubgroup(unittest.TestCase):
    def make_deep_eg(self):
        e = TypeError(1)
        for i in range(Py_C_RECURSION_LIMIT + 1):
            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:
        
        
        
        yield exc, tbs
    tbs.pop()


class LeafGeneratorTest(unittest.TestCase):
    
    
    

    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()
        
        
        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):
        
        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)]

        
        self.assertSequenceEqual(leaves(match), leaves(sg))

        match_leaves = leaves(match)
        rest_leaves = leaves(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)

        
        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__)
                self.assertEqual(
                    getattr(eg, '__notes__', None),
                    getattr(part, '__notes__', None))

        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]

        
        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:
            e.add_note(f"the note: {id(e)}")
            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, rest = self.split_exception_group(eg, SyntaxError)
        self.assertIsNone(match)
        self.assertMatchesTemplate(rest, ExceptionGroup, eg_template)

        
        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, rest = self.split_exception_group(eg, ValueError)
        self.assertMatchesTemplate(match, ExceptionGroup, valueErrors_template)
        self.assertMatchesTemplate(rest, ExceptionGroup, typeErrors_template)

        
        match, rest = self.split_exception_group(eg, (TypeError, SyntaxError))
        self.assertMatchesTemplate(match, ExceptionGroup, typeErrors_template)
        self.assertMatchesTemplate(rest, ExceptionGroup, valueErrors_template)

        
        match, rest = eg.split(ExceptionGroup)
        self.assertIs(match, eg)
        self.assertIsNone(rest)

        
        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, rest = self.split_exception_group(beg, TypeError)
        self.assertIsNone(match)
        self.assertMatchesTemplate(
            rest, BaseExceptionGroup, [ValueError(1), KeyboardInterrupt(2)])

        
        match, rest = self.split_exception_group(
            beg, (ValueError, KeyboardInterrupt))
        self.assertMatchesTemplate(
            match, BaseExceptionGroup, [ValueError(1), KeyboardInterrupt(2)])
        self.assertIsNone(rest)

        
        match, rest = self.split_exception_group(beg, ValueError)
        self.assertMatchesTemplate(
            match, ExceptionGroup, [ValueError(1)])
        self.assertMatchesTemplate(
            rest, BaseExceptionGroup, [KeyboardInterrupt(2)])

        
        match, rest = self.split_exception_group(beg, KeyboardInterrupt)
        self.assertMatchesTemplate(
            match, BaseExceptionGroup, [KeyboardInterrupt(2)])
        self.assertMatchesTemplate(
            rest, ExceptionGroup, [ValueError(1)])

    def test_split_copies_notes(self):
        
        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):
        
        
        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__'))

    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)


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, rest = self.split_exception_group(eg, OSError)
        self.assertIsNone(match)
        self.assertMatchesTemplate(
            rest, ExceptionGroup, [ValueError(1), [TypeError(2)]])

        
        match, rest = self.split_exception_group(eg, (ValueError, TypeError))
        self.assertMatchesTemplate(
            match, ExceptionGroup, [ValueError(1), [TypeError(2)]])
        self.assertIsNone(rest)

        
        match, rest = self.split_exception_group(eg, ValueError)
        self.assertMatchesTemplate(match, ExceptionGroup, [ValueError(1)])
        self.assertMatchesTemplate(rest, ExceptionGroup, [[TypeError(2)]])

        
        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):
                
                
                
                
                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, rest = self.split_exception_group(eg, OSError)
        self.assertIsNone(match)
        self.assertMatchesTemplate(
            rest, BaseExceptionGroup, [ValueError(1), KeyboardInterrupt(2)])

        
        match, rest = self.split_exception_group(
            eg, (ValueError, KeyboardInterrupt))
        self.assertMatchesTemplate(
            match, BaseExceptionGroup, [ValueError(1), KeyboardInterrupt(2)])
        self.assertIsNone(rest)

        
        match, rest = self.split_exception_group(eg, ValueError)
        self.assertMatchesTemplate(match, ExceptionGroup, [ValueError(1)])
        self.assertMatchesTemplate(
            rest, BaseExceptionGroup, [KeyboardInterrupt(2)])

        
        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, 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, 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, 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, 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()