@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,693 @@
1
+ from __future__ import annotations
2
+
3
+ import socket
4
+ import sys
5
+ from socket import AddressFamily, SocketKind
6
+ from typing import TYPE_CHECKING
7
+
8
+ import attrs
9
+ import pytest
10
+
11
+ import trio
12
+ from trio._highlevel_open_tcp_stream import (
13
+ close_all,
14
+ format_host_port,
15
+ open_tcp_stream,
16
+ reorder_for_rfc_6555_section_5_4,
17
+ )
18
+ from trio.socket import AF_INET, AF_INET6, IPPROTO_TCP, SOCK_STREAM, SocketType
19
+
20
+ if TYPE_CHECKING:
21
+ from collections.abc import Sequence
22
+
23
+ from trio.testing import MockClock
24
+
25
+ if sys.version_info < (3, 11):
26
+ from exceptiongroup import BaseExceptionGroup
27
+
28
+
29
+ def test_close_all() -> None:
30
+ class CloseMe(SocketType):
31
+ closed = False
32
+
33
+ def close(self) -> None:
34
+ self.closed = True
35
+
36
+ class CloseKiller(SocketType):
37
+ def close(self) -> None:
38
+ raise OSError("os error text")
39
+
40
+ c: CloseMe = CloseMe()
41
+ with close_all() as to_close:
42
+ to_close.add(c)
43
+ assert c.closed
44
+
45
+ c = CloseMe()
46
+ with pytest.raises(RuntimeError):
47
+ with close_all() as to_close:
48
+ to_close.add(c)
49
+ raise RuntimeError
50
+ assert c.closed
51
+
52
+ c = CloseMe()
53
+ with pytest.raises(OSError, match="os error text"):
54
+ with close_all() as to_close:
55
+ to_close.add(CloseKiller())
56
+ to_close.add(c)
57
+ assert c.closed
58
+
59
+
60
+ def test_reorder_for_rfc_6555_section_5_4() -> None:
61
+ def fake4(
62
+ i: int,
63
+ ) -> tuple[socket.AddressFamily, socket.SocketKind, int, str, tuple[str, int]]:
64
+ return (
65
+ AF_INET,
66
+ SOCK_STREAM,
67
+ IPPROTO_TCP,
68
+ "",
69
+ (f"10.0.0.{i}", 80),
70
+ )
71
+
72
+ def fake6(
73
+ i: int,
74
+ ) -> tuple[socket.AddressFamily, socket.SocketKind, int, str, tuple[str, int]]:
75
+ return (AF_INET6, SOCK_STREAM, IPPROTO_TCP, "", (f"::{i}", 80))
76
+
77
+ for fake in fake4, fake6:
78
+ # No effect on homogeneous lists
79
+ targets = [fake(0), fake(1), fake(2)]
80
+ reorder_for_rfc_6555_section_5_4(targets)
81
+ assert targets == [fake(0), fake(1), fake(2)]
82
+
83
+ # Single item lists also OK
84
+ targets = [fake(0)]
85
+ reorder_for_rfc_6555_section_5_4(targets)
86
+ assert targets == [fake(0)]
87
+
88
+ # If the list starts out with different families in positions 0 and 1,
89
+ # then it's left alone
90
+ orig = [fake4(0), fake6(0), fake4(1), fake6(1)]
91
+ targets = list(orig)
92
+ reorder_for_rfc_6555_section_5_4(targets)
93
+ assert targets == orig
94
+
95
+ # If not, it's reordered
96
+ targets = [fake4(0), fake4(1), fake4(2), fake6(0), fake6(1)]
97
+ reorder_for_rfc_6555_section_5_4(targets)
98
+ assert targets == [fake4(0), fake6(0), fake4(1), fake4(2), fake6(1)]
99
+
100
+
101
+ def test_format_host_port() -> None:
102
+ assert format_host_port("127.0.0.1", 80) == "127.0.0.1:80"
103
+ assert format_host_port(b"127.0.0.1", 80) == "127.0.0.1:80"
104
+ assert format_host_port("example.com", 443) == "example.com:443"
105
+ assert format_host_port(b"example.com", 443) == "example.com:443"
106
+ assert format_host_port("::1", "http") == "[::1]:http"
107
+ assert format_host_port(b"::1", "http") == "[::1]:http"
108
+
109
+
110
+ # Make sure we can connect to localhost using real kernel sockets
111
+ async def test_open_tcp_stream_real_socket_smoketest() -> None:
112
+ listen_sock = trio.socket.socket()
113
+ await listen_sock.bind(("127.0.0.1", 0))
114
+ _, listen_port = listen_sock.getsockname()
115
+ listen_sock.listen(1)
116
+ client_stream = await open_tcp_stream("127.0.0.1", listen_port)
117
+ server_sock, _ = await listen_sock.accept()
118
+ await client_stream.send_all(b"x")
119
+ assert await server_sock.recv(1) == b"x"
120
+ await client_stream.aclose()
121
+ server_sock.close()
122
+
123
+ listen_sock.close()
124
+
125
+
126
+ async def test_open_tcp_stream_input_validation() -> None:
127
+ with pytest.raises(ValueError, match=r"^host must be str or bytes, not None$"):
128
+ await open_tcp_stream(None, 80) # type: ignore[arg-type]
129
+ with pytest.raises(TypeError):
130
+ await open_tcp_stream("127.0.0.1", b"80") # type: ignore[arg-type]
131
+
132
+
133
+ def can_bind_127_0_0_2() -> bool:
134
+ with socket.socket() as s:
135
+ try:
136
+ s.bind(("127.0.0.2", 0))
137
+ except OSError:
138
+ return False
139
+ # s.getsockname() is typed as returning Any
140
+ return s.getsockname()[0] == "127.0.0.2" # type: ignore[no-any-return]
141
+
142
+
143
+ async def test_local_address_real() -> None:
144
+ with trio.socket.socket() as listener:
145
+ await listener.bind(("127.0.0.1", 0))
146
+ listener.listen()
147
+
148
+ # It's hard to test local_address properly, because you need multiple
149
+ # local addresses that you can bind to. Fortunately, on most Linux
150
+ # systems, you can bind to any 127.*.*.* address, and they all go
151
+ # through the loopback interface. So we can use a non-standard
152
+ # loopback address. On other systems, the only address we know for
153
+ # certain we have is 127.0.0.1, so we can't really test local_address=
154
+ # properly -- passing local_address=127.0.0.1 is indistinguishable
155
+ # from not passing local_address= at all. But, we can still do a smoke
156
+ # test to make sure the local_address= code doesn't crash.
157
+ local_address = "127.0.0.2" if can_bind_127_0_0_2() else "127.0.0.1"
158
+
159
+ async with await open_tcp_stream(
160
+ *listener.getsockname(),
161
+ local_address=local_address,
162
+ ) as client_stream:
163
+ assert client_stream.socket.getsockname()[0] == local_address
164
+ if hasattr(trio.socket, "IP_BIND_ADDRESS_NO_PORT"):
165
+ assert client_stream.socket.getsockopt(
166
+ trio.socket.IPPROTO_IP,
167
+ trio.socket.IP_BIND_ADDRESS_NO_PORT,
168
+ )
169
+ server_sock, remote_addr = await listener.accept()
170
+ await client_stream.aclose()
171
+ server_sock.close()
172
+ # accept returns tuple[SocketType, object], due to typeshed returning `Any`
173
+ assert remote_addr[0] == local_address
174
+
175
+ # Trying to connect to an ipv4 address with the ipv6 wildcard
176
+ # local_address should fail
177
+ with pytest.raises(
178
+ OSError,
179
+ match=r"^all attempts to connect* to *127\.0\.0\.\d:\d+ failed$",
180
+ ):
181
+ await open_tcp_stream(*listener.getsockname(), local_address="::")
182
+
183
+ # But the ipv4 wildcard address should work
184
+ async with await open_tcp_stream(
185
+ *listener.getsockname(),
186
+ local_address="0.0.0.0",
187
+ ) as client_stream:
188
+ server_sock, remote_addr = await listener.accept()
189
+ server_sock.close()
190
+ assert remote_addr == client_stream.socket.getsockname()
191
+
192
+
193
+ # Now, thorough tests using fake sockets
194
+
195
+
196
+ @attrs.define(eq=False, slots=False)
197
+ class FakeSocket(trio.socket.SocketType):
198
+ scenario: Scenario
199
+ _family: AddressFamily
200
+ _type: SocketKind
201
+ _proto: int
202
+
203
+ ip: str | int | None = None
204
+ port: str | int | None = None
205
+ succeeded: bool = False
206
+ closed: bool = False
207
+ failing: bool = False
208
+
209
+ @property
210
+ def type(self) -> SocketKind:
211
+ return self._type
212
+
213
+ @property
214
+ def family(self) -> AddressFamily: # pragma: no cover
215
+ return self._family
216
+
217
+ @property
218
+ def proto(self) -> int: # pragma: no cover
219
+ return self._proto
220
+
221
+ async def connect(self, sockaddr: tuple[str | int, str | int | None]) -> None:
222
+ self.ip = sockaddr[0]
223
+ self.port = sockaddr[1]
224
+ assert self.ip not in self.scenario.sockets
225
+ self.scenario.sockets[self.ip] = self
226
+ self.scenario.connect_times[self.ip] = trio.current_time()
227
+ delay, result = self.scenario.ip_dict[self.ip]
228
+ await trio.sleep(delay)
229
+ if result == "error":
230
+ raise OSError("sorry")
231
+ if result == "postconnect_fail":
232
+ self.failing = True
233
+ self.succeeded = True
234
+
235
+ def close(self) -> None:
236
+ self.closed = True
237
+
238
+ # called when SocketStream is constructed
239
+ def setsockopt(self, *args: object, **kwargs: object) -> None:
240
+ if self.failing:
241
+ # raise something that isn't OSError as SocketStream
242
+ # ignores those
243
+ raise KeyboardInterrupt
244
+
245
+
246
+ class Scenario(trio.abc.SocketFactory, trio.abc.HostnameResolver):
247
+ def __init__(
248
+ self,
249
+ port: int,
250
+ ip_list: Sequence[tuple[str, float, str]],
251
+ supported_families: set[AddressFamily],
252
+ ) -> None:
253
+ # ip_list have to be unique
254
+ ip_order = [ip for (ip, _, _) in ip_list]
255
+ assert len(set(ip_order)) == len(ip_list)
256
+ ip_dict: dict[str | int, tuple[float, str]] = {}
257
+ for ip, delay, result in ip_list:
258
+ assert delay >= 0
259
+ assert result in ["error", "success", "postconnect_fail"]
260
+ ip_dict[ip] = (delay, result)
261
+
262
+ self.port = port
263
+ self.ip_order = ip_order
264
+ self.ip_dict = ip_dict
265
+ self.supported_families = supported_families
266
+ self.socket_count = 0
267
+ self.sockets: dict[str | int, FakeSocket] = {}
268
+ self.connect_times: dict[str | int, float] = {}
269
+
270
+ def socket(
271
+ self,
272
+ family: AddressFamily | int | None = None,
273
+ type_: SocketKind | int | None = None,
274
+ proto: int | None = None,
275
+ ) -> SocketType:
276
+ assert isinstance(family, AddressFamily)
277
+ assert isinstance(type_, SocketKind)
278
+ assert proto is not None
279
+ if family not in self.supported_families:
280
+ raise OSError("pretending not to support this family")
281
+ self.socket_count += 1
282
+ return FakeSocket(self, family, type_, proto)
283
+
284
+ def _ip_to_gai_entry(self, ip: str) -> tuple[
285
+ AddressFamily,
286
+ SocketKind,
287
+ int,
288
+ str,
289
+ tuple[str, int, int, int] | tuple[str, int] | tuple[int, bytes],
290
+ ]:
291
+ sockaddr: tuple[str, int] | tuple[str, int, int, int] | tuple[int, bytes]
292
+ if ":" in ip:
293
+ family = trio.socket.AF_INET6
294
+ sockaddr = (ip, self.port, 0, 0)
295
+ else:
296
+ family = trio.socket.AF_INET
297
+ sockaddr = (ip, self.port)
298
+ return (family, SOCK_STREAM, IPPROTO_TCP, "", sockaddr)
299
+
300
+ async def getaddrinfo(
301
+ self,
302
+ host: bytes | None,
303
+ port: bytes | str | int | None,
304
+ family: int = -1,
305
+ type: int = -1,
306
+ proto: int = -1,
307
+ flags: int = -1,
308
+ ) -> list[
309
+ tuple[
310
+ AddressFamily,
311
+ SocketKind,
312
+ int,
313
+ str,
314
+ tuple[str, int, int, int] | tuple[str, int] | tuple[int, bytes],
315
+ ]
316
+ ]:
317
+ assert host == b"test.example.com"
318
+ assert port == self.port
319
+ assert family == trio.socket.AF_UNSPEC
320
+ assert type == trio.socket.SOCK_STREAM
321
+ assert proto == 0
322
+ assert flags == 0
323
+ return [self._ip_to_gai_entry(ip) for ip in self.ip_order]
324
+
325
+ async def getnameinfo(
326
+ self,
327
+ sockaddr: tuple[str, int] | tuple[str, int, int, int],
328
+ flags: int,
329
+ ) -> tuple[str, str]:
330
+ raise NotImplementedError
331
+
332
+ def check(self, succeeded: SocketType | None) -> None:
333
+ # sockets only go into self.sockets when connect is called; make sure
334
+ # all the sockets that were created did in fact go in there.
335
+ assert self.socket_count == len(self.sockets)
336
+
337
+ for ip, socket_ in self.sockets.items():
338
+ assert ip in self.ip_dict
339
+ if socket_ is not succeeded:
340
+ assert socket_.closed
341
+ assert socket_.port == self.port
342
+
343
+
344
+ async def run_scenario(
345
+ # The port to connect to
346
+ port: int,
347
+ # A list of
348
+ # (ip, delay, result)
349
+ # tuples, where delay is in seconds and result is "success" or "error"
350
+ # The ip's will be returned from getaddrinfo in this order, and then
351
+ # connect() calls to them will have the given result.
352
+ ip_list: Sequence[tuple[str, float, str]],
353
+ *,
354
+ # If False, AF_INET4/6 sockets error out on creation, before connect is
355
+ # even called.
356
+ ipv4_supported: bool = True,
357
+ ipv6_supported: bool = True,
358
+ # Normally, we return (winning_sock, scenario object)
359
+ # If this is True, we require there to be an exception, and return
360
+ # (exception, scenario object)
361
+ expect_error: tuple[type[BaseException], ...] | type[BaseException] = (),
362
+ happy_eyeballs_delay: float | None = 0.25,
363
+ local_address: str | None = None,
364
+ ) -> tuple[SocketType, Scenario] | tuple[BaseException, Scenario]:
365
+ supported_families = set()
366
+ if ipv4_supported:
367
+ supported_families.add(trio.socket.AF_INET)
368
+ if ipv6_supported:
369
+ supported_families.add(trio.socket.AF_INET6)
370
+ scenario = Scenario(port, ip_list, supported_families)
371
+ trio.socket.set_custom_hostname_resolver(scenario)
372
+ trio.socket.set_custom_socket_factory(scenario)
373
+
374
+ try:
375
+ stream = await open_tcp_stream(
376
+ "test.example.com",
377
+ port,
378
+ happy_eyeballs_delay=happy_eyeballs_delay,
379
+ local_address=local_address,
380
+ )
381
+ assert expect_error == ()
382
+ scenario.check(stream.socket)
383
+ return (stream.socket, scenario)
384
+ except AssertionError: # pragma: no cover
385
+ raise
386
+ except expect_error as exc:
387
+ scenario.check(None)
388
+ return (exc, scenario)
389
+
390
+
391
+ async def test_one_host_quick_success(autojump_clock: MockClock) -> None:
392
+ sock, _scenario = await run_scenario(80, [("1.2.3.4", 0.123, "success")])
393
+ assert isinstance(sock, FakeSocket)
394
+ assert sock.ip == "1.2.3.4"
395
+ assert trio.current_time() == 0.123
396
+
397
+
398
+ async def test_one_host_slow_success(autojump_clock: MockClock) -> None:
399
+ sock, _scenario = await run_scenario(81, [("1.2.3.4", 100, "success")])
400
+ assert isinstance(sock, FakeSocket)
401
+ assert sock.ip == "1.2.3.4"
402
+ assert trio.current_time() == 100
403
+
404
+
405
+ async def test_one_host_quick_fail(autojump_clock: MockClock) -> None:
406
+ exc, _scenario = await run_scenario(
407
+ 82,
408
+ [("1.2.3.4", 0.123, "error")],
409
+ expect_error=OSError,
410
+ )
411
+ assert isinstance(exc, OSError)
412
+ assert trio.current_time() == 0.123
413
+
414
+
415
+ async def test_one_host_slow_fail(autojump_clock: MockClock) -> None:
416
+ exc, _scenario = await run_scenario(
417
+ 83,
418
+ [("1.2.3.4", 100, "error")],
419
+ expect_error=OSError,
420
+ )
421
+ assert isinstance(exc, OSError)
422
+ assert trio.current_time() == 100
423
+
424
+
425
+ async def test_one_host_failed_after_connect(autojump_clock: MockClock) -> None:
426
+ exc, _scenario = await run_scenario(
427
+ 83,
428
+ [("1.2.3.4", 1, "postconnect_fail")],
429
+ expect_error=KeyboardInterrupt,
430
+ )
431
+ assert isinstance(exc, KeyboardInterrupt)
432
+
433
+
434
+ # With the default 0.250 second delay, the third attempt will win
435
+ async def test_basic_fallthrough(autojump_clock: MockClock) -> None:
436
+ sock, scenario = await run_scenario(
437
+ 80,
438
+ [
439
+ ("1.1.1.1", 1, "success"),
440
+ ("2.2.2.2", 1, "success"),
441
+ ("3.3.3.3", 0.2, "success"),
442
+ ],
443
+ )
444
+ assert isinstance(sock, FakeSocket)
445
+ assert sock.ip == "3.3.3.3"
446
+ # current time is default time + default time + connection time
447
+ assert trio.current_time() == (0.250 + 0.250 + 0.2)
448
+ assert scenario.connect_times == {
449
+ "1.1.1.1": 0,
450
+ "2.2.2.2": 0.250,
451
+ "3.3.3.3": 0.500,
452
+ }
453
+
454
+
455
+ async def test_early_success(autojump_clock: MockClock) -> None:
456
+ sock, scenario = await run_scenario(
457
+ 80,
458
+ [
459
+ ("1.1.1.1", 1, "success"),
460
+ ("2.2.2.2", 0.1, "success"),
461
+ ("3.3.3.3", 0.2, "success"),
462
+ ],
463
+ )
464
+ assert isinstance(sock, FakeSocket)
465
+ assert sock.ip == "2.2.2.2"
466
+ assert trio.current_time() == (0.250 + 0.1)
467
+ assert scenario.connect_times == {
468
+ "1.1.1.1": 0,
469
+ "2.2.2.2": 0.250,
470
+ # 3.3.3.3 was never even started
471
+ }
472
+
473
+
474
+ # With a 0.450 second delay, the first attempt will win
475
+ async def test_custom_delay(autojump_clock: MockClock) -> None:
476
+ sock, scenario = await run_scenario(
477
+ 80,
478
+ [
479
+ ("1.1.1.1", 1, "success"),
480
+ ("2.2.2.2", 1, "success"),
481
+ ("3.3.3.3", 0.2, "success"),
482
+ ],
483
+ happy_eyeballs_delay=0.450,
484
+ )
485
+ assert isinstance(sock, FakeSocket)
486
+ assert sock.ip == "1.1.1.1"
487
+ assert trio.current_time() == 1
488
+ assert scenario.connect_times == {
489
+ "1.1.1.1": 0,
490
+ "2.2.2.2": 0.450,
491
+ "3.3.3.3": 0.900,
492
+ }
493
+
494
+
495
+ async def test_none_default(autojump_clock: MockClock) -> None:
496
+ """Copy of test_basic_fallthrough, but specifying the delay =None"""
497
+ sock, scenario = await run_scenario(
498
+ 80,
499
+ [
500
+ ("1.1.1.1", 1, "success"),
501
+ ("2.2.2.2", 1, "success"),
502
+ ("3.3.3.3", 0.2, "success"),
503
+ ],
504
+ happy_eyeballs_delay=None,
505
+ )
506
+ assert isinstance(sock, FakeSocket)
507
+ assert sock.ip == "3.3.3.3"
508
+ # current time is default time + default time + connection time
509
+ assert trio.current_time() == (0.250 + 0.250 + 0.2)
510
+ assert scenario.connect_times == {
511
+ "1.1.1.1": 0,
512
+ "2.2.2.2": 0.250,
513
+ "3.3.3.3": 0.500,
514
+ }
515
+
516
+
517
+ async def test_custom_errors_expedite(autojump_clock: MockClock) -> None:
518
+ sock, scenario = await run_scenario(
519
+ 80,
520
+ [
521
+ ("1.1.1.1", 0.1, "error"),
522
+ ("2.2.2.2", 0.2, "error"),
523
+ ("3.3.3.3", 10, "success"),
524
+ # .25 is the default timeout
525
+ ("4.4.4.4", 0.25, "success"),
526
+ ],
527
+ )
528
+ assert isinstance(sock, FakeSocket)
529
+ assert sock.ip == "4.4.4.4"
530
+ assert trio.current_time() == (0.1 + 0.2 + 0.25 + 0.25)
531
+ assert scenario.connect_times == {
532
+ "1.1.1.1": 0,
533
+ "2.2.2.2": 0.1,
534
+ "3.3.3.3": 0.1 + 0.2,
535
+ "4.4.4.4": 0.1 + 0.2 + 0.25,
536
+ }
537
+
538
+
539
+ async def test_all_fail(autojump_clock: MockClock) -> None:
540
+ exc, scenario = await run_scenario(
541
+ 80,
542
+ [
543
+ ("1.1.1.1", 0.1, "error"),
544
+ ("2.2.2.2", 0.2, "error"),
545
+ ("3.3.3.3", 10, "error"),
546
+ ("4.4.4.4", 0.250, "error"),
547
+ ],
548
+ expect_error=OSError,
549
+ )
550
+ assert isinstance(exc, OSError)
551
+
552
+ subexceptions = (pytest.RaisesExc(OSError, match="^sorry$"),) * 4
553
+ assert pytest.RaisesGroup(
554
+ *subexceptions,
555
+ match="all attempts to connect to test.example.com:80 failed",
556
+ ).matches(exc.__cause__)
557
+
558
+ assert trio.current_time() == (0.1 + 0.2 + 10)
559
+ assert scenario.connect_times == {
560
+ "1.1.1.1": 0,
561
+ "2.2.2.2": 0.1,
562
+ "3.3.3.3": 0.1 + 0.2,
563
+ "4.4.4.4": 0.1 + 0.2 + 0.25,
564
+ }
565
+
566
+
567
+ async def test_multi_success(autojump_clock: MockClock) -> None:
568
+ sock, scenario = await run_scenario(
569
+ 80,
570
+ [
571
+ ("1.1.1.1", 0.5, "error"),
572
+ ("2.2.2.2", 10, "success"),
573
+ ("3.3.3.3", 10 - 1, "success"),
574
+ ("4.4.4.4", 10 - 2, "success"),
575
+ ("5.5.5.5", 0.5, "error"),
576
+ ],
577
+ happy_eyeballs_delay=1,
578
+ )
579
+ assert not scenario.sockets["1.1.1.1"].succeeded
580
+ assert (
581
+ scenario.sockets["2.2.2.2"].succeeded
582
+ or scenario.sockets["3.3.3.3"].succeeded
583
+ or scenario.sockets["4.4.4.4"].succeeded
584
+ )
585
+ assert not scenario.sockets["5.5.5.5"].succeeded
586
+ assert isinstance(sock, FakeSocket)
587
+ assert sock.ip in ["2.2.2.2", "3.3.3.3", "4.4.4.4"]
588
+ assert trio.current_time() == (0.5 + 10)
589
+ assert scenario.connect_times == {
590
+ "1.1.1.1": 0,
591
+ "2.2.2.2": 0.5,
592
+ "3.3.3.3": 1.5,
593
+ "4.4.4.4": 2.5,
594
+ "5.5.5.5": 3.5,
595
+ }
596
+
597
+
598
+ async def test_does_reorder(autojump_clock: MockClock) -> None:
599
+ sock, scenario = await run_scenario(
600
+ 80,
601
+ [
602
+ ("1.1.1.1", 10, "error"),
603
+ # This would win if we tried it first...
604
+ ("2.2.2.2", 1, "success"),
605
+ # But in fact we try this first, because of section 5.4
606
+ ("::3", 0.5, "success"),
607
+ ],
608
+ happy_eyeballs_delay=1,
609
+ )
610
+ assert isinstance(sock, FakeSocket)
611
+ assert sock.ip == "::3"
612
+ assert trio.current_time() == 1 + 0.5
613
+ assert scenario.connect_times == {
614
+ "1.1.1.1": 0,
615
+ "::3": 1,
616
+ }
617
+
618
+
619
+ async def test_handles_no_ipv4(autojump_clock: MockClock) -> None:
620
+ sock, scenario = await run_scenario(
621
+ 80,
622
+ # Here the ipv6 addresses fail at socket creation time, so the connect
623
+ # configuration doesn't matter
624
+ [
625
+ ("::1", 10, "success"),
626
+ ("2.2.2.2", 0, "success"),
627
+ ("::3", 0.1, "success"),
628
+ ("4.4.4.4", 0, "success"),
629
+ ],
630
+ happy_eyeballs_delay=1,
631
+ ipv4_supported=False,
632
+ )
633
+ assert isinstance(sock, FakeSocket)
634
+ assert sock.ip == "::3"
635
+ assert trio.current_time() == 1 + 0.1
636
+ assert scenario.connect_times == {
637
+ "::1": 0,
638
+ "::3": 1.0,
639
+ }
640
+
641
+
642
+ async def test_handles_no_ipv6(autojump_clock: MockClock) -> None:
643
+ sock, scenario = await run_scenario(
644
+ 80,
645
+ # Here the ipv6 addresses fail at socket creation time, so the connect
646
+ # configuration doesn't matter
647
+ [
648
+ ("::1", 0, "success"),
649
+ ("2.2.2.2", 10, "success"),
650
+ ("::3", 0, "success"),
651
+ ("4.4.4.4", 0.1, "success"),
652
+ ],
653
+ happy_eyeballs_delay=1,
654
+ ipv6_supported=False,
655
+ )
656
+ assert isinstance(sock, FakeSocket)
657
+ assert sock.ip == "4.4.4.4"
658
+ assert trio.current_time() == 1 + 0.1
659
+ assert scenario.connect_times == {
660
+ "2.2.2.2": 0,
661
+ "4.4.4.4": 1.0,
662
+ }
663
+
664
+
665
+ async def test_no_hosts(autojump_clock: MockClock) -> None:
666
+ exc, _scenario = await run_scenario(80, [], expect_error=OSError)
667
+ assert "no results found" in str(exc)
668
+
669
+
670
+ async def test_cancel(autojump_clock: MockClock) -> None:
671
+ with trio.move_on_after(5) as cancel_scope:
672
+ exc, scenario = await run_scenario(
673
+ 80,
674
+ [
675
+ ("1.1.1.1", 10, "success"),
676
+ ("2.2.2.2", 10, "success"),
677
+ ("3.3.3.3", 10, "success"),
678
+ ("4.4.4.4", 10, "success"),
679
+ ],
680
+ expect_error=BaseExceptionGroup,
681
+ )
682
+ assert isinstance(exc, BaseException)
683
+ # What comes out should be 1 or more Cancelled errors that all belong
684
+ # to this cancel_scope; this is the easiest way to check that
685
+ raise exc
686
+ assert cancel_scope.cancelled_caught
687
+
688
+ assert trio.current_time() == 5
689
+
690
+ # This should have been called already, but just to make sure, since the
691
+ # exception-handling logic in run_scenario is a bit complicated and the
692
+ # main thing we care about here is that all the sockets were cleaned up.
693
+ scenario.check(succeeded=None)