@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.
Files changed (196) hide show
  1. package/bin/kgraph-launcher +15 -3
  2. package/lib/kgraph/scripts/build-bundle.sh +17 -4
  3. package/lib/site-packages/outcome/__init__.py +20 -0
  4. package/lib/site-packages/outcome/_impl.py +239 -0
  5. package/lib/site-packages/outcome/_util.py +33 -0
  6. package/lib/site-packages/outcome/_version.py +7 -0
  7. package/lib/site-packages/outcome/py.typed +0 -0
  8. package/lib/site-packages/outcome-1.3.0.post0.dist-info/INSTALLER +1 -0
  9. package/lib/site-packages/outcome-1.3.0.post0.dist-info/LICENSE +3 -0
  10. package/lib/site-packages/outcome-1.3.0.post0.dist-info/LICENSE.APACHE2 +202 -0
  11. package/lib/site-packages/outcome-1.3.0.post0.dist-info/LICENSE.MIT +20 -0
  12. package/lib/site-packages/outcome-1.3.0.post0.dist-info/METADATA +63 -0
  13. package/lib/site-packages/outcome-1.3.0.post0.dist-info/RECORD +13 -0
  14. package/lib/site-packages/outcome-1.3.0.post0.dist-info/WHEEL +6 -0
  15. package/lib/site-packages/outcome-1.3.0.post0.dist-info/top_level.txt +1 -0
  16. package/lib/site-packages/sniffio/__init__.py +17 -0
  17. package/lib/site-packages/sniffio/_impl.py +95 -0
  18. package/lib/site-packages/sniffio/_tests/__init__.py +0 -0
  19. package/lib/site-packages/sniffio/_tests/test_sniffio.py +84 -0
  20. package/lib/site-packages/sniffio/_version.py +3 -0
  21. package/lib/site-packages/sniffio/py.typed +0 -0
  22. package/lib/site-packages/sniffio-1.3.1.dist-info/INSTALLER +1 -0
  23. package/lib/site-packages/sniffio-1.3.1.dist-info/LICENSE +3 -0
  24. package/lib/site-packages/sniffio-1.3.1.dist-info/LICENSE.APACHE2 +202 -0
  25. package/lib/site-packages/sniffio-1.3.1.dist-info/LICENSE.MIT +20 -0
  26. package/lib/site-packages/sniffio-1.3.1.dist-info/METADATA +104 -0
  27. package/lib/site-packages/sniffio-1.3.1.dist-info/RECORD +14 -0
  28. package/lib/site-packages/sniffio-1.3.1.dist-info/WHEEL +5 -0
  29. package/lib/site-packages/sniffio-1.3.1.dist-info/top_level.txt +1 -0
  30. package/lib/site-packages/sortedcontainers/__init__.py +74 -0
  31. package/lib/site-packages/sortedcontainers/sorteddict.py +812 -0
  32. package/lib/site-packages/sortedcontainers/sortedlist.py +2646 -0
  33. package/lib/site-packages/sortedcontainers/sortedset.py +733 -0
  34. package/lib/site-packages/sortedcontainers-2.4.0.dist-info/INSTALLER +1 -0
  35. package/lib/site-packages/sortedcontainers-2.4.0.dist-info/LICENSE +13 -0
  36. package/lib/site-packages/sortedcontainers-2.4.0.dist-info/METADATA +264 -0
  37. package/lib/site-packages/sortedcontainers-2.4.0.dist-info/RECORD +10 -0
  38. package/lib/site-packages/sortedcontainers-2.4.0.dist-info/WHEEL +6 -0
  39. package/lib/site-packages/sortedcontainers-2.4.0.dist-info/top_level.txt +1 -0
  40. package/lib/site-packages/trio/__init__.py +133 -0
  41. package/lib/site-packages/trio/__main__.py +3 -0
  42. package/lib/site-packages/trio/_abc.py +714 -0
  43. package/lib/site-packages/trio/_channel.py +610 -0
  44. package/lib/site-packages/trio/_core/__init__.py +94 -0
  45. package/lib/site-packages/trio/_core/_asyncgens.py +243 -0
  46. package/lib/site-packages/trio/_core/_concat_tb.py +26 -0
  47. package/lib/site-packages/trio/_core/_entry_queue.py +223 -0
  48. package/lib/site-packages/trio/_core/_exceptions.py +169 -0
  49. package/lib/site-packages/trio/_core/_generated_instrumentation.py +50 -0
  50. package/lib/site-packages/trio/_core/_generated_io_epoll.py +98 -0
  51. package/lib/site-packages/trio/_core/_generated_io_kqueue.py +153 -0
  52. package/lib/site-packages/trio/_core/_generated_io_windows.py +204 -0
  53. package/lib/site-packages/trio/_core/_generated_run.py +269 -0
  54. package/lib/site-packages/trio/_core/_generated_windows_ffi.py +10 -0
  55. package/lib/site-packages/trio/_core/_instrumentation.py +117 -0
  56. package/lib/site-packages/trio/_core/_io_common.py +31 -0
  57. package/lib/site-packages/trio/_core/_io_epoll.py +385 -0
  58. package/lib/site-packages/trio/_core/_io_kqueue.py +292 -0
  59. package/lib/site-packages/trio/_core/_io_windows.py +1036 -0
  60. package/lib/site-packages/trio/_core/_ki.py +271 -0
  61. package/lib/site-packages/trio/_core/_local.py +104 -0
  62. package/lib/site-packages/trio/_core/_mock_clock.py +165 -0
  63. package/lib/site-packages/trio/_core/_parking_lot.py +317 -0
  64. package/lib/site-packages/trio/_core/_run.py +3148 -0
  65. package/lib/site-packages/trio/_core/_run_context.py +15 -0
  66. package/lib/site-packages/trio/_core/_tests/__init__.py +0 -0
  67. package/lib/site-packages/trio/_core/_tests/test_asyncgen.py +339 -0
  68. package/lib/site-packages/trio/_core/_tests/test_cancelled.py +222 -0
  69. package/lib/site-packages/trio/_core/_tests/test_exceptiongroup_gc.py +103 -0
  70. package/lib/site-packages/trio/_core/_tests/test_guest_mode.py +755 -0
  71. package/lib/site-packages/trio/_core/_tests/test_instrumentation.py +315 -0
  72. package/lib/site-packages/trio/_core/_tests/test_io.py +522 -0
  73. package/lib/site-packages/trio/_core/_tests/test_ki.py +703 -0
  74. package/lib/site-packages/trio/_core/_tests/test_local.py +118 -0
  75. package/lib/site-packages/trio/_core/_tests/test_mock_clock.py +193 -0
  76. package/lib/site-packages/trio/_core/_tests/test_parking_lot.py +389 -0
  77. package/lib/site-packages/trio/_core/_tests/test_run.py +3024 -0
  78. package/lib/site-packages/trio/_core/_tests/test_thread_cache.py +227 -0
  79. package/lib/site-packages/trio/_core/_tests/test_tutil.py +13 -0
  80. package/lib/site-packages/trio/_core/_tests/test_unbounded_queue.py +154 -0
  81. package/lib/site-packages/trio/_core/_tests/test_windows.py +305 -0
  82. package/lib/site-packages/trio/_core/_tests/tutil.py +117 -0
  83. package/lib/site-packages/trio/_core/_tests/type_tests/nursery_start.py +79 -0
  84. package/lib/site-packages/trio/_core/_tests/type_tests/run.py +51 -0
  85. package/lib/site-packages/trio/_core/_thread_cache.py +317 -0
  86. package/lib/site-packages/trio/_core/_traps.py +318 -0
  87. package/lib/site-packages/trio/_core/_unbounded_queue.py +163 -0
  88. package/lib/site-packages/trio/_core/_wakeup_socketpair.py +75 -0
  89. package/lib/site-packages/trio/_core/_windows_cffi.py +313 -0
  90. package/lib/site-packages/trio/_deprecate.py +171 -0
  91. package/lib/site-packages/trio/_dtls.py +1380 -0
  92. package/lib/site-packages/trio/_file_io.py +513 -0
  93. package/lib/site-packages/trio/_highlevel_generic.py +125 -0
  94. package/lib/site-packages/trio/_highlevel_open_tcp_listeners.py +251 -0
  95. package/lib/site-packages/trio/_highlevel_open_tcp_stream.py +397 -0
  96. package/lib/site-packages/trio/_highlevel_open_unix_stream.py +65 -0
  97. package/lib/site-packages/trio/_highlevel_serve_listeners.py +148 -0
  98. package/lib/site-packages/trio/_highlevel_socket.py +423 -0
  99. package/lib/site-packages/trio/_highlevel_ssl_helpers.py +180 -0
  100. package/lib/site-packages/trio/_path.py +289 -0
  101. package/lib/site-packages/trio/_repl.py +159 -0
  102. package/lib/site-packages/trio/_signals.py +185 -0
  103. package/lib/site-packages/trio/_socket.py +1326 -0
  104. package/lib/site-packages/trio/_ssl.py +964 -0
  105. package/lib/site-packages/trio/_subprocess.py +1178 -0
  106. package/lib/site-packages/trio/_subprocess_platform/__init__.py +123 -0
  107. package/lib/site-packages/trio/_subprocess_platform/kqueue.py +48 -0
  108. package/lib/site-packages/trio/_subprocess_platform/waitid.py +113 -0
  109. package/lib/site-packages/trio/_subprocess_platform/windows.py +11 -0
  110. package/lib/site-packages/trio/_sync.py +908 -0
  111. package/lib/site-packages/trio/_tests/__init__.py +0 -0
  112. package/lib/site-packages/trio/_tests/astrill-codesigning-cert.cer +0 -0
  113. package/lib/site-packages/trio/_tests/check_type_completeness.py +247 -0
  114. package/lib/site-packages/trio/_tests/module_with_deprecations.py +22 -0
  115. package/lib/site-packages/trio/_tests/pytest_plugin.py +54 -0
  116. package/lib/site-packages/trio/_tests/test_abc.py +72 -0
  117. package/lib/site-packages/trio/_tests/test_channel.py +750 -0
  118. package/lib/site-packages/trio/_tests/test_contextvars.py +56 -0
  119. package/lib/site-packages/trio/_tests/test_deprecate.py +277 -0
  120. package/lib/site-packages/trio/_tests/test_deprecate_strict_exception_groups_false.py +64 -0
  121. package/lib/site-packages/trio/_tests/test_dtls.py +950 -0
  122. package/lib/site-packages/trio/_tests/test_exports.py +626 -0
  123. package/lib/site-packages/trio/_tests/test_fakenet.py +317 -0
  124. package/lib/site-packages/trio/_tests/test_file_io.py +269 -0
  125. package/lib/site-packages/trio/_tests/test_highlevel_generic.py +98 -0
  126. package/lib/site-packages/trio/_tests/test_highlevel_open_tcp_listeners.py +419 -0
  127. package/lib/site-packages/trio/_tests/test_highlevel_open_tcp_stream.py +693 -0
  128. package/lib/site-packages/trio/_tests/test_highlevel_open_unix_stream.py +86 -0
  129. package/lib/site-packages/trio/_tests/test_highlevel_serve_listeners.py +186 -0
  130. package/lib/site-packages/trio/_tests/test_highlevel_socket.py +336 -0
  131. package/lib/site-packages/trio/_tests/test_highlevel_ssl_helpers.py +169 -0
  132. package/lib/site-packages/trio/_tests/test_path.py +279 -0
  133. package/lib/site-packages/trio/_tests/test_repl.py +428 -0
  134. package/lib/site-packages/trio/_tests/test_scheduler_determinism.py +47 -0
  135. package/lib/site-packages/trio/_tests/test_signals.py +186 -0
  136. package/lib/site-packages/trio/_tests/test_socket.py +1253 -0
  137. package/lib/site-packages/trio/_tests/test_ssl.py +1371 -0
  138. package/lib/site-packages/trio/_tests/test_subprocess.py +767 -0
  139. package/lib/site-packages/trio/_tests/test_sync.py +735 -0
  140. package/lib/site-packages/trio/_tests/test_testing.py +682 -0
  141. package/lib/site-packages/trio/_tests/test_testing_raisesgroup.py +1128 -0
  142. package/lib/site-packages/trio/_tests/test_threads.py +1173 -0
  143. package/lib/site-packages/trio/_tests/test_timeouts.py +281 -0
  144. package/lib/site-packages/trio/_tests/test_tracing.py +88 -0
  145. package/lib/site-packages/trio/_tests/test_trio.py +8 -0
  146. package/lib/site-packages/trio/_tests/test_unix_pipes.py +288 -0
  147. package/lib/site-packages/trio/_tests/test_util.py +349 -0
  148. package/lib/site-packages/trio/_tests/test_wait_for_object.py +225 -0
  149. package/lib/site-packages/trio/_tests/test_windows_pipes.py +112 -0
  150. package/lib/site-packages/trio/_tests/tools/__init__.py +0 -0
  151. package/lib/site-packages/trio/_tests/tools/test_gen_exports.py +179 -0
  152. package/lib/site-packages/trio/_tests/tools/test_mypy_annotate.py +140 -0
  153. package/lib/site-packages/trio/_tests/tools/test_sync_requirements.py +80 -0
  154. package/lib/site-packages/trio/_tests/type_tests/check_wraps.py +9 -0
  155. package/lib/site-packages/trio/_tests/type_tests/open_memory_channel.py +4 -0
  156. package/lib/site-packages/trio/_tests/type_tests/path.py +140 -0
  157. package/lib/site-packages/trio/_tests/type_tests/subprocesses.py +23 -0
  158. package/lib/site-packages/trio/_tests/type_tests/task_status.py +29 -0
  159. package/lib/site-packages/trio/_threads.py +610 -0
  160. package/lib/site-packages/trio/_timeouts.py +197 -0
  161. package/lib/site-packages/trio/_tools/__init__.py +0 -0
  162. package/lib/site-packages/trio/_tools/gen_exports.py +401 -0
  163. package/lib/site-packages/trio/_tools/mypy_annotate.py +126 -0
  164. package/lib/site-packages/trio/_tools/sync_requirements.py +98 -0
  165. package/lib/site-packages/trio/_tools/windows_ffi_build.py +220 -0
  166. package/lib/site-packages/trio/_unix_pipes.py +197 -0
  167. package/lib/site-packages/trio/_util.py +385 -0
  168. package/lib/site-packages/trio/_version.py +3 -0
  169. package/lib/site-packages/trio/_wait_for_object.py +67 -0
  170. package/lib/site-packages/trio/_windows_pipes.py +144 -0
  171. package/lib/site-packages/trio/abc.py +23 -0
  172. package/lib/site-packages/trio/from_thread.py +13 -0
  173. package/lib/site-packages/trio/lowlevel.py +95 -0
  174. package/lib/site-packages/trio/py.typed +0 -0
  175. package/lib/site-packages/trio/socket.py +602 -0
  176. package/lib/site-packages/trio/testing/__init__.py +58 -0
  177. package/lib/site-packages/trio/testing/_check_streams.py +570 -0
  178. package/lib/site-packages/trio/testing/_checkpoints.py +69 -0
  179. package/lib/site-packages/trio/testing/_fake_net.py +584 -0
  180. package/lib/site-packages/trio/testing/_memory_streams.py +633 -0
  181. package/lib/site-packages/trio/testing/_network.py +36 -0
  182. package/lib/site-packages/trio/testing/_raises_group.py +1015 -0
  183. package/lib/site-packages/trio/testing/_sequencer.py +87 -0
  184. package/lib/site-packages/trio/testing/_trio_test.py +50 -0
  185. package/lib/site-packages/trio/to_thread.py +4 -0
  186. package/lib/site-packages/trio-0.33.0.dist-info/INSTALLER +1 -0
  187. package/lib/site-packages/trio-0.33.0.dist-info/METADATA +186 -0
  188. package/lib/site-packages/trio-0.33.0.dist-info/RECORD +156 -0
  189. package/lib/site-packages/trio-0.33.0.dist-info/REQUESTED +0 -0
  190. package/lib/site-packages/trio-0.33.0.dist-info/WHEEL +5 -0
  191. package/lib/site-packages/trio-0.33.0.dist-info/entry_points.txt +2 -0
  192. package/lib/site-packages/trio-0.33.0.dist-info/licenses/LICENSE +3 -0
  193. package/lib/site-packages/trio-0.33.0.dist-info/licenses/LICENSE.APACHE2 +202 -0
  194. package/lib/site-packages/trio-0.33.0.dist-info/licenses/LICENSE.MIT +22 -0
  195. package/lib/site-packages/trio-0.33.0.dist-info/top_level.txt +1 -0
  196. 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)