Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@ when several unused TCP ports are required in a test.
port1, port2 = unused_tcp_port_factory(), unused_tcp_port_factory()
...

``unused_udp_port`` and ``unused_udp_port_factory``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Work just like their TCP counterparts but return unused UDP ports.


Async fixtures
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Asynchronous fixtures are defined just like ordinary pytest fixtures, except they should be coroutines or asynchronous generators.
Expand Down
35 changes: 29 additions & 6 deletions pytest_asyncio/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,16 +174,21 @@ def event_loop(request):
loop.close()


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


@pytest.fixture
def unused_tcp_port():
return _unused_tcp_port()
return _unused_port()


@pytest.fixture
def unused_udp_port():
return _unused_port(socket.SOCK_DGRAM)


@pytest.fixture
Expand All @@ -193,10 +198,28 @@ def unused_tcp_port_factory():

def factory():
"""Return an unused port."""
port = _unused_tcp_port()
port = _unused_port()

while port in produced:
port = _unused_port()

produced.add(port)

return port
return factory


@pytest.fixture
def unused_udp_port_factory():
"""A factory function, producing different unused UDP ports."""
produced = set()

def factory():
"""Return an unused port."""
port = _unused_port(socket.SOCK_DGRAM)

while port in produced:
port = _unused_tcp_port()
port = _unused_port(socket.SOCK_DGRAM)

produced.add(port)

Expand Down
100 changes: 99 additions & 1 deletion tests/test_simple.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,35 @@ async def closer(_, writer):
await server1.wait_closed()


@pytest.mark.asyncio
async def test_unused_udp_port_fixture(unused_udp_port, event_loop):
"""Test the unused TCP port fixture."""

class Closer:
def connection_made(self, transport):
pass

def connection_lost(self, *arg, **kwd):
pass

transport1, _ = await event_loop.create_datagram_endpoint(
Closer,
local_addr=('127.0.0.1', unused_udp_port),
reuse_port=False,
reuse_address=False,
)

with pytest.raises(IOError):
await event_loop.create_datagram_endpoint(
Closer,
local_addr=('127.0.0.1', unused_udp_port),
reuse_port=False,
reuse_address=False,
)

transport1.abort()


@pytest.mark.asyncio
async def test_unused_port_factory_fixture(unused_tcp_port_factory, event_loop):
"""Test the unused TCP port factory fixture."""
Expand Down Expand Up @@ -91,6 +120,54 @@ async def closer(_, writer):
await server3.wait_closed()


@pytest.mark.asyncio
async def test_unused_udp_port_factory_fixture(unused_udp_port_factory,
event_loop):
"""Test the unused UDP port factory fixture."""

class Closer:
def connection_made(self, transport):
pass

def connection_lost(self, *arg, **kwd):
pass

port1, port2, port3 = (unused_udp_port_factory(), unused_udp_port_factory(),
unused_udp_port_factory())

transport1, _ = await event_loop.create_datagram_endpoint(
Closer,
local_addr=('127.0.0.1', port1),
reuse_port=False,
reuse_address=False,
)
transport2, _ = await event_loop.create_datagram_endpoint(
Closer,
local_addr=('127.0.0.1', port2),
reuse_port=False,
reuse_address=False,
)
transport3, _ = await event_loop.create_datagram_endpoint(
Closer,
local_addr=('127.0.0.1', port3),
reuse_port=False,
reuse_address=False,
)

for port in port1, port2, port3:
with pytest.raises(IOError):
await event_loop.create_datagram_endpoint(
Closer,
local_addr=('127.0.0.1', port),
reuse_port=False,
reuse_address=False,
)

transport1.abort()
transport2.abort()
transport3.abort()


def test_unused_port_factory_duplicate(unused_tcp_port_factory, monkeypatch):
"""Test correct avoidance of duplicate ports."""
counter = 0
Expand All @@ -104,13 +181,34 @@ def mock_unused_tcp_port():
else:
return 10000 + counter

monkeypatch.setattr(pytest_asyncio.plugin, '_unused_tcp_port',
monkeypatch.setattr(pytest_asyncio.plugin, '_unused_port',
mock_unused_tcp_port)

assert unused_tcp_port_factory() == 10000
assert unused_tcp_port_factory() > 10000


def test_unused_udp_port_factory_duplicate(unused_udp_port_factory,
monkeypatch):
"""Test correct avoidance of duplicate UDP ports."""
counter = 0

def mock_unused_udp_port(_ignored):
"""Force some duplicate ports."""
nonlocal counter
counter += 1
if counter < 5:
return 10000
else:
return 10000 + counter

monkeypatch.setattr(pytest_asyncio.plugin, '_unused_port',
mock_unused_udp_port)

assert unused_udp_port_factory() == 10000
assert unused_udp_port_factory() > 10000


class Test:
"""Test that asyncio marked functions work in test methods."""

Expand Down