@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,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)
|