8000 Add tests for happy eyeballs and its internal workings. · python/cpython@d8b4492 · GitHub
[go: up one dir, main page]

Skip to content

Commit d8b4492

Browse files
Add tests for happy eyeballs and its internal workings.
One of the new tests may fail intermittently due to #86296.
1 parent 73e5180 commit d8b4492

File tree

2 files changed

+226
-0
lines changed

2 files changed

+226
-0
lines changed

Lib/test/test_asyncio/test_base_events.py

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,58 @@ def test_ipaddr_info_no_inet_pton(self, m_socket):
145145
socket.SOCK_STREAM,
146146
socket.IPPROTO_TCP))
147147

148+
def test_interleave_ipaddrs(self):
149+
addrinfos = [
150+
(socket.AF_INET6, 0, 0, '', ('2001:db8::1', 1)),
151+
(socket.AF_INET6, 0, 0, '', ('2001:db8::2', 2)),
152+
(socket.AF_INET6, 0, 0, '', ('2001:db8::3', 3)),
153+
(socket.AF_INET6, 0, 0, '', ('2001:db8::4', 4)),
154+
(socket.AF_INET, 0, 0, '', ('192.0.2.1', 5)),
155+
(socket.AF_INET, 0, 0, '', ('192.0.2.2', 6)),
156+
(socket.AF_INET, 0, 0, '', ('192.0.2.3', 7)),
157+
(socket.AF_INET, 0, 0, '', ('192.0.2.4', 8)),
158+
]
159+
160+
self.assertEqual(
161+
[
162+
(socket.AF_INET6, 0, 0, '', ('2001:db8::1', 1)),
163+
(socket.AF_INET, 0, 0, '', ('192.0.2.1', 5)),
164+
(socket.AF_INET6, 0, 0, '', ('2001:db8::2', 2)),
165+
(socket.AF_INET, 0, 0, '', ('192.0.2.2', 6)),
166+
(socket.AF_INET6, 0, 0, '', ('2001:db8::3', 3)),
167+
(socket.AF_INET, 0, 0, '', ('192.0.2.3', 7)),
168+
(socket.AF_INET6, 0, 0, '', ('2001:db8::4', 4)),
169+
(socket.AF_INET, 0, 0, '', ('192.0.2.4', 8)),
170+
],
171+
base_events._interleave_addrinfos(addrinfos)
172+
)
173+
174+
def test_interleave_ipaddrs_first_address_family_count(self):
175+
addrinfos = [
176+
(socket.AF_INET6, 0, 0, '', ('2001:db8::1', 1)),
177+
(socket.AF_INET6, 0, 0, '', ('2001:db8::2', 2)),
178+
(socket.AF_INET6, 0, 0, '', ('2001:db8::3', 3)),
179+
(socket.AF_INET6, 0, 0, '', ('2001:db8::4', 4)),
180+
(socket.AF_INET, 0, 0, '', ('192.0.2.1', 5)),
181+
(socket.AF_INET, 0, 0, '', ('192.0.2.2', 6)),
182+
(socket.AF_INET, 0, 0, '', ('192.0.2.3', 7)),
183+
(socket.AF_INET, 0, 0, '', ('192.0.2.4', 8)),
184+
]
185+
186+
self.assertEqual(
187+
[
188+
(socket.AF_INET6, 0, 0, '', ('2001:db8::1', 1)),
189+
(socket.AF_INET6, 0, 0, '', ('2001:db8::2', 2)),
190+
(socket.AF_INET, 0, 0, '', ('192.0.2.1', 5)),
191+
(socket.AF_INET6, 0, 0, '', ('2001:db8::3', 3)),
192+
(socket.AF_INET, 0, 0, '', ('192.0.2.2', 6)),
193+
(socket.AF_INET6, 0, 0, '', ('2001:db8::4', 4)),
194+
(socket.AF_INET, 0, 0, '', ('192.0.2.3', 7)),
195+
(socket.AF_INET, 0, 0, '', ('192.0.2.4', 8)),
196+
],
197+
base_events._interleave_addrinfos(addrinfos, 2)
198+
)
199+
148200

149201
class BaseEventLoopTests(test_utils.TestCase):
150202

@@ -1431,6 +1483,65 @@ def getaddrinfo_task(*args, **kwds):
14311483
self.assertRaises(
14321484
OSError, self.loop.run_until_complete, coro)
14331485

1486+
@unittest.skipUnless(socket_helper.IPV6_ENABLED, 'no IPv6 support')
1487+
@patch_socket
1488+
def test_create_connection_happy_eyeballs(self, m_socket):
1489+
1490+
class MyProto(asyncio.Protocol):
1491+
pass
1492+
1493+
async def getaddrinfo(*args, **kw):
1494+
return [(socket.AF_INET6, 0, 0, '', ('2001:db8::1', 1)),
1495+
(socket.AF_INET, 0, 0, '', ('192.0.2.1', 5))]
1496+
1497+
async def sock_connect(sock, address):
1498+
if address[0] == '2001:db8::1':
1499+
await asyncio.sleep(1)
1500+
sock.connect(address)
1501+
1502+
self.loop._add_reader = mock.Mock()
1503+
self.loop._add_writer = mock.Mock()
1504+
self.loop.getaddrinfo = getaddrinfo
1505+
self.loop.sock_connect = sock_connect
1506+
1507+
coro = self.loop.create_connection(MyProto, 'example.com', 80, happy_eyeballs_delay=0.3)
1508+
transport, protocol = self.loop.run_until_complete(coro)
1509+
try:
1510+
sock = transport._sock
1511+
sock.connect.assert_called_with(('192.0.2.1', 5))
1512+
finally:
1513+
transport.close()
1514+
test_utils.run_briefly(self.loop) # allow transport to close
1515+
1516+
@patch_socket
1517+
def test_create_connection_happy_eyeballs_ipv4_only(self, m_socket):
1518+
1519+
class MyProto(asyncio.Protocol):
1520+
pass
1521+
1522+
async def getaddrinfo(*args, **kw):
1523+
return [(socket.AF_INET, 0, 0, '', ('192.0.2.1', 5)),
1524+
(socket.AF_INET, 0, 0, '', ('192.0.2.2', 6))]
1525+
1526+
async def sock_connect(sock, address):
1527+
if address[0] == '192.0.2.1':
1528+
await asyncio.sleep(1)
1529+
sock.connect(address)
1530+
1531+
self.loop._add_reader = mock.Mock()
1532+
self.loop._add_writer = mock.Mock()
1533+
self.loop.getaddrinfo = getaddrinfo
1534+
self.loop.sock_connect = sock_connect
1535+
1536+
coro = self.loop.create_connection(MyProto, 'example.com', 80, happy_eyeballs_delay=0.3)
1537+
transport, protocol = self.loop.run_until_complete(coro)
1538+
try:
1539+
sock = transport._sock
1540+
sock.connect.assert_called_with(('192.0.2.2', 6))
1541+
finally:
1542+
transport.close()
1543+
test_utils.run_briefly(self.loop) # allow transport to close
1544+
14341545
@patch_socket
14351546
def test_create_connection_bluetooth(self, m_socket):
14361547
# See http://bugs.python.org/issue27136, fallback to getaddrinfo when
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import asyncio
2+
import functools
3+
import unittest
4+
from asyncio.staggered import staggered_race
5+
6+
7+
# To prevent a warning "test altered the execution environment"
8+
def tearDownModule():
9+
asyncio.set_event_loop_policy(None)
10+
11+
12+
class TestStaggered(unittest.IsolatedAsyncioTestCase):
13+
@staticmethod
14+
async def waiting_coroutine(return_value, wait_seconds, success):
15+
await asyncio.sleep(wait_seconds)
16+
if success:
17+
return return_value
18+
raise RuntimeError(str(return_value))
19+
20+
def get_waiting_coroutine_factory(self, return_value, wait_seconds, success):
21+
return functools.partial(self.waiting_coroutine, return_value, wait_seconds, success)
22+
23+
async def test_single_success(self):
24+
winner_result, winner_idx, exceptions = await staggered_race(
25+
(self.get_waiting_coroutine_factory(0, 0.1, True),),
26+
0.1,
27+
)
28+
self.assertEqual(winner_result, 0)
29+
self.assertEqual(winner_idx, 0)
30+
self.assertEqual(len(exceptions), 1)
31+
self.assertIsNone(exceptions[0])
32+
33+
async def test_single_fail(self):
34+
winner_result, winner_idx, exceptions = await staggered_race(
35+
(self.get_waiting_coroutine_factory(0, 0.1, False),),
36+
0.1,
37+
)
38+
self.assertEqual(winner_result, None)
39+
self.assertEqual(winner_idx, None)
40+
self.assertEqual(len(exceptions), 1)
41+
self.assertIsInstance(exceptions[0], RuntimeError)
42+
43+
async def test_first_win(self):
44+
winner_result, winner_idx, exceptions = await staggered_race(
45+
(
46+
self.get_waiting_coroutine_factory(0, 0.2, True),
47+
self.get_waiting_coroutine_factory(1, 0.2, True),
48+
),
49+
0.1,
50+
)
51+
self.assertEqual(winner_result, 0)
52+
self.assertEqual(winner_idx, 0)
53+
self.assertEqual(len(exceptions), 2)
54+
self.assertIsNone(exceptions[0])
55+
self.assertIsInstance(exceptions[1], asyncio.CancelledError)
56+
57+
async def test_second_win(self):
58+
winner_result, winner_idx, exceptions = await staggered_race(
59+
(
60+
self.get_waiting_coroutine_factory(0, 0.3, True),
61+
self.get_waiting_coroutine_factory(1, 0.1, True),
62+
),
63+
0.1,
64+
)
65+
self.assertEqual(winner_result, 1)
66+
self.assertEqual(winner_idx, 1)
67+
self.assertEqual(len(exceptions), 2)
68+
self.assertIsInstance(exceptions[0], asyncio.CancelledError)
69+
self.assertIsNone(exceptions[1])
70+
71+
async def test_first_fail(self):
72+
winner_result, winner_idx, exceptions = await staggered_race(
73+
(
74+
self.get_waiting_coroutine_factory(0, 0.2, False),
75+
self.get_waiting_coroutine_factory(1, 0.2, True),
76+
),
77+
0.1,
78+
)
79+
self.assertEqual(winner_result, 1)
80+
self.assertEqual(winner_idx, 1)
81+
self.assertEqual(len(exceptions), 2)
82+
self.assertIsInstance(exceptions[0], RuntimeError)
83+
self.assertIsNone(exceptions[1])
84+
85+
async def test_second_fail(self):
86+
winner_result, winner_idx, exceptions = await staggered_race(
87+
(
88+
self.get_waiting_coroutine_factory(0, 0.2, True),
89+
self.get_waiting_coroutine_factory(1, 0, False),
90+
),
91+
0.1,
92+
)
93+
self.assertEqual(winner_result, 0)
94+
self.assertEqual(winner_idx, 0)
95+
self.assertEqual(len(exceptions), 2)
96+
self.assertIsNone(exceptions[0])
97+
self.assertIsInstance(exceptions[1], RuntimeError)
98+
99+
async def test_simultaneous_success_fail(self):
100+
# There's a potential race condition here:
101+
# https://github.com/python/cpython/issues/86296
102+
for _ in range(50):
103+
winner_result, winner_idx, exceptions = await staggered_race(
104+
(
105+
self.get_waiting_coroutine_factory(0, 0.1, True),
106+
self.get_waiting_coroutine_factory(1, 0.05, False),
107+
self.get_waiting_coroutine_factory(2, 0.05, True)
108+
),
109+
0.05,
110+
)
111+
self.assertEqual(winner_result, 0)
112+
self.assertEqual(winner_idx, 0)
113+
114+
115+

0 commit comments

Comments
 (0)
1241
0