userver: en/testsuite/plugins/tcp_mockserver.py Source File
Loading...
Searching...
No Matches
tcp_mockserver.py
1import asyncio
2import contextlib
3import socket
4
5import pytest
6
7from testsuite.utils import cached_property, net
8
9
11 """TCP/IP mockserver."""
12
13 def __init__(self, server):
14 self._handler = None
15 self._sockets = tuple(server.sockets)
16
17 async def _client_connected_cb(self, reader, writer):
18 try:
19 if self._handler is None:
20 raise RuntimeError(
21 'No client handler installed, use client_handler()',
22 )
23 return await self._handler(reader, writer)
24 except Exception:
25 writer.close()
26 pytest.fail('Mockserver handler failure')
27
28 @cached_property
29 def sockets(self) -> tuple[socket.socket]:
30 """Returns list of server sockets."""
31 return self._sockets
32
33 @cached_property
34 def address(self) -> tuple[str, int]:
35 """
36 Returns service address (host, port)
37 """
38 assert self._sockets
39 return self._sockets[0].getsockname()[:2]
40
41 @contextlib.asynccontextmanager
42 async def open_connection(self, timeout=10.0):
43 """Async context manager creates connection to the service.
44
45 :param timeout: timeout to establish connection.
46
47 Returns pair (read, writer).
48
49 Connection is closed when context manager is done.
50
51 Wrapper around :func:`asyncio.open_connection`
52
53
54 .. code-block:: python
55
56 async with server.open_connection() as (reader, writer):
57 ...
58 """
59 host, port = self.address
60 coro = asyncio.open_connection(host=host, port=port)
61 try:
62 reader, writer = await asyncio.wait_for(coro, timeout=timeout)
63 yield reader, writer
64 finally:
65 writer.close()
66
67 @contextlib.contextmanager
68 def client_handler(self, handler):
69 """Context manager to install per-test client handler.
70
71 .. code-block:: python
72
73 async def handle_client(reader, writer):
74 writer.write(b'hello\\r\\n')
75 await writer.drain()
76 writer.close()
77
78 with _tcp_mockserver.client_handler(handle_client):
79 ...
80 """
81 old_handler = self._handler
82 try:
83 self._handler = handler
84 yield
85 finally:
86 self._handler = old_handler
87
88
90 def __init__(self):
91 self.client_handler = None
92
93 def __call__(self):
94 if self.client_handler is None:
95 pytest.fail('No client handler attached')
96 reader = asyncio.StreamReader()
97 protocol = asyncio.StreamReaderProtocol(
98 reader,
99 self.client_handler,
100 )
101 return protocol
102
103 @contextlib.contextmanager
104 def attach_client_handler(self, handler):
105 try:
106 self.client_handler = handler
107 yield
108 finally:
109 self.client_handler = None
110
111
112@pytest.fixture(scope='session')
113async def create_tcp_mockserver():
114 @contextlib.asynccontextmanager
115 async def create_mockserver(
116 *,
117 host='localhost',
118 port=0,
119 sock=None,
120 **kwargs,
121 ):
122 factory = ProtocolFactory()
123 async with net.create_tcp_server(
124 factory,
125 host=host,
126 port=port,
127 sock=sock,
128 **kwargs,
129 ) as server:
130 mockserver = Mockserver(server)
131 with factory.attach_client_handler(
132 mockserver._client_connected_cb,
133 ):
134 yield mockserver
135
136 return create_mockserver