@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,227 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import threading
|
|
5
|
+
import time
|
|
6
|
+
from contextlib import contextmanager
|
|
7
|
+
from queue import Queue
|
|
8
|
+
from typing import TYPE_CHECKING, NoReturn
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
from .. import _thread_cache
|
|
13
|
+
from .._thread_cache import ThreadCache, start_thread_soon
|
|
14
|
+
from .tutil import gc_collect_harder, slow
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from collections.abc import Iterator
|
|
18
|
+
|
|
19
|
+
from outcome import Outcome
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_thread_cache_basics() -> None:
|
|
23
|
+
q: Queue[Outcome[object]] = Queue()
|
|
24
|
+
|
|
25
|
+
def fn() -> NoReturn:
|
|
26
|
+
raise RuntimeError("hi")
|
|
27
|
+
|
|
28
|
+
def deliver(outcome: Outcome[object]) -> None:
|
|
29
|
+
q.put(outcome)
|
|
30
|
+
|
|
31
|
+
start_thread_soon(fn, deliver)
|
|
32
|
+
|
|
33
|
+
outcome = q.get()
|
|
34
|
+
with pytest.raises(RuntimeError, match="hi"):
|
|
35
|
+
outcome.unwrap()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_thread_cache_deref() -> None:
|
|
39
|
+
res = [False]
|
|
40
|
+
|
|
41
|
+
class del_me:
|
|
42
|
+
def __call__(self) -> int:
|
|
43
|
+
return 42
|
|
44
|
+
|
|
45
|
+
def __del__(self) -> None:
|
|
46
|
+
res[0] = True
|
|
47
|
+
|
|
48
|
+
q: Queue[Outcome[int]] = Queue()
|
|
49
|
+
|
|
50
|
+
def deliver(outcome: Outcome[int]) -> None:
|
|
51
|
+
q.put(outcome)
|
|
52
|
+
|
|
53
|
+
start_thread_soon(del_me(), deliver)
|
|
54
|
+
outcome = q.get()
|
|
55
|
+
assert outcome.unwrap() == 42
|
|
56
|
+
|
|
57
|
+
gc_collect_harder()
|
|
58
|
+
assert res[0]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@slow
|
|
62
|
+
def test_spawning_new_thread_from_deliver_reuses_starting_thread() -> None:
|
|
63
|
+
# We know that no-one else is using the thread cache, so if we keep
|
|
64
|
+
# submitting new jobs the instant the previous one is finished, we should
|
|
65
|
+
# keep getting the same thread over and over. This tests both that the
|
|
66
|
+
# thread cache is LIFO, and that threads can be assigned new work *before*
|
|
67
|
+
# deliver exits.
|
|
68
|
+
|
|
69
|
+
# Make sure there are a few threads running, so if we weren't LIFO then we
|
|
70
|
+
# could grab the wrong one.
|
|
71
|
+
q: Queue[Outcome[object]] = Queue()
|
|
72
|
+
COUNT = 5
|
|
73
|
+
for _ in range(COUNT):
|
|
74
|
+
start_thread_soon(lambda: time.sleep(1), lambda result: q.put(result))
|
|
75
|
+
for _ in range(COUNT):
|
|
76
|
+
q.get().unwrap()
|
|
77
|
+
|
|
78
|
+
seen_threads = set()
|
|
79
|
+
done = threading.Event()
|
|
80
|
+
|
|
81
|
+
def deliver(n: int, _: object) -> None:
|
|
82
|
+
print(n)
|
|
83
|
+
seen_threads.add(threading.current_thread())
|
|
84
|
+
if n == 0:
|
|
85
|
+
done.set()
|
|
86
|
+
else:
|
|
87
|
+
start_thread_soon(lambda: None, lambda _: deliver(n - 1, _))
|
|
88
|
+
|
|
89
|
+
start_thread_soon(lambda: None, lambda _: deliver(5, _))
|
|
90
|
+
|
|
91
|
+
done.wait()
|
|
92
|
+
|
|
93
|
+
assert len(seen_threads) == 1
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@slow
|
|
97
|
+
def test_idle_threads_exit(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
98
|
+
# Temporarily set the idle timeout to something tiny, to speed up the
|
|
99
|
+
# test. (But non-zero, so that the worker loop will at least yield the
|
|
100
|
+
# CPU.)
|
|
101
|
+
monkeypatch.setattr(_thread_cache, "IDLE_TIMEOUT", 0.0001)
|
|
102
|
+
|
|
103
|
+
q: Queue[threading.Thread] = Queue()
|
|
104
|
+
start_thread_soon(lambda: None, lambda _: q.put(threading.current_thread()))
|
|
105
|
+
seen_thread = q.get()
|
|
106
|
+
# Since the idle timeout is 0, after sleeping for 1 second, the thread
|
|
107
|
+
# should have exited
|
|
108
|
+
time.sleep(1)
|
|
109
|
+
assert not seen_thread.is_alive()
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@contextmanager
|
|
113
|
+
def _join_started_threads() -> Iterator[None]:
|
|
114
|
+
before = frozenset(threading.enumerate())
|
|
115
|
+
try:
|
|
116
|
+
yield
|
|
117
|
+
finally:
|
|
118
|
+
for thread in threading.enumerate():
|
|
119
|
+
if thread not in before:
|
|
120
|
+
thread.join(timeout=1.0)
|
|
121
|
+
assert not thread.is_alive()
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def test_race_between_idle_exit_and_job_assignment(
|
|
125
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
126
|
+
) -> None:
|
|
127
|
+
# This is a lock where the first few times you try to acquire it with a
|
|
128
|
+
# timeout, it waits until the lock is available and then pretends to time
|
|
129
|
+
# out. Using this in our thread cache implementation causes the following
|
|
130
|
+
# sequence:
|
|
131
|
+
#
|
|
132
|
+
# 1. start_thread_soon grabs the worker thread, assigns it a job, and
|
|
133
|
+
# releases its lock.
|
|
134
|
+
# 2. The worker thread wakes up (because the lock has been released), but
|
|
135
|
+
# the JankyLock lies to it and tells it that the lock timed out. So the
|
|
136
|
+
# worker thread tries to exit.
|
|
137
|
+
# 3. The worker thread checks for the race between exiting and being
|
|
138
|
+
# assigned a job, and discovers that it *is* in the process of being
|
|
139
|
+
# assigned a job, so it loops around and tries to acquire the lock
|
|
140
|
+
# again.
|
|
141
|
+
# 4. Eventually the JankyLock admits that the lock is available, and
|
|
142
|
+
# everything proceeds as normal.
|
|
143
|
+
|
|
144
|
+
class JankyLock:
|
|
145
|
+
def __init__(self) -> None:
|
|
146
|
+
self._lock = threading.Lock()
|
|
147
|
+
self._counter = 3
|
|
148
|
+
|
|
149
|
+
def acquire(self, timeout: int = -1) -> bool:
|
|
150
|
+
got_it = self._lock.acquire(timeout=timeout)
|
|
151
|
+
if timeout == -1:
|
|
152
|
+
return True
|
|
153
|
+
elif got_it:
|
|
154
|
+
if self._counter > 0:
|
|
155
|
+
self._counter -= 1
|
|
156
|
+
self._lock.release()
|
|
157
|
+
return False
|
|
158
|
+
return True
|
|
159
|
+
else:
|
|
160
|
+
return False
|
|
161
|
+
|
|
162
|
+
def release(self) -> None:
|
|
163
|
+
self._lock.release()
|
|
164
|
+
|
|
165
|
+
monkeypatch.setattr(_thread_cache, "Lock", JankyLock)
|
|
166
|
+
|
|
167
|
+
with _join_started_threads():
|
|
168
|
+
tc = ThreadCache()
|
|
169
|
+
done = threading.Event()
|
|
170
|
+
tc.start_thread_soon(lambda: None, lambda _: done.set())
|
|
171
|
+
done.wait()
|
|
172
|
+
# Let's kill the thread we started, so it doesn't hang around until the
|
|
173
|
+
# test suite finishes. Doesn't really do any harm, but it can be confusing
|
|
174
|
+
# to see it in debug output.
|
|
175
|
+
monkeypatch.setattr(_thread_cache, "IDLE_TIMEOUT", 0.0001)
|
|
176
|
+
tc.start_thread_soon(lambda: None, lambda _: None)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def test_raise_in_deliver(capfd: pytest.CaptureFixture[str]) -> None:
|
|
180
|
+
seen_threads = set()
|
|
181
|
+
|
|
182
|
+
def track_threads() -> None:
|
|
183
|
+
seen_threads.add(threading.current_thread())
|
|
184
|
+
|
|
185
|
+
def deliver(_: object) -> NoReturn:
|
|
186
|
+
done.set()
|
|
187
|
+
raise RuntimeError("don't do this")
|
|
188
|
+
|
|
189
|
+
done = threading.Event()
|
|
190
|
+
start_thread_soon(track_threads, deliver)
|
|
191
|
+
done.wait()
|
|
192
|
+
done = threading.Event()
|
|
193
|
+
start_thread_soon(track_threads, lambda _: done.set())
|
|
194
|
+
done.wait()
|
|
195
|
+
assert len(seen_threads) == 1
|
|
196
|
+
err = capfd.readouterr().err
|
|
197
|
+
assert "don't do this" in err
|
|
198
|
+
assert "delivering result" in err
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
@pytest.mark.skipif(not hasattr(os, "fork"), reason="os.fork isn't supported")
|
|
202
|
+
def test_clear_thread_cache_after_fork() -> None:
|
|
203
|
+
assert hasattr(os, "fork")
|
|
204
|
+
|
|
205
|
+
def foo() -> None:
|
|
206
|
+
pass
|
|
207
|
+
|
|
208
|
+
# ensure the thread cache exists
|
|
209
|
+
done = threading.Event()
|
|
210
|
+
start_thread_soon(foo, lambda _: done.set())
|
|
211
|
+
done.wait()
|
|
212
|
+
|
|
213
|
+
child_pid = os.fork()
|
|
214
|
+
|
|
215
|
+
# try using it
|
|
216
|
+
done = threading.Event()
|
|
217
|
+
start_thread_soon(foo, lambda _: done.set())
|
|
218
|
+
done.wait()
|
|
219
|
+
|
|
220
|
+
if child_pid != 0:
|
|
221
|
+
# if this test fails, this will hang, triggering a timeout.
|
|
222
|
+
os.waitpid(child_pid, 0)
|
|
223
|
+
else: # pragma: no cover # coverage is shut down by os._exit(0)
|
|
224
|
+
# we would *want* to allow coverage to take a snapshot here. check
|
|
225
|
+
# git blame for how to do that. however, that times out for some
|
|
226
|
+
# reason when having `tests/` in `source` for coverage.py.
|
|
227
|
+
os._exit(0)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from .tutil import check_sequence_matches
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_check_sequence_matches() -> None:
|
|
7
|
+
check_sequence_matches([1, 2, 3], [1, 2, 3])
|
|
8
|
+
with pytest.raises(AssertionError):
|
|
9
|
+
check_sequence_matches([1, 3, 2], [1, 2, 3])
|
|
10
|
+
check_sequence_matches([1, 2, 3, 4], [1, {2, 3}, 4])
|
|
11
|
+
check_sequence_matches([1, 3, 2, 4], [1, {2, 3}, 4])
|
|
12
|
+
with pytest.raises(AssertionError):
|
|
13
|
+
check_sequence_matches([1, 2, 4, 3], [1, {2, 3}, 4])
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import itertools
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from ... import _core
|
|
8
|
+
from ...testing import assert_checkpoints, wait_all_tasks_blocked
|
|
9
|
+
|
|
10
|
+
pytestmark = pytest.mark.filterwarnings(
|
|
11
|
+
"ignore:.*UnboundedQueue:trio.TrioDeprecationWarning",
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
async def test_UnboundedQueue_basic() -> None:
|
|
16
|
+
q: _core.UnboundedQueue[str | int | None] = _core.UnboundedQueue()
|
|
17
|
+
q.put_nowait("hi")
|
|
18
|
+
assert await q.get_batch() == ["hi"]
|
|
19
|
+
with pytest.raises(_core.WouldBlock):
|
|
20
|
+
q.get_batch_nowait()
|
|
21
|
+
q.put_nowait(1)
|
|
22
|
+
q.put_nowait(2)
|
|
23
|
+
q.put_nowait(3)
|
|
24
|
+
assert q.get_batch_nowait() == [1, 2, 3]
|
|
25
|
+
|
|
26
|
+
assert q.empty()
|
|
27
|
+
assert q.qsize() == 0
|
|
28
|
+
q.put_nowait(None)
|
|
29
|
+
assert not q.empty()
|
|
30
|
+
assert q.qsize() == 1
|
|
31
|
+
|
|
32
|
+
stats = q.statistics()
|
|
33
|
+
assert stats.qsize == 1
|
|
34
|
+
assert stats.tasks_waiting == 0
|
|
35
|
+
|
|
36
|
+
# smoke test
|
|
37
|
+
repr(q)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
async def test_UnboundedQueue_blocking() -> None:
|
|
41
|
+
record = []
|
|
42
|
+
q = _core.UnboundedQueue[int]()
|
|
43
|
+
|
|
44
|
+
async def get_batch_consumer() -> None:
|
|
45
|
+
while True:
|
|
46
|
+
batch = await q.get_batch()
|
|
47
|
+
assert batch
|
|
48
|
+
record.append(batch)
|
|
49
|
+
|
|
50
|
+
async def aiter_consumer() -> None:
|
|
51
|
+
async for batch in q:
|
|
52
|
+
assert batch
|
|
53
|
+
record.append(batch)
|
|
54
|
+
|
|
55
|
+
for consumer in (get_batch_consumer, aiter_consumer):
|
|
56
|
+
record.clear()
|
|
57
|
+
async with _core.open_nursery() as nursery:
|
|
58
|
+
nursery.start_soon(consumer)
|
|
59
|
+
await _core.wait_all_tasks_blocked()
|
|
60
|
+
stats = q.statistics()
|
|
61
|
+
assert stats.qsize == 0
|
|
62
|
+
assert stats.tasks_waiting == 1
|
|
63
|
+
q.put_nowait(10)
|
|
64
|
+
q.put_nowait(11)
|
|
65
|
+
await _core.wait_all_tasks_blocked()
|
|
66
|
+
q.put_nowait(12)
|
|
67
|
+
await _core.wait_all_tasks_blocked()
|
|
68
|
+
assert record == [[10, 11], [12]]
|
|
69
|
+
nursery.cancel_scope.cancel()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
async def test_UnboundedQueue_fairness() -> None:
|
|
73
|
+
q = _core.UnboundedQueue[int]()
|
|
74
|
+
|
|
75
|
+
# If there's no-one else around, we can put stuff in and take it out
|
|
76
|
+
# again, no problem
|
|
77
|
+
q.put_nowait(1)
|
|
78
|
+
q.put_nowait(2)
|
|
79
|
+
assert q.get_batch_nowait() == [1, 2]
|
|
80
|
+
|
|
81
|
+
result = None
|
|
82
|
+
|
|
83
|
+
async def get_batch(q: _core.UnboundedQueue[int]) -> None:
|
|
84
|
+
nonlocal result
|
|
85
|
+
result = await q.get_batch()
|
|
86
|
+
|
|
87
|
+
# But if someone else is waiting to read, then they get dibs
|
|
88
|
+
async with _core.open_nursery() as nursery:
|
|
89
|
+
nursery.start_soon(get_batch, q)
|
|
90
|
+
await _core.wait_all_tasks_blocked()
|
|
91
|
+
q.put_nowait(3)
|
|
92
|
+
q.put_nowait(4)
|
|
93
|
+
with pytest.raises(_core.WouldBlock):
|
|
94
|
+
q.get_batch_nowait()
|
|
95
|
+
assert result == [3, 4]
|
|
96
|
+
|
|
97
|
+
# If two tasks are trying to read, they alternate
|
|
98
|
+
record = []
|
|
99
|
+
|
|
100
|
+
async def reader(name: str) -> None:
|
|
101
|
+
while True:
|
|
102
|
+
record.append((name, await q.get_batch()))
|
|
103
|
+
|
|
104
|
+
async with _core.open_nursery() as nursery:
|
|
105
|
+
nursery.start_soon(reader, "a")
|
|
106
|
+
await _core.wait_all_tasks_blocked()
|
|
107
|
+
nursery.start_soon(reader, "b")
|
|
108
|
+
await _core.wait_all_tasks_blocked()
|
|
109
|
+
|
|
110
|
+
for i in range(20):
|
|
111
|
+
q.put_nowait(i)
|
|
112
|
+
await _core.wait_all_tasks_blocked()
|
|
113
|
+
|
|
114
|
+
nursery.cancel_scope.cancel()
|
|
115
|
+
|
|
116
|
+
assert record == list(zip(itertools.cycle("ab"), [[i] for i in range(20)]))
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
async def test_UnboundedQueue_trivial_yields() -> None:
|
|
120
|
+
q = _core.UnboundedQueue[None]()
|
|
121
|
+
|
|
122
|
+
q.put_nowait(None)
|
|
123
|
+
with assert_checkpoints():
|
|
124
|
+
await q.get_batch()
|
|
125
|
+
|
|
126
|
+
q.put_nowait(None)
|
|
127
|
+
with assert_checkpoints():
|
|
128
|
+
async for _ in q: # pragma: no branch
|
|
129
|
+
break
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
async def test_UnboundedQueue_no_spurious_wakeups() -> None:
|
|
133
|
+
# If we have two tasks waiting, and put two items into the queue... then
|
|
134
|
+
# only one task wakes up
|
|
135
|
+
record = []
|
|
136
|
+
|
|
137
|
+
async def getter(q: _core.UnboundedQueue[int], i: int) -> None:
|
|
138
|
+
got = await q.get_batch()
|
|
139
|
+
record.append((i, got))
|
|
140
|
+
|
|
141
|
+
async with _core.open_nursery() as nursery:
|
|
142
|
+
q = _core.UnboundedQueue[int]()
|
|
143
|
+
nursery.start_soon(getter, q, 1)
|
|
144
|
+
await wait_all_tasks_blocked()
|
|
145
|
+
nursery.start_soon(getter, q, 2)
|
|
146
|
+
await wait_all_tasks_blocked()
|
|
147
|
+
|
|
148
|
+
for i in range(10):
|
|
149
|
+
q.put_nowait(i)
|
|
150
|
+
await wait_all_tasks_blocked()
|
|
151
|
+
|
|
152
|
+
assert record == [(1, list(range(10)))]
|
|
153
|
+
|
|
154
|
+
nursery.cancel_scope.cancel()
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
import tempfile
|
|
6
|
+
from contextlib import contextmanager
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
from unittest.mock import create_autospec
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
on_windows = os.name == "nt"
|
|
13
|
+
# Mark all the tests in this file as being windows-only
|
|
14
|
+
pytestmark = pytest.mark.skipif(not on_windows, reason="windows only")
|
|
15
|
+
|
|
16
|
+
assert (
|
|
17
|
+
sys.platform == "win32" or not TYPE_CHECKING
|
|
18
|
+
) # Skip type checking when not on Windows
|
|
19
|
+
|
|
20
|
+
from ... import _core, sleep
|
|
21
|
+
from ...testing import wait_all_tasks_blocked
|
|
22
|
+
from .tutil import gc_collect_harder, restore_unraisablehook, slow
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from collections.abc import Generator
|
|
26
|
+
from io import BufferedWriter
|
|
27
|
+
|
|
28
|
+
if on_windows:
|
|
29
|
+
from .._windows_cffi import (
|
|
30
|
+
INVALID_HANDLE_VALUE,
|
|
31
|
+
FileFlags,
|
|
32
|
+
Handle,
|
|
33
|
+
ffi,
|
|
34
|
+
kernel32,
|
|
35
|
+
raise_winerror,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_winerror(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
40
|
+
# this is unfortunately needed as the generated ffi is read-only
|
|
41
|
+
class FFIWrapper:
|
|
42
|
+
def getwinerror(self) -> None:
|
|
43
|
+
raise NotImplementedError("this is a fake implementation")
|
|
44
|
+
|
|
45
|
+
mock = create_autospec(ffi.getwinerror)
|
|
46
|
+
monkeypatch.setattr("trio._core._windows_cffi.ffi", FFIWrapper)
|
|
47
|
+
monkeypatch.setattr("trio._core._windows_cffi.ffi.getwinerror", mock)
|
|
48
|
+
|
|
49
|
+
# Returning none = no error, should not happen.
|
|
50
|
+
mock.return_value = None
|
|
51
|
+
with pytest.raises(RuntimeError, match=r"^No error set\?$"):
|
|
52
|
+
raise_winerror()
|
|
53
|
+
mock.assert_called_once_with()
|
|
54
|
+
mock.reset_mock()
|
|
55
|
+
|
|
56
|
+
with pytest.raises(RuntimeError, match=r"^No error set\?$"):
|
|
57
|
+
raise_winerror(38)
|
|
58
|
+
mock.assert_called_once_with(38)
|
|
59
|
+
mock.reset_mock()
|
|
60
|
+
|
|
61
|
+
mock.return_value = (12, "test error")
|
|
62
|
+
with pytest.raises(
|
|
63
|
+
OSError,
|
|
64
|
+
match=r"^\[WinError 12\] test error: 'file_1' -> 'file_2'$",
|
|
65
|
+
) as exc:
|
|
66
|
+
raise_winerror(filename="file_1", filename2="file_2")
|
|
67
|
+
mock.assert_called_once_with()
|
|
68
|
+
mock.reset_mock()
|
|
69
|
+
assert exc.value.winerror == 12
|
|
70
|
+
assert exc.value.strerror == "test error"
|
|
71
|
+
assert exc.value.filename == "file_1"
|
|
72
|
+
assert exc.value.filename2 == "file_2"
|
|
73
|
+
|
|
74
|
+
# With an explicit number passed in, it overrides what getwinerror() returns.
|
|
75
|
+
with pytest.raises(
|
|
76
|
+
OSError,
|
|
77
|
+
match=r"^\[WinError 18\] test error: 'a/file' -> 'b/file'$",
|
|
78
|
+
) as exc:
|
|
79
|
+
raise_winerror(18, filename="a/file", filename2="b/file")
|
|
80
|
+
mock.assert_called_once_with(18)
|
|
81
|
+
mock.reset_mock()
|
|
82
|
+
assert exc.value.winerror == 18
|
|
83
|
+
assert exc.value.strerror == "test error"
|
|
84
|
+
assert exc.value.filename == "a/file"
|
|
85
|
+
assert exc.value.filename2 == "b/file"
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# The undocumented API that this is testing should be changed to stop using
|
|
89
|
+
# UnboundedQueue (or just removed until we have time to redo it), but until
|
|
90
|
+
# then we filter out the warning.
|
|
91
|
+
@pytest.mark.filterwarnings("ignore:.*UnboundedQueue:trio.TrioDeprecationWarning")
|
|
92
|
+
async def test_completion_key_listen() -> None:
|
|
93
|
+
from .. import _io_windows
|
|
94
|
+
|
|
95
|
+
async def post(key: int) -> None:
|
|
96
|
+
iocp = Handle(ffi.cast("HANDLE", _core.current_iocp()))
|
|
97
|
+
for i in range(10):
|
|
98
|
+
print("post", i)
|
|
99
|
+
if i % 3 == 0:
|
|
100
|
+
await _core.checkpoint()
|
|
101
|
+
success = kernel32.PostQueuedCompletionStatus(iocp, i, key, ffi.NULL)
|
|
102
|
+
assert success
|
|
103
|
+
|
|
104
|
+
with _core.monitor_completion_key() as (key, queue):
|
|
105
|
+
async with _core.open_nursery() as nursery:
|
|
106
|
+
nursery.start_soon(post, key)
|
|
107
|
+
i = 0
|
|
108
|
+
print("loop")
|
|
109
|
+
async for batch in queue: # pragma: no branch
|
|
110
|
+
print("got some", batch)
|
|
111
|
+
for info in batch:
|
|
112
|
+
assert isinstance(info, _io_windows.CompletionKeyEventInfo)
|
|
113
|
+
assert info.lpOverlapped == 0
|
|
114
|
+
assert info.dwNumberOfBytesTransferred == i
|
|
115
|
+
i += 1
|
|
116
|
+
if i == 10:
|
|
117
|
+
break
|
|
118
|
+
print("end loop")
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
async def test_readinto_overlapped() -> None:
|
|
122
|
+
data = b"1" * 1024 + b"2" * 1024 + b"3" * 1024 + b"4" * 1024
|
|
123
|
+
buffer = bytearray(len(data))
|
|
124
|
+
|
|
125
|
+
with tempfile.TemporaryDirectory() as tdir:
|
|
126
|
+
tfile = os.path.join(tdir, "numbers.txt")
|
|
127
|
+
with open( # noqa: ASYNC230 # This is a test, synchronous is ok
|
|
128
|
+
tfile,
|
|
129
|
+
"wb",
|
|
130
|
+
) as fp:
|
|
131
|
+
fp.write(data)
|
|
132
|
+
fp.flush()
|
|
133
|
+
|
|
134
|
+
rawname = tfile.encode("utf-16le") + b"\0\0"
|
|
135
|
+
rawname_buf = ffi.from_buffer(rawname)
|
|
136
|
+
handle = kernel32.CreateFileW(
|
|
137
|
+
ffi.cast("LPCWSTR", rawname_buf),
|
|
138
|
+
FileFlags.GENERIC_READ,
|
|
139
|
+
FileFlags.FILE_SHARE_READ,
|
|
140
|
+
ffi.NULL, # no security attributes
|
|
141
|
+
FileFlags.OPEN_EXISTING,
|
|
142
|
+
FileFlags.FILE_FLAG_OVERLAPPED,
|
|
143
|
+
ffi.NULL, # no template file
|
|
144
|
+
)
|
|
145
|
+
if handle == INVALID_HANDLE_VALUE: # pragma: no cover
|
|
146
|
+
raise_winerror()
|
|
147
|
+
|
|
148
|
+
try:
|
|
149
|
+
with memoryview(buffer) as buffer_view:
|
|
150
|
+
|
|
151
|
+
async def read_region(start: int, end: int) -> None:
|
|
152
|
+
await _core.readinto_overlapped(
|
|
153
|
+
handle,
|
|
154
|
+
buffer_view[start:end],
|
|
155
|
+
start,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
_core.register_with_iocp(handle)
|
|
159
|
+
async with _core.open_nursery() as nursery:
|
|
160
|
+
for start in range(0, 4096, 512):
|
|
161
|
+
nursery.start_soon(read_region, start, start + 512)
|
|
162
|
+
|
|
163
|
+
assert buffer == data
|
|
164
|
+
|
|
165
|
+
with pytest.raises((BufferError, TypeError)):
|
|
166
|
+
await _core.readinto_overlapped(handle, b"immutable")
|
|
167
|
+
finally:
|
|
168
|
+
kernel32.CloseHandle(handle)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
@contextmanager
|
|
172
|
+
def pipe_with_overlapped_read() -> Generator[tuple[BufferedWriter, int], None, None]:
|
|
173
|
+
import msvcrt
|
|
174
|
+
from asyncio.windows_utils import pipe
|
|
175
|
+
|
|
176
|
+
read_handle, write_handle = pipe(overlapped=(True, False))
|
|
177
|
+
try:
|
|
178
|
+
write_fd = msvcrt.open_osfhandle(write_handle, 0)
|
|
179
|
+
yield os.fdopen(write_fd, "wb", closefd=False), read_handle
|
|
180
|
+
finally:
|
|
181
|
+
kernel32.CloseHandle(Handle(ffi.cast("HANDLE", read_handle)))
|
|
182
|
+
kernel32.CloseHandle(Handle(ffi.cast("HANDLE", write_handle)))
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
@restore_unraisablehook()
|
|
186
|
+
def test_forgot_to_register_with_iocp() -> None:
|
|
187
|
+
with pipe_with_overlapped_read() as (write_fp, read_handle):
|
|
188
|
+
with write_fp:
|
|
189
|
+
write_fp.write(b"test\n")
|
|
190
|
+
|
|
191
|
+
left_run_yet = False
|
|
192
|
+
|
|
193
|
+
async def main() -> None:
|
|
194
|
+
target = bytearray(1)
|
|
195
|
+
try:
|
|
196
|
+
async with _core.open_nursery() as nursery:
|
|
197
|
+
nursery.start_soon(
|
|
198
|
+
_core.readinto_overlapped,
|
|
199
|
+
read_handle,
|
|
200
|
+
target,
|
|
201
|
+
name="xyz",
|
|
202
|
+
)
|
|
203
|
+
await wait_all_tasks_blocked()
|
|
204
|
+
nursery.cancel_scope.cancel()
|
|
205
|
+
finally:
|
|
206
|
+
# Run loop is exited without unwinding running tasks, so
|
|
207
|
+
# we don't get here until the main() coroutine is GC'ed
|
|
208
|
+
assert left_run_yet
|
|
209
|
+
|
|
210
|
+
with pytest.raises(_core.TrioInternalError) as exc_info:
|
|
211
|
+
_core.run(main)
|
|
212
|
+
left_run_yet = True
|
|
213
|
+
assert "Failed to cancel overlapped I/O in xyz " in str(exc_info.value)
|
|
214
|
+
assert "forget to call register_with_iocp()?" in str(exc_info.value)
|
|
215
|
+
|
|
216
|
+
# Make sure the Nursery.__del__ assertion about dangling children
|
|
217
|
+
# gets put with the correct test
|
|
218
|
+
del exc_info
|
|
219
|
+
gc_collect_harder()
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
@slow
|
|
223
|
+
async def test_too_late_to_cancel() -> None:
|
|
224
|
+
import time
|
|
225
|
+
|
|
226
|
+
with pipe_with_overlapped_read() as (write_fp, read_handle):
|
|
227
|
+
_core.register_with_iocp(read_handle)
|
|
228
|
+
target = bytearray(6)
|
|
229
|
+
async with _core.open_nursery() as nursery:
|
|
230
|
+
# Start an async read in the background
|
|
231
|
+
nursery.start_soon(_core.readinto_overlapped, read_handle, target)
|
|
232
|
+
await wait_all_tasks_blocked()
|
|
233
|
+
|
|
234
|
+
# Synchronous write to the other end of the pipe
|
|
235
|
+
with write_fp:
|
|
236
|
+
write_fp.write(b"test1\ntest2\n")
|
|
237
|
+
|
|
238
|
+
# Note: not trio.sleep! We're making sure the OS level
|
|
239
|
+
# ReadFile completes, before Trio has a chance to execute
|
|
240
|
+
# another checkpoint and notice it completed.
|
|
241
|
+
time.sleep(1) # noqa: ASYNC251
|
|
242
|
+
nursery.cancel_scope.cancel()
|
|
243
|
+
assert target[:6] == b"test1\n"
|
|
244
|
+
|
|
245
|
+
# Do another I/O to make sure we've actually processed the
|
|
246
|
+
# fallback completion that was posted when CancelIoEx failed.
|
|
247
|
+
assert await _core.readinto_overlapped(read_handle, target) == 6
|
|
248
|
+
assert target[:6] == b"test2\n"
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def test_lsp_that_hooks_select_gives_good_error(
|
|
252
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
253
|
+
) -> None:
|
|
254
|
+
from .. import _io_windows
|
|
255
|
+
from .._windows_cffi import CData, WSAIoctls, _handle
|
|
256
|
+
|
|
257
|
+
def patched_get_underlying(
|
|
258
|
+
sock: int | CData,
|
|
259
|
+
*,
|
|
260
|
+
which: int = WSAIoctls.SIO_BASE_HANDLE,
|
|
261
|
+
) -> CData:
|
|
262
|
+
if hasattr(sock, "fileno"): # pragma: no branch
|
|
263
|
+
sock = sock.fileno()
|
|
264
|
+
if which == WSAIoctls.SIO_BSP_HANDLE_SELECT:
|
|
265
|
+
return _handle(sock + 1)
|
|
266
|
+
else:
|
|
267
|
+
return _handle(sock)
|
|
268
|
+
|
|
269
|
+
monkeypatch.setattr(_io_windows, "_get_underlying_socket", patched_get_underlying)
|
|
270
|
+
with pytest.raises(
|
|
271
|
+
RuntimeError,
|
|
272
|
+
match="SIO_BASE_HANDLE and SIO_BSP_HANDLE_SELECT differ",
|
|
273
|
+
):
|
|
274
|
+
_core.run(sleep, 0)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def test_lsp_that_completely_hides_base_socket_gives_good_error(
|
|
278
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
279
|
+
) -> None:
|
|
280
|
+
# This tests behavior with an LSP that fails SIO_BASE_HANDLE and returns
|
|
281
|
+
# self for SIO_BSP_HANDLE_SELECT (like Komodia), but also returns
|
|
282
|
+
# self for SIO_BSP_HANDLE_POLL. No known LSP does this, but we want to
|
|
283
|
+
# make sure we get an error rather than an infinite loop.
|
|
284
|
+
|
|
285
|
+
from .. import _io_windows
|
|
286
|
+
from .._windows_cffi import CData, WSAIoctls, _handle
|
|
287
|
+
|
|
288
|
+
def patched_get_underlying(
|
|
289
|
+
sock: int | CData,
|
|
290
|
+
*,
|
|
291
|
+
which: int = WSAIoctls.SIO_BASE_HANDLE,
|
|
292
|
+
) -> CData:
|
|
293
|
+
if hasattr(sock, "fileno"): # pragma: no branch
|
|
294
|
+
sock = sock.fileno()
|
|
295
|
+
if which == WSAIoctls.SIO_BASE_HANDLE:
|
|
296
|
+
raise OSError("nope")
|
|
297
|
+
else:
|
|
298
|
+
return _handle(sock)
|
|
299
|
+
|
|
300
|
+
monkeypatch.setattr(_io_windows, "_get_underlying_socket", patched_get_underlying)
|
|
301
|
+
with pytest.raises(
|
|
302
|
+
RuntimeError,
|
|
303
|
+
match="SIO_BASE_HANDLE failed and SIO_BSP_HANDLE_POLL didn't return a diff",
|
|
304
|
+
):
|
|
305
|
+
_core.run(sleep, 0)
|