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