From a80e3fd3771038f6bd1069a6f5d18b13398b1faa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Li=C5=A1ka?= Date: Thu, 6 Apr 2023 23:24:49 +0200 Subject: [PATCH] Use a shared file-system lock in ``create_server`` (#11294) With parallel run of tests, one gets "Address already in use" errors as all tests attempt to bind to the same port. Fix it with a shared file-system lock. Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> --- pyproject.toml | 1 + tests/test_build_linkcheck.py | 6 ++++-- tests/utils.py | 22 +++++++++++++++------- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 4c3badd26..6c2d707c7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -93,6 +93,7 @@ test = [ "pytest>=4.6", "html5lib", "cython", + "filelock" ] [[project.authors]] diff --git a/tests/test_build_linkcheck.py b/tests/test_build_linkcheck.py index 0592eb3ae..acfebd649 100644 --- a/tests/test_build_linkcheck.py +++ b/tests/test_build_linkcheck.py @@ -99,7 +99,8 @@ def test_defaults(app): @pytest.mark.sphinx('linkcheck', testroot='linkcheck-too-many-retries', freshenv=True) def test_too_many_retries(app): - app.build() + with http_server(DefaultsHandler): + app.build() # Text output assert (app.outdir / 'output.txt').exists() @@ -688,7 +689,8 @@ def test_get_after_head_raises_connection_error(app): @pytest.mark.sphinx('linkcheck', testroot='linkcheck-documents_exclude', freshenv=True) def test_linkcheck_exclude_documents(app): - app.build() + with http_server(DefaultsHandler): + app.build() with open(app.outdir / 'output.json', encoding='utf-8') as fp: content = [json.loads(record) for record in fp] diff --git a/tests/utils.py b/tests/utils.py index a0c00a6dd..429bbd2b2 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -4,10 +4,16 @@ import pathlib import threading from ssl import PROTOCOL_TLS_SERVER, SSLContext +import filelock + # Generated with: # $ openssl req -new -x509 -days 3650 -nodes -out cert.pem \ # -keyout cert.pem -addext "subjectAltName = DNS:localhost" -CERT_FILE = str(pathlib.Path(__file__).parent / "certs" / "cert.pem") +TESTS_ROOT = pathlib.Path(__file__).parent +CERT_FILE = str(TESTS_ROOT / "certs" / "cert.pem") + +# File lock for tests +LOCK_PATH = str(TESTS_ROOT / 'test-server.lock') class HttpServerThread(threading.Thread): @@ -34,12 +40,14 @@ class HttpsServerThread(HttpServerThread): def create_server(thread_class): def server(handler): - server_thread = thread_class(handler, daemon=True) - server_thread.start() - try: - yield server_thread - finally: - server_thread.terminate() + lock = filelock.FileLock(LOCK_PATH) + with lock: + server_thread = thread_class(handler, daemon=True) + server_thread.start() + try: + yield server_thread + finally: + server_thread.terminate() return contextlib.contextmanager(server)