From 396b8df7e9457404e37c78aa3eacb40321679a7a Mon Sep 17 00:00:00 2001 From: James Addison Date: Thu, 7 Mar 2024 00:37:03 +0000 Subject: [PATCH 01/19] tests: increase timeout for linkcheck local-https test server --- tests/roots/test-linkcheck-localserver-https/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/roots/test-linkcheck-localserver-https/conf.py b/tests/roots/test-linkcheck-localserver-https/conf.py index a2ce01e6547..31c3237e855 100644 --- a/tests/roots/test-linkcheck-localserver-https/conf.py +++ b/tests/roots/test-linkcheck-localserver-https/conf.py @@ -1,2 +1,2 @@ exclude_patterns = ['_build'] -linkcheck_timeout = 0.05 +linkcheck_timeout = 0.20 From cf50d4cce129cd0f0f1fdb234af1cb948b79a5bc Mon Sep 17 00:00:00 2001 From: James Addison Date: Thu, 7 Mar 2024 01:03:35 +0000 Subject: [PATCH 02/19] Revert "tests: increase timeout for linkcheck local-https test server" This reverts commit 396b8df7e9457404e37c78aa3eacb40321679a7a. --- tests/roots/test-linkcheck-localserver-https/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/roots/test-linkcheck-localserver-https/conf.py b/tests/roots/test-linkcheck-localserver-https/conf.py index 31c3237e855..a2ce01e6547 100644 --- a/tests/roots/test-linkcheck-localserver-https/conf.py +++ b/tests/roots/test-linkcheck-localserver-https/conf.py @@ -1,2 +1,2 @@ exclude_patterns = ['_build'] -linkcheck_timeout = 0.20 +linkcheck_timeout = 0.05 From 911d9c90f734c098c215ce5dc8a3fba7a7bd8f74 Mon Sep 17 00:00:00 2001 From: James Addison Date: Thu, 7 Mar 2024 01:18:34 +0000 Subject: [PATCH 03/19] tests: implement socket-based healthcheck before yielding test HTTP(S) servers for use in test suite --- tests/utils.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/utils.py b/tests/utils.py index 32636b7936c..b220dc44fae 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,6 +1,7 @@ import contextlib import http.server import pathlib +import socket import threading from ssl import PROTOCOL_TLS_SERVER, SSLContext @@ -15,11 +16,15 @@ # File lock for tests LOCK_PATH = str(TESTS_ROOT / 'test-server.lock') +HOST_NAME = "localhost" +HOST_PORT = 7777 +HOST = (HOST_NAME, HOST_PORT) + class HttpServerThread(threading.Thread): def __init__(self, handler, *args, **kwargs): super().__init__(*args, **kwargs) - self.server = http.server.ThreadingHTTPServer(("localhost", 7777), handler) + self.server = http.server.ThreadingHTTPServer(HOST, handler) def run(self): self.server.serve_forever(poll_interval=0.001) @@ -44,8 +49,13 @@ def server(handler): with lock: server_thread = thread_class(handler, daemon=True) server_thread.start() + timeout = 0.5 try: + socket.create_connection(HOST, timeout=timeout) yield server_thread + except Exception as e: + msg = f"Healthcheck: failure to connect to test server at {HOST} within {timeout}s" + raise Exception(msg) from e finally: server_thread.terminate() return contextlib.contextmanager(server) From c3075c54d93363617b00779b8d13124b48aa8bd6 Mon Sep 17 00:00:00 2001 From: James Addison Date: Thu, 7 Mar 2024 09:42:53 +0000 Subject: [PATCH 04/19] tests: update error message for non-responsive test server: healthcheck -> readiness --- tests/utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/utils.py b/tests/utils.py index b220dc44fae..03e339f4544 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -54,8 +54,10 @@ def server(handler): socket.create_connection(HOST, timeout=timeout) yield server_thread except Exception as e: - msg = f"Healthcheck: failure to connect to test server at {HOST} within {timeout}s" - raise Exception(msg) from e + raise Exception( + "Failed waiting for readiness of test server " + f"at {HOST_NAME}:{HOST_PORT} within {timeout}s" + ) from e finally: server_thread.terminate() return contextlib.contextmanager(server) From 34c25276673aa1659d380f2c919884432d632eb0 Mon Sep 17 00:00:00 2001 From: James Addison Date: Thu, 7 Mar 2024 09:55:42 +0000 Subject: [PATCH 05/19] tests: remove fairly-redundant exception re-raise logic --- tests/utils.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/utils.py b/tests/utils.py index 03e339f4544..1df056ba503 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -53,11 +53,6 @@ def server(handler): try: socket.create_connection(HOST, timeout=timeout) yield server_thread - except Exception as e: - raise Exception( - "Failed waiting for readiness of test server " - f"at {HOST_NAME}:{HOST_PORT} within {timeout}s" - ) from e finally: server_thread.terminate() return contextlib.contextmanager(server) From 813c23c4a321189e86bb0fe2a8d833362f5ef4c1 Mon Sep 17 00:00:00 2001 From: James Addison Date: Thu, 7 Mar 2024 09:56:17 +0000 Subject: [PATCH 06/19] tests: resource cleanup: close the readiness-check client socket --- tests/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/utils.py b/tests/utils.py index 1df056ba503..279d5ddf774 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -51,7 +51,7 @@ def server(handler): server_thread.start() timeout = 0.5 try: - socket.create_connection(HOST, timeout=timeout) + socket.create_connection(HOST, timeout=timeout).close() yield server_thread finally: server_thread.terminate() From 912b55e7a3ba135ad90b2d0cd674c1db4a6f016f Mon Sep 17 00:00:00 2001 From: James Addison Date: Thu, 7 Mar 2024 09:57:22 +0000 Subject: [PATCH 07/19] tests: refactor-out single-use variable --- tests/utils.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/utils.py b/tests/utils.py index 279d5ddf774..ce05d22f00a 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -49,9 +49,8 @@ def server(handler): with lock: server_thread = thread_class(handler, daemon=True) server_thread.start() - timeout = 0.5 try: - socket.create_connection(HOST, timeout=timeout).close() + socket.create_connection(HOST, timeout=0.5).close() yield server_thread finally: server_thread.terminate() From cf149ad9ae942760808a9ea43e0770073df7e0fc Mon Sep 17 00:00:00 2001 From: James Addison Date: Thu, 7 Mar 2024 09:58:11 +0000 Subject: [PATCH 08/19] tests: test server setup: add explanatory comment --- tests/utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/utils.py b/tests/utils.py index ce05d22f00a..f8202b48d95 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -50,6 +50,7 @@ def server(handler): server_thread = thread_class(handler, daemon=True) server_thread.start() try: + # await test server connectivity before yielding a result socket.create_connection(HOST, timeout=0.5).close() yield server_thread finally: From 8c3751e1be976b328a0f03314a1a45de556724a0 Mon Sep 17 00:00:00 2001 From: James Addison Date: Thu, 7 Mar 2024 09:59:55 +0000 Subject: [PATCH 09/19] Add CHANGES.rst entry --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index d39895a9f29..8de0fd00ebb 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -90,6 +90,9 @@ Bugs fixed Patch by Bénédikt Tran. * #12008: Fix case-sensitive lookup of ``std:label`` names in intersphinx inventory. Patch by Michael Goerz. +* #12038: Resolve ``linkcheck`` unit test timeouts on Windows by adding a readiness + check to the test HTTP(S) server setup code. + Patch by James Addison. Testing ------- From 4364c6cc7b022fc2bacff09ca688ae77c233cf6f Mon Sep 17 00:00:00 2001 From: James Addison Date: Thu, 7 Mar 2024 11:27:06 +0000 Subject: [PATCH 10/19] ci: run a matrix of Windows tests, and temporarily disable Ubuntu tests --- .github/workflows/main.yml | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 25cec164901..4d3a2b8bd55 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -46,6 +46,7 @@ jobs: docutils: "0.19" steps: + if: false - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python }} uses: actions/setup-python@v5 @@ -74,20 +75,38 @@ jobs: windows: runs-on: windows-2019 - name: Windows + name: Windows ${{ matrix.python }} (Docutils ${{ matrix.docutils }}) + strategy: + fail-fast: false + matrix: + python: + - "3.9" + - "3.10" + - "3.11" + - "3.12" + - "3.13-dev" + docutils: + - "0.18" + - "0.20" + include: + # test every supported Docutils version for the latest supported Python + - python: "3.12" + docutils: "0.19" steps: - uses: actions/checkout@v4 - - name: Set up Python + - name: Set up Python ${{ matrix.python }} uses: actions/setup-python@v5 with: - python-version: "3" + python-version: ${{ matrix.python }} - name: Check Python version run: python --version - name: Install dependencies run: | python -m pip install --upgrade pip python -m pip install .[test] + - name: Install Docutils ${{ matrix.docutils }} + run: python -m pip install --upgrade "docutils~=${{ matrix.docutils }}.0" - name: Test with pytest run: python -m pytest -vv --durations 25 env: From 6bb479765934531aa25161047ed562e8c67dda92 Mon Sep 17 00:00:00 2001 From: James Addison Date: Thu, 7 Mar 2024 11:30:20 +0000 Subject: [PATCH 11/19] ci: fixup: relocate temporary 'if' condition --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4d3a2b8bd55..ece50bee3d4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -26,6 +26,7 @@ env: jobs: ubuntu: + if: false runs-on: ubuntu-latest name: Python ${{ matrix.python }} (Docutils ${{ matrix.docutils }}) strategy: @@ -46,7 +47,6 @@ jobs: docutils: "0.19" steps: - if: false - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python }} uses: actions/setup-python@v5 From 95dd097c120535674f71e9f69431b019fa324857 Mon Sep 17 00:00:00 2001 From: James Addison Date: Thu, 7 Mar 2024 17:16:45 +0000 Subject: [PATCH 12/19] Code review: change variable name 'HOST' to 'ADDRESS' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- tests/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/utils.py b/tests/utils.py index f8202b48d95..4cdd75ccf2a 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -18,13 +18,13 @@ HOST_NAME = "localhost" HOST_PORT = 7777 -HOST = (HOST_NAME, HOST_PORT) +ADDRESS = (HOST_NAME, HOST_PORT) class HttpServerThread(threading.Thread): def __init__(self, handler, *args, **kwargs): super().__init__(*args, **kwargs) - self.server = http.server.ThreadingHTTPServer(HOST, handler) + self.server = http.server.ThreadingHTTPServer(ADDRESS, handler) def run(self): self.server.serve_forever(poll_interval=0.001) @@ -51,7 +51,7 @@ def server(handler): server_thread.start() try: # await test server connectivity before yielding a result - socket.create_connection(HOST, timeout=0.5).close() + socket.create_connection(ADDRESS, timeout=0.5).close() yield server_thread finally: server_thread.terminate() From 9720039a703f5a11681cf5ce6ce65c7c881befa6 Mon Sep 17 00:00:00 2001 From: James Addison Date: Thu, 7 Mar 2024 17:25:44 +0000 Subject: [PATCH 13/19] Code review: use a context-manager to handle client socket connection resource cleanup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- tests/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/utils.py b/tests/utils.py index 4cdd75ccf2a..176b703f626 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -51,7 +51,8 @@ def server(handler): server_thread.start() try: # await test server connectivity before yielding a result - socket.create_connection(ADDRESS, timeout=0.5).close() + with socket.create_connection(ADDRESS, timeout=0.5): + pass yield server_thread finally: server_thread.terminate() From 144299994fed1ba20844d0927ac9ce163877ac6f Mon Sep 17 00:00:00 2001 From: James Addison Date: Thu, 7 Mar 2024 19:36:22 +0000 Subject: [PATCH 14/19] Revert "Code review: use a context-manager to handle client socket connection resource cleanup" This reverts commit 9720039a703f5a11681cf5ce6ce65c7c881befa6. --- tests/utils.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/utils.py b/tests/utils.py index 176b703f626..4cdd75ccf2a 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -51,8 +51,7 @@ def server(handler): server_thread.start() try: # await test server connectivity before yielding a result - with socket.create_connection(ADDRESS, timeout=0.5): - pass + socket.create_connection(ADDRESS, timeout=0.5).close() yield server_thread finally: server_thread.terminate() From 6b2f11be330eb0fce1e943d333b5255cda989b49 Mon Sep 17 00:00:00 2001 From: James Addison Date: Thu, 7 Mar 2024 19:48:14 +0000 Subject: [PATCH 15/19] tests: attempt to improve commentary and link it more closely with the code lines --- tests/utils.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/utils.py b/tests/utils.py index 4cdd75ccf2a..3b54fe975f2 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -50,9 +50,8 @@ def server(handler): server_thread = thread_class(handler, daemon=True) server_thread.start() try: - # await test server connectivity before yielding a result - socket.create_connection(ADDRESS, timeout=0.5).close() - yield server_thread + socket.create_connection(ADDRESS, timeout=0.5).close() # attempt connection + yield server_thread # connection has been confirmed possible; proceed finally: server_thread.terminate() return contextlib.contextmanager(server) From df5687ee1997c29e13785f6228418e3cc8430a34 Mon Sep 17 00:00:00 2001 From: James Addison Date: Thu, 7 Mar 2024 19:48:37 +0000 Subject: [PATCH 16/19] Revert "ci: fixup: relocate temporary 'if' condition" This reverts commit 6bb479765934531aa25161047ed562e8c67dda92. --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ece50bee3d4..4d3a2b8bd55 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -26,7 +26,6 @@ env: jobs: ubuntu: - if: false runs-on: ubuntu-latest name: Python ${{ matrix.python }} (Docutils ${{ matrix.docutils }}) strategy: @@ -47,6 +46,7 @@ jobs: docutils: "0.19" steps: + if: false - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python }} uses: actions/setup-python@v5 From ed1144dde511934338ced68603eeefec3b17addf Mon Sep 17 00:00:00 2001 From: James Addison Date: Thu, 7 Mar 2024 19:48:38 +0000 Subject: [PATCH 17/19] Revert "ci: run a matrix of Windows tests, and temporarily disable Ubuntu tests" This reverts commit 4364c6cc7b022fc2bacff09ca688ae77c233cf6f. --- .github/workflows/main.yml | 25 +++---------------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4d3a2b8bd55..25cec164901 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -46,7 +46,6 @@ jobs: docutils: "0.19" steps: - if: false - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python }} uses: actions/setup-python@v5 @@ -75,38 +74,20 @@ jobs: windows: runs-on: windows-2019 - name: Windows ${{ matrix.python }} (Docutils ${{ matrix.docutils }}) - strategy: - fail-fast: false - matrix: - python: - - "3.9" - - "3.10" - - "3.11" - - "3.12" - - "3.13-dev" - docutils: - - "0.18" - - "0.20" - include: - # test every supported Docutils version for the latest supported Python - - python: "3.12" - docutils: "0.19" + name: Windows steps: - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python }} + - name: Set up Python uses: actions/setup-python@v5 with: - python-version: ${{ matrix.python }} + python-version: "3" - name: Check Python version run: python --version - name: Install dependencies run: | python -m pip install --upgrade pip python -m pip install .[test] - - name: Install Docutils ${{ matrix.docutils }} - run: python -m pip install --upgrade "docutils~=${{ matrix.docutils }}.0" - name: Test with pytest run: python -m pytest -vv --durations 25 env: From 7719f1ce53d9f848e9c621f264549b44a74506f7 Mon Sep 17 00:00:00 2001 From: James Addison Date: Thu, 7 Mar 2024 19:50:02 +0000 Subject: [PATCH 18/19] tests: nitpick: add full-stops to comment sentences (despite their questionable grammar). --- tests/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/utils.py b/tests/utils.py index 3b54fe975f2..b9c3fd262f5 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -50,8 +50,8 @@ def server(handler): server_thread = thread_class(handler, daemon=True) server_thread.start() try: - socket.create_connection(ADDRESS, timeout=0.5).close() # attempt connection - yield server_thread # connection has been confirmed possible; proceed + socket.create_connection(ADDRESS, timeout=0.5).close() # attempt connection. + yield server_thread # connection has been confirmed possible; proceed. finally: server_thread.terminate() return contextlib.contextmanager(server) From ea77de2afd41ee0b58d022fdeec390bc9bba92b6 Mon Sep 17 00:00:00 2001 From: James Addison Date: Thu, 7 Mar 2024 20:04:35 +0000 Subject: [PATCH 19/19] tests: nitpick: capitalize comment sentences. --- tests/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/utils.py b/tests/utils.py index b9c3fd262f5..c82c449dbde 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -50,8 +50,8 @@ def server(handler): server_thread = thread_class(handler, daemon=True) server_thread.start() try: - socket.create_connection(ADDRESS, timeout=0.5).close() # attempt connection. - yield server_thread # connection has been confirmed possible; proceed. + socket.create_connection(ADDRESS, timeout=0.5).close() # Attempt connection. + yield server_thread # Connection has been confirmed possible; proceed. finally: server_thread.terminate() return contextlib.contextmanager(server)