@ajksunkang-aios/kgraph-linux-x64 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/kgraph-launcher +15 -3
- package/lib/kgraph/scripts/build-bundle.sh +17 -4
- package/lib/site-packages/outcome/__init__.py +20 -0
- package/lib/site-packages/outcome/_impl.py +239 -0
- package/lib/site-packages/outcome/_util.py +33 -0
- package/lib/site-packages/outcome/_version.py +7 -0
- package/lib/site-packages/outcome/py.typed +0 -0
- package/lib/site-packages/outcome-1.3.0.post0.dist-info/INSTALLER +1 -0
- package/lib/site-packages/outcome-1.3.0.post0.dist-info/LICENSE +3 -0
- package/lib/site-packages/outcome-1.3.0.post0.dist-info/LICENSE.APACHE2 +202 -0
- package/lib/site-packages/outcome-1.3.0.post0.dist-info/LICENSE.MIT +20 -0
- package/lib/site-packages/outcome-1.3.0.post0.dist-info/METADATA +63 -0
- package/lib/site-packages/outcome-1.3.0.post0.dist-info/RECORD +13 -0
- package/lib/site-packages/outcome-1.3.0.post0.dist-info/WHEEL +6 -0
- package/lib/site-packages/outcome-1.3.0.post0.dist-info/top_level.txt +1 -0
- package/lib/site-packages/sniffio/__init__.py +17 -0
- package/lib/site-packages/sniffio/_impl.py +95 -0
- package/lib/site-packages/sniffio/_tests/__init__.py +0 -0
- package/lib/site-packages/sniffio/_tests/test_sniffio.py +84 -0
- package/lib/site-packages/sniffio/_version.py +3 -0
- package/lib/site-packages/sniffio/py.typed +0 -0
- package/lib/site-packages/sniffio-1.3.1.dist-info/INSTALLER +1 -0
- package/lib/site-packages/sniffio-1.3.1.dist-info/LICENSE +3 -0
- package/lib/site-packages/sniffio-1.3.1.dist-info/LICENSE.APACHE2 +202 -0
- package/lib/site-packages/sniffio-1.3.1.dist-info/LICENSE.MIT +20 -0
- package/lib/site-packages/sniffio-1.3.1.dist-info/METADATA +104 -0
- package/lib/site-packages/sniffio-1.3.1.dist-info/RECORD +14 -0
- package/lib/site-packages/sniffio-1.3.1.dist-info/WHEEL +5 -0
- package/lib/site-packages/sniffio-1.3.1.dist-info/top_level.txt +1 -0
- package/lib/site-packages/sortedcontainers/__init__.py +74 -0
- package/lib/site-packages/sortedcontainers/sorteddict.py +812 -0
- package/lib/site-packages/sortedcontainers/sortedlist.py +2646 -0
- package/lib/site-packages/sortedcontainers/sortedset.py +733 -0
- package/lib/site-packages/sortedcontainers-2.4.0.dist-info/INSTALLER +1 -0
- package/lib/site-packages/sortedcontainers-2.4.0.dist-info/LICENSE +13 -0
- package/lib/site-packages/sortedcontainers-2.4.0.dist-info/METADATA +264 -0
- package/lib/site-packages/sortedcontainers-2.4.0.dist-info/RECORD +10 -0
- package/lib/site-packages/sortedcontainers-2.4.0.dist-info/WHEEL +6 -0
- package/lib/site-packages/sortedcontainers-2.4.0.dist-info/top_level.txt +1 -0
- package/lib/site-packages/trio/__init__.py +133 -0
- package/lib/site-packages/trio/__main__.py +3 -0
- package/lib/site-packages/trio/_abc.py +714 -0
- package/lib/site-packages/trio/_channel.py +610 -0
- package/lib/site-packages/trio/_core/__init__.py +94 -0
- package/lib/site-packages/trio/_core/_asyncgens.py +243 -0
- package/lib/site-packages/trio/_core/_concat_tb.py +26 -0
- package/lib/site-packages/trio/_core/_entry_queue.py +223 -0
- package/lib/site-packages/trio/_core/_exceptions.py +169 -0
- package/lib/site-packages/trio/_core/_generated_instrumentation.py +50 -0
- package/lib/site-packages/trio/_core/_generated_io_epoll.py +98 -0
- package/lib/site-packages/trio/_core/_generated_io_kqueue.py +153 -0
- package/lib/site-packages/trio/_core/_generated_io_windows.py +204 -0
- package/lib/site-packages/trio/_core/_generated_run.py +269 -0
- package/lib/site-packages/trio/_core/_generated_windows_ffi.py +10 -0
- package/lib/site-packages/trio/_core/_instrumentation.py +117 -0
- package/lib/site-packages/trio/_core/_io_common.py +31 -0
- package/lib/site-packages/trio/_core/_io_epoll.py +385 -0
- package/lib/site-packages/trio/_core/_io_kqueue.py +292 -0
- package/lib/site-packages/trio/_core/_io_windows.py +1036 -0
- package/lib/site-packages/trio/_core/_ki.py +271 -0
- package/lib/site-packages/trio/_core/_local.py +104 -0
- package/lib/site-packages/trio/_core/_mock_clock.py +165 -0
- package/lib/site-packages/trio/_core/_parking_lot.py +317 -0
- package/lib/site-packages/trio/_core/_run.py +3148 -0
- package/lib/site-packages/trio/_core/_run_context.py +15 -0
- package/lib/site-packages/trio/_core/_tests/__init__.py +0 -0
- package/lib/site-packages/trio/_core/_tests/test_asyncgen.py +339 -0
- package/lib/site-packages/trio/_core/_tests/test_cancelled.py +222 -0
- package/lib/site-packages/trio/_core/_tests/test_exceptiongroup_gc.py +103 -0
- package/lib/site-packages/trio/_core/_tests/test_guest_mode.py +755 -0
- package/lib/site-packages/trio/_core/_tests/test_instrumentation.py +315 -0
- package/lib/site-packages/trio/_core/_tests/test_io.py +522 -0
- package/lib/site-packages/trio/_core/_tests/test_ki.py +703 -0
- package/lib/site-packages/trio/_core/_tests/test_local.py +118 -0
- package/lib/site-packages/trio/_core/_tests/test_mock_clock.py +193 -0
- package/lib/site-packages/trio/_core/_tests/test_parking_lot.py +389 -0
- package/lib/site-packages/trio/_core/_tests/test_run.py +3024 -0
- package/lib/site-packages/trio/_core/_tests/test_thread_cache.py +227 -0
- package/lib/site-packages/trio/_core/_tests/test_tutil.py +13 -0
- package/lib/site-packages/trio/_core/_tests/test_unbounded_queue.py +154 -0
- package/lib/site-packages/trio/_core/_tests/test_windows.py +305 -0
- package/lib/site-packages/trio/_core/_tests/tutil.py +117 -0
- package/lib/site-packages/trio/_core/_tests/type_tests/nursery_start.py +79 -0
- package/lib/site-packages/trio/_core/_tests/type_tests/run.py +51 -0
- package/lib/site-packages/trio/_core/_thread_cache.py +317 -0
- package/lib/site-packages/trio/_core/_traps.py +318 -0
- package/lib/site-packages/trio/_core/_unbounded_queue.py +163 -0
- package/lib/site-packages/trio/_core/_wakeup_socketpair.py +75 -0
- package/lib/site-packages/trio/_core/_windows_cffi.py +313 -0
- package/lib/site-packages/trio/_deprecate.py +171 -0
- package/lib/site-packages/trio/_dtls.py +1380 -0
- package/lib/site-packages/trio/_file_io.py +513 -0
- package/lib/site-packages/trio/_highlevel_generic.py +125 -0
- package/lib/site-packages/trio/_highlevel_open_tcp_listeners.py +251 -0
- package/lib/site-packages/trio/_highlevel_open_tcp_stream.py +397 -0
- package/lib/site-packages/trio/_highlevel_open_unix_stream.py +65 -0
- package/lib/site-packages/trio/_highlevel_serve_listeners.py +148 -0
- package/lib/site-packages/trio/_highlevel_socket.py +423 -0
- package/lib/site-packages/trio/_highlevel_ssl_helpers.py +180 -0
- package/lib/site-packages/trio/_path.py +289 -0
- package/lib/site-packages/trio/_repl.py +159 -0
- package/lib/site-packages/trio/_signals.py +185 -0
- package/lib/site-packages/trio/_socket.py +1326 -0
- package/lib/site-packages/trio/_ssl.py +964 -0
- package/lib/site-packages/trio/_subprocess.py +1178 -0
- package/lib/site-packages/trio/_subprocess_platform/__init__.py +123 -0
- package/lib/site-packages/trio/_subprocess_platform/kqueue.py +48 -0
- package/lib/site-packages/trio/_subprocess_platform/waitid.py +113 -0
- package/lib/site-packages/trio/_subprocess_platform/windows.py +11 -0
- package/lib/site-packages/trio/_sync.py +908 -0
- package/lib/site-packages/trio/_tests/__init__.py +0 -0
- package/lib/site-packages/trio/_tests/astrill-codesigning-cert.cer +0 -0
- package/lib/site-packages/trio/_tests/check_type_completeness.py +247 -0
- package/lib/site-packages/trio/_tests/module_with_deprecations.py +22 -0
- package/lib/site-packages/trio/_tests/pytest_plugin.py +54 -0
- package/lib/site-packages/trio/_tests/test_abc.py +72 -0
- package/lib/site-packages/trio/_tests/test_channel.py +750 -0
- package/lib/site-packages/trio/_tests/test_contextvars.py +56 -0
- package/lib/site-packages/trio/_tests/test_deprecate.py +277 -0
- package/lib/site-packages/trio/_tests/test_deprecate_strict_exception_groups_false.py +64 -0
- package/lib/site-packages/trio/_tests/test_dtls.py +950 -0
- package/lib/site-packages/trio/_tests/test_exports.py +626 -0
- package/lib/site-packages/trio/_tests/test_fakenet.py +317 -0
- package/lib/site-packages/trio/_tests/test_file_io.py +269 -0
- package/lib/site-packages/trio/_tests/test_highlevel_generic.py +98 -0
- package/lib/site-packages/trio/_tests/test_highlevel_open_tcp_listeners.py +419 -0
- package/lib/site-packages/trio/_tests/test_highlevel_open_tcp_stream.py +693 -0
- package/lib/site-packages/trio/_tests/test_highlevel_open_unix_stream.py +86 -0
- package/lib/site-packages/trio/_tests/test_highlevel_serve_listeners.py +186 -0
- package/lib/site-packages/trio/_tests/test_highlevel_socket.py +336 -0
- package/lib/site-packages/trio/_tests/test_highlevel_ssl_helpers.py +169 -0
- package/lib/site-packages/trio/_tests/test_path.py +279 -0
- package/lib/site-packages/trio/_tests/test_repl.py +428 -0
- package/lib/site-packages/trio/_tests/test_scheduler_determinism.py +47 -0
- package/lib/site-packages/trio/_tests/test_signals.py +186 -0
- package/lib/site-packages/trio/_tests/test_socket.py +1253 -0
- package/lib/site-packages/trio/_tests/test_ssl.py +1371 -0
- package/lib/site-packages/trio/_tests/test_subprocess.py +767 -0
- package/lib/site-packages/trio/_tests/test_sync.py +735 -0
- package/lib/site-packages/trio/_tests/test_testing.py +682 -0
- package/lib/site-packages/trio/_tests/test_testing_raisesgroup.py +1128 -0
- package/lib/site-packages/trio/_tests/test_threads.py +1173 -0
- package/lib/site-packages/trio/_tests/test_timeouts.py +281 -0
- package/lib/site-packages/trio/_tests/test_tracing.py +88 -0
- package/lib/site-packages/trio/_tests/test_trio.py +8 -0
- package/lib/site-packages/trio/_tests/test_unix_pipes.py +288 -0
- package/lib/site-packages/trio/_tests/test_util.py +349 -0
- package/lib/site-packages/trio/_tests/test_wait_for_object.py +225 -0
- package/lib/site-packages/trio/_tests/test_windows_pipes.py +112 -0
- package/lib/site-packages/trio/_tests/tools/__init__.py +0 -0
- package/lib/site-packages/trio/_tests/tools/test_gen_exports.py +179 -0
- package/lib/site-packages/trio/_tests/tools/test_mypy_annotate.py +140 -0
- package/lib/site-packages/trio/_tests/tools/test_sync_requirements.py +80 -0
- package/lib/site-packages/trio/_tests/type_tests/check_wraps.py +9 -0
- package/lib/site-packages/trio/_tests/type_tests/open_memory_channel.py +4 -0
- package/lib/site-packages/trio/_tests/type_tests/path.py +140 -0
- package/lib/site-packages/trio/_tests/type_tests/subprocesses.py +23 -0
- package/lib/site-packages/trio/_tests/type_tests/task_status.py +29 -0
- package/lib/site-packages/trio/_threads.py +610 -0
- package/lib/site-packages/trio/_timeouts.py +197 -0
- package/lib/site-packages/trio/_tools/__init__.py +0 -0
- package/lib/site-packages/trio/_tools/gen_exports.py +401 -0
- package/lib/site-packages/trio/_tools/mypy_annotate.py +126 -0
- package/lib/site-packages/trio/_tools/sync_requirements.py +98 -0
- package/lib/site-packages/trio/_tools/windows_ffi_build.py +220 -0
- package/lib/site-packages/trio/_unix_pipes.py +197 -0
- package/lib/site-packages/trio/_util.py +385 -0
- package/lib/site-packages/trio/_version.py +3 -0
- package/lib/site-packages/trio/_wait_for_object.py +67 -0
- package/lib/site-packages/trio/_windows_pipes.py +144 -0
- package/lib/site-packages/trio/abc.py +23 -0
- package/lib/site-packages/trio/from_thread.py +13 -0
- package/lib/site-packages/trio/lowlevel.py +95 -0
- package/lib/site-packages/trio/py.typed +0 -0
- package/lib/site-packages/trio/socket.py +602 -0
- package/lib/site-packages/trio/testing/__init__.py +58 -0
- package/lib/site-packages/trio/testing/_check_streams.py +570 -0
- package/lib/site-packages/trio/testing/_checkpoints.py +69 -0
- package/lib/site-packages/trio/testing/_fake_net.py +584 -0
- package/lib/site-packages/trio/testing/_memory_streams.py +633 -0
- package/lib/site-packages/trio/testing/_network.py +36 -0
- package/lib/site-packages/trio/testing/_raises_group.py +1015 -0
- package/lib/site-packages/trio/testing/_sequencer.py +87 -0
- package/lib/site-packages/trio/testing/_trio_test.py +50 -0
- package/lib/site-packages/trio/to_thread.py +4 -0
- package/lib/site-packages/trio-0.33.0.dist-info/INSTALLER +1 -0
- package/lib/site-packages/trio-0.33.0.dist-info/METADATA +186 -0
- package/lib/site-packages/trio-0.33.0.dist-info/RECORD +156 -0
- package/lib/site-packages/trio-0.33.0.dist-info/REQUESTED +0 -0
- package/lib/site-packages/trio-0.33.0.dist-info/WHEEL +5 -0
- package/lib/site-packages/trio-0.33.0.dist-info/entry_points.txt +2 -0
- package/lib/site-packages/trio-0.33.0.dist-info/licenses/LICENSE +3 -0
- package/lib/site-packages/trio-0.33.0.dist-info/licenses/LICENSE.APACHE2 +202 -0
- package/lib/site-packages/trio-0.33.0.dist-info/licenses/LICENSE.MIT +22 -0
- package/lib/site-packages/trio-0.33.0.dist-info/top_level.txt +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import threading
|
|
4
|
+
from typing import TYPE_CHECKING, Final
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from ._run import Runner, Task
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class RunContext(threading.local):
|
|
11
|
+
runner: Runner
|
|
12
|
+
task: Task
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
GLOBAL_RUN_CONTEXT: Final = RunContext()
|
|
File without changes
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import contextlib
|
|
4
|
+
import sys
|
|
5
|
+
import weakref
|
|
6
|
+
from math import inf
|
|
7
|
+
from types import AsyncGeneratorType
|
|
8
|
+
from typing import TYPE_CHECKING, NoReturn
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
from ... import _core
|
|
13
|
+
from .tutil import gc_collect_harder, restore_unraisablehook
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from collections.abc import AsyncGenerator
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_asyncgen_basics() -> None:
|
|
20
|
+
collected = []
|
|
21
|
+
|
|
22
|
+
async def example(cause: str) -> AsyncGenerator[int, None]:
|
|
23
|
+
try:
|
|
24
|
+
with contextlib.suppress(GeneratorExit):
|
|
25
|
+
yield 42
|
|
26
|
+
await _core.checkpoint()
|
|
27
|
+
except _core.Cancelled:
|
|
28
|
+
assert "exhausted" not in cause
|
|
29
|
+
task_name = _core.current_task().name
|
|
30
|
+
assert cause in task_name or task_name == "<init>"
|
|
31
|
+
assert _core.current_effective_deadline() == -inf
|
|
32
|
+
with pytest.raises(_core.Cancelled):
|
|
33
|
+
await _core.checkpoint()
|
|
34
|
+
collected.append(cause)
|
|
35
|
+
else:
|
|
36
|
+
assert "async_main" in _core.current_task().name
|
|
37
|
+
assert "exhausted" in cause
|
|
38
|
+
assert _core.current_effective_deadline() == inf
|
|
39
|
+
await _core.checkpoint()
|
|
40
|
+
collected.append(cause)
|
|
41
|
+
|
|
42
|
+
saved = []
|
|
43
|
+
|
|
44
|
+
async def async_main() -> None:
|
|
45
|
+
# GC'ed before exhausted
|
|
46
|
+
with pytest.warns( # noqa: PT031
|
|
47
|
+
ResourceWarning,
|
|
48
|
+
match="Async generator.*collected before.*exhausted",
|
|
49
|
+
):
|
|
50
|
+
assert await example("abandoned").asend(None) == 42
|
|
51
|
+
gc_collect_harder()
|
|
52
|
+
await _core.wait_all_tasks_blocked()
|
|
53
|
+
assert collected.pop() == "abandoned"
|
|
54
|
+
|
|
55
|
+
aiter_ = example("exhausted 1")
|
|
56
|
+
try:
|
|
57
|
+
assert await aiter_.asend(None) == 42
|
|
58
|
+
finally:
|
|
59
|
+
await aiter_.aclose()
|
|
60
|
+
assert collected.pop() == "exhausted 1"
|
|
61
|
+
|
|
62
|
+
# Also fine if you exhaust it at point of use
|
|
63
|
+
async for val in example("exhausted 2"):
|
|
64
|
+
assert val == 42
|
|
65
|
+
assert collected.pop() == "exhausted 2"
|
|
66
|
+
|
|
67
|
+
gc_collect_harder()
|
|
68
|
+
|
|
69
|
+
# No problems saving the geniter when using either of these patterns
|
|
70
|
+
aiter_ = example("exhausted 3")
|
|
71
|
+
try:
|
|
72
|
+
saved.append(aiter_)
|
|
73
|
+
assert await aiter_.asend(None) == 42
|
|
74
|
+
finally:
|
|
75
|
+
await aiter_.aclose()
|
|
76
|
+
assert collected.pop() == "exhausted 3"
|
|
77
|
+
|
|
78
|
+
# Also fine if you exhaust it at point of use
|
|
79
|
+
saved.append(example("exhausted 4"))
|
|
80
|
+
async for val in saved[-1]:
|
|
81
|
+
assert val == 42
|
|
82
|
+
assert collected.pop() == "exhausted 4"
|
|
83
|
+
|
|
84
|
+
# Leave one referenced-but-unexhausted and make sure it gets cleaned up
|
|
85
|
+
saved.append(example("outlived run"))
|
|
86
|
+
assert await saved[-1].asend(None) == 42
|
|
87
|
+
assert collected == []
|
|
88
|
+
|
|
89
|
+
_core.run(async_main)
|
|
90
|
+
assert collected.pop() == "outlived run"
|
|
91
|
+
for agen in saved:
|
|
92
|
+
assert isinstance(agen, AsyncGeneratorType)
|
|
93
|
+
assert agen.ag_frame is None # all should now be exhausted
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
async def test_asyncgen_throws_during_finalization(
|
|
97
|
+
caplog: pytest.LogCaptureFixture,
|
|
98
|
+
) -> None:
|
|
99
|
+
record = []
|
|
100
|
+
|
|
101
|
+
async def agen() -> AsyncGenerator[int, None]:
|
|
102
|
+
try:
|
|
103
|
+
yield 1
|
|
104
|
+
finally:
|
|
105
|
+
await _core.cancel_shielded_checkpoint()
|
|
106
|
+
record.append("crashing")
|
|
107
|
+
raise ValueError("oops")
|
|
108
|
+
|
|
109
|
+
with restore_unraisablehook():
|
|
110
|
+
await agen().asend(None)
|
|
111
|
+
gc_collect_harder()
|
|
112
|
+
await _core.wait_all_tasks_blocked()
|
|
113
|
+
assert record == ["crashing"]
|
|
114
|
+
# Following type ignore is because typing for LogCaptureFixture is wrong
|
|
115
|
+
exc_type, exc_value, _exc_traceback = caplog.records[0].exc_info # type: ignore[misc]
|
|
116
|
+
assert exc_type is ValueError
|
|
117
|
+
assert str(exc_value) == "oops"
|
|
118
|
+
assert "during finalization of async generator" in caplog.records[0].message
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def test_firstiter_after_closing() -> None:
|
|
122
|
+
saved = []
|
|
123
|
+
record = []
|
|
124
|
+
|
|
125
|
+
async def funky_agen() -> AsyncGenerator[int, None]:
|
|
126
|
+
try:
|
|
127
|
+
yield 1
|
|
128
|
+
except GeneratorExit:
|
|
129
|
+
record.append("cleanup 1")
|
|
130
|
+
raise
|
|
131
|
+
try:
|
|
132
|
+
yield 2
|
|
133
|
+
finally:
|
|
134
|
+
record.append("cleanup 2")
|
|
135
|
+
await funky_agen().asend(None)
|
|
136
|
+
|
|
137
|
+
async def async_main() -> None:
|
|
138
|
+
aiter_ = funky_agen()
|
|
139
|
+
saved.append(aiter_)
|
|
140
|
+
assert await aiter_.asend(None) == 1
|
|
141
|
+
assert await aiter_.asend(None) == 2
|
|
142
|
+
|
|
143
|
+
_core.run(async_main)
|
|
144
|
+
assert record == ["cleanup 2", "cleanup 1"]
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def test_interdependent_asyncgen_cleanup_order() -> None:
|
|
148
|
+
saved: list[AsyncGenerator[int, None]] = []
|
|
149
|
+
record: list[int | str] = []
|
|
150
|
+
|
|
151
|
+
async def innermost() -> AsyncGenerator[int, None]:
|
|
152
|
+
try:
|
|
153
|
+
yield 1
|
|
154
|
+
finally:
|
|
155
|
+
await _core.cancel_shielded_checkpoint()
|
|
156
|
+
record.append("innermost")
|
|
157
|
+
|
|
158
|
+
async def agen(
|
|
159
|
+
label: int,
|
|
160
|
+
inner: AsyncGenerator[int, None],
|
|
161
|
+
) -> AsyncGenerator[int, None]:
|
|
162
|
+
try:
|
|
163
|
+
yield await inner.asend(None)
|
|
164
|
+
finally:
|
|
165
|
+
# Either `inner` has already been cleaned up, or
|
|
166
|
+
# we're about to exhaust it. Either way, we wind
|
|
167
|
+
# up with `record` containing the labels in
|
|
168
|
+
# innermost-to-outermost order.
|
|
169
|
+
with pytest.raises(StopAsyncIteration):
|
|
170
|
+
await inner.asend(None)
|
|
171
|
+
record.append(label)
|
|
172
|
+
|
|
173
|
+
async def async_main() -> None:
|
|
174
|
+
# This makes a chain of 101 interdependent asyncgens:
|
|
175
|
+
# agen(99)'s cleanup will iterate agen(98)'s will iterate
|
|
176
|
+
# ... agen(0)'s will iterate innermost()'s
|
|
177
|
+
ag_chain = innermost()
|
|
178
|
+
for idx in range(100):
|
|
179
|
+
ag_chain = agen(idx, ag_chain)
|
|
180
|
+
saved.append(ag_chain)
|
|
181
|
+
assert await ag_chain.asend(None) == 1
|
|
182
|
+
assert record == []
|
|
183
|
+
|
|
184
|
+
_core.run(async_main)
|
|
185
|
+
assert record == ["innermost", *range(100)]
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@restore_unraisablehook()
|
|
189
|
+
def test_last_minute_gc_edge_case() -> None:
|
|
190
|
+
saved: list[AsyncGenerator[int, None]] = []
|
|
191
|
+
record = []
|
|
192
|
+
needs_retry = True
|
|
193
|
+
|
|
194
|
+
async def agen() -> AsyncGenerator[int, None]:
|
|
195
|
+
try:
|
|
196
|
+
yield 1
|
|
197
|
+
finally:
|
|
198
|
+
record.append("cleaned up")
|
|
199
|
+
|
|
200
|
+
def collect_at_opportune_moment(token: _core._entry_queue.TrioToken) -> None:
|
|
201
|
+
runner = _core._run.GLOBAL_RUN_CONTEXT.runner
|
|
202
|
+
assert runner.system_nursery is not None
|
|
203
|
+
if runner.system_nursery._closed and isinstance(
|
|
204
|
+
runner.asyncgens.alive,
|
|
205
|
+
weakref.WeakSet,
|
|
206
|
+
):
|
|
207
|
+
saved.clear()
|
|
208
|
+
record.append("final collection")
|
|
209
|
+
gc_collect_harder()
|
|
210
|
+
record.append("done")
|
|
211
|
+
else:
|
|
212
|
+
try:
|
|
213
|
+
token.run_sync_soon(collect_at_opportune_moment, token)
|
|
214
|
+
except _core.RunFinishedError: # pragma: no cover
|
|
215
|
+
nonlocal needs_retry
|
|
216
|
+
needs_retry = True
|
|
217
|
+
|
|
218
|
+
async def async_main() -> None:
|
|
219
|
+
token = _core.current_trio_token()
|
|
220
|
+
token.run_sync_soon(collect_at_opportune_moment, token)
|
|
221
|
+
saved.append(agen())
|
|
222
|
+
await saved[-1].asend(None)
|
|
223
|
+
|
|
224
|
+
ATTEMPT_AMOUNT = 50
|
|
225
|
+
|
|
226
|
+
# Actually running into the edge case requires that the run_sync_soon task
|
|
227
|
+
# execute in between the system nursery's closure and the strong-ification
|
|
228
|
+
# of runner.asyncgens. There's about a 25% chance that it doesn't
|
|
229
|
+
# (if the run_sync_soon task runs before init on one tick and after init
|
|
230
|
+
# on the next tick); if we try enough times, we can make the chance of
|
|
231
|
+
# failure as small as we want.
|
|
232
|
+
for _ in range(ATTEMPT_AMOUNT):
|
|
233
|
+
needs_retry = False
|
|
234
|
+
record.clear()
|
|
235
|
+
saved.clear()
|
|
236
|
+
_core.run(async_main)
|
|
237
|
+
if needs_retry: # pragma: no cover
|
|
238
|
+
assert record == ["cleaned up"]
|
|
239
|
+
else:
|
|
240
|
+
assert record == ["final collection", "done", "cleaned up"]
|
|
241
|
+
break
|
|
242
|
+
else: # pragma: no cover
|
|
243
|
+
pytest.fail(
|
|
244
|
+
"Didn't manage to hit the trailing_finalizer_asyncgens case "
|
|
245
|
+
f"despite trying {ATTEMPT_AMOUNT} times",
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
async def step_outside_async_context(aiter_: AsyncGenerator[int, None]) -> None:
|
|
250
|
+
# abort_fns run outside of task context, at least if they're
|
|
251
|
+
# triggered by a deadline expiry rather than a direct
|
|
252
|
+
# cancellation. Thus, an asyncgen first iterated inside one
|
|
253
|
+
# will appear non-Trio, and since no other hooks were installed,
|
|
254
|
+
# will use the last-ditch fallback handling (that tries to mimic
|
|
255
|
+
# CPython's behavior with no hooks).
|
|
256
|
+
#
|
|
257
|
+
# NB: the strangeness with aiter being an attribute of abort_fn is
|
|
258
|
+
# to make it as easy as possible to ensure we don't hang onto a
|
|
259
|
+
# reference to aiter inside the guts of the run loop.
|
|
260
|
+
def abort_fn(_: _core.RaiseCancelT) -> _core.Abort:
|
|
261
|
+
with pytest.raises(StopIteration, match="42"):
|
|
262
|
+
abort_fn.aiter.asend(None).send(None) # type: ignore[attr-defined] # Callables don't have attribute "aiter"
|
|
263
|
+
del abort_fn.aiter # type: ignore[attr-defined]
|
|
264
|
+
return _core.Abort.SUCCEEDED
|
|
265
|
+
|
|
266
|
+
abort_fn.aiter = aiter_ # type: ignore[attr-defined]
|
|
267
|
+
|
|
268
|
+
async with _core.open_nursery() as nursery:
|
|
269
|
+
nursery.start_soon(_core.wait_task_rescheduled, abort_fn)
|
|
270
|
+
await _core.wait_all_tasks_blocked()
|
|
271
|
+
nursery.cancel_scope.deadline = _core.current_time()
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
async def test_fallback_when_no_hook_claims_it(
|
|
275
|
+
capsys: pytest.CaptureFixture[str],
|
|
276
|
+
) -> None:
|
|
277
|
+
async def well_behaved() -> AsyncGenerator[int, None]:
|
|
278
|
+
yield 42
|
|
279
|
+
|
|
280
|
+
async def yields_after_yield() -> AsyncGenerator[int, None]:
|
|
281
|
+
with pytest.raises(GeneratorExit):
|
|
282
|
+
yield 42
|
|
283
|
+
yield 100
|
|
284
|
+
|
|
285
|
+
async def awaits_after_yield() -> AsyncGenerator[int, None]:
|
|
286
|
+
with pytest.raises(GeneratorExit):
|
|
287
|
+
yield 42
|
|
288
|
+
await _core.cancel_shielded_checkpoint()
|
|
289
|
+
|
|
290
|
+
with restore_unraisablehook():
|
|
291
|
+
await step_outside_async_context(well_behaved())
|
|
292
|
+
gc_collect_harder()
|
|
293
|
+
assert capsys.readouterr().err == ""
|
|
294
|
+
|
|
295
|
+
await step_outside_async_context(yields_after_yield())
|
|
296
|
+
gc_collect_harder()
|
|
297
|
+
assert "ignored GeneratorExit" in capsys.readouterr().err
|
|
298
|
+
|
|
299
|
+
await step_outside_async_context(awaits_after_yield())
|
|
300
|
+
gc_collect_harder()
|
|
301
|
+
assert "awaited something during finalization" in capsys.readouterr().err
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def test_delegation_to_existing_hooks() -> None:
|
|
305
|
+
record = []
|
|
306
|
+
|
|
307
|
+
def my_firstiter(agen: AsyncGenerator[object, NoReturn]) -> None:
|
|
308
|
+
assert isinstance(agen, AsyncGeneratorType)
|
|
309
|
+
record.append("firstiter " + agen.ag_frame.f_locals["arg"])
|
|
310
|
+
|
|
311
|
+
def my_finalizer(agen: AsyncGenerator[object, NoReturn]) -> None:
|
|
312
|
+
assert isinstance(agen, AsyncGeneratorType)
|
|
313
|
+
record.append("finalizer " + agen.ag_frame.f_locals["arg"])
|
|
314
|
+
|
|
315
|
+
async def example(arg: str) -> AsyncGenerator[int, None]:
|
|
316
|
+
try:
|
|
317
|
+
yield 42
|
|
318
|
+
finally:
|
|
319
|
+
with pytest.raises(_core.Cancelled):
|
|
320
|
+
await _core.checkpoint()
|
|
321
|
+
record.append("trio collected " + arg)
|
|
322
|
+
|
|
323
|
+
async def async_main() -> None:
|
|
324
|
+
await step_outside_async_context(example("theirs"))
|
|
325
|
+
assert await example("ours").asend(None) == 42
|
|
326
|
+
gc_collect_harder()
|
|
327
|
+
assert record == ["firstiter theirs", "finalizer theirs"]
|
|
328
|
+
record[:] = []
|
|
329
|
+
await _core.wait_all_tasks_blocked()
|
|
330
|
+
assert record == ["trio collected ours"]
|
|
331
|
+
|
|
332
|
+
with restore_unraisablehook():
|
|
333
|
+
old_hooks = sys.get_asyncgen_hooks()
|
|
334
|
+
sys.set_asyncgen_hooks(my_firstiter, my_finalizer)
|
|
335
|
+
try:
|
|
336
|
+
_core.run(async_main)
|
|
337
|
+
finally:
|
|
338
|
+
assert sys.get_asyncgen_hooks() == (my_firstiter, my_finalizer)
|
|
339
|
+
sys.set_asyncgen_hooks(*old_hooks)
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import pickle
|
|
2
|
+
import re
|
|
3
|
+
from math import inf
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
import trio
|
|
8
|
+
from trio import Cancelled
|
|
9
|
+
from trio.lowlevel import current_task
|
|
10
|
+
from trio.testing import wait_all_tasks_blocked
|
|
11
|
+
|
|
12
|
+
from .test_ki import ki_self
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_Cancelled_init() -> None:
|
|
16
|
+
with pytest.raises(TypeError, match=r"^trio.Cancelled has no public constructor$"):
|
|
17
|
+
raise Cancelled
|
|
18
|
+
|
|
19
|
+
with pytest.raises(TypeError, match=r"^trio.Cancelled has no public constructor$"):
|
|
20
|
+
Cancelled(source="explicit")
|
|
21
|
+
|
|
22
|
+
# private constructor should not raise
|
|
23
|
+
Cancelled._create(source="explicit")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
async def test_Cancelled_str() -> None:
|
|
27
|
+
cancelled = Cancelled._create(source="explicit")
|
|
28
|
+
assert str(cancelled) == "cancelled due to explicit"
|
|
29
|
+
# note: repr(current_task()) is often fairly verbose
|
|
30
|
+
assert re.fullmatch(
|
|
31
|
+
r"cancelled due to deadline from task "
|
|
32
|
+
r"<Task 'trio._core._tests.test_cancelled.test_Cancelled_str' at 0x\w*>",
|
|
33
|
+
str(
|
|
34
|
+
Cancelled._create(
|
|
35
|
+
source="deadline",
|
|
36
|
+
source_task=repr(current_task()),
|
|
37
|
+
)
|
|
38
|
+
),
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
assert re.fullmatch(
|
|
42
|
+
rf"cancelled due to nursery with reason 'pigs flying' from task {current_task()!r}",
|
|
43
|
+
str(
|
|
44
|
+
Cancelled._create(
|
|
45
|
+
source="nursery",
|
|
46
|
+
source_task=repr(current_task()),
|
|
47
|
+
reason="pigs flying",
|
|
48
|
+
)
|
|
49
|
+
),
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_Cancelled_subclass() -> None:
|
|
54
|
+
with pytest.raises(TypeError):
|
|
55
|
+
type("Subclass", (Cancelled,), {})
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# https://github.com/python-trio/trio/issues/3248
|
|
59
|
+
def test_Cancelled_pickle() -> None:
|
|
60
|
+
cancelled = Cancelled._create(source="KeyboardInterrupt")
|
|
61
|
+
pickled_cancelled = pickle.loads(pickle.dumps(cancelled))
|
|
62
|
+
assert isinstance(pickled_cancelled, Cancelled)
|
|
63
|
+
assert cancelled.source == pickled_cancelled.source
|
|
64
|
+
assert cancelled.source_task == pickled_cancelled.source_task
|
|
65
|
+
assert cancelled.reason == pickled_cancelled.reason
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
async def test_cancel_reason() -> None:
|
|
69
|
+
with trio.CancelScope() as cs:
|
|
70
|
+
cs.cancel(reason="hello")
|
|
71
|
+
with pytest.raises(
|
|
72
|
+
Cancelled,
|
|
73
|
+
match=rf"^cancelled due to explicit with reason 'hello' from task {current_task()!r}$",
|
|
74
|
+
) as excinfo:
|
|
75
|
+
await trio.lowlevel.checkpoint()
|
|
76
|
+
assert excinfo.value.source == "explicit"
|
|
77
|
+
assert excinfo.value.reason == "hello"
|
|
78
|
+
assert excinfo.value.source_task == repr(current_task())
|
|
79
|
+
|
|
80
|
+
with trio.CancelScope(deadline=-inf) as cs:
|
|
81
|
+
with pytest.raises(Cancelled, match=r"^cancelled due to deadline"):
|
|
82
|
+
await trio.lowlevel.checkpoint()
|
|
83
|
+
|
|
84
|
+
with trio.CancelScope() as cs:
|
|
85
|
+
cs.deadline = -inf
|
|
86
|
+
with pytest.raises(
|
|
87
|
+
Cancelled,
|
|
88
|
+
match=r"^cancelled due to deadline",
|
|
89
|
+
):
|
|
90
|
+
await trio.lowlevel.checkpoint()
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
match_str = r"^cancelled due to nursery with reason 'child task raised exception ValueError\(\)' from task {0!r}$"
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
async def cancelled_task(
|
|
97
|
+
fail_task: trio.lowlevel.Task, task_status: trio.TaskStatus
|
|
98
|
+
) -> None:
|
|
99
|
+
task_status.started()
|
|
100
|
+
with pytest.raises(Cancelled, match=match_str.format(fail_task)):
|
|
101
|
+
await trio.sleep_forever()
|
|
102
|
+
raise TypeError
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# failing_task raises before cancelled_task is started
|
|
106
|
+
async def test_cancel_reason_nursery() -> None:
|
|
107
|
+
async def failing_task(task_status: trio.TaskStatus[trio.lowlevel.Task]) -> None:
|
|
108
|
+
task_status.started(current_task())
|
|
109
|
+
raise ValueError
|
|
110
|
+
|
|
111
|
+
with pytest.RaisesGroup(ValueError, TypeError):
|
|
112
|
+
async with trio.open_nursery() as nursery:
|
|
113
|
+
fail_task = await nursery.start(failing_task)
|
|
114
|
+
with pytest.raises(Cancelled, match=match_str.format(fail_task)):
|
|
115
|
+
await wait_all_tasks_blocked()
|
|
116
|
+
await nursery.start(cancelled_task, fail_task)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# wait until both tasks are running before failing_task raises
|
|
120
|
+
async def test_cancel_reason_nursery2() -> None:
|
|
121
|
+
async def failing_task(task_status: trio.TaskStatus[trio.lowlevel.Task]) -> None:
|
|
122
|
+
task_status.started(current_task())
|
|
123
|
+
await wait_all_tasks_blocked()
|
|
124
|
+
raise ValueError
|
|
125
|
+
|
|
126
|
+
with pytest.RaisesGroup(ValueError, TypeError):
|
|
127
|
+
async with trio.open_nursery() as nursery:
|
|
128
|
+
fail_task = await nursery.start(failing_task)
|
|
129
|
+
await nursery.start(cancelled_task, fail_task)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
# failing_task raises before calling task_status.started()
|
|
133
|
+
async def test_cancel_reason_nursery3() -> None:
|
|
134
|
+
async def failing_task(task_status: trio.TaskStatus[None]) -> None:
|
|
135
|
+
raise ValueError
|
|
136
|
+
|
|
137
|
+
parent_task = current_task()
|
|
138
|
+
|
|
139
|
+
async def cancelled_task() -> None:
|
|
140
|
+
# We don't have a way of distinguishing that the nursery code block failed
|
|
141
|
+
# because it failed to `start()` a task.
|
|
142
|
+
with pytest.raises(
|
|
143
|
+
Cancelled,
|
|
144
|
+
match=re.escape(
|
|
145
|
+
rf"cancelled due to nursery with reason 'Code block inside nursery contextmanager raised exception ValueError()' from task {parent_task!r}"
|
|
146
|
+
),
|
|
147
|
+
):
|
|
148
|
+
await trio.sleep_forever()
|
|
149
|
+
|
|
150
|
+
with pytest.RaisesGroup(ValueError):
|
|
151
|
+
async with trio.open_nursery() as nursery:
|
|
152
|
+
nursery.start_soon(cancelled_task)
|
|
153
|
+
await wait_all_tasks_blocked()
|
|
154
|
+
await nursery.start(failing_task)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
async def test_cancel_reason_not_overwritten() -> None:
|
|
158
|
+
with trio.CancelScope() as cs:
|
|
159
|
+
cs.cancel()
|
|
160
|
+
with pytest.raises(
|
|
161
|
+
Cancelled,
|
|
162
|
+
match=rf"^cancelled due to explicit from task {current_task()!r}$",
|
|
163
|
+
):
|
|
164
|
+
await trio.lowlevel.checkpoint()
|
|
165
|
+
cs.deadline = -inf
|
|
166
|
+
with pytest.raises(
|
|
167
|
+
Cancelled,
|
|
168
|
+
match=rf"^cancelled due to explicit from task {current_task()!r}$",
|
|
169
|
+
):
|
|
170
|
+
await trio.lowlevel.checkpoint()
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
async def test_cancel_reason_not_overwritten_2() -> None:
|
|
174
|
+
with trio.CancelScope() as cs:
|
|
175
|
+
cs.deadline = -inf
|
|
176
|
+
with pytest.raises(Cancelled, match=r"^cancelled due to deadline$"):
|
|
177
|
+
await trio.lowlevel.checkpoint()
|
|
178
|
+
cs.cancel()
|
|
179
|
+
with pytest.raises(Cancelled, match=r"^cancelled due to deadline$"):
|
|
180
|
+
await trio.lowlevel.checkpoint()
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
async def test_nested_child_source() -> None:
|
|
184
|
+
ev = trio.Event()
|
|
185
|
+
parent_task = current_task()
|
|
186
|
+
|
|
187
|
+
async def child() -> None:
|
|
188
|
+
ev.set()
|
|
189
|
+
with pytest.raises(
|
|
190
|
+
Cancelled,
|
|
191
|
+
match=rf"^cancelled due to nursery with reason 'Code block inside nursery contextmanager raised exception ValueError\(\)' from task {parent_task!r}$",
|
|
192
|
+
):
|
|
193
|
+
await trio.sleep_forever()
|
|
194
|
+
|
|
195
|
+
with pytest.RaisesGroup(ValueError):
|
|
196
|
+
async with trio.open_nursery() as nursery:
|
|
197
|
+
nursery.start_soon(child)
|
|
198
|
+
await ev.wait()
|
|
199
|
+
raise ValueError
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
async def test_reason_delayed_ki() -> None:
|
|
203
|
+
# simplified version of test_ki.test_ki_protection_works check #2
|
|
204
|
+
parent_task = current_task()
|
|
205
|
+
|
|
206
|
+
async def sleeper(name: str) -> None:
|
|
207
|
+
with pytest.raises(
|
|
208
|
+
Cancelled,
|
|
209
|
+
match=rf"^cancelled due to KeyboardInterrupt from task {parent_task!r}$",
|
|
210
|
+
):
|
|
211
|
+
while True:
|
|
212
|
+
await trio.lowlevel.checkpoint()
|
|
213
|
+
|
|
214
|
+
async def raiser(name: str) -> None:
|
|
215
|
+
ki_self()
|
|
216
|
+
|
|
217
|
+
with pytest.RaisesGroup(KeyboardInterrupt):
|
|
218
|
+
async with trio.open_nursery() as nursery:
|
|
219
|
+
nursery.start_soon(sleeper, "s1")
|
|
220
|
+
nursery.start_soon(sleeper, "s2")
|
|
221
|
+
nursery.start_soon(trio.lowlevel.enable_ki_protection(raiser), "r1")
|
|
222
|
+
# __aexit__ blocks, and then receives the KI
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import gc
|
|
4
|
+
import sys
|
|
5
|
+
from traceback import extract_tb
|
|
6
|
+
from typing import TYPE_CHECKING, NoReturn
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from .._concat_tb import concat_tb
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from collections.abc import Callable
|
|
14
|
+
from types import TracebackType
|
|
15
|
+
|
|
16
|
+
if sys.version_info < (3, 11):
|
|
17
|
+
from exceptiongroup import ExceptionGroup
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def raiser1() -> NoReturn:
|
|
21
|
+
raiser1_2()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def raiser1_2() -> NoReturn:
|
|
25
|
+
raiser1_3()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def raiser1_3() -> NoReturn:
|
|
29
|
+
raise ValueError("raiser1_string")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def raiser2() -> NoReturn:
|
|
33
|
+
raiser2_2()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def raiser2_2() -> NoReturn:
|
|
37
|
+
raise KeyError("raiser2_string")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_exc(raiser: Callable[[], NoReturn]) -> Exception:
|
|
41
|
+
try:
|
|
42
|
+
raiser()
|
|
43
|
+
except Exception as exc:
|
|
44
|
+
return exc
|
|
45
|
+
raise AssertionError("raiser should always raise") # pragma: no cover
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def get_tb(raiser: Callable[[], NoReturn]) -> TracebackType | None:
|
|
49
|
+
return get_exc(raiser).__traceback__
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_concat_tb() -> None:
|
|
53
|
+
tb1 = get_tb(raiser1)
|
|
54
|
+
tb2 = get_tb(raiser2)
|
|
55
|
+
|
|
56
|
+
# These return a list of (filename, lineno, fn name, text) tuples
|
|
57
|
+
# https://docs.python.org/3/library/traceback.html#traceback.extract_tb
|
|
58
|
+
entries1 = extract_tb(tb1)
|
|
59
|
+
entries2 = extract_tb(tb2)
|
|
60
|
+
|
|
61
|
+
tb12 = concat_tb(tb1, tb2)
|
|
62
|
+
assert extract_tb(tb12) == entries1 + entries2
|
|
63
|
+
|
|
64
|
+
tb21 = concat_tb(tb2, tb1)
|
|
65
|
+
assert extract_tb(tb21) == entries2 + entries1
|
|
66
|
+
|
|
67
|
+
# Check degenerate cases
|
|
68
|
+
assert extract_tb(concat_tb(None, tb1)) == entries1
|
|
69
|
+
assert extract_tb(concat_tb(tb1, None)) == entries1
|
|
70
|
+
assert concat_tb(None, None) is None
|
|
71
|
+
|
|
72
|
+
# Make sure the original tracebacks didn't get mutated by mistake
|
|
73
|
+
assert extract_tb(get_tb(raiser1)) == entries1
|
|
74
|
+
assert extract_tb(get_tb(raiser2)) == entries2
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# Unclear if this can still fail, removing the `del` from _concat_tb.copy_tb does not seem
|
|
78
|
+
# to trigger it (on a platform where the `del` is executed)
|
|
79
|
+
@pytest.mark.skipif(
|
|
80
|
+
sys.implementation.name != "cpython",
|
|
81
|
+
reason="Only makes sense with refcounting GC",
|
|
82
|
+
)
|
|
83
|
+
def test_ExceptionGroup_catch_doesnt_create_cyclic_garbage() -> None:
|
|
84
|
+
# https://github.com/python-trio/trio/pull/2063
|
|
85
|
+
gc.collect()
|
|
86
|
+
old_flags = gc.get_debug()
|
|
87
|
+
|
|
88
|
+
def make_multi() -> NoReturn:
|
|
89
|
+
raise ExceptionGroup("", [get_exc(raiser1), get_exc(raiser2)])
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
gc.set_debug(gc.DEBUG_SAVEALL)
|
|
93
|
+
with pytest.raises(ExceptionGroup) as excinfo:
|
|
94
|
+
# covers ~~MultiErrorCatcher.__exit__ and~~ _concat_tb.copy_tb
|
|
95
|
+
# TODO: is the above comment true anymore? as this no longer uses MultiError.catch
|
|
96
|
+
raise make_multi()
|
|
97
|
+
for exc in excinfo.value.exceptions:
|
|
98
|
+
assert isinstance(exc, (ValueError, KeyError))
|
|
99
|
+
gc.collect()
|
|
100
|
+
assert not gc.garbage
|
|
101
|
+
finally:
|
|
102
|
+
gc.set_debug(old_flags)
|
|
103
|
+
gc.garbage.clear()
|