Skip to content

Commit d48569e

Browse files
dbuseasvetlov
andauthored
Add unused port helpers for UDP (#99)
* Add unused port helpers for UDP Extends the unused_tcp_port and unused_tcp_port_factory mechanisms for UDP ports. * Update pytest_asyncio/plugin.py * Add changenote Co-authored-by: Andrew Svetlov <[email protected]>
1 parent 02d8f10 commit d48569e

File tree

3 files changed

+131
-9
lines changed

3 files changed

+131
-9
lines changed

README.rst

+7-1
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,11 @@ when several unused TCP ports are required in a test.
109109
port1, port2 = unused_tcp_port_factory(), unused_tcp_port_factory()
110110
...
111111
112+
``unused_udp_port`` and ``unused_udp_port_factory``
113+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
114+
Work just like their TCP counterparts but return unused UDP ports.
115+
116+
112117
Async fixtures
113118
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
114119
Asynchronous fixtures are defined just like ordinary pytest fixtures, except they should be coroutines or asynchronous generators.
@@ -166,7 +171,7 @@ Note about unittest
166171
-------------------
167172

168173
Test classes subclassing the standard `unittest <https://docs.python.org/3/library/unittest.html>`__ library are not supported, users
169-
are recommended to use `unitest.IsolatedAsyncioTestCase <https://docs.python.org/3/library/unittest.html#unittest.IsolatedAsyncioTestCase>`__
174+
are recommended to use `unitest.IsolatedAsyncioTestCase <https://docs.python.org/3/library/unittest.html#unittest.IsolatedAsyncioTestCase>`__
170175
or an async framework such as `asynctest <https://asynctest.readthedocs.io/en/latest>`__.
171176

172177
Changelog
@@ -177,6 +182,7 @@ Changelog
177182
- Drop support for Python 3.6
178183
- Fixed an issue when pytest-asyncio was used in combination with `flaky` or inherited asynchronous Hypothesis tests. `#178 <https://github.com/pytest-dev/pytest-asyncio/issues/178>`_ `#231 <https://github.com/pytest-dev/pytest-asyncio/issues/231>`_
179184
- Added `flaky <https://pypi.org/project/flaky/>`_ to test dependencies
185+
- Added ``unused_udp_port`` and ``unused_udp_port_factory`` fixtures (similar to ``unused_tcp_port`` and ``unused_tcp_port_factory`` counterparts. `#99 <https://github.com/pytest-dev/pytest-asyncio/issues/99>`_
180186

181187
0.16.0 (2021-10-16)
182188
~~~~~~~~~~~~~~~~~~~

pytest_asyncio/plugin.py

+30-6
Original file line numberDiff line numberDiff line change
@@ -224,16 +224,21 @@ def event_loop(request):
224224
loop.close()
225225

226226

227-
def _unused_tcp_port():
228-
"""Find an unused localhost TCP port from 1024-65535 and return it."""
229-
with contextlib.closing(socket.socket()) as sock:
227+
def _unused_port(socket_type):
228+
"""Find an unused localhost port from 1024-65535 and return it."""
229+
with contextlib.closing(socket.socket(type=socket_type)) as sock:
230230
sock.bind(("127.0.0.1", 0))
231231
return sock.getsockname()[1]
232232

233233

234234
@pytest.fixture
235235
def unused_tcp_port():
236-
return _unused_tcp_port()
236+
return _unused_port(socket.SOCK_STREAM)
237+
238+
239+
@pytest.fixture
240+
def unused_udp_port():
241+
return _unused_port(socket.SOCK_DGRAM)
237242

238243

239244
@pytest.fixture(scope="session")
@@ -243,10 +248,29 @@ def unused_tcp_port_factory():
243248

244249
def factory():
245250
"""Return an unused port."""
246-
port = _unused_tcp_port()
251+
port = _unused_port(socket.SOCK_STREAM)
252+
253+
while port in produced:
254+
port = _unused_port(socket.SOCK_STREAM)
255+
256+
produced.add(port)
257+
258+
return port
259+
260+
return factory
261+
262+
263+
@pytest.fixture(scope="session")
264+
def unused_udp_port_factory():
265+
"""A factory function, producing different unused UDP ports."""
266+
produced = set()
267+
268+
def factory():
269+
"""Return an unused port."""
270+
port = _unused_port(socket.SOCK_DGRAM)
247271

248272
while port in produced:
249-
port = _unused_tcp_port()
273+
port = _unused_port(socket.SOCK_DGRAM)
250274

251275
produced.add(port)
252276

tests/test_simple.py

+94-2
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,33 @@ async def closer(_, writer):
5151
await server1.wait_closed()
5252

5353

54+
@pytest.mark.asyncio
55+
async def test_unused_udp_port_fixture(unused_udp_port, event_loop):
56+
"""Test the unused TCP port fixture."""
57+
58+
class Closer:
59+
def connection_made(self, transport):
60+
pass
61+
62+
def connection_lost(self, *arg, **kwd):
63+
pass
64+
65+
transport1, _ = await event_loop.create_datagram_endpoint(
66+
Closer,
67+
local_addr=("127.0.0.1", unused_udp_port),
68+
reuse_port=False,
69+
)
70+
71+
with pytest.raises(IOError):
72+
await event_loop.create_datagram_endpoint(
73+
Closer,
74+
local_addr=("127.0.0.1", unused_udp_port),
75+
reuse_port=False,
76+
)
77+
78+
transport1.abort()
79+
80+
5481
@pytest.mark.asyncio
5582
async def test_unused_port_factory_fixture(unused_tcp_port_factory, event_loop):
5683
"""Test the unused TCP port factory fixture."""
@@ -80,11 +107,57 @@ async def closer(_, writer):
80107
await server3.wait_closed()
81108

82109

110+
@pytest.mark.asyncio
111+
async def test_unused_udp_port_factory_fixture(unused_udp_port_factory, event_loop):
112+
"""Test the unused UDP port factory fixture."""
113+
114+
class Closer:
115+
def connection_made(self, transport):
116+
pass
117+
118+
def connection_lost(self, *arg, **kwd):
119+
pass
120+
121+
port1, port2, port3 = (
122+
unused_udp_port_factory(),
123+
unused_udp_port_factory(),
124+
unused_udp_port_factory(),
125+
)
126+
127+
transport1, _ = await event_loop.create_datagram_endpoint(
128+
Closer,
129+
local_addr=("127.0.0.1", port1),
130+
reuse_port=False,
131+
)
132+
transport2, _ = await event_loop.create_datagram_endpoint(
133+
Closer,
134+
local_addr=("127.0.0.1", port2),
135+
reuse_port=False,
136+
)
137+
transport3, _ = await event_loop.create_datagram_endpoint(
138+
Closer,
139+
local_addr=("127.0.0.1", port3),
140+
reuse_port=False,
141+
)
142+
143+
for port in port1, port2, port3:
144+
with pytest.raises(IOError):
145+
await event_loop.create_datagram_endpoint(
146+
Closer,
147+
local_addr=("127.0.0.1", port),
148+
reuse_port=False,
149+
)
150+
151+
transport1.abort()
152+
transport2.abort()
153+
transport3.abort()
154+
155+
83156
def test_unused_port_factory_duplicate(unused_tcp_port_factory, monkeypatch):
84157
"""Test correct avoidance of duplicate ports."""
85158
counter = 0
86159

87-
def mock_unused_tcp_port():
160+
def mock_unused_tcp_port(_ignored):
88161
"""Force some duplicate ports."""
89162
nonlocal counter
90163
counter += 1
@@ -93,12 +166,31 @@ def mock_unused_tcp_port():
93166
else:
94167
return 10000 + counter
95168

96-
monkeypatch.setattr(pytest_asyncio.plugin, "_unused_tcp_port", mock_unused_tcp_port)
169+
monkeypatch.setattr(pytest_asyncio.plugin, "_unused_port", mock_unused_tcp_port)
97170

98171
assert unused_tcp_port_factory() == 10000
99172
assert unused_tcp_port_factory() > 10000
100173

101174

175+
def test_unused_udp_port_factory_duplicate(unused_udp_port_factory, monkeypatch):
176+
"""Test correct avoidance of duplicate UDP ports."""
177+
counter = 0
178+
179+
def mock_unused_udp_port(_ignored):
180+
"""Force some duplicate ports."""
181+
nonlocal counter
182+
counter += 1
183+
if counter < 5:
184+
return 10000
185+
else:
186+
return 10000 + counter
187+
188+
monkeypatch.setattr(pytest_asyncio.plugin, "_unused_port", mock_unused_udp_port)
189+
190+
assert unused_udp_port_factory() == 10000
191+
assert unused_udp_port_factory() > 10000
192+
193+
102194
class TestMarkerInClassBasedTests:
103195
"""Test that asyncio marked functions work for methods of test classes."""
104196

0 commit comments

Comments
 (0)