From 1852d0f087bf09227886c5bd7b144383ac5c6eea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Thu, 22 Aug 2024 11:11:12 +0200 Subject: [PATCH] intersphinx: Fix double forward slashes in inventory urls (#12807) --- CHANGES.rst | 4 ++ sphinx/ext/intersphinx/_load.py | 5 +- tests/test_extensions/test_ext_intersphinx.py | 71 +++++++++++++++++++ 3 files changed, 79 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 3f0ce6e7b..5785d62ef 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -83,6 +83,10 @@ Bugs fixed e.g., ``index.html#foo`` becomes ``#foo``. (note: continuation of a partial fix added in Sphinx 7.3.0) Patch by James Addison (with reference to prior work by Eric Norige) +* #12782: intersphinx: fix double forward slashes when generating the inventory + file URL (user-defined base URL of an intersphinx project are left untouched + even if they end with double forward slashes). + Patch by Bénédikt Tran. Testing ------- diff --git a/sphinx/ext/intersphinx/_load.py b/sphinx/ext/intersphinx/_load.py index 1973de379..358ae1f7c 100644 --- a/sphinx/ext/intersphinx/_load.py +++ b/sphinx/ext/intersphinx/_load.py @@ -211,7 +211,10 @@ def _fetch_inventory_group( for location in project.locations: # location is either None or a non-empty string - inv = f'{project.target_uri}/{INVENTORY_FILENAME}' if location is None else location + if location is None: + inv = posixpath.join(project.target_uri, INVENTORY_FILENAME) + else: + inv = location # decide whether the inventory must be read: always read local # files; remote ones only if the cache time is expired diff --git a/tests/test_extensions/test_ext_intersphinx.py b/tests/test_extensions/test_ext_intersphinx.py index c41ac982c..99dde92a5 100644 --- a/tests/test_extensions/test_ext_intersphinx.py +++ b/tests/test_extensions/test_ext_intersphinx.py @@ -12,6 +12,7 @@ from docutils import nodes from sphinx import addnodes from sphinx.builders.html import INVENTORY_FILENAME +from sphinx.config import Config from sphinx.errors import ConfigError from sphinx.ext.intersphinx import ( inspect_main, @@ -768,3 +769,73 @@ def test_intersphinx_cache_limit(app): config=app.config, srcdir=app.srcdir, ) + + +def test_intersphinx_fetch_inventory_group_url(): + class InventoryHandler(http.server.BaseHTTPRequestHandler): + def do_GET(self): + self.send_response(200, 'OK') + self.end_headers() + self.wfile.write(INVENTORY_V2) + + def log_message(*args, **kwargs): + # Silenced. + pass + + with http_server(InventoryHandler) as server: + url1 = f'http://localhost:{server.server_port}' + url2 = f'http://localhost:{server.server_port}/' + + config = Config() + config.intersphinx_cache_limit = -1 + config.intersphinx_mapping = { + '1': (url1, None), + '2': (url2, None), + } + + now = int(time.time()) + # we can use 'srcdir=None' since we are raising in _fetch_inventory + kwds = {'cache': {}, 'now': now, 'config': config, 'srcdir': None} + # We need an exception with its 'args' attribute set (see error + # handling in sphinx.ext.intersphinx._load._fetch_inventory_group). + side_effect = ValueError('') + + project1 = _IntersphinxProject( + name='1', target_uri=url1, locations=(url1, None) + ) + with mock.patch( + 'sphinx.ext.intersphinx._load._fetch_inventory', side_effect=side_effect + ) as mockfn: + assert not _fetch_inventory_group(project=project1, **kwds) + mockfn.assert_any_call( + target_uri=url1, + inv_location=url1, + config=config, + srcdir=None, + ) + mockfn.assert_any_call( + target_uri=url1, + inv_location=url1 + '/' + INVENTORY_FILENAME, + config=config, + srcdir=None, + ) + + project2 = _IntersphinxProject( + name='2', target_uri=url2, locations=(url2, None) + ) + with mock.patch( + 'sphinx.ext.intersphinx._load._fetch_inventory', side_effect=side_effect + ) as mockfn: + assert not _fetch_inventory_group(project=project2, **kwds) + mockfn.assert_any_call( + target_uri=url2, + inv_location=url2, + config=config, + srcdir=None, + ) + mockfn.assert_any_call( + target_uri=url2, + inv_location=url2 + INVENTORY_FILENAME, + config=config, + srcdir=None, + )