Current section

Files

Jump to
erlang_python priv tests test_tcp.py
Raw

priv/tests/test_tcp.py

# Copyright 2026 Benoit Chesneau
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
TCP protocol tests adapted from uvloop's test_tcp.py.
These tests verify high-level TCP operations:
- create_server
- create_connection
- Transport and Protocol interactions
- Data transmission and flow control
"""
import asyncio
import socket
import threading
import time
import unittest
from . import _testbase as tb
class _TestCreateServer:
"""Tests for create_server functionality."""
def test_create_server_basic(self):
"""Test basic TCP server creation."""
connections = []
class ServerProtocol(asyncio.Protocol):
def connection_made(self, transport):
connections.append(transport)
transport.write(b'welcome')
def data_received(self, data):
pass
def connection_lost(self, exc):
pass
async def main():
server = await self.loop.create_server(
ServerProtocol, '127.0.0.1', 0
)
port = server.sockets[0].getsockname()[1]
# Connect a client
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setblocking(False)
await self.loop.sock_connect(sock, ('127.0.0.1', port))
data = await self.loop.sock_recv(sock, 1024)
sock.close()
server.close()
await server.wait_closed()
return data
result = self.loop.run_until_complete(main())
self.assertEqual(result, b'welcome')
self.assertEqual(len(connections), 1)
def test_create_server_multiple_clients(self):
"""Test server handling multiple clients."""
connections = []
received_data = []
class ServerProtocol(asyncio.Protocol):
def connection_made(self, transport):
self.transport = transport
connections.append(transport)
def data_received(self, data):
received_data.append(data)
self.transport.write(b'echo:' + data)
def connection_lost(self, exc):
pass
async def main():
server = await self.loop.create_server(
ServerProtocol, '127.0.0.1', 0
)
port = server.sockets[0].getsockname()[1]
async def client(msg):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setblocking(False)
await self.loop.sock_connect(sock, ('127.0.0.1', port))
await self.loop.sock_sendall(sock, msg)
data = await self.loop.sock_recv(sock, 1024)
sock.close()
return data
results = await asyncio.gather(
client(b'msg1'),
client(b'msg2'),
client(b'msg3'),
)
server.close()
await server.wait_closed()
return results
results = self.loop.run_until_complete(main())
self.assertEqual(len(results), 3)
self.assertEqual(len(connections), 3)
self.assertEqual(sorted(received_data), [b'msg1', b'msg2', b'msg3'])
def test_create_server_close_during_accept(self):
"""Test closing server during accept."""
async def main():
server = await self.loop.create_server(
asyncio.Protocol, '127.0.0.1', 0
)
self.assertTrue(server.is_serving())
server.close()
await server.wait_closed()
self.assertFalse(server.is_serving())
self.loop.run_until_complete(main())
def test_create_server_reuse_address(self):
"""Test server with reuse_address option."""
async def main():
server1 = await self.loop.create_server(
asyncio.Protocol, '127.0.0.1', 0,
reuse_address=True
)
port = server1.sockets[0].getsockname()[1]
server1.close()
await server1.wait_closed()
# Should be able to bind to same port quickly
server2 = await self.loop.create_server(
asyncio.Protocol, '127.0.0.1', port,
reuse_address=True
)
server2.close()
await server2.wait_closed()
self.loop.run_until_complete(main())
def test_create_server_start_serving_false(self):
"""Test server with start_serving=False."""
class ServerProtocol(asyncio.Protocol):
pass
async def main():
server = await self.loop.create_server(
ServerProtocol, '127.0.0.1', 0,
start_serving=False
)
self.assertFalse(server.is_serving())
await server.start_serving()
self.assertTrue(server.is_serving())
server.close()
await server.wait_closed()
self.loop.run_until_complete(main())
class _TestCreateConnection:
"""Tests for create_connection functionality."""
def test_create_connection_basic(self):
"""Test basic TCP connection."""
port = tb.find_free_port()
received = []
class ServerProtocol(asyncio.Protocol):
def connection_made(self, transport):
self.transport = transport
def data_received(self, data):
received.append(data)
self.transport.write(b'echo:' + data)
self.transport.close()
async def main():
server = await self.loop.create_server(
ServerProtocol, '127.0.0.1', port
)
class ClientProtocol(asyncio.Protocol):
def __init__(self):
self.received = []
self.done = asyncio.get_event_loop().create_future()
def connection_made(self, transport):
self.transport = transport
transport.write(b'hello')
def data_received(self, data):
self.received.append(data)
def connection_lost(self, exc):
if not self.done.done():
self.done.set_result(self.received)
transport, protocol = await self.loop.create_connection(
ClientProtocol, '127.0.0.1', port
)
result = await protocol.done
server.close()
await server.wait_closed()
return result
result = self.loop.run_until_complete(main())
self.assertEqual(result, [b'echo:hello'])
self.assertEqual(received, [b'hello'])
def test_create_connection_with_existing_socket(self):
"""Test create_connection with existing socket."""
port = tb.find_free_port()
class ServerProtocol(asyncio.Protocol):
def connection_made(self, transport):
transport.write(b'hello')
transport.close()
async def main():
server = await self.loop.create_server(
ServerProtocol, '127.0.0.1', port
)
# Create and connect socket manually
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setblocking(False)
await self.loop.sock_connect(sock, ('127.0.0.1', port))
class ClientProtocol(asyncio.Protocol):
def __init__(self):
self.data = bytearray()
self.done = asyncio.get_event_loop().create_future()
def data_received(self, data):
self.data.extend(data)
def connection_lost(self, exc):
if not self.done.done():
self.done.set_result(bytes(self.data))
# Pass existing socket
transport, protocol = await self.loop.create_connection(
ClientProtocol, sock=sock
)
result = await protocol.done
server.close()
await server.wait_closed()
return result
result = self.loop.run_until_complete(main())
self.assertEqual(result, b'hello')
class _TestTransportProtocol:
"""Tests for Transport and Protocol interactions."""
def test_protocol_callbacks(self):
"""Test protocol callback sequence."""
callbacks = []
class TestProtocol(asyncio.Protocol):
def connection_made(self, transport):
callbacks.append('connection_made')
self.transport = transport
def data_received(self, data):
callbacks.append(f'data_received:{data}')
def eof_received(self):
callbacks.append('eof_received')
def connection_lost(self, exc):
callbacks.append(f'connection_lost:{exc}')
async def main():
server = await self.loop.create_server(
TestProtocol, '127.0.0.1', 0
)
port = server.sockets[0].getsockname()[1]
# Connect and send data
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('127.0.0.1', port))
sock.sendall(b'hello')
time.sleep(0.05) # Give server time to process
sock.close()
await asyncio.sleep(0.1)
server.close()
await server.wait_closed()
self.loop.run_until_complete(main())
self.assertIn('connection_made', callbacks)
self.assertTrue(any('data_received' in c for c in callbacks))
def test_transport_write(self):
"""Test transport write operation."""
received = bytearray()
done = threading.Event()
def server_thread(port):
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind(('127.0.0.1', port))
server.listen(1)
server.settimeout(5)
conn, _ = server.accept()
while True:
data = conn.recv(1024)
if not data:
break
received.extend(data)
conn.close()
server.close()
done.set()
port = tb.find_free_port()
thread = threading.Thread(target=server_thread, args=(port,))
thread.start()
time.sleep(0.05)
class ClientProtocol(asyncio.Protocol):
def connection_made(self, transport):
transport.write(b'hello ')
transport.write(b'world')
transport.close()
async def main():
await self.loop.create_connection(
ClientProtocol, '127.0.0.1', port
)
await asyncio.sleep(0.1)
self.loop.run_until_complete(main())
done.wait(timeout=5)
thread.join(timeout=5)
self.assertEqual(bytes(received), b'hello world')
def test_transport_close(self):
"""Test transport close operation."""
closed = []
class TestProtocol(asyncio.Protocol):
def connection_made(self, transport):
self.transport = transport
def connection_lost(self, exc):
closed.append(exc)
async def main():
server = await self.loop.create_server(
TestProtocol, '127.0.0.1', 0
)
port = server.sockets[0].getsockname()[1]
# Connect
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('127.0.0.1', port))
sock.close()
await asyncio.sleep(0.1)
server.close()
await server.wait_closed()
self.loop.run_until_complete(main())
# connection_lost should be called
self.assertEqual(len(closed), 1)
def test_transport_get_extra_info(self):
"""Test transport get_extra_info method."""
extra_info = {}
class TestProtocol(asyncio.Protocol):
def connection_made(self, transport):
extra_info['socket'] = transport.get_extra_info('socket')
extra_info['sockname'] = transport.get_extra_info('sockname')
extra_info['peername'] = transport.get_extra_info('peername')
extra_info['unknown'] = transport.get_extra_info('unknown', 'default')
transport.close()
async def main():
server = await self.loop.create_server(
TestProtocol, '127.0.0.1', 0
)
port = server.sockets[0].getsockname()[1]
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('127.0.0.1', port))
await asyncio.sleep(0.1)
sock.close()
server.close()
await server.wait_closed()
self.loop.run_until_complete(main())
self.assertIsNotNone(extra_info.get('socket'))
self.assertIsNotNone(extra_info.get('sockname'))
self.assertEqual(extra_info.get('unknown'), 'default')
def test_transport_pause_resume_reading(self):
"""Test transport pause_reading and resume_reading."""
data_chunks = []
paused = []
class TestProtocol(asyncio.Protocol):
def connection_made(self, transport):
self.transport = transport
def data_received(self, data):
data_chunks.append(data)
if len(data_chunks) == 1:
self.transport.pause_reading()
paused.append(True)
# Resume after a delay
asyncio.get_event_loop().call_later(
0.05, self.transport.resume_reading
)
async def main():
server = await self.loop.create_server(
TestProtocol, '127.0.0.1', 0
)
port = server.sockets[0].getsockname()[1]
# Send data
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('127.0.0.1', port))
sock.sendall(b'chunk1')
await asyncio.sleep(0.01)
sock.sendall(b'chunk2')
await asyncio.sleep(0.1)
sock.close()
server.close()
await server.wait_closed()
self.loop.run_until_complete(main())
# Should have paused and received data
self.assertTrue(paused)
self.assertTrue(len(data_chunks) >= 1)
class _TestLargeData:
"""Tests for large data transmission."""
def test_large_data_transfer(self):
"""Test transferring large amounts of data."""
data_size = 5 * 1024 * 1024 # 5 MB
received = bytearray()
class ServerProtocol(asyncio.Protocol):
def connection_made(self, transport):
self.transport = transport
def data_received(self, data):
received.extend(data)
def connection_lost(self, exc):
pass
async def main():
server = await self.loop.create_server(
ServerProtocol, '127.0.0.1', 0
)
port = server.sockets[0].getsockname()[1]
# Connect and send large data
class ClientProtocol(asyncio.Protocol):
def connection_made(self, transport):
self.transport = transport
# Send large data
transport.write(b'x' * data_size)
transport.close()
await self.loop.create_connection(
ClientProtocol, '127.0.0.1', port
)
# Wait for data to be received
for _ in range(100):
if len(received) >= data_size:
break
await asyncio.sleep(0.1)
server.close()
await server.wait_closed()
return len(received)
result = self.loop.run_until_complete(main())
self.assertEqual(result, data_size)
class _TestServerSockets:
"""Tests for server socket management."""
def test_server_sockets_property(self):
"""Test server.sockets property."""
async def main():
server = await self.loop.create_server(
asyncio.Protocol, '127.0.0.1', 0
)
sockets = server.sockets
self.assertIsInstance(sockets, tuple)
self.assertEqual(len(sockets), 1)
# Check for socket-like object (asyncio may wrap in TransportSocket)
self.assertTrue(hasattr(sockets[0], 'fileno'))
server.close()
await server.wait_closed()
self.loop.run_until_complete(main())
def test_server_get_loop(self):
"""Test server.get_loop() method."""
async def main():
server = await self.loop.create_server(
asyncio.Protocol, '127.0.0.1', 0
)
self.assertIs(server.get_loop(), self.loop)
server.close()
await server.wait_closed()
self.loop.run_until_complete(main())
def test_server_context_manager(self):
"""Test server as async context manager."""
async def main():
async with await self.loop.create_server(
asyncio.Protocol, '127.0.0.1', 0
) as server:
self.assertTrue(server.is_serving())
# Should be closed after context exits
self.loop.run_until_complete(main())
# =============================================================================
# Test classes that combine mixins with test cases
# =============================================================================
class TestErlangCreateServer(_TestCreateServer, tb.ErlangTestCase):
pass
class TestAIOCreateServer(_TestCreateServer, tb.AIOTestCase):
pass
class TestErlangCreateConnection(_TestCreateConnection, tb.ErlangTestCase):
pass
class TestAIOCreateConnection(_TestCreateConnection, tb.AIOTestCase):
pass
class TestErlangTransportProtocol(_TestTransportProtocol, tb.ErlangTestCase):
pass
class TestAIOTransportProtocol(_TestTransportProtocol, tb.AIOTestCase):
pass
class TestErlangLargeData(_TestLargeData, tb.ErlangTestCase):
pass
class TestAIOLargeData(_TestLargeData, tb.AIOTestCase):
pass
class TestErlangServerSockets(_TestServerSockets, tb.ErlangTestCase):
pass
class TestAIOServerSockets(_TestServerSockets, tb.AIOTestCase):
pass
if __name__ == '__main__':
unittest.main()