userver: en/testsuite/plugins/network.py Source File
Loading...
Searching...
No Matches
network.py
1import platform
2import socket
3import typing
4
5import pytest
6
7
8class BaseError(Exception):
9 """Base class for errors from this module."""
10
11
13 """Raised if there are not free ports for worker"""
14
15
16@pytest.fixture(scope='session')
17def get_free_port(
18 _get_free_port_sock_storing,
19 _get_free_port_range_based,
20) -> typing.Callable[[], int]:
21 """
22 Returns an ephemeral TCP port that is free for IPv4 and for IPv6.
23
24 @ingroup userver_testsuite_fixtures
25 Part of the [yandex-taxi-testsuite](https://github.com/yandex/yandex-taxi-testsuite/blob/develop/testsuite/plugins/network.py#L17)
26 """
27 if platform.system() == 'Linux':
28 return _get_free_port_sock_storing
29 return _get_free_port_range_based
30
31
32@pytest.fixture(scope='session')
33def _get_free_port_sock_storing(
34 _testsuite_default_af,
35 _testsuite_socket_cleanup,
36) -> typing.Callable[[], int]:
37 family, address = _testsuite_default_af
38
39 # Relies on https://github.com/torvalds/linux/commit/aacd9289af8b82f5fb01b
40 def _get_free_port():
41 sock = socket.socket(family, socket.SOCK_STREAM)
42 try:
43 sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
44 sock.bind((address, 0))
45 _testsuite_socket_cleanup(sock) # shared variable
46 return sock.getsockname()[1]
47 except OSError:
48 raise NoEnabledPorts()
49
50 return _get_free_port
51
52
53@pytest.fixture(scope='session')
54def _get_free_port_range_based(
55 _testsuite_default_af,
56) -> typing.Callable[[], int]:
57 family, address = _testsuite_default_af
58 port_seq = iter(range(61000, 2048, -1))
59
60 def _get_free_port():
61 for port in port_seq:
62 if _is_port_free(port, family, address):
63 return port
64 raise NoEnabledPorts()
65
66 return _get_free_port
67
68
69@pytest.fixture(scope='session')
70def _testsuite_socket_cleanup():
71 sock_list: list[socket.socket] = []
72 try:
73 yield sock_list.append
74 finally:
75 for sock in sock_list:
76 sock.close()
77
78
79@pytest.fixture(scope='session')
80def _testsuite_default_af():
81 for family, address in _get_inet_families():
82 return family, address
83 raise RuntimeError('No suitable address families available')
84
85
86def _is_port_free(port_num: int, family: int, address: str) -> bool:
87 try:
88 with socket.socket(family, socket.SOCK_STREAM) as sock:
89 sock.bind((address, port_num))
90 except OSError:
91 return False
92 else:
93 return True
94
95
96def _is_af_available(family: int, address: str):
97 return _is_port_free(0, family, address)
98
99
100def _get_inet_families():
101 for family_str, address in (('AF_INET6', '::'), ('AF_INET', '127.0.0.1')):
102 family = getattr(socket, family_str, None)
103 if family and _is_af_available(family, address):
104 yield family, address