|
1 | | -# Regression: explicit exception chaining via `raise X from Y` exposes |
2 | | -# __cause__/__context__/__suppress_context__. |
3 | | -# |
4 | | -# NB: implicit __context__ chaining (auto-set to the exception being handled) is |
5 | | -# deliberately NOT done yet — the frame exception stack isn't reliably popped |
6 | | -# (internally-consumed StopIterations linger; the same bug breaks bare `raise` |
7 | | -# outside a handler), so the attribute is exposed and settable but defaults to |
8 | | -# None rather than being populated from that unreliable state. |
| 1 | +# Regression: exception chaining — __cause__ (explicit, via `raise X from Y`), |
| 2 | +# implicit __context__ (the exception being handled when a new one is raised), |
| 3 | +# and __suppress_context__. Also covers the exception-stack hygiene that makes |
| 4 | +# these reliable: internally-consumed StopIterations no longer linger, so a bare |
| 5 | +# `raise` outside a handler is a RuntimeError and __context__ isn't spuriously |
| 6 | +# populated. |
9 | 7 |
|
10 | 8 |
|
11 | 9 | # `raise X from Y` sets __cause__ to the instance and suppresses context. |
|
18 | 16 | assert isinstance(k.__cause__, ValueError), k.__cause__ |
19 | 17 | assert str(k.__cause__) == "inner", str(k.__cause__) |
20 | 18 | assert k.__suppress_context__ is True, k.__suppress_context__ |
| 19 | + # __context__ is still set implicitly (suppress only affects display). |
| 20 | + assert isinstance(k.__context__, ValueError), k.__context__ |
| 21 | + |
| 22 | + |
| 23 | +# Implicit chaining without `from`: __context__ is the handled exception. |
| 24 | +try: |
| 25 | + try: |
| 26 | + raise ValueError("v1") |
| 27 | + except ValueError: |
| 28 | + raise KeyError("k1") |
| 29 | +except KeyError as k: |
| 30 | + assert k.__cause__ is None, k.__cause__ |
| 31 | + assert isinstance(k.__context__, ValueError), k.__context__ |
| 32 | + assert str(k.__context__) == "v1", str(k.__context__) |
| 33 | + assert k.__suppress_context__ is False, k.__suppress_context__ |
21 | 34 |
|
22 | 35 |
|
23 | 36 | # `raise X from None` -> cause None, still suppressed. |
|
28 | 41 | assert k.__suppress_context__ is True, k.__suppress_context__ |
29 | 42 |
|
30 | 43 |
|
31 | | -# Plain exception: all three default cleanly. |
| 44 | +# Plain exception raised outside any handler: no cause, no context. |
32 | 45 | try: |
33 | 46 | raise ValueError("plain") |
34 | 47 | except ValueError as e: |
|
37 | 50 | assert e.__suppress_context__ is False, e.__suppress_context__ |
38 | 51 |
|
39 | 52 |
|
40 | | -# The chaining attributes are writable; setting __cause__ also suppresses context. |
| 53 | +# A bare `raise` with no active exception is a RuntimeError (not an abort, and |
| 54 | +# not a stale leftover exception). |
| 55 | +try: |
| 56 | + raise |
| 57 | +except RuntimeError as e: |
| 58 | + assert str(e) == "No active exception to reraise", str(e) |
| 59 | + |
| 60 | + |
| 61 | +# Iterating a generator / comprehensions while handling an exception must not |
| 62 | +# disturb the active exception (exception-stack hygiene). |
| 63 | +def gen(): |
| 64 | + yield 1 |
| 65 | + yield 2 |
| 66 | + yield 3 |
| 67 | + |
| 68 | + |
| 69 | +try: |
| 70 | + raise ValueError("active") |
| 71 | +except ValueError as e: |
| 72 | + assert set(gen()) == {1, 2, 3} |
| 73 | + assert [x for x in range(4)] == [0, 1, 2, 3] |
| 74 | + assert {k: k * k for k in range(3)} == {0: 0, 1: 1, 2: 4} |
| 75 | + assert isinstance(e, ValueError) and str(e) == "active" |
| 76 | + |
| 77 | + |
| 78 | +# Chaining attributes are writable; setting __cause__ also suppresses context. |
41 | 79 | try: |
42 | 80 | raise ValueError("x") |
43 | 81 | except ValueError as e: |
|
51 | 89 | assert e.__suppress_context__ is False |
52 | 90 |
|
53 | 91 |
|
54 | | -# `from` works with a freshly-constructed cause too. |
55 | | -try: |
56 | | - raise ValueError("v") from TypeError("t") |
57 | | -except ValueError as e: |
58 | | - assert isinstance(e.__cause__, TypeError), e.__cause__ |
59 | | - assert str(e.__cause__) == "t", str(e.__cause__) |
60 | | - |
61 | | - |
62 | 92 | print("EXCEPTION_CHAINING_OK") |
0 commit comments