mirror of https://github.com/MagicStack/uvloop.git
426 lines
15 KiB
Python
426 lines
15 KiB
Python
import asyncio
|
|
import os
|
|
import socket
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
import uuid
|
|
|
|
from uvloop import _testbase as tb
|
|
|
|
|
|
class MyDatagramProto(asyncio.DatagramProtocol):
|
|
done = None
|
|
|
|
def __init__(self, loop=None):
|
|
self.state = 'INITIAL'
|
|
self.nbytes = 0
|
|
if loop is not None:
|
|
self.done = asyncio.Future(loop=loop)
|
|
|
|
def connection_made(self, transport):
|
|
self.transport = transport
|
|
assert self.state == 'INITIAL', self.state
|
|
self.state = 'INITIALIZED'
|
|
|
|
def datagram_received(self, data, addr):
|
|
assert self.state == 'INITIALIZED', self.state
|
|
self.nbytes += len(data)
|
|
|
|
def error_received(self, exc):
|
|
assert self.state == 'INITIALIZED', self.state
|
|
raise exc
|
|
|
|
def connection_lost(self, exc):
|
|
assert self.state == 'INITIALIZED', self.state
|
|
self.state = 'CLOSED'
|
|
if self.done:
|
|
self.done.set_result(None)
|
|
|
|
|
|
class _TestUDP:
|
|
|
|
def _test_create_datagram_endpoint_addrs(self, family, lc_addr):
|
|
class TestMyDatagramProto(MyDatagramProto):
|
|
def __init__(inner_self):
|
|
super().__init__(loop=self.loop)
|
|
|
|
def datagram_received(self, data, addr):
|
|
super().datagram_received(data, addr)
|
|
self.transport.sendto(b'resp:' + data, addr)
|
|
|
|
coro = self.loop.create_datagram_endpoint(
|
|
TestMyDatagramProto,
|
|
local_addr=lc_addr,
|
|
family=family)
|
|
|
|
s_transport, server = self.loop.run_until_complete(coro)
|
|
|
|
remote_addr = s_transport.get_extra_info('sockname')
|
|
host, port, *_ = remote_addr
|
|
|
|
self.assertIsInstance(server, TestMyDatagramProto)
|
|
self.assertEqual('INITIALIZED', server.state)
|
|
self.assertIs(server.transport, s_transport)
|
|
|
|
extra = {}
|
|
if hasattr(socket, 'SO_REUSEPORT') and \
|
|
sys.version_info[:3] >= (3, 5, 1):
|
|
extra['reuse_port'] = True
|
|
|
|
coro = self.loop.create_datagram_endpoint(
|
|
lambda: MyDatagramProto(loop=self.loop),
|
|
family=family,
|
|
remote_addr=(host, port),
|
|
**extra)
|
|
transport, client = self.loop.run_until_complete(coro)
|
|
|
|
self.assertIsInstance(client, MyDatagramProto)
|
|
self.assertEqual('INITIALIZED', client.state)
|
|
self.assertIs(client.transport, transport)
|
|
|
|
transport.sendto(b'xxx')
|
|
tb.run_until(self.loop, lambda: server.nbytes)
|
|
self.assertEqual(3, server.nbytes)
|
|
tb.run_until(self.loop, lambda: client.nbytes)
|
|
|
|
# received
|
|
self.assertEqual(8, client.nbytes)
|
|
|
|
# https://github.com/MagicStack/uvloop/issues/319
|
|
# uvloop should behave the same as asyncio when given remote_addr
|
|
transport.sendto(b'xxx', remote_addr)
|
|
tb.run_until(
|
|
self.loop, lambda: server.nbytes > 3 or client.done.done())
|
|
self.assertEqual(6, server.nbytes)
|
|
tb.run_until(self.loop, lambda: client.nbytes > 8)
|
|
|
|
# received
|
|
self.assertEqual(16, client.nbytes)
|
|
|
|
# reject sendto with a different port
|
|
with self.assertRaisesRegex(
|
|
ValueError, "Invalid address.*" + repr(remote_addr)
|
|
):
|
|
bad_addr = list(remote_addr)
|
|
bad_addr[1] += 1
|
|
bad_addr = tuple(bad_addr)
|
|
transport.sendto(b"xxx", bad_addr)
|
|
|
|
# reject sento with unresolved hostname
|
|
if remote_addr[0] != lc_addr[0]:
|
|
with self.assertRaisesRegex(
|
|
ValueError, "Invalid address.*" + repr(remote_addr)
|
|
):
|
|
bad_addr = list(remote_addr)
|
|
bad_addr[0] = lc_addr[0]
|
|
bad_addr = tuple(bad_addr)
|
|
transport.sendto(b"xxx", bad_addr)
|
|
|
|
# extra info is available
|
|
self.assertIsNotNone(transport.get_extra_info('sockname'))
|
|
|
|
# close connection
|
|
transport.close()
|
|
self.loop.run_until_complete(client.done)
|
|
self.assertEqual('CLOSED', client.state)
|
|
server.transport.close()
|
|
self.loop.run_until_complete(server.done)
|
|
|
|
def test_create_datagram_endpoint_addrs_ipv4(self):
|
|
self._test_create_datagram_endpoint_addrs(
|
|
socket.AF_INET, ('127.0.0.1', 0))
|
|
|
|
def test_create_datagram_endpoint_addrs_ipv4_nameaddr(self):
|
|
self._test_create_datagram_endpoint_addrs(
|
|
socket.AF_INET, ('localhost', 0))
|
|
|
|
def _test_create_datagram_endpoint_addrs_ipv6(self):
|
|
self._test_create_datagram_endpoint_addrs(
|
|
socket.AF_INET6, ('::1', 0))
|
|
|
|
def test_create_datagram_endpoint_ipv6_family(self):
|
|
class TestMyDatagramProto(MyDatagramProto):
|
|
def __init__(inner_self):
|
|
super().__init__(loop=self.loop)
|
|
|
|
def datagram_received(self, data, addr):
|
|
super().datagram_received(data, addr)
|
|
self.transport.sendto(b'resp:' + data, addr)
|
|
|
|
coro = self.loop.create_datagram_endpoint(
|
|
TestMyDatagramProto, local_addr=None, family=socket.AF_INET6)
|
|
s_transport = None
|
|
try:
|
|
s_transport, server = self.loop.run_until_complete(coro)
|
|
finally:
|
|
if s_transport:
|
|
s_transport.close()
|
|
# let it close
|
|
self.loop.run_until_complete(
|
|
asyncio.sleep(0.1))
|
|
|
|
def test_create_datagram_endpoint_sock(self):
|
|
sock = None
|
|
local_address = ('127.0.0.1', 0)
|
|
infos = self.loop.run_until_complete(
|
|
self.loop.getaddrinfo(
|
|
*local_address, type=socket.SOCK_DGRAM))
|
|
for family, type, proto, cname, address in infos:
|
|
try:
|
|
sock = socket.socket(family=family, type=type, proto=proto)
|
|
sock.setblocking(False)
|
|
sock.bind(address)
|
|
except Exception:
|
|
pass
|
|
else:
|
|
break
|
|
else:
|
|
assert False, 'Can not create socket.'
|
|
|
|
with sock:
|
|
try:
|
|
f = self.loop.create_datagram_endpoint(
|
|
lambda: MyDatagramProto(loop=self.loop), sock=sock)
|
|
except TypeError as ex:
|
|
# asyncio in 3.5.0 doesn't have the 'sock' argument
|
|
if 'got an unexpected keyword argument' not in ex.args[0]:
|
|
raise
|
|
else:
|
|
tr, pr = self.loop.run_until_complete(f)
|
|
self.assertIsInstance(pr, MyDatagramProto)
|
|
tr.close()
|
|
self.loop.run_until_complete(pr.done)
|
|
|
|
@unittest.skipIf(sys.version_info < (3, 5, 1),
|
|
"asyncio in 3.5.0 doesn't have the 'sock' argument")
|
|
def test_create_datagram_endpoint_sock_unix_domain(self):
|
|
|
|
class Proto(asyncio.DatagramProtocol):
|
|
done = None
|
|
|
|
def __init__(self, loop):
|
|
self.state = 'INITIAL'
|
|
self.addrs = set()
|
|
self.done = asyncio.Future(loop=loop)
|
|
self.data = b''
|
|
|
|
def connection_made(self, transport):
|
|
self.transport = transport
|
|
assert self.state == 'INITIAL', self.state
|
|
self.state = 'INITIALIZED'
|
|
|
|
def datagram_received(self, data, addr):
|
|
assert self.state == 'INITIALIZED', self.state
|
|
self.addrs.add(addr)
|
|
self.data += data
|
|
if self.data == b'STOP' and not self.done.done():
|
|
self.done.set_result(True)
|
|
|
|
def error_received(self, exc):
|
|
assert self.state == 'INITIALIZED', self.state
|
|
if not self.done.done():
|
|
self.done.set_exception(exc or RuntimeError())
|
|
|
|
def connection_lost(self, exc):
|
|
assert self.state == 'INITIALIZED', self.state
|
|
self.state = 'CLOSED'
|
|
if self.done and not self.done.done():
|
|
self.done.set_result(None)
|
|
|
|
tmp_file = os.path.join(tempfile.gettempdir(), str(uuid.uuid4()))
|
|
sock = socket.socket(socket.AF_UNIX, type=socket.SOCK_DGRAM)
|
|
sock.bind(tmp_file)
|
|
|
|
with sock:
|
|
pr = Proto(loop=self.loop)
|
|
f = self.loop.create_datagram_endpoint(
|
|
lambda: pr, sock=sock)
|
|
tr, pr_prime = self.loop.run_until_complete(f)
|
|
self.assertIs(pr, pr_prime)
|
|
|
|
tmp_file2 = os.path.join(tempfile.gettempdir(), str(uuid.uuid4()))
|
|
sock2 = socket.socket(socket.AF_UNIX, type=socket.SOCK_DGRAM)
|
|
sock2.bind(tmp_file2)
|
|
|
|
with sock2:
|
|
f2 = self.loop.create_datagram_endpoint(
|
|
asyncio.DatagramProtocol, sock=sock2)
|
|
tr2, pr2 = self.loop.run_until_complete(f2)
|
|
|
|
tr2.sendto(b'STOP', tmp_file)
|
|
|
|
self.loop.run_until_complete(pr.done)
|
|
|
|
tr.close()
|
|
tr2.close()
|
|
|
|
# Let transports close
|
|
self.loop.run_until_complete(asyncio.sleep(0.2))
|
|
|
|
self.assertIn(tmp_file2, pr.addrs)
|
|
|
|
def test_create_datagram_1(self):
|
|
server_addr = ('127.0.0.1', 8888)
|
|
client_addr = ('127.0.0.1', 0)
|
|
|
|
async def run():
|
|
server_transport, client_protocol = \
|
|
await self.loop.create_datagram_endpoint(
|
|
asyncio.DatagramProtocol,
|
|
local_addr=server_addr)
|
|
|
|
client_transport, client_conn = \
|
|
await self.loop.create_datagram_endpoint(
|
|
asyncio.DatagramProtocol,
|
|
remote_addr=server_addr,
|
|
local_addr=client_addr)
|
|
|
|
client_transport.close()
|
|
server_transport.close()
|
|
|
|
# let transports close
|
|
await asyncio.sleep(0.1)
|
|
|
|
self.loop.run_until_complete(run())
|
|
|
|
def test_socketpair(self):
|
|
peername = asyncio.Future(loop=self.loop)
|
|
|
|
class Proto(MyDatagramProto):
|
|
def datagram_received(self, data, addr):
|
|
super().datagram_received(data, addr)
|
|
peername.set_result(addr)
|
|
|
|
s1, s2 = socket.socketpair(socket.AF_UNIX, socket.SOCK_DGRAM, 0)
|
|
|
|
with s1, s2:
|
|
try:
|
|
f = self.loop.create_datagram_endpoint(
|
|
lambda: Proto(loop=self.loop), sock=s1)
|
|
except TypeError as ex:
|
|
# asyncio in 3.5.0 doesn't have the 'sock' argument
|
|
if 'got an unexpected keyword argument' not in ex.args[0]:
|
|
raise
|
|
else:
|
|
tr, pr = self.loop.run_until_complete(f)
|
|
self.assertIsInstance(pr, Proto)
|
|
|
|
s2.send(b'hello, socketpair')
|
|
addr = self.loop.run_until_complete(
|
|
asyncio.wait_for(peername, 1, loop=self.loop))
|
|
if sys.platform.startswith('linux'):
|
|
self.assertEqual(addr, None)
|
|
else:
|
|
self.assertEqual(addr, '')
|
|
self.assertEqual(pr.nbytes, 17)
|
|
|
|
if not self.is_asyncio_loop():
|
|
# asyncio doesn't support sendto(xx) on UDP sockets
|
|
# https://git.io/Jfqbw
|
|
data = b'from uvloop'
|
|
tr.sendto(data)
|
|
result = self.loop.run_until_complete(asyncio.wait_for(
|
|
self.loop.run_in_executor(None, s2.recv, 1024),
|
|
1, loop=self.loop))
|
|
self.assertEqual(data, result)
|
|
|
|
tr.close()
|
|
self.loop.run_until_complete(pr.done)
|
|
|
|
def _skip_create_datagram_endpoint_reuse_addr(self):
|
|
if self.implementation == 'asyncio':
|
|
if sys.version_info[:2] >= (3, 11):
|
|
raise unittest.SkipTest()
|
|
if (3, 8, 0) <= sys.version_info < (3, 8, 1):
|
|
raise unittest.SkipTest()
|
|
if (3, 7, 0) <= sys.version_info < (3, 7, 6):
|
|
raise unittest.SkipTest()
|
|
if sys.version_info < (3, 6, 10):
|
|
raise unittest.SkipTest()
|
|
|
|
def test_create_datagram_endpoint_reuse_address_error(self):
|
|
# bpo-37228: Ensure that explicit passing of `reuse_address=True`
|
|
# raises an error, as it is not safe to use SO_REUSEADDR when using UDP
|
|
|
|
self._skip_create_datagram_endpoint_reuse_addr()
|
|
|
|
coro = self.loop.create_datagram_endpoint(
|
|
lambda: MyDatagramProto(loop=self.loop),
|
|
local_addr=('127.0.0.1', 0),
|
|
reuse_address=True)
|
|
|
|
with self.assertRaises(ValueError):
|
|
self.loop.run_until_complete(coro)
|
|
|
|
def test_create_datagram_endpoint_reuse_address_warning(self):
|
|
# bpo-37228: Deprecate *reuse_address* parameter
|
|
|
|
self._skip_create_datagram_endpoint_reuse_addr()
|
|
|
|
coro = self.loop.create_datagram_endpoint(
|
|
lambda: MyDatagramProto(loop=self.loop),
|
|
local_addr=('127.0.0.1', 0),
|
|
reuse_address=False)
|
|
|
|
with self.assertWarns(DeprecationWarning):
|
|
tr, pr = self.loop.run_until_complete(coro)
|
|
|
|
tr.close()
|
|
self.loop.run_until_complete(pr.done)
|
|
|
|
|
|
class Test_UV_UDP(_TestUDP, tb.UVTestCase):
|
|
|
|
def test_create_datagram_endpoint_wrong_sock(self):
|
|
sock = socket.socket(socket.AF_INET)
|
|
with sock:
|
|
coro = self.loop.create_datagram_endpoint(lambda: None, sock=sock)
|
|
with self.assertRaisesRegex(ValueError,
|
|
'A UDP Socket was expected'):
|
|
self.loop.run_until_complete(coro)
|
|
|
|
def test_udp_sendto_dns(self):
|
|
coro = self.loop.create_datagram_endpoint(
|
|
asyncio.DatagramProtocol,
|
|
local_addr=('127.0.0.1', 0),
|
|
family=socket.AF_INET)
|
|
|
|
s_transport, server = self.loop.run_until_complete(coro)
|
|
|
|
with self.assertRaisesRegex(ValueError, 'DNS lookup'):
|
|
s_transport.sendto(b'aaaa', ('example.com', 80))
|
|
|
|
with self.assertRaisesRegex(ValueError, 'socket family mismatch'):
|
|
s_transport.sendto(b'aaaa', ('::1', 80))
|
|
|
|
s_transport.close()
|
|
self.loop.run_until_complete(asyncio.sleep(0.01))
|
|
|
|
def test_send_after_close(self):
|
|
coro = self.loop.create_datagram_endpoint(
|
|
asyncio.DatagramProtocol,
|
|
local_addr=('127.0.0.1', 0),
|
|
family=socket.AF_INET)
|
|
|
|
s_transport, _ = self.loop.run_until_complete(coro)
|
|
|
|
s_transport.close()
|
|
s_transport.sendto(b'aaaa', ('127.0.0.1', 80))
|
|
self.loop.run_until_complete(asyncio.sleep(0.01))
|
|
s_transport.sendto(b'aaaa', ('127.0.0.1', 80))
|
|
|
|
@unittest.skipUnless(tb.has_IPv6, 'no IPv6')
|
|
def test_create_datagram_endpoint_addrs_ipv6(self):
|
|
self._test_create_datagram_endpoint_addrs_ipv6()
|
|
|
|
|
|
class Test_AIO_UDP(_TestUDP, tb.AIOTestCase):
|
|
@unittest.skipUnless(tb.has_IPv6, 'no IPv6')
|
|
@unittest.skipIf(
|
|
sys.version_info[:3] < (3, 6, 7) or sys.version_info[:3] == (3, 7, 0),
|
|
'bpo-27500: bug fixed in Python 3.6.7, 3.7.1 and above.',
|
|
)
|
|
def test_create_datagram_endpoint_addrs_ipv6(self):
|
|
self._test_create_datagram_endpoint_addrs_ipv6()
|