@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,767 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import gc
|
|
4
|
+
import os
|
|
5
|
+
import random
|
|
6
|
+
import signal
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
from collections.abc import AsyncIterator, Callable
|
|
10
|
+
from contextlib import AbstractAsyncContextManager, asynccontextmanager
|
|
11
|
+
from functools import partial
|
|
12
|
+
from pathlib import Path as SyncPath
|
|
13
|
+
from signal import Signals
|
|
14
|
+
from typing import (
|
|
15
|
+
TYPE_CHECKING,
|
|
16
|
+
Any,
|
|
17
|
+
NoReturn,
|
|
18
|
+
TypeAlias,
|
|
19
|
+
)
|
|
20
|
+
from unittest import mock
|
|
21
|
+
|
|
22
|
+
import pytest
|
|
23
|
+
|
|
24
|
+
import trio
|
|
25
|
+
|
|
26
|
+
from .. import (
|
|
27
|
+
Event,
|
|
28
|
+
Process,
|
|
29
|
+
_core,
|
|
30
|
+
fail_after,
|
|
31
|
+
move_on_after,
|
|
32
|
+
run_process,
|
|
33
|
+
sleep,
|
|
34
|
+
sleep_forever,
|
|
35
|
+
)
|
|
36
|
+
from .._core._tests.tutil import skip_if_fbsd_pipes_broken, slow
|
|
37
|
+
from ..lowlevel import open_process
|
|
38
|
+
from ..testing import MockClock, assert_no_checkpoints, wait_all_tasks_blocked
|
|
39
|
+
|
|
40
|
+
if TYPE_CHECKING:
|
|
41
|
+
from types import FrameType
|
|
42
|
+
|
|
43
|
+
from .._abc import ReceiveStream
|
|
44
|
+
|
|
45
|
+
if sys.platform == "win32":
|
|
46
|
+
SignalType: TypeAlias = None
|
|
47
|
+
else:
|
|
48
|
+
SignalType: TypeAlias = Signals
|
|
49
|
+
|
|
50
|
+
SIGKILL: SignalType
|
|
51
|
+
SIGTERM: SignalType
|
|
52
|
+
SIGUSR1: SignalType
|
|
53
|
+
|
|
54
|
+
posix = os.name == "posix"
|
|
55
|
+
if (not TYPE_CHECKING and posix) or sys.platform != "win32":
|
|
56
|
+
from signal import SIGKILL, SIGTERM, SIGUSR1
|
|
57
|
+
else:
|
|
58
|
+
SIGKILL, SIGTERM, SIGUSR1 = None, None, None
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# Since Windows has very few command-line utilities generally available,
|
|
62
|
+
# all of our subprocesses are Python processes running short bits of
|
|
63
|
+
# (mostly) cross-platform code.
|
|
64
|
+
def python(code: str) -> list[str]:
|
|
65
|
+
return [sys.executable, "-u", "-c", "import sys; " + code]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
EXIT_TRUE = python("sys.exit(0)")
|
|
69
|
+
EXIT_FALSE = python("sys.exit(1)")
|
|
70
|
+
CAT = python("sys.stdout.buffer.write(sys.stdin.buffer.read())")
|
|
71
|
+
|
|
72
|
+
if posix:
|
|
73
|
+
|
|
74
|
+
def SLEEP(seconds: int) -> list[str]:
|
|
75
|
+
return ["sleep", str(seconds)]
|
|
76
|
+
|
|
77
|
+
else:
|
|
78
|
+
|
|
79
|
+
def SLEEP(seconds: int) -> list[str]:
|
|
80
|
+
return python(f"import time; time.sleep({seconds})")
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@asynccontextmanager
|
|
84
|
+
async def open_process_then_kill( # type: ignore[misc, explicit-any]
|
|
85
|
+
*args: Any,
|
|
86
|
+
**kwargs: Any,
|
|
87
|
+
) -> AsyncIterator[Process]:
|
|
88
|
+
proc = await open_process(*args, **kwargs)
|
|
89
|
+
try:
|
|
90
|
+
yield proc
|
|
91
|
+
finally:
|
|
92
|
+
proc.kill()
|
|
93
|
+
await proc.wait()
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@asynccontextmanager
|
|
97
|
+
async def run_process_in_nursery( # type: ignore[misc, explicit-any]
|
|
98
|
+
*args: Any,
|
|
99
|
+
**kwargs: Any,
|
|
100
|
+
) -> AsyncIterator[Process]:
|
|
101
|
+
async with _core.open_nursery() as nursery:
|
|
102
|
+
kwargs.setdefault("check", False)
|
|
103
|
+
value = await nursery.start(partial(run_process, *args, **kwargs))
|
|
104
|
+
assert isinstance(value, Process)
|
|
105
|
+
proc: Process = value
|
|
106
|
+
yield proc
|
|
107
|
+
nursery.cancel_scope.cancel()
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
background_process_param = pytest.mark.parametrize(
|
|
111
|
+
"background_process",
|
|
112
|
+
[open_process_then_kill, run_process_in_nursery],
|
|
113
|
+
ids=["open_process", "run_process in nursery"],
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
BackgroundProcessType: TypeAlias = Callable[ # type: ignore[explicit-any]
|
|
117
|
+
...,
|
|
118
|
+
AbstractAsyncContextManager[Process],
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@background_process_param
|
|
123
|
+
async def test_basic(background_process: BackgroundProcessType) -> None:
|
|
124
|
+
async with background_process(EXIT_TRUE) as proc:
|
|
125
|
+
await proc.wait()
|
|
126
|
+
assert isinstance(proc, Process)
|
|
127
|
+
assert proc._pidfd is None
|
|
128
|
+
assert proc.returncode == 0
|
|
129
|
+
assert repr(proc) == f"<trio.Process {EXIT_TRUE}: exited with status 0>"
|
|
130
|
+
|
|
131
|
+
async with background_process(EXIT_FALSE) as proc:
|
|
132
|
+
await proc.wait()
|
|
133
|
+
assert proc.returncode == 1
|
|
134
|
+
assert repr(proc) == "<trio.Process {!r}: {}>".format(
|
|
135
|
+
EXIT_FALSE,
|
|
136
|
+
"exited with status 1",
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@background_process_param
|
|
141
|
+
async def test_basic_no_pidfd(background_process: BackgroundProcessType) -> None:
|
|
142
|
+
with mock.patch("trio._subprocess.can_try_pidfd_open", new=False):
|
|
143
|
+
async with background_process(EXIT_TRUE) as proc:
|
|
144
|
+
assert proc._pidfd is None
|
|
145
|
+
await proc.wait()
|
|
146
|
+
assert isinstance(proc, Process)
|
|
147
|
+
assert proc._pidfd is None
|
|
148
|
+
assert proc.returncode == 0
|
|
149
|
+
assert repr(proc) == f"<trio.Process {EXIT_TRUE}: exited with status 0>"
|
|
150
|
+
|
|
151
|
+
async with background_process(EXIT_FALSE) as proc:
|
|
152
|
+
await proc.wait()
|
|
153
|
+
assert proc.returncode == 1
|
|
154
|
+
assert repr(proc) == "<trio.Process {!r}: {}>".format(
|
|
155
|
+
EXIT_FALSE,
|
|
156
|
+
"exited with status 1",
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@background_process_param
|
|
161
|
+
async def test_auto_update_returncode(
|
|
162
|
+
background_process: BackgroundProcessType,
|
|
163
|
+
) -> None:
|
|
164
|
+
async with background_process(SLEEP(9999)) as p:
|
|
165
|
+
assert p.returncode is None
|
|
166
|
+
assert "running" in repr(p)
|
|
167
|
+
p.kill()
|
|
168
|
+
p._proc.wait()
|
|
169
|
+
assert p.returncode is not None
|
|
170
|
+
assert "exited" in repr(p)
|
|
171
|
+
assert p._pidfd is None
|
|
172
|
+
assert p.returncode is not None
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
@background_process_param
|
|
176
|
+
async def test_multi_wait(background_process: BackgroundProcessType) -> None:
|
|
177
|
+
async with background_process(SLEEP(10)) as proc:
|
|
178
|
+
# Check that wait (including multi-wait) tolerates being cancelled
|
|
179
|
+
async with _core.open_nursery() as nursery:
|
|
180
|
+
nursery.start_soon(proc.wait)
|
|
181
|
+
nursery.start_soon(proc.wait)
|
|
182
|
+
nursery.start_soon(proc.wait)
|
|
183
|
+
await wait_all_tasks_blocked()
|
|
184
|
+
nursery.cancel_scope.cancel()
|
|
185
|
+
|
|
186
|
+
# Now try waiting for real
|
|
187
|
+
async with _core.open_nursery() as nursery:
|
|
188
|
+
nursery.start_soon(proc.wait)
|
|
189
|
+
nursery.start_soon(proc.wait)
|
|
190
|
+
nursery.start_soon(proc.wait)
|
|
191
|
+
await wait_all_tasks_blocked()
|
|
192
|
+
proc.kill()
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
@background_process_param
|
|
196
|
+
async def test_multi_wait_no_pidfd(background_process: BackgroundProcessType) -> None:
|
|
197
|
+
with mock.patch("trio._subprocess.can_try_pidfd_open", new=False):
|
|
198
|
+
async with background_process(SLEEP(10)) as proc:
|
|
199
|
+
# Check that wait (including multi-wait) tolerates being cancelled
|
|
200
|
+
async with _core.open_nursery() as nursery:
|
|
201
|
+
nursery.start_soon(proc.wait)
|
|
202
|
+
nursery.start_soon(proc.wait)
|
|
203
|
+
nursery.start_soon(proc.wait)
|
|
204
|
+
await wait_all_tasks_blocked()
|
|
205
|
+
nursery.cancel_scope.cancel()
|
|
206
|
+
|
|
207
|
+
# Now try waiting for real
|
|
208
|
+
async with _core.open_nursery() as nursery:
|
|
209
|
+
nursery.start_soon(proc.wait)
|
|
210
|
+
nursery.start_soon(proc.wait)
|
|
211
|
+
nursery.start_soon(proc.wait)
|
|
212
|
+
await wait_all_tasks_blocked()
|
|
213
|
+
proc.kill()
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
COPY_STDIN_TO_STDOUT_AND_BACKWARD_TO_STDERR = python(
|
|
217
|
+
"data = sys.stdin.buffer.read(); "
|
|
218
|
+
"sys.stdout.buffer.write(data); "
|
|
219
|
+
"sys.stderr.buffer.write(data[::-1])",
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
@background_process_param
|
|
224
|
+
async def test_pipes(background_process: BackgroundProcessType) -> None:
|
|
225
|
+
async with background_process(
|
|
226
|
+
COPY_STDIN_TO_STDOUT_AND_BACKWARD_TO_STDERR,
|
|
227
|
+
stdin=subprocess.PIPE,
|
|
228
|
+
stdout=subprocess.PIPE,
|
|
229
|
+
stderr=subprocess.PIPE,
|
|
230
|
+
) as proc:
|
|
231
|
+
msg = b"the quick brown fox jumps over the lazy dog"
|
|
232
|
+
|
|
233
|
+
async def feed_input() -> None:
|
|
234
|
+
assert proc.stdin is not None
|
|
235
|
+
await proc.stdin.send_all(msg)
|
|
236
|
+
await proc.stdin.aclose()
|
|
237
|
+
|
|
238
|
+
async def check_output(stream: ReceiveStream, expected: bytes) -> None:
|
|
239
|
+
seen = bytearray()
|
|
240
|
+
async for chunk in stream:
|
|
241
|
+
seen += chunk
|
|
242
|
+
assert seen == expected
|
|
243
|
+
|
|
244
|
+
assert proc.stdout is not None
|
|
245
|
+
assert proc.stderr is not None
|
|
246
|
+
|
|
247
|
+
async with _core.open_nursery() as nursery:
|
|
248
|
+
# fail eventually if something is broken
|
|
249
|
+
nursery.cancel_scope.deadline = _core.current_time() + 30.0
|
|
250
|
+
nursery.start_soon(feed_input)
|
|
251
|
+
nursery.start_soon(check_output, proc.stdout, msg)
|
|
252
|
+
nursery.start_soon(check_output, proc.stderr, msg[::-1])
|
|
253
|
+
|
|
254
|
+
assert not nursery.cancel_scope.cancelled_caught
|
|
255
|
+
assert await proc.wait() == 0
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
@background_process_param
|
|
259
|
+
async def test_interactive(background_process: BackgroundProcessType) -> None:
|
|
260
|
+
# Test some back-and-forth with a subprocess. This one works like so:
|
|
261
|
+
# in: 32\n
|
|
262
|
+
# out: 0000...0000\n (32 zeroes)
|
|
263
|
+
# err: 1111...1111\n (64 ones)
|
|
264
|
+
# in: 10\n
|
|
265
|
+
# out: 2222222222\n (10 twos)
|
|
266
|
+
# err: 3333....3333\n (20 threes)
|
|
267
|
+
# in: EOF
|
|
268
|
+
# out: EOF
|
|
269
|
+
# err: EOF
|
|
270
|
+
|
|
271
|
+
async with background_process(
|
|
272
|
+
python(
|
|
273
|
+
"idx = 0\n"
|
|
274
|
+
"while True:\n"
|
|
275
|
+
" line = sys.stdin.readline()\n"
|
|
276
|
+
" if line == '': break\n"
|
|
277
|
+
" request = int(line.strip())\n"
|
|
278
|
+
" print(str(idx * 2) * request)\n"
|
|
279
|
+
" print(str(idx * 2 + 1) * request * 2, file=sys.stderr)\n"
|
|
280
|
+
" idx += 1\n",
|
|
281
|
+
),
|
|
282
|
+
stdin=subprocess.PIPE,
|
|
283
|
+
stdout=subprocess.PIPE,
|
|
284
|
+
stderr=subprocess.PIPE,
|
|
285
|
+
) as proc:
|
|
286
|
+
newline = b"\n" if posix else b"\r\n"
|
|
287
|
+
|
|
288
|
+
async def expect(idx: int, request: int) -> None:
|
|
289
|
+
async with _core.open_nursery() as nursery:
|
|
290
|
+
|
|
291
|
+
async def drain_one(
|
|
292
|
+
stream: ReceiveStream,
|
|
293
|
+
count: int,
|
|
294
|
+
digit: int,
|
|
295
|
+
) -> None:
|
|
296
|
+
while count > 0:
|
|
297
|
+
result = await stream.receive_some(count)
|
|
298
|
+
assert result == (f"{digit}".encode() * len(result))
|
|
299
|
+
count -= len(result)
|
|
300
|
+
assert count == 0
|
|
301
|
+
assert await stream.receive_some(len(newline)) == newline
|
|
302
|
+
|
|
303
|
+
assert proc.stdout is not None
|
|
304
|
+
assert proc.stderr is not None
|
|
305
|
+
nursery.start_soon(drain_one, proc.stdout, request, idx * 2)
|
|
306
|
+
nursery.start_soon(drain_one, proc.stderr, request * 2, idx * 2 + 1)
|
|
307
|
+
|
|
308
|
+
assert proc.stdin is not None
|
|
309
|
+
assert proc.stdout is not None
|
|
310
|
+
assert proc.stderr is not None
|
|
311
|
+
with fail_after(5):
|
|
312
|
+
await proc.stdin.send_all(b"12")
|
|
313
|
+
await sleep(0.1)
|
|
314
|
+
await proc.stdin.send_all(b"345" + newline)
|
|
315
|
+
await expect(0, 12345)
|
|
316
|
+
await proc.stdin.send_all(b"100" + newline + b"200" + newline)
|
|
317
|
+
await expect(1, 100)
|
|
318
|
+
await expect(2, 200)
|
|
319
|
+
await proc.stdin.send_all(b"0" + newline)
|
|
320
|
+
await expect(3, 0)
|
|
321
|
+
await proc.stdin.send_all(b"999999")
|
|
322
|
+
with move_on_after(0.1) as scope:
|
|
323
|
+
await expect(4, 0)
|
|
324
|
+
assert scope.cancelled_caught
|
|
325
|
+
await proc.stdin.send_all(newline)
|
|
326
|
+
await expect(4, 999999)
|
|
327
|
+
await proc.stdin.aclose()
|
|
328
|
+
assert await proc.stdout.receive_some(1) == b""
|
|
329
|
+
assert await proc.stderr.receive_some(1) == b""
|
|
330
|
+
await proc.wait()
|
|
331
|
+
|
|
332
|
+
assert proc.returncode == 0
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
async def test_run() -> None:
|
|
336
|
+
data = bytes(random.randint(0, 255) for _ in range(2**18))
|
|
337
|
+
|
|
338
|
+
result = await run_process(
|
|
339
|
+
CAT,
|
|
340
|
+
stdin=data,
|
|
341
|
+
capture_stdout=True,
|
|
342
|
+
capture_stderr=True,
|
|
343
|
+
)
|
|
344
|
+
assert result.args == CAT
|
|
345
|
+
assert result.returncode == 0
|
|
346
|
+
assert result.stdout == data
|
|
347
|
+
assert result.stderr == b""
|
|
348
|
+
|
|
349
|
+
result = await run_process(CAT, capture_stdout=True)
|
|
350
|
+
assert result.args == CAT
|
|
351
|
+
assert result.returncode == 0
|
|
352
|
+
assert result.stdout == b""
|
|
353
|
+
assert result.stderr is None
|
|
354
|
+
|
|
355
|
+
result = await run_process(
|
|
356
|
+
COPY_STDIN_TO_STDOUT_AND_BACKWARD_TO_STDERR,
|
|
357
|
+
stdin=data,
|
|
358
|
+
capture_stdout=True,
|
|
359
|
+
capture_stderr=True,
|
|
360
|
+
)
|
|
361
|
+
assert result.args == COPY_STDIN_TO_STDOUT_AND_BACKWARD_TO_STDERR
|
|
362
|
+
assert result.returncode == 0
|
|
363
|
+
assert result.stdout == data
|
|
364
|
+
assert result.stderr == data[::-1]
|
|
365
|
+
|
|
366
|
+
# invalid combinations
|
|
367
|
+
with pytest.raises(UnicodeError):
|
|
368
|
+
await run_process(CAT, stdin="oh no, it's text")
|
|
369
|
+
|
|
370
|
+
pipe_stdout_error = r"^stdout=subprocess\.PIPE is only valid with nursery\.start, since that's the only way to access the pipe(; use nursery\.start or pass the data you want to write directly)*$"
|
|
371
|
+
with pytest.raises(ValueError, match=pipe_stdout_error):
|
|
372
|
+
await run_process(CAT, stdin=subprocess.PIPE)
|
|
373
|
+
with pytest.raises(ValueError, match=pipe_stdout_error):
|
|
374
|
+
await run_process(CAT, stdout=subprocess.PIPE)
|
|
375
|
+
with pytest.raises(
|
|
376
|
+
ValueError,
|
|
377
|
+
match=pipe_stdout_error.replace("stdout", "stderr", 1),
|
|
378
|
+
):
|
|
379
|
+
await run_process(CAT, stderr=subprocess.PIPE)
|
|
380
|
+
with pytest.raises(
|
|
381
|
+
ValueError,
|
|
382
|
+
match=r"^can't specify both stdout and capture_stdout$",
|
|
383
|
+
):
|
|
384
|
+
await run_process(CAT, capture_stdout=True, stdout=subprocess.DEVNULL)
|
|
385
|
+
with pytest.raises(
|
|
386
|
+
ValueError,
|
|
387
|
+
match=r"^can't specify both stderr and capture_stderr$",
|
|
388
|
+
):
|
|
389
|
+
await run_process(CAT, capture_stderr=True, stderr=None)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
async def test_run_check() -> None:
|
|
393
|
+
cmd = python("sys.stderr.buffer.write(b'test\\n'); sys.exit(1)")
|
|
394
|
+
with pytest.raises(subprocess.CalledProcessError) as excinfo:
|
|
395
|
+
await run_process(cmd, stdin=subprocess.DEVNULL, capture_stderr=True)
|
|
396
|
+
assert excinfo.value.cmd == cmd
|
|
397
|
+
assert excinfo.value.returncode == 1
|
|
398
|
+
assert excinfo.value.stderr == b"test\n"
|
|
399
|
+
assert excinfo.value.stdout is None
|
|
400
|
+
|
|
401
|
+
result = await run_process(
|
|
402
|
+
cmd,
|
|
403
|
+
capture_stdout=True,
|
|
404
|
+
capture_stderr=True,
|
|
405
|
+
check=False,
|
|
406
|
+
)
|
|
407
|
+
assert result.args == cmd
|
|
408
|
+
assert result.stdout == b""
|
|
409
|
+
assert result.stderr == b"test\n"
|
|
410
|
+
assert result.returncode == 1
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
@skip_if_fbsd_pipes_broken
|
|
414
|
+
async def test_run_with_broken_pipe() -> None:
|
|
415
|
+
result = await run_process(
|
|
416
|
+
[sys.executable, "-c", "import sys; sys.stdin.close()"],
|
|
417
|
+
stdin=b"x" * 131072,
|
|
418
|
+
)
|
|
419
|
+
assert result.returncode == 0
|
|
420
|
+
assert result.stdout is result.stderr is None
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
@background_process_param
|
|
424
|
+
async def test_stderr_stdout(background_process: BackgroundProcessType) -> None:
|
|
425
|
+
async with background_process(
|
|
426
|
+
COPY_STDIN_TO_STDOUT_AND_BACKWARD_TO_STDERR,
|
|
427
|
+
stdin=subprocess.PIPE,
|
|
428
|
+
stdout=subprocess.PIPE,
|
|
429
|
+
stderr=subprocess.STDOUT,
|
|
430
|
+
) as proc:
|
|
431
|
+
assert proc.stdio is not None
|
|
432
|
+
assert proc.stdout is not None
|
|
433
|
+
assert proc.stderr is None
|
|
434
|
+
await proc.stdio.send_all(b"1234")
|
|
435
|
+
await proc.stdio.send_eof()
|
|
436
|
+
|
|
437
|
+
output = []
|
|
438
|
+
while True:
|
|
439
|
+
chunk = await proc.stdio.receive_some(16)
|
|
440
|
+
if chunk == b"":
|
|
441
|
+
break
|
|
442
|
+
output.append(chunk)
|
|
443
|
+
assert b"".join(output) == b"12344321"
|
|
444
|
+
assert proc.returncode == 0
|
|
445
|
+
|
|
446
|
+
# equivalent test with run_process()
|
|
447
|
+
result = await run_process(
|
|
448
|
+
COPY_STDIN_TO_STDOUT_AND_BACKWARD_TO_STDERR,
|
|
449
|
+
stdin=b"1234",
|
|
450
|
+
capture_stdout=True,
|
|
451
|
+
stderr=subprocess.STDOUT,
|
|
452
|
+
)
|
|
453
|
+
assert result.returncode == 0
|
|
454
|
+
assert result.stdout == b"12344321"
|
|
455
|
+
assert result.stderr is None
|
|
456
|
+
|
|
457
|
+
# this one hits the branch where stderr=STDOUT but stdout
|
|
458
|
+
# is not redirected
|
|
459
|
+
async with background_process(
|
|
460
|
+
CAT,
|
|
461
|
+
stdin=subprocess.PIPE,
|
|
462
|
+
stderr=subprocess.STDOUT,
|
|
463
|
+
) as proc:
|
|
464
|
+
assert proc.stdout is None
|
|
465
|
+
assert proc.stderr is None
|
|
466
|
+
await proc.stdin.aclose()
|
|
467
|
+
await proc.wait()
|
|
468
|
+
assert proc.returncode == 0
|
|
469
|
+
|
|
470
|
+
if posix:
|
|
471
|
+
try:
|
|
472
|
+
r, w = os.pipe()
|
|
473
|
+
|
|
474
|
+
async with background_process(
|
|
475
|
+
COPY_STDIN_TO_STDOUT_AND_BACKWARD_TO_STDERR,
|
|
476
|
+
stdin=subprocess.PIPE,
|
|
477
|
+
stdout=w,
|
|
478
|
+
stderr=subprocess.STDOUT,
|
|
479
|
+
) as proc:
|
|
480
|
+
os.close(w)
|
|
481
|
+
assert proc.stdio is None
|
|
482
|
+
assert proc.stdout is None
|
|
483
|
+
assert proc.stderr is None
|
|
484
|
+
await proc.stdin.send_all(b"1234")
|
|
485
|
+
await proc.stdin.aclose()
|
|
486
|
+
assert await proc.wait() == 0
|
|
487
|
+
assert os.read(r, 4096) == b"12344321"
|
|
488
|
+
assert os.read(r, 4096) == b""
|
|
489
|
+
finally:
|
|
490
|
+
os.close(r)
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
async def test_errors() -> None:
|
|
494
|
+
with pytest.raises(TypeError) as excinfo:
|
|
495
|
+
# call-overload on unix, call-arg on windows
|
|
496
|
+
await open_process(["ls"], encoding="utf-8") # type: ignore
|
|
497
|
+
assert "unbuffered byte streams" in str(excinfo.value)
|
|
498
|
+
assert "the 'encoding' option is not supported" in str(excinfo.value)
|
|
499
|
+
|
|
500
|
+
if posix:
|
|
501
|
+
with pytest.raises(TypeError) as excinfo:
|
|
502
|
+
await open_process(["ls"], shell=True)
|
|
503
|
+
with pytest.raises(TypeError) as excinfo:
|
|
504
|
+
await open_process("ls", shell=False)
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
@background_process_param
|
|
508
|
+
async def test_signals(background_process: BackgroundProcessType) -> None:
|
|
509
|
+
async def test_one_signal(
|
|
510
|
+
send_it: Callable[[Process], None],
|
|
511
|
+
signum: signal.Signals | None,
|
|
512
|
+
) -> None:
|
|
513
|
+
with move_on_after(1.0) as scope:
|
|
514
|
+
async with background_process(SLEEP(3600)) as proc:
|
|
515
|
+
send_it(proc)
|
|
516
|
+
await proc.wait()
|
|
517
|
+
assert not scope.cancelled_caught
|
|
518
|
+
if posix:
|
|
519
|
+
assert signum is not None
|
|
520
|
+
assert proc.returncode == -signum
|
|
521
|
+
else:
|
|
522
|
+
assert proc.returncode != 0
|
|
523
|
+
|
|
524
|
+
await test_one_signal(Process.kill, SIGKILL)
|
|
525
|
+
await test_one_signal(Process.terminate, SIGTERM)
|
|
526
|
+
# Test that we can send arbitrary signals.
|
|
527
|
+
#
|
|
528
|
+
# We used to use SIGINT here, but it turns out that the Python interpreter
|
|
529
|
+
# has race conditions that can cause it to explode in weird ways if it
|
|
530
|
+
# tries to handle SIGINT during startup. SIGUSR1's default disposition is
|
|
531
|
+
# to terminate the target process, and Python doesn't try to do anything
|
|
532
|
+
# clever to handle it.
|
|
533
|
+
if (not TYPE_CHECKING and posix) or sys.platform != "win32":
|
|
534
|
+
await test_one_signal(lambda proc: proc.send_signal(SIGUSR1), SIGUSR1)
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
@pytest.mark.skipif(not posix, reason="POSIX specific")
|
|
538
|
+
@background_process_param
|
|
539
|
+
async def test_wait_reapable_fails(background_process: BackgroundProcessType) -> None:
|
|
540
|
+
if TYPE_CHECKING and sys.platform == "win32":
|
|
541
|
+
return
|
|
542
|
+
old_sigchld = signal.signal(signal.SIGCHLD, signal.SIG_IGN)
|
|
543
|
+
try:
|
|
544
|
+
# With SIGCHLD disabled, the wait() syscall will wait for the
|
|
545
|
+
# process to exit but then fail with ECHILD. Make sure we
|
|
546
|
+
# support this case as the stdlib subprocess module does.
|
|
547
|
+
async with background_process(SLEEP(3600)) as proc:
|
|
548
|
+
async with _core.open_nursery() as nursery:
|
|
549
|
+
nursery.start_soon(proc.wait)
|
|
550
|
+
await wait_all_tasks_blocked()
|
|
551
|
+
proc.kill()
|
|
552
|
+
nursery.cancel_scope.deadline = _core.current_time() + 1.0
|
|
553
|
+
assert not nursery.cancel_scope.cancelled_caught
|
|
554
|
+
assert proc.returncode == 0 # exit status unknowable, so...
|
|
555
|
+
finally:
|
|
556
|
+
signal.signal(signal.SIGCHLD, old_sigchld)
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
@pytest.mark.skipif(not posix, reason="POSIX specific")
|
|
560
|
+
@background_process_param
|
|
561
|
+
async def test_wait_reapable_fails_no_pidfd(
|
|
562
|
+
background_process: BackgroundProcessType,
|
|
563
|
+
) -> None:
|
|
564
|
+
if TYPE_CHECKING and sys.platform == "win32":
|
|
565
|
+
return
|
|
566
|
+
with mock.patch("trio._subprocess.can_try_pidfd_open", new=False):
|
|
567
|
+
old_sigchld = signal.signal(signal.SIGCHLD, signal.SIG_IGN)
|
|
568
|
+
try:
|
|
569
|
+
# With SIGCHLD disabled, the wait() syscall will wait for the
|
|
570
|
+
# process to exit but then fail with ECHILD. Make sure we
|
|
571
|
+
# support this case as the stdlib subprocess module does.
|
|
572
|
+
async with background_process(SLEEP(3600)) as proc:
|
|
573
|
+
async with _core.open_nursery() as nursery:
|
|
574
|
+
nursery.start_soon(proc.wait)
|
|
575
|
+
await wait_all_tasks_blocked()
|
|
576
|
+
proc.kill()
|
|
577
|
+
nursery.cancel_scope.deadline = _core.current_time() + 1.0
|
|
578
|
+
assert not nursery.cancel_scope.cancelled_caught
|
|
579
|
+
assert proc.returncode == 0 # exit status unknowable, so...
|
|
580
|
+
finally:
|
|
581
|
+
signal.signal(signal.SIGCHLD, old_sigchld)
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
@slow
|
|
585
|
+
def test_waitid_eintr() -> None:
|
|
586
|
+
# This only matters on PyPy (where we're coding EINTR handling
|
|
587
|
+
# ourselves) but the test works on all waitid platforms.
|
|
588
|
+
from .._subprocess_platform import wait_child_exiting
|
|
589
|
+
|
|
590
|
+
if TYPE_CHECKING and (sys.platform == "win32" or sys.platform == "darwin"):
|
|
591
|
+
return
|
|
592
|
+
|
|
593
|
+
if not wait_child_exiting.__module__.endswith("waitid"):
|
|
594
|
+
pytest.skip("waitid only")
|
|
595
|
+
|
|
596
|
+
# despite the TYPE_CHECKING early return silencing warnings about signal.SIGALRM etc
|
|
597
|
+
# this import is still checked on win32&darwin and raises [attr-defined].
|
|
598
|
+
# Linux doesn't raise [attr-defined] though, so we need [unused-ignore]
|
|
599
|
+
from .._subprocess_platform.waitid import ( # type: ignore[attr-defined, unused-ignore]
|
|
600
|
+
sync_wait_reapable,
|
|
601
|
+
)
|
|
602
|
+
|
|
603
|
+
got_alarm = False
|
|
604
|
+
sleeper = subprocess.Popen(["sleep", "3600"])
|
|
605
|
+
|
|
606
|
+
def on_alarm(sig: int, frame: FrameType | None) -> None:
|
|
607
|
+
nonlocal got_alarm
|
|
608
|
+
got_alarm = True
|
|
609
|
+
sleeper.kill()
|
|
610
|
+
|
|
611
|
+
old_sigalrm = signal.signal(signal.SIGALRM, on_alarm)
|
|
612
|
+
try:
|
|
613
|
+
signal.alarm(1)
|
|
614
|
+
sync_wait_reapable(sleeper.pid)
|
|
615
|
+
assert sleeper.wait(timeout=1) == -9
|
|
616
|
+
finally:
|
|
617
|
+
if sleeper.returncode is None: # pragma: no cover
|
|
618
|
+
# We only get here if something fails in the above;
|
|
619
|
+
# if the test passes, wait() will reap the process
|
|
620
|
+
sleeper.kill()
|
|
621
|
+
sleeper.wait()
|
|
622
|
+
signal.signal(signal.SIGALRM, old_sigalrm)
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
async def test_custom_deliver_cancel() -> None:
|
|
626
|
+
custom_deliver_cancel_called = False
|
|
627
|
+
|
|
628
|
+
async def custom_deliver_cancel(proc: Process) -> None:
|
|
629
|
+
nonlocal custom_deliver_cancel_called
|
|
630
|
+
custom_deliver_cancel_called = True
|
|
631
|
+
proc.terminate()
|
|
632
|
+
# Make sure this does get cancelled when the process exits, and that
|
|
633
|
+
# the process really exited.
|
|
634
|
+
try:
|
|
635
|
+
await sleep_forever()
|
|
636
|
+
finally:
|
|
637
|
+
assert proc.returncode is not None
|
|
638
|
+
|
|
639
|
+
async with _core.open_nursery() as nursery:
|
|
640
|
+
nursery.start_soon(
|
|
641
|
+
partial(run_process, SLEEP(9999), deliver_cancel=custom_deliver_cancel),
|
|
642
|
+
)
|
|
643
|
+
await wait_all_tasks_blocked()
|
|
644
|
+
nursery.cancel_scope.cancel()
|
|
645
|
+
|
|
646
|
+
assert custom_deliver_cancel_called
|
|
647
|
+
|
|
648
|
+
|
|
649
|
+
def test_bad_deliver_cancel() -> None:
|
|
650
|
+
async def custom_deliver_cancel(proc: Process) -> None:
|
|
651
|
+
proc.terminate()
|
|
652
|
+
raise ValueError("foo")
|
|
653
|
+
|
|
654
|
+
async def do_stuff() -> None:
|
|
655
|
+
async with _core.open_nursery() as nursery:
|
|
656
|
+
nursery.start_soon(
|
|
657
|
+
partial(run_process, SLEEP(9999), deliver_cancel=custom_deliver_cancel),
|
|
658
|
+
)
|
|
659
|
+
await wait_all_tasks_blocked()
|
|
660
|
+
nursery.cancel_scope.cancel()
|
|
661
|
+
|
|
662
|
+
# double wrap from our nursery + the internal nursery
|
|
663
|
+
with pytest.RaisesGroup(
|
|
664
|
+
pytest.RaisesGroup(pytest.RaisesExc(ValueError, match="^foo$"))
|
|
665
|
+
):
|
|
666
|
+
_core.run(do_stuff, strict_exception_groups=True)
|
|
667
|
+
|
|
668
|
+
|
|
669
|
+
async def test_warn_on_failed_cancel_terminate(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
670
|
+
original_terminate = Process.terminate
|
|
671
|
+
|
|
672
|
+
def broken_terminate(self: Process) -> NoReturn:
|
|
673
|
+
original_terminate(self)
|
|
674
|
+
raise OSError("whoops")
|
|
675
|
+
|
|
676
|
+
monkeypatch.setattr(Process, "terminate", broken_terminate)
|
|
677
|
+
|
|
678
|
+
with pytest.warns(RuntimeWarning, match=".*whoops.*"): # noqa: PT031
|
|
679
|
+
async with _core.open_nursery() as nursery:
|
|
680
|
+
nursery.start_soon(run_process, SLEEP(9999))
|
|
681
|
+
await wait_all_tasks_blocked()
|
|
682
|
+
nursery.cancel_scope.cancel()
|
|
683
|
+
|
|
684
|
+
|
|
685
|
+
@pytest.mark.skipif(not posix, reason="posix only")
|
|
686
|
+
async def test_warn_on_cancel_SIGKILL_escalation(
|
|
687
|
+
autojump_clock: MockClock,
|
|
688
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
689
|
+
) -> None:
|
|
690
|
+
monkeypatch.setattr(Process, "terminate", lambda *args: None)
|
|
691
|
+
|
|
692
|
+
with pytest.warns(RuntimeWarning, match=".*ignored SIGTERM.*"): # noqa: PT031
|
|
693
|
+
async with _core.open_nursery() as nursery:
|
|
694
|
+
nursery.start_soon(run_process, SLEEP(9999))
|
|
695
|
+
await wait_all_tasks_blocked()
|
|
696
|
+
nursery.cancel_scope.cancel()
|
|
697
|
+
|
|
698
|
+
|
|
699
|
+
# the background_process_param exercises a lot of run_process cases, but it uses
|
|
700
|
+
# check=False, so lets have a test that uses check=True as well
|
|
701
|
+
async def test_run_process_background_fail() -> None:
|
|
702
|
+
with pytest.RaisesGroup(subprocess.CalledProcessError):
|
|
703
|
+
async with _core.open_nursery() as nursery:
|
|
704
|
+
value = await nursery.start(run_process, EXIT_FALSE)
|
|
705
|
+
assert isinstance(value, Process)
|
|
706
|
+
proc: Process = value
|
|
707
|
+
assert proc.returncode == 1
|
|
708
|
+
|
|
709
|
+
|
|
710
|
+
@pytest.mark.skipif(
|
|
711
|
+
not SyncPath("/dev/fd").exists(),
|
|
712
|
+
reason="requires a way to iterate through open files",
|
|
713
|
+
)
|
|
714
|
+
async def test_for_leaking_fds() -> None:
|
|
715
|
+
gc.collect() # address possible flakiness on PyPy
|
|
716
|
+
|
|
717
|
+
starting_fds = set(SyncPath("/dev/fd").iterdir()) # noqa: ASYNC240
|
|
718
|
+
await run_process(EXIT_TRUE)
|
|
719
|
+
assert set(SyncPath("/dev/fd").iterdir()) == starting_fds # noqa: ASYNC240
|
|
720
|
+
|
|
721
|
+
with pytest.raises(subprocess.CalledProcessError):
|
|
722
|
+
await run_process(EXIT_FALSE)
|
|
723
|
+
assert set(SyncPath("/dev/fd").iterdir()) == starting_fds # noqa: ASYNC240
|
|
724
|
+
|
|
725
|
+
with pytest.raises(PermissionError):
|
|
726
|
+
await run_process(["/dev/fd/0"])
|
|
727
|
+
assert set(SyncPath("/dev/fd").iterdir()) == starting_fds # noqa: ASYNC240
|
|
728
|
+
|
|
729
|
+
|
|
730
|
+
async def test_run_process_internal_error(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
731
|
+
# There's probably less extreme ways of triggering errors inside the nursery
|
|
732
|
+
# in run_process.
|
|
733
|
+
async def very_broken_open(*args: object, **kwargs: object) -> str:
|
|
734
|
+
return "oops"
|
|
735
|
+
|
|
736
|
+
monkeypatch.setattr(trio._subprocess, "_open_process", very_broken_open)
|
|
737
|
+
with pytest.RaisesGroup(AttributeError, AttributeError):
|
|
738
|
+
await run_process(EXIT_TRUE, capture_stdout=True)
|
|
739
|
+
|
|
740
|
+
|
|
741
|
+
# regression test for #2209
|
|
742
|
+
async def test_subprocess_pidfd_unnotified() -> None:
|
|
743
|
+
noticed_exit = None
|
|
744
|
+
|
|
745
|
+
async def wait_and_tell(proc: Process) -> None:
|
|
746
|
+
nonlocal noticed_exit
|
|
747
|
+
noticed_exit = Event()
|
|
748
|
+
await proc.wait()
|
|
749
|
+
noticed_exit.set()
|
|
750
|
+
|
|
751
|
+
proc = await open_process(SLEEP(9999))
|
|
752
|
+
async with _core.open_nursery() as nursery:
|
|
753
|
+
nursery.start_soon(wait_and_tell, proc)
|
|
754
|
+
await wait_all_tasks_blocked()
|
|
755
|
+
assert isinstance(noticed_exit, Event)
|
|
756
|
+
proc.terminate()
|
|
757
|
+
# without giving trio a chance to do so,
|
|
758
|
+
with assert_no_checkpoints():
|
|
759
|
+
# wait until the process has actually exited;
|
|
760
|
+
proc._proc.wait()
|
|
761
|
+
# force a call to poll (that closes the pidfd on linux)
|
|
762
|
+
proc.poll()
|
|
763
|
+
with move_on_after(5):
|
|
764
|
+
# Some platforms use threads to wait for exit, so it might take a bit
|
|
765
|
+
# for everything to notice
|
|
766
|
+
await noticed_exit.wait()
|
|
767
|
+
assert noticed_exit.is_set(), "child task wasn't woken after poll, DEADLOCK"
|