test_exception_group.py 33 KB

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