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