Skip to content
This repository has been archived by the owner on Oct 27, 2024. It is now read-only.

Commit

Permalink
Allow running tests in parallel with pytest-xdist
Browse files Browse the repository at this point in the history
The blocker for this was that gevent monkeypatching broke xdist. By
skipping `subprocess` and `thread` monkeypatching, this can be avoided.

We also need to make sure that not all processes choose the same port.
At least in my tests, choosing a free port by binding to port 0 did not
cause problematic race conditions, although I'm not sure that it always
works.
  • Loading branch information
karlb committed May 27, 2019
1 parent 53fbff1 commit f790d1d
Show file tree
Hide file tree
Showing 6 changed files with 12 additions and 43 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ script:
- pip install -r requirements-dev.txt
- pip install -e .
- make lint
- pytest -v --cov=./ tests/
- pytest -v --cov=./ tests/ -n auto
- codecov
1 change: 1 addition & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pytest==4.4.1
pytest-runner
pytest-cov
pytest-structlog==0.1
pytest-xdist
coverage>=4.5.2

ipython==4.2.1
Expand Down
2 changes: 1 addition & 1 deletion src/pathfinding_service/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

# there were some issues with the 'thread' resolver, remove it from the options
config.resolver = ["dnspython", "ares", "block"] # noqa
monkey.patch_all() # isort:skip # noqa
monkey.patch_all(subprocess=False, thread=False) # isort:skip # noqa

from typing import Dict

Expand Down
2 changes: 1 addition & 1 deletion src/request_collector/cli.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from gevent import monkey # isort:skip # noqa

monkey.patch_all() # isort:skip # noqa
monkey.patch_all(subprocess=False, thread=False) # isort:skip # noqa

import click
import structlog
Expand Down
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# there were some issues with the 'thread' resolver, remove it from the options
config.resolver = ["dnspython", "ares", "block"] # noqa
monkey.patch_all() # isort:skip # noqa
monkey.patch_all(subprocess=False, thread=False) # isort:skip # noqa

import gc

Expand Down
46 changes: 7 additions & 39 deletions tests/pathfinding/fixtures/api.py
Original file line number Diff line number Diff line change
@@ -1,53 +1,21 @@
# pylint: disable=redefined-outer-name
from itertools import count
import socket
from typing import Iterator

import psutil
import pytest

from pathfinding_service.api import ServiceApi
from pathfinding_service.config import API_PATH, DEFAULT_API_PORT
from pathfinding_service.config import API_PATH
from raiden.utils.typing import Address


def get_free_port(address: str, initial_port: int):
"""Find an unused TCP port in a specified range. This should not
be used in misson-critical applications - a race condition may
occur if someone grabs the port before caller of this function
has chance to use it.
Parameters:
address : an ip address of interface to use
initial_port : port to start iteration with
Return:
Iterator that will return next unused port on a specified
interface
"""

try:
# On OSX this function requires root privileges
psutil.net_connections()
except psutil.AccessDenied:
return count(initial_port)

def _unused_ports():
for port in count(initial_port):
# check if the port is being used
connect_using_port = (
conn
for conn in psutil.net_connections()
if hasattr(conn, "laddr") and conn.laddr[0] == address and conn.laddr[1] == port
)

# only generate unused ports
if not any(connect_using_port):
yield port

return _unused_ports()


@pytest.fixture(scope="session")
def free_port() -> int:
return next(get_free_port("localhost", DEFAULT_API_PORT))
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(("localhost", 0)) # binding to port 0 will choose a free socket
port = sock.getsockname()[1]
sock.close()
return port


@pytest.fixture(scope="session")
Expand Down

0 comments on commit f790d1d

Please sign in to comment.