2009-09-09 14:56:53 -05:00
""" Test the intersphinx extension. """
2024-07-22 06:59:59 -05:00
from __future__ import annotations
2020-11-08 14:57:06 -06:00
import http . server
2024-07-31 02:16:10 -05:00
import time
2024-07-22 06:59:59 -05:00
from typing import TYPE_CHECKING
2019-03-07 08:35:36 -06:00
from unittest import mock
2009-09-09 14:56:53 -05:00
2017-03-21 10:03:05 -05:00
import pytest
2018-02-19 07:39:14 -06:00
from docutils import nodes
2009-09-09 14:56:53 -05:00
from sphinx import addnodes
2025-01-16 10:57:28 -06:00
from sphinx . _cli . util . errors import strip_escape_sequences
2024-04-25 06:07:05 -05:00
from sphinx . builders . html import INVENTORY_FILENAME
2024-08-22 04:11:12 -05:00
from sphinx . config import Config
2024-07-22 06:59:59 -05:00
from sphinx . errors import ConfigError
2018-02-19 07:39:14 -06:00
from sphinx . ext . intersphinx import setup as intersphinx_setup
2025-01-15 15:41:34 -06:00
from sphinx . ext . intersphinx . _cli import inspect_main
2024-07-31 02:16:10 -05:00
from sphinx . ext . intersphinx . _load import (
_fetch_inventory ,
_fetch_inventory_group ,
_get_safe_url ,
2025-01-04 19:06:51 -06:00
_InvConfig ,
2024-07-31 02:16:10 -05:00
_strip_basic_auth ,
2025-01-15 15:41:34 -06:00
load_mappings ,
validate_intersphinx_mapping ,
2024-07-31 02:16:10 -05:00
)
2025-01-15 15:41:34 -06:00
from sphinx . ext . intersphinx . _resolve import missing_reference
2024-07-31 02:16:10 -05:00
from sphinx . ext . intersphinx . _shared import _IntersphinxProject
2025-01-16 18:41:11 -06:00
from sphinx . util . inventory import _InventoryItem
2020-11-20 12:04:26 -06:00
2024-06-17 05:42:22 -05:00
from tests . test_util . intersphinx_data import (
INVENTORY_V2 ,
INVENTORY_V2_AMBIGUOUS_TERMS ,
INVENTORY_V2_NO_VERSION ,
)
2024-01-16 20:38:46 -06:00
from tests . utils import http_server
2020-11-08 14:57:06 -06:00
2024-07-22 06:59:59 -05:00
2025-01-26 13:41:15 -06:00
class FakeList ( list [ str ] ) :
2024-07-22 06:59:59 -05:00
def __iter__ ( self ) - > NoReturn :
raise NotImplementedError
2009-09-09 14:56:53 -05:00
2017-04-22 10:21:22 -05:00
def fake_node ( domain , type , target , content , * * attrs ) :
contnode = nodes . emphasis ( content , content )
node = addnodes . pending_xref ( ' ' )
node [ ' reftarget ' ] = target
node [ ' reftype ' ] = type
node [ ' refdomain ' ] = domain
node . attributes . update ( attrs )
node + = contnode
return node , contnode
def reference_check ( app , * args , * * kwds ) :
node , contnode = fake_node ( * args , * * kwds )
return missing_reference ( app , app . env , node , contnode )
2021-07-16 07:34:35 -05:00
def set_config ( app , mapping ) :
2024-07-22 06:59:59 -05:00
# copy *mapping* so that normalization does not alter it
app . config . intersphinx_mapping = mapping . copy ( )
2021-07-16 07:34:35 -05:00
app . config . intersphinx_cache_limit = 0
2021-10-31 07:56:26 -05:00
app . config . intersphinx_disabled_reftypes = [ ]
2025-01-04 19:06:51 -06:00
app . config . intersphinx_timeout = None
2021-07-16 07:34:35 -05:00
2024-04-19 18:47:30 -05:00
@mock.patch ( ' sphinx.ext.intersphinx._load.InventoryFile ' )
2025-01-04 18:34:49 -06:00
@mock.patch ( ' sphinx.ext.intersphinx._load.requests.get ' )
2024-08-12 16:34:03 -05:00
@pytest.mark.sphinx ( ' html ' , testroot = ' root ' )
2025-01-04 18:34:49 -06:00
def test_fetch_inventory_redirection ( get_request , InventoryFile , app ) :
mocked_get = get_request . return_value . __enter__ . return_value
2016-09-01 09:30:04 -05:00
intersphinx_setup ( app )
2025-01-04 18:34:49 -06:00
mocked_get . content = b ' # Sphinx inventory version 2 '
2016-08-10 21:19:42 -05:00
# same uri and inv, not redirected
2025-01-04 18:34:49 -06:00
mocked_get . url = ' https://hostname/ ' + INVENTORY_FILENAME
2024-07-23 17:17:58 -05:00
_fetch_inventory (
target_uri = ' https://hostname/ ' ,
inv_location = ' https://hostname/ ' + INVENTORY_FILENAME ,
2025-01-04 19:06:51 -06:00
config = _InvConfig . from_config ( app . config ) ,
2024-07-23 17:17:58 -05:00
srcdir = app . srcdir ,
)
2024-07-23 09:35:55 -05:00
assert ' intersphinx inventory has moved ' not in app . status . getvalue ( )
2025-01-06 18:40:57 -06:00
assert InventoryFile . loads . call_args [ 1 ] [ ' uri ' ] == ' https://hostname/ '
2016-08-10 21:19:42 -05:00
# same uri and inv, redirected
2024-07-23 09:35:55 -05:00
app . status . seek ( 0 )
app . status . truncate ( 0 )
2025-01-04 18:34:49 -06:00
mocked_get . url = ' https://hostname/new/ ' + INVENTORY_FILENAME
2016-08-10 21:19:42 -05:00
2024-07-23 17:17:58 -05:00
_fetch_inventory (
target_uri = ' https://hostname/ ' ,
inv_location = ' https://hostname/ ' + INVENTORY_FILENAME ,
2025-01-04 19:06:51 -06:00
config = _InvConfig . from_config ( app . config ) ,
2024-07-23 17:17:58 -05:00
srcdir = app . srcdir ,
)
2024-08-11 08:58:56 -05:00
assert app . status . getvalue ( ) == (
' intersphinx inventory has moved: '
' https://hostname/ %s -> https://hostname/new/ %s \n '
% ( INVENTORY_FILENAME , INVENTORY_FILENAME )
)
2025-01-06 18:40:57 -06:00
assert InventoryFile . loads . call_args [ 1 ] [ ' uri ' ] == ' https://hostname/new '
2016-08-10 21:19:42 -05:00
# different uri and inv, not redirected
2024-07-23 09:35:55 -05:00
app . status . seek ( 0 )
app . status . truncate ( 0 )
2025-01-04 18:34:49 -06:00
mocked_get . url = ' https://hostname/new/ ' + INVENTORY_FILENAME
2016-08-10 21:19:42 -05:00
2024-07-23 17:17:58 -05:00
_fetch_inventory (
target_uri = ' https://hostname/ ' ,
inv_location = ' https://hostname/new/ ' + INVENTORY_FILENAME ,
2025-01-04 19:06:51 -06:00
config = _InvConfig . from_config ( app . config ) ,
2024-07-23 17:17:58 -05:00
srcdir = app . srcdir ,
)
2024-07-23 09:35:55 -05:00
assert ' intersphinx inventory has moved ' not in app . status . getvalue ( )
2025-01-06 18:40:57 -06:00
assert InventoryFile . loads . call_args [ 1 ] [ ' uri ' ] == ' https://hostname/ '
2016-08-10 21:19:42 -05:00
# different uri and inv, redirected
2024-07-23 09:35:55 -05:00
app . status . seek ( 0 )
app . status . truncate ( 0 )
2025-01-04 18:34:49 -06:00
mocked_get . url = ' https://hostname/other/ ' + INVENTORY_FILENAME
2016-08-10 21:19:42 -05:00
2024-07-23 17:17:58 -05:00
_fetch_inventory (
target_uri = ' https://hostname/ ' ,
inv_location = ' https://hostname/new/ ' + INVENTORY_FILENAME ,
2025-01-04 19:06:51 -06:00
config = _InvConfig . from_config ( app . config ) ,
2024-07-23 17:17:58 -05:00
srcdir = app . srcdir ,
)
2024-08-11 08:58:56 -05:00
assert app . status . getvalue ( ) == (
' intersphinx inventory has moved: '
' https://hostname/new/ %s -> https://hostname/other/ %s \n '
% ( INVENTORY_FILENAME , INVENTORY_FILENAME )
)
2025-01-06 18:40:57 -06:00
assert InventoryFile . loads . call_args [ 1 ] [ ' uri ' ] == ' https://hostname/ '
2016-08-10 21:19:42 -05:00
2024-08-12 16:34:03 -05:00
@pytest.mark.sphinx ( ' html ' , testroot = ' root ' )
2024-07-23 09:35:55 -05:00
def test_missing_reference ( tmp_path , app ) :
2023-07-27 18:39:12 -05:00
inv_file = tmp_path / ' inventory '
2024-03-25 05:03:44 -05:00
inv_file . write_bytes ( INVENTORY_V2 )
2024-08-11 08:58:56 -05:00
set_config (
app ,
{
' python ' : ( ' https://docs.python.org/ ' , str ( inv_file ) ) ,
' py3k ' : ( ' https://docs.python.org/py3k/ ' , str ( inv_file ) ) ,
' py3krel ' : ( ' py3k ' , str ( inv_file ) ) , # relative path
' py3krelparent ' : ( ' ../../py3k ' , str ( inv_file ) ) , # relative path, parent dir
} ,
)
2009-09-09 14:56:53 -05:00
# load the inventory and check if it's done correctly
2024-07-22 07:32:44 -05:00
validate_intersphinx_mapping ( app , app . config )
2009-09-09 14:56:53 -05:00
load_mappings ( app )
inv = app . env . intersphinx_inventory
2025-01-16 18:41:11 -06:00
assert inv [ ' py:module ' ] [ ' module2 ' ] == _InventoryItem (
project_name = ' foo ' ,
project_version = ' 2.0 ' ,
uri = ' https://docs.python.org/foo.html#module-module2 ' ,
display_name = ' - ' ,
2024-08-11 08:58:56 -05:00
)
2009-09-09 14:56:53 -05:00
2010-08-05 04:58:43 -05:00
# check resolution when a target is found
2017-04-22 10:21:22 -05:00
rn = reference_check ( app , ' py ' , ' func ' , ' module1.func ' , ' foo ' )
2009-09-09 14:56:53 -05:00
assert isinstance ( rn , nodes . reference )
2015-02-22 22:20:35 -06:00
assert rn [ ' refuri ' ] == ' https://docs.python.org/sub/foo.html#module1.func '
2009-09-09 14:56:53 -05:00
assert rn [ ' reftitle ' ] == ' (in foo v2.0) '
2010-08-05 04:58:43 -05:00
assert rn [ 0 ] . astext ( ) == ' foo '
2009-09-09 14:56:53 -05:00
# create unresolvable nodes and check None return value
2017-04-22 10:21:22 -05:00
assert reference_check ( app , ' py ' , ' foo ' , ' module1.func ' , ' foo ' ) is None
assert reference_check ( app , ' py ' , ' func ' , ' foo ' , ' foo ' ) is None
assert reference_check ( app , ' py ' , ' func ' , ' foo ' , ' foo ' ) is None
2010-07-27 06:20:58 -05:00
# check handling of prefixes
# prefix given, target found: prefix is stripped
2017-04-22 10:21:22 -05:00
rn = reference_check ( app , ' py ' , ' mod ' , ' py3k:module2 ' , ' py3k:module2 ' )
2010-08-05 04:58:43 -05:00
assert rn [ 0 ] . astext ( ) == ' module2 '
# prefix given, but not in title: nothing stripped
2017-04-22 10:21:22 -05:00
rn = reference_check ( app , ' py ' , ' mod ' , ' py3k:module2 ' , ' module2 ' )
2010-07-27 06:20:58 -05:00
assert rn [ 0 ] . astext ( ) == ' module2 '
2010-08-05 04:58:43 -05:00
# prefix given, but explicit: nothing stripped
2024-08-11 08:58:56 -05:00
rn = reference_check (
app , ' py ' , ' mod ' , ' py3k:module2 ' , ' py3k:module2 ' , refexplicit = True
)
2010-08-05 04:58:43 -05:00
assert rn [ 0 ] . astext ( ) == ' py3k:module2 '
2021-07-16 06:12:52 -05:00
# prefix given, target not found and nonexplicit title: prefix is not stripped
2024-08-11 08:58:56 -05:00
node , contnode = fake_node (
' py ' , ' mod ' , ' py3k:unknown ' , ' py3k:unknown ' , refexplicit = False
)
2010-08-05 04:58:43 -05:00
rn = missing_reference ( app , app . env , node , contnode )
2010-07-27 06:20:58 -05:00
assert rn is None
2021-07-16 06:12:52 -05:00
assert contnode [ 0 ] . astext ( ) == ' py3k:unknown '
2010-07-27 06:20:58 -05:00
# prefix given, target not found and explicit title: nothing is changed
2024-08-11 08:58:56 -05:00
node , contnode = fake_node (
' py ' , ' mod ' , ' py3k:unknown ' , ' py3k:unknown ' , refexplicit = True
)
2010-08-05 04:58:43 -05:00
rn = missing_reference ( app , app . env , node , contnode )
2010-07-27 06:20:58 -05:00
assert rn is None
assert contnode [ 0 ] . astext ( ) == ' py3k:unknown '
2011-09-03 14:41:26 -05:00
2017-04-22 10:21:22 -05:00
# check relative paths
rn = reference_check ( app , ' py ' , ' mod ' , ' py3krel:module1 ' , ' foo ' )
assert rn [ ' refuri ' ] == ' py3k/foo.html#module-module1 '
rn = reference_check ( app , ' py ' , ' mod ' , ' py3krelparent:module1 ' , ' foo ' )
assert rn [ ' refuri ' ] == ' ../../py3k/foo.html#module-module1 '
2024-08-11 08:58:56 -05:00
rn = reference_check (
app , ' py ' , ' mod ' , ' py3krel:module1 ' , ' foo ' , refdoc = ' sub/dir/test '
)
2017-04-22 10:21:22 -05:00
assert rn [ ' refuri ' ] == ' ../../py3k/foo.html#module-module1 '
2024-08-11 08:58:56 -05:00
rn = reference_check (
app , ' py ' , ' mod ' , ' py3krelparent:module1 ' , ' foo ' , refdoc = ' sub/dir/test '
)
2017-04-22 10:21:22 -05:00
assert rn [ ' refuri ' ] == ' ../../../../py3k/foo.html#module-module1 '
# check refs of standard domain
rn = reference_check ( app , ' std ' , ' doc ' , ' docname ' , ' docname ' )
assert rn [ ' refuri ' ] == ' https://docs.python.org/docname.html '
2024-08-12 16:34:03 -05:00
@pytest.mark.sphinx ( ' html ' , testroot = ' root ' )
2024-07-23 09:35:55 -05:00
def test_missing_reference_pydomain ( tmp_path , app ) :
2023-07-27 18:39:12 -05:00
inv_file = tmp_path / ' inventory '
2024-03-25 05:03:44 -05:00
inv_file . write_bytes ( INVENTORY_V2 )
2024-08-11 08:58:56 -05:00
set_config (
app ,
{
' python ' : ( ' https://docs.python.org/ ' , str ( inv_file ) ) ,
} ,
)
2017-04-22 10:21:22 -05:00
# load the inventory and check if it's done correctly
2024-07-22 07:32:44 -05:00
validate_intersphinx_mapping ( app , app . config )
2017-04-22 10:21:22 -05:00
load_mappings ( app )
2017-01-29 10:16:10 -06:00
# no context data
kwargs = { }
node , contnode = fake_node ( ' py ' , ' func ' , ' func ' , ' func() ' , * * kwargs )
rn = missing_reference ( app , app . env , node , contnode )
assert rn is None
2017-04-22 10:21:22 -05:00
# py:module context helps to search objects
2017-01-29 10:16:10 -06:00
kwargs = { ' py:module ' : ' module1 ' }
node , contnode = fake_node ( ' py ' , ' func ' , ' func ' , ' func() ' , * * kwargs )
rn = missing_reference ( app , app . env , node , contnode )
2017-04-22 10:21:22 -05:00
assert rn . astext ( ) == ' func() '
2017-01-29 10:16:10 -06:00
2020-02-20 10:22:24 -06:00
# py:attr context helps to search objects
kwargs = { ' py:module ' : ' module1 ' }
node , contnode = fake_node ( ' py ' , ' attr ' , ' Foo.bar ' , ' Foo.bar ' , * * kwargs )
rn = missing_reference ( app , app . env , node , contnode )
assert rn . astext ( ) == ' Foo.bar '
2015-09-28 15:29:46 -05:00
2024-08-12 16:34:03 -05:00
@pytest.mark.sphinx ( ' html ' , testroot = ' root ' )
2024-07-23 09:35:55 -05:00
def test_missing_reference_stddomain ( tmp_path , app ) :
2023-07-27 18:39:12 -05:00
inv_file = tmp_path / ' inventory '
2024-03-25 05:03:44 -05:00
inv_file . write_bytes ( INVENTORY_V2 )
2024-08-11 08:58:56 -05:00
set_config (
app ,
{
' cmd ' : ( ' https://docs.python.org/ ' , str ( inv_file ) ) ,
} ,
)
2015-09-28 15:29:46 -05:00
2017-04-22 10:21:22 -05:00
# load the inventory and check if it's done correctly
2024-07-22 07:32:44 -05:00
validate_intersphinx_mapping ( app , app . config )
2017-04-22 10:21:22 -05:00
load_mappings ( app )
2015-09-28 15:29:46 -05:00
2017-04-22 10:21:22 -05:00
# no context data
kwargs = { }
node , contnode = fake_node ( ' std ' , ' option ' , ' -l ' , ' -l ' , * * kwargs )
rn = missing_reference ( app , app . env , node , contnode )
assert rn is None
2015-09-28 15:29:46 -05:00
2017-04-22 10:21:22 -05:00
# std:program context helps to search objects
kwargs = { ' std:program ' : ' ls ' }
node , contnode = fake_node ( ' std ' , ' option ' , ' -l ' , ' ls -l ' , * * kwargs )
rn = missing_reference ( app , app . env , node , contnode )
assert rn . astext ( ) == ' ls -l '
2017-01-29 03:10:49 -06:00
2018-01-01 08:29:10 -06:00
# refers inventory by name
kwargs = { }
node , contnode = fake_node ( ' std ' , ' option ' , ' cmd:ls -l ' , ' -l ' , * * kwargs )
rn = missing_reference ( app , app . env , node , contnode )
assert rn . astext ( ) == ' -l '
2022-06-09 03:40:40 -05:00
# term reference (normal)
node , contnode = fake_node ( ' std ' , ' term ' , ' a term ' , ' a term ' )
rn = missing_reference ( app , app . env , node , contnode )
assert rn . astext ( ) == ' a term '
# term reference (case insensitive)
node , contnode = fake_node ( ' std ' , ' term ' , ' A TERM ' , ' A TERM ' )
rn = missing_reference ( app , app . env , node , contnode )
assert rn . astext ( ) == ' A TERM '
2024-03-02 05:39:51 -06:00
# label reference (normal)
node , contnode = fake_node ( ' std ' , ' ref ' , ' The-Julia-Domain ' , ' The-Julia-Domain ' )
rn = missing_reference ( app , app . env , node , contnode )
assert rn . astext ( ) == ' The Julia Domain '
# label reference (case insensitive)
node , contnode = fake_node ( ' std ' , ' ref ' , ' the-julia-domain ' , ' the-julia-domain ' )
rn = missing_reference ( app , app . env , node , contnode )
assert rn . astext ( ) == ' The Julia Domain '
2011-09-03 14:41:26 -05:00
2024-08-11 11:22:37 -05:00
@pytest.mark.parametrize (
( ' term ' , ' expected_ambiguity ' ) ,
[
( ' A TERM ' , False ) ,
( ' B TERM ' , True ) ,
] ,
)
2024-08-12 16:34:03 -05:00
@pytest.mark.sphinx ( ' html ' , testroot = ' root ' )
2024-08-11 11:22:37 -05:00
def test_ambiguous_reference_handling ( term , expected_ambiguity , tmp_path , app , warning ) :
2024-06-17 05:42:22 -05:00
inv_file = tmp_path / ' inventory '
inv_file . write_bytes ( INVENTORY_V2_AMBIGUOUS_TERMS )
2024-08-11 08:58:56 -05:00
set_config (
app ,
{
' cmd ' : ( ' https://docs.python.org/ ' , str ( inv_file ) ) ,
} ,
)
2024-06-17 05:42:22 -05:00
# load the inventory
2024-07-22 07:32:44 -05:00
validate_intersphinx_mapping ( app , app . config )
2024-06-17 05:42:22 -05:00
load_mappings ( app )
# term reference (case insensitive)
2024-08-11 11:22:37 -05:00
node , contnode = fake_node ( ' std ' , ' term ' , term , term )
2024-06-17 05:42:22 -05:00
missing_reference ( app , app . env , node , contnode )
2024-08-11 11:22:37 -05:00
ambiguity = f ' multiple matches found for std:term: { term } ' in warning . getvalue ( )
assert ambiguity is expected_ambiguity
2024-06-17 05:42:22 -05:00
2017-04-22 21:33:49 -05:00
@pytest.mark.sphinx ( ' html ' , testroot = ' ext-intersphinx-cppdomain ' )
2024-07-23 09:35:55 -05:00
def test_missing_reference_cppdomain ( tmp_path , app ) :
2023-07-27 18:39:12 -05:00
inv_file = tmp_path / ' inventory '
2024-03-25 05:03:44 -05:00
inv_file . write_bytes ( INVENTORY_V2 )
2024-08-11 08:58:56 -05:00
set_config (
app ,
{
' python ' : ( ' https://docs.python.org/ ' , str ( inv_file ) ) ,
} ,
)
2017-04-22 21:33:49 -05:00
# load the inventory and check if it's done correctly
2024-07-22 07:32:44 -05:00
validate_intersphinx_mapping ( app , app . config )
2017-04-22 21:33:49 -05:00
load_mappings ( app )
app . build ( )
2022-04-26 21:04:19 -05:00
html = ( app . outdir / ' index.html ' ) . read_text ( encoding = ' utf8 ' )
2024-08-11 08:58:56 -05:00
assert (
' <a class= " reference external " '
' href= " https://docs.python.org/index.html#cpp_foo_bar " '
' title= " (in foo v2.0) " > '
' <code class= " xref cpp cpp-class docutils literal notranslate " > '
' <span class= " pre " >Bar</span></code></a> '
) in html
assert (
' <a class= " reference external " '
' href= " https://docs.python.org/index.html#foons " '
' title= " (in foo v2.0) " ><span class= " n " ><span class= " pre " >foons</span></span></a> '
) in html
assert (
' <a class= " reference external " '
' href= " https://docs.python.org/index.html#foons_bartype " '
' title= " (in foo v2.0) " ><span class= " n " ><span class= " pre " >bartype</span></span></a> '
) in html
2017-09-28 13:43:46 -05:00
2017-04-22 21:33:49 -05:00
2024-08-12 16:34:03 -05:00
@pytest.mark.sphinx ( ' html ' , testroot = ' root ' )
2024-07-23 09:35:55 -05:00
def test_missing_reference_jsdomain ( tmp_path , app ) :
2023-07-27 18:39:12 -05:00
inv_file = tmp_path / ' inventory '
2024-03-25 05:03:44 -05:00
inv_file . write_bytes ( INVENTORY_V2 )
2024-08-11 08:58:56 -05:00
set_config (
app ,
{
' python ' : ( ' https://docs.python.org/ ' , str ( inv_file ) ) ,
} ,
)
2017-04-22 11:02:29 -05:00
# load the inventory and check if it's done correctly
2024-07-22 07:32:44 -05:00
validate_intersphinx_mapping ( app , app . config )
2017-04-22 11:02:29 -05:00
load_mappings ( app )
# no context data
kwargs = { }
node , contnode = fake_node ( ' js ' , ' meth ' , ' baz ' , ' baz() ' , * * kwargs )
rn = missing_reference ( app , app . env , node , contnode )
assert rn is None
# js:module and js:object context helps to search objects
kwargs = { ' js:module ' : ' foo ' , ' js:object ' : ' bar ' }
node , contnode = fake_node ( ' js ' , ' meth ' , ' baz ' , ' baz() ' , * * kwargs )
rn = missing_reference ( app , app . env , node , contnode )
assert rn . astext ( ) == ' baz() '
2024-08-12 16:34:03 -05:00
@pytest.mark.sphinx ( ' html ' , testroot = ' root ' )
2024-07-23 09:35:55 -05:00
def test_missing_reference_disabled_domain ( tmp_path , app ) :
2023-07-27 18:39:12 -05:00
inv_file = tmp_path / ' inventory '
2024-03-25 05:03:44 -05:00
inv_file . write_bytes ( INVENTORY_V2 )
2024-08-11 08:58:56 -05:00
set_config (
app ,
{
' inv ' : ( ' https://docs.python.org/ ' , str ( inv_file ) ) ,
} ,
)
2021-07-16 07:34:35 -05:00
# load the inventory and check if it's done correctly
2024-07-22 07:32:44 -05:00
validate_intersphinx_mapping ( app , app . config )
2021-07-16 07:34:35 -05:00
load_mappings ( app )
2021-10-02 04:12:49 -05:00
def case ( * , term , doc , py ) :
2021-07-16 07:34:35 -05:00
def assert_ ( rn , expected ) :
if expected is None :
assert rn is None
else :
assert rn . astext ( ) == expected
kwargs = { }
2021-10-02 04:12:49 -05:00
node , contnode = fake_node ( ' std ' , ' term ' , ' a term ' , ' a term ' , * * kwargs )
rn = missing_reference ( app , app . env , node , contnode )
assert_ ( rn , ' a term ' if term else None )
node , contnode = fake_node ( ' std ' , ' term ' , ' inv:a term ' , ' a term ' , * * kwargs )
rn = missing_reference ( app , app . env , node , contnode )
assert_ ( rn , ' a term ' )
2021-07-16 07:34:35 -05:00
node , contnode = fake_node ( ' std ' , ' doc ' , ' docname ' , ' docname ' , * * kwargs )
rn = missing_reference ( app , app . env , node , contnode )
2021-10-02 04:12:49 -05:00
assert_ ( rn , ' docname ' if doc else None )
2021-07-16 07:34:35 -05:00
node , contnode = fake_node ( ' std ' , ' doc ' , ' inv:docname ' , ' docname ' , * * kwargs )
rn = missing_reference ( app , app . env , node , contnode )
2021-10-02 04:12:49 -05:00
assert_ ( rn , ' docname ' )
2021-07-16 07:34:35 -05:00
# an arbitrary ref in another domain
node , contnode = fake_node ( ' py ' , ' func ' , ' module1.func ' , ' func() ' , * * kwargs )
rn = missing_reference ( app , app . env , node , contnode )
2021-10-02 04:12:49 -05:00
assert_ ( rn , ' func() ' if py else None )
2021-07-16 07:34:35 -05:00
node , contnode = fake_node ( ' py ' , ' func ' , ' inv:module1.func ' , ' func() ' , * * kwargs )
rn = missing_reference ( app , app . env , node , contnode )
2021-10-02 04:12:49 -05:00
assert_ ( rn , ' func() ' )
2021-07-16 07:34:35 -05:00
# the base case, everything should resolve
2021-10-31 07:56:26 -05:00
assert app . config . intersphinx_disabled_reftypes == [ ]
2021-10-02 04:12:49 -05:00
case ( term = True , doc = True , py = True )
# disabled a single ref type
2021-10-31 07:56:26 -05:00
app . config . intersphinx_disabled_reftypes = [ ' std:doc ' ]
2021-10-02 04:12:49 -05:00
case ( term = True , doc = False , py = True )
2021-07-16 07:34:35 -05:00
2021-10-02 04:12:49 -05:00
# disabled a whole domain
2021-10-31 07:56:26 -05:00
app . config . intersphinx_disabled_reftypes = [ ' std:* ' ]
2021-10-02 04:12:49 -05:00
case ( term = False , doc = False , py = True )
2021-07-16 07:34:35 -05:00
# disabled all domains
2021-10-31 07:56:26 -05:00
app . config . intersphinx_disabled_reftypes = [ ' * ' ]
2021-10-02 04:12:49 -05:00
case ( term = False , doc = False , py = False )
2021-07-16 07:34:35 -05:00
2024-08-12 16:34:03 -05:00
@pytest.mark.sphinx ( ' html ' , testroot = ' root ' )
2024-07-23 09:35:55 -05:00
def test_inventory_not_having_version ( tmp_path , app ) :
2023-07-27 18:39:12 -05:00
inv_file = tmp_path / ' inventory '
2024-03-25 05:03:44 -05:00
inv_file . write_bytes ( INVENTORY_V2_NO_VERSION )
2024-08-11 08:58:56 -05:00
set_config (
app ,
{
' python ' : ( ' https://docs.python.org/ ' , str ( inv_file ) ) ,
} ,
)
2018-01-29 09:14:53 -06:00
# load the inventory and check if it's done correctly
2024-07-22 07:32:44 -05:00
validate_intersphinx_mapping ( app , app . config )
2018-01-29 09:14:53 -06:00
load_mappings ( app )
rn = reference_check ( app , ' py ' , ' mod ' , ' module1 ' , ' foo ' )
assert isinstance ( rn , nodes . reference )
assert rn [ ' refuri ' ] == ' https://docs.python.org/foo.html#module-module1 '
assert rn [ ' reftitle ' ] == ' (in foo) '
assert rn [ 0 ] . astext ( ) == ' Long Module desc '
2024-08-12 16:34:03 -05:00
@pytest.mark.sphinx ( ' html ' , testroot = ' root ' )
2024-07-23 09:35:55 -05:00
def test_validate_intersphinx_mapping_warnings ( app ) :
2024-07-22 07:32:44 -05:00
""" Check warnings in :func:`sphinx.ext.intersphinx.validate_intersphinx_mapping`. """
2024-07-22 06:59:59 -05:00
bad_intersphinx_mapping = {
' ' : ( ' 789.example ' , None ) , # invalid project name (value)
12345 : ( ' 456.example ' , None ) , # invalid project name (type)
None : ( ' 123.example ' , None ) , # invalid project name (type)
' https://example/ ' : None , # Sphinx 0.5 style value (None)
' https://server/ ' : ' inventory ' , # Sphinx 0.5 style value (str)
' bad-dict-item ' : 0 , # invalid dict item type
' unpack-except-1 ' : [ 0 ] , # invalid dict item size (native ValueError)
' unpack-except-2 ' : FakeList ( ) , # invalid dict item size (custom exception)
' bad-uri-type-1 ' : ( 123456789 , None ) , # invalid target URI type
' bad-uri-type-2 ' : ( None , None ) , # invalid target URI type
' bad-uri-value ' : ( ' ' , None ) , # invalid target URI value
' good ' : ( ' example.org ' , None ) , # duplicated target URI (good entry)
' dedup-good ' : ( ' example.org ' , None ) , # duplicated target URI
' bad-location-1 ' : ( ' a.example ' , 1 ) , # invalid inventory location (single input, bad type)
' bad-location-2 ' : ( ' b.example ' , ' ' ) , # invalid inventory location (single input, bad string)
' bad-location-3 ' : ( ' c.example ' , [ 2 , ' x ' ] ) , # invalid inventory location (sequence input, bad type)
' bad-location-4 ' : ( ' d.example ' , [ ' y ' , ' ' ] ) , # invalid inventory location (sequence input, bad string)
' good-target-1 ' : ( ' e.example ' , None ) , # valid inventory location (None)
' good-target-2 ' : ( ' f.example ' , ( ' x ' , ) ) , # valid inventory location (sequence input)
2024-08-11 08:58:56 -05:00
} # fmt: skip
2024-07-22 06:59:59 -05:00
set_config ( app , bad_intersphinx_mapping )
# normalise the inventory and check if it's done correctly
with pytest . raises (
ConfigError ,
match = r ' ^Invalid `intersphinx_mapping` configuration \ (16 errors \ ).$ ' ,
) :
2024-07-22 07:32:44 -05:00
validate_intersphinx_mapping ( app , app . config )
2025-01-16 10:57:28 -06:00
warnings = strip_escape_sequences ( app . warning . getvalue ( ) ) . splitlines ( )
2024-07-22 06:59:59 -05:00
assert len ( warnings ) == len ( bad_intersphinx_mapping ) - 3
assert warnings == [
" ERROR: Invalid intersphinx project identifier ` ' ' ` in intersphinx_mapping. Project identifiers must be non-empty strings. " ,
2024-08-11 08:58:56 -05:00
' ERROR: Invalid intersphinx project identifier `12345` in intersphinx_mapping. Project identifiers must be non-empty strings. ' ,
' ERROR: Invalid intersphinx project identifier `None` in intersphinx_mapping. Project identifiers must be non-empty strings. ' ,
2024-07-22 06:59:59 -05:00
" ERROR: Invalid value `None` in intersphinx_mapping[ ' https://example/ ' ]. Expected a two-element tuple or list. " ,
" ERROR: Invalid value ` ' inventory ' ` in intersphinx_mapping[ ' https://server/ ' ]. Expected a two-element tuple or list. " ,
" ERROR: Invalid value `0` in intersphinx_mapping[ ' bad-dict-item ' ]. Expected a two-element tuple or list. " ,
" ERROR: Invalid value `[0]` in intersphinx_mapping[ ' unpack-except-1 ' ]. Values must be a (target URI, inventory locations) pair. " ,
" ERROR: Invalid value `[]` in intersphinx_mapping[ ' unpack-except-2 ' ]. Values must be a (target URI, inventory locations) pair. " ,
" ERROR: Invalid target URI value `123456789` in intersphinx_mapping[ ' bad-uri-type-1 ' ][0]. Target URIs must be unique non-empty strings. " ,
" ERROR: Invalid target URI value `None` in intersphinx_mapping[ ' bad-uri-type-2 ' ][0]. Target URIs must be unique non-empty strings. " ,
" ERROR: Invalid target URI value ` ' ' ` in intersphinx_mapping[ ' bad-uri-value ' ][0]. Target URIs must be unique non-empty strings. " ,
" ERROR: Invalid target URI value ` ' example.org ' ` in intersphinx_mapping[ ' dedup-good ' ][0]. Target URIs must be unique (other instance in intersphinx_mapping[ ' good ' ]). " ,
" ERROR: Invalid inventory location value `1` in intersphinx_mapping[ ' bad-location-1 ' ][1]. Inventory locations must be non-empty strings or None. " ,
" ERROR: Invalid inventory location value ` ' ' ` in intersphinx_mapping[ ' bad-location-2 ' ][1]. Inventory locations must be non-empty strings or None. " ,
" ERROR: Invalid inventory location value `2` in intersphinx_mapping[ ' bad-location-3 ' ][1]. Inventory locations must be non-empty strings or None. " ,
2024-08-11 08:58:56 -05:00
" ERROR: Invalid inventory location value ` ' ' ` in intersphinx_mapping[ ' bad-location-4 ' ][1]. Inventory locations must be non-empty strings or None. " ,
2024-07-22 06:59:59 -05:00
]
2015-10-15 16:37:55 -05:00
2024-08-12 16:34:03 -05:00
@pytest.mark.sphinx ( ' html ' , testroot = ' root ' )
2024-07-23 09:35:55 -05:00
def test_load_mappings_fallback ( tmp_path , app ) :
2023-07-27 18:39:12 -05:00
inv_file = tmp_path / ' inventory '
2024-03-25 05:03:44 -05:00
inv_file . write_bytes ( INVENTORY_V2 )
2021-07-16 07:34:35 -05:00
set_config ( app , { } )
2018-01-30 10:21:20 -06:00
# connect to invalid path
app . config . intersphinx_mapping = {
' fallback ' : ( ' https://docs.python.org/py3k/ ' , ' /invalid/inventory/path ' ) ,
}
2024-07-22 07:32:44 -05:00
validate_intersphinx_mapping ( app , app . config )
2018-01-30 10:21:20 -06:00
load_mappings ( app )
2024-08-11 08:58:56 -05:00
assert ' failed to reach any of the inventories ' in app . warning . getvalue ( )
2018-01-30 10:21:20 -06:00
rn = reference_check ( app , ' py ' , ' func ' , ' module1.func ' , ' foo ' )
assert rn is None
# clear messages
2024-07-23 09:35:55 -05:00
app . status . truncate ( 0 )
app . warning . truncate ( 0 )
2018-01-30 10:21:20 -06:00
# add fallbacks to mapping
app . config . intersphinx_mapping = {
2024-08-11 08:58:56 -05:00
' fallback ' : (
' https://docs.python.org/py3k/ ' ,
( ' /invalid/inventory/path ' , str ( inv_file ) ) ,
) ,
2018-01-30 10:21:20 -06:00
}
2024-07-22 07:32:44 -05:00
validate_intersphinx_mapping ( app , app . config )
2018-01-30 10:21:20 -06:00
load_mappings ( app )
2024-08-11 08:58:56 -05:00
assert (
' encountered some issues with some of the inventories '
) in app . status . getvalue ( )
assert app . warning . getvalue ( ) == ' '
2018-01-30 10:21:20 -06:00
rn = reference_check ( app , ' py ' , ' func ' , ' module1.func ' , ' foo ' )
assert isinstance ( rn , nodes . reference )
2023-02-17 17:05:57 -06:00
class TestStripBasicAuth :
2015-10-15 16:37:55 -05:00
""" Tests for sphinx.ext.intersphinx._strip_basic_auth() """
2024-01-14 15:13:46 -06:00
2015-10-15 16:37:55 -05:00
def test_auth_stripped ( self ) :
2024-01-14 15:13:46 -06:00
""" Basic auth creds stripped from URL containing creds """
2015-10-15 16:37:55 -05:00
url = ' https://user:12345@domain.com/project/objects.inv '
expected = ' https://domain.com/project/objects.inv '
2016-08-17 22:41:38 -05:00
actual = _strip_basic_auth ( url )
2024-08-11 08:58:56 -05:00
assert actual == expected
2015-10-15 16:37:55 -05:00
def test_no_auth ( self ) :
2024-01-14 15:13:46 -06:00
""" Url unchanged if param doesn ' t contain basic auth creds """
2015-10-15 16:37:55 -05:00
url = ' https://domain.com/project/objects.inv '
expected = ' https://domain.com/project/objects.inv '
2016-08-17 22:41:38 -05:00
actual = _strip_basic_auth ( url )
2024-08-11 08:58:56 -05:00
assert actual == expected
2015-10-15 16:37:55 -05:00
2016-04-24 21:08:03 -05:00
def test_having_port ( self ) :
2024-01-14 15:13:46 -06:00
""" Basic auth creds correctly stripped from URL containing creds even if URL
contains port
"""
2016-04-24 21:08:03 -05:00
url = ' https://user:12345@domain.com:8080/project/objects.inv '
expected = ' https://domain.com:8080/project/objects.inv '
2016-08-17 22:41:38 -05:00
actual = _strip_basic_auth ( url )
2024-08-11 08:58:56 -05:00
assert actual == expected
2016-04-24 21:08:03 -05:00
2015-10-15 16:37:55 -05:00
2015-10-22 01:09:16 -05:00
def test_getsafeurl_authed ( ) :
""" _get_safe_url() with a url with basic auth """
url = ' https://user:12345@domain.com/project/objects.inv '
expected = ' https://user@domain.com/project/objects.inv '
actual = _get_safe_url ( url )
2024-08-11 08:58:56 -05:00
assert actual == expected
2015-10-22 01:09:16 -05:00
2016-08-17 11:25:29 -05:00
def test_getsafeurl_authed_having_port ( ) :
""" _get_safe_url() with a url with basic auth having port """
url = ' https://user:12345@domain.com:8080/project/objects.inv '
expected = ' https://user@domain.com:8080/project/objects.inv '
actual = _get_safe_url ( url )
2024-08-11 08:58:56 -05:00
assert actual == expected
2016-08-17 11:25:29 -05:00
2015-10-22 01:09:16 -05:00
def test_getsafeurl_unauthed ( ) :
""" _get_safe_url() with a url without basic auth """
url = ' https://domain.com/project/objects.inv '
expected = ' https://domain.com/project/objects.inv '
actual = _get_safe_url ( url )
2024-08-11 08:58:56 -05:00
assert actual == expected
2017-03-21 10:03:05 -05:00
2018-02-22 09:49:08 -06:00
def test_inspect_main_noargs ( capsys ) :
""" inspect_main interface, without arguments """
2023-08-16 17:49:30 -05:00
assert inspect_main ( [ ] ) == 1
2017-03-21 10:03:05 -05:00
expected = (
2024-08-11 08:58:56 -05:00
' Print out an inventory file. \n '
' Error: must specify local path or URL to an inventory file. '
2017-03-21 10:03:05 -05:00
)
stdout , stderr = capsys . readouterr ( )
2024-08-11 08:58:56 -05:00
assert stdout == ' '
assert stderr == expected + ' \n '
2017-03-21 10:03:05 -05:00
2023-07-27 18:39:12 -05:00
def test_inspect_main_file ( capsys , tmp_path ) :
2018-02-22 09:49:08 -06:00
""" inspect_main interface, with file argument """
2023-07-27 18:39:12 -05:00
inv_file = tmp_path / ' inventory '
2024-03-25 05:03:44 -05:00
inv_file . write_bytes ( INVENTORY_V2 )
2017-03-21 10:03:05 -05:00
2018-02-22 09:49:08 -06:00
inspect_main ( [ str ( inv_file ) ] )
2017-03-21 10:03:05 -05:00
stdout , stderr = capsys . readouterr ( )
2024-08-11 08:58:56 -05:00
assert stdout . startswith ( ' c:function \n ' )
assert stderr == ' '
2017-03-21 10:03:05 -05:00
2020-11-08 14:57:06 -06:00
def test_inspect_main_url ( capsys ) :
2018-02-22 09:49:08 -06:00
""" inspect_main interface, with url argument """
2024-08-11 08:58:56 -05:00
2020-11-08 14:57:06 -06:00
class InventoryHandler ( http . server . BaseHTTPRequestHandler ) :
def do_GET ( self ) :
2024-08-11 08:58:56 -05:00
self . send_response ( 200 , ' OK ' )
2020-11-08 14:57:06 -06:00
self . end_headers ( )
2024-03-25 05:03:44 -05:00
self . wfile . write ( INVENTORY_V2 )
2017-03-21 10:03:05 -05:00
2020-11-08 14:57:06 -06:00
def log_message ( * args , * * kwargs ) :
# Silenced.
pass
2017-03-21 10:03:05 -05:00
2024-04-04 04:25:53 -05:00
with http_server ( InventoryHandler ) as server :
url = f ' http://localhost: { server . server_port } / { INVENTORY_FILENAME } '
2020-11-08 14:57:06 -06:00
inspect_main ( [ url ] )
2017-03-21 10:03:05 -05:00
stdout , stderr = capsys . readouterr ( )
2024-08-11 08:58:56 -05:00
assert stdout . startswith ( ' c:function \n ' )
assert stderr == ' '
2021-04-04 11:34:45 -05:00
@pytest.mark.sphinx ( ' html ' , testroot = ' ext-intersphinx-role ' )
2024-07-23 09:35:55 -05:00
def test_intersphinx_role ( app ) :
2021-04-04 11:34:45 -05:00
inv_file = app . srcdir / ' inventory '
2024-03-25 05:03:44 -05:00
inv_file . write_bytes ( INVENTORY_V2 )
2021-04-04 11:34:45 -05:00
app . config . intersphinx_mapping = {
2024-01-13 22:18:57 -06:00
' inv ' : ( ' https://example.org/ ' , str ( inv_file ) ) ,
2021-04-04 11:34:45 -05:00
}
app . config . intersphinx_cache_limit = 0
app . config . nitpicky = True
# load the inventory and check if it's done correctly
2024-07-22 07:32:44 -05:00
validate_intersphinx_mapping ( app , app . config )
2021-04-04 11:34:45 -05:00
load_mappings ( app )
app . build ( )
2022-04-26 21:04:19 -05:00
content = ( app . outdir / ' index.html ' ) . read_text ( encoding = ' utf8 ' )
2025-01-16 10:57:28 -06:00
warnings = strip_escape_sequences ( app . warning . getvalue ( ) ) . splitlines ( )
👌 Handle external references pointing to object types (#12133)
This commit fixes the issue of `objects.inv` denoting object names, whilst the `external` role only allows for role names.
As an example, take the `objects.inv` for the sphinx documentation, which contains:
```
py:function
compile : usage/domains/python.html#compile
```
A user might understandably expect that they could reference this using `` :external:py:function:`compile` ``, but actually this would previously error with:
```
WARNING: role for external cross-reference not found: py:function
```
this is because, `function` is the object type, yet `external` expects the related role name `func`.
It should not be necessary for the user to know about this distinction,
so in this commit, we add logic, to first look if the name relates to a role name (as previous, to not be back-breaking) but, if not, then also look if the name relates to an object that has a known role and, if so, use that.
Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
2024-03-19 07:42:50 -05:00
index_path = app . srcdir / ' index.rst '
assert warnings == [
2024-07-20 15:07:06 -05:00
f " { index_path } :21: WARNING: role for external cross-reference not found in domain ' py ' : ' nope ' [intersphinx.external] " ,
f " { index_path } :28: WARNING: role for external cross-reference not found in domains ' cpp ' , ' std ' : ' nope ' [intersphinx.external] " ,
f " { index_path } :39: WARNING: inventory for external cross-reference not found: ' invNope ' [intersphinx.external] " ,
f " { index_path } :44: WARNING: role for external cross-reference not found in domain ' c ' : ' function ' (perhaps you meant one of: ' func ' , ' identifier ' , ' type ' ) [intersphinx.external] " ,
f " { index_path } :45: WARNING: role for external cross-reference not found in domains ' cpp ' , ' std ' : ' function ' (perhaps you meant one of: ' cpp:func ' , ' cpp:identifier ' , ' cpp:type ' ) [intersphinx.external] " ,
f ' { index_path } :9: WARNING: external py:mod reference target not found: module3 [ref.mod] ' ,
f ' { index_path } :14: WARNING: external py:mod reference target not found: module10 [ref.mod] ' ,
f ' { index_path } :19: WARNING: external py:meth reference target not found: inv:Foo.bar [ref.meth] ' ,
👌 Handle external references pointing to object types (#12133)
This commit fixes the issue of `objects.inv` denoting object names, whilst the `external` role only allows for role names.
As an example, take the `objects.inv` for the sphinx documentation, which contains:
```
py:function
compile : usage/domains/python.html#compile
```
A user might understandably expect that they could reference this using `` :external:py:function:`compile` ``, but actually this would previously error with:
```
WARNING: role for external cross-reference not found: py:function
```
this is because, `function` is the object type, yet `external` expects the related role name `func`.
It should not be necessary for the user to know about this distinction,
so in this commit, we add logic, to first look if the name relates to a role name (as previous, to not be back-breaking) but, if not, then also look if the name relates to an object that has a known role and, if so, use that.
Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
2024-03-19 07:42:50 -05:00
]
2021-04-04 11:34:45 -05:00
2024-01-13 22:18:57 -06:00
html = ' <a class= " reference external " href= " https://example.org/ {} " title= " (in foo v2.0) " > '
2021-11-06 07:25:49 -05:00
assert html . format ( ' foo.html#module-module1 ' ) in content
assert html . format ( ' foo.html#module-module2 ' ) in content
2021-07-13 06:41:41 -05:00
2021-11-06 07:25:49 -05:00
assert html . format ( ' sub/foo.html#module1.func ' ) in content
2021-11-06 08:02:37 -05:00
# default domain
assert html . format ( ' index.html#std_uint8_t ' ) in content
# std roles without domain prefix
assert html . format ( ' docname.html ' ) in content
assert html . format ( ' index.html#cmdoption-ls-l ' ) in content
# explicit inventory
assert html . format ( ' cfunc.html#CFunc ' ) in content
# explicit title
assert html . format ( ' index.html#foons ' ) in content
2024-07-31 02:16:10 -05:00
if TYPE_CHECKING :
2025-01-11 19:04:14 -06:00
from typing import NoReturn
2024-07-31 02:16:10 -05:00
from sphinx . ext . intersphinx . _shared import InventoryCacheEntry
2024-08-12 16:34:03 -05:00
@pytest.mark.sphinx ( ' html ' , testroot = ' root ' )
2024-10-07 15:19:20 -05:00
@pytest.mark.parametrize (
( ' cache_limit ' , ' expected_expired ' ) ,
[
( 5 , False ) ,
( 1 , True ) ,
( 0 , True ) ,
( - 1 , False ) ,
] ,
)
def test_intersphinx_cache_limit ( app , monkeypatch , cache_limit , expected_expired ) :
2024-07-31 02:16:10 -05:00
url = ' https://example.org/ '
2024-10-07 15:19:20 -05:00
app . config . intersphinx_cache_limit = cache_limit
2024-07-31 02:16:10 -05:00
app . config . intersphinx_mapping = {
' inv ' : ( url , None ) ,
}
2025-01-04 19:06:51 -06:00
app . config . intersphinx_timeout = None
2024-07-31 02:16:10 -05:00
# load the inventory and check if it's done correctly
intersphinx_cache : dict [ str , InventoryCacheEntry ] = {
2024-10-07 15:19:20 -05:00
url : ( ' inv ' , 0 , { } ) , # Timestamp of last cache write is zero.
2024-07-31 02:16:10 -05:00
}
validate_intersphinx_mapping ( app , app . config )
2024-10-07 15:19:20 -05:00
# The test's `now` is two days after the cache was created.
now = 2 * 86400
monkeypatch . setattr ( ' time.time ' , lambda : now )
# `_fetch_inventory_group` calls `_fetch_inventory`.
# We replace it with a mock to test whether it has been called.
# If it has been called, it means the cache had expired.
mock_fetch_inventory = mock . Mock ( return_value = ( ' inv ' , now , { } ) )
monkeypatch . setattr (
' sphinx.ext.intersphinx._load._fetch_inventory ' , mock_fetch_inventory
)
2024-07-31 02:16:10 -05:00
for name , ( uri , locations ) in app . config . intersphinx_mapping . values ( ) :
project = _IntersphinxProject ( name = name , target_uri = uri , locations = locations )
2024-10-07 15:19:20 -05:00
updated = _fetch_inventory_group (
2024-07-31 02:16:10 -05:00
project = project ,
cache = intersphinx_cache ,
now = now ,
2025-01-04 19:06:51 -06:00
config = _InvConfig . from_config ( app . config ) ,
2024-07-31 02:16:10 -05:00
srcdir = app . srcdir ,
)
2024-10-07 15:19:20 -05:00
# If we hadn't mocked `_fetch_inventory`, it would've made
# a request to `https://example.org/` and found no inventory
# file. That would've been an error, and `updated` would've been
# False even if the cache had expired. The mock makes it behave
# "correctly".
assert updated is expected_expired
# Double-check: If the cache was expired, `mock_fetch_inventory`
# must've been called.
assert mock_fetch_inventory . called is expected_expired
2024-08-22 04:11:12 -05:00
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 ,
)