2009-09-09 14:56:53 -05:00
""" Test the intersphinx extension. """
2020-11-08 14:57:06 -06:00
import http . server
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
2024-04-25 06:07:05 -05:00
from sphinx . builders . html import INVENTORY_FILENAME
2020-11-11 05:00:27 -06:00
from sphinx . ext . intersphinx import (
fetch_inventory ,
inspect_main ,
load_mappings ,
missing_reference ,
normalize_intersphinx_mapping ,
)
2018-02-19 07:39:14 -06:00
from sphinx . ext . intersphinx import setup as intersphinx_setup
2024-04-19 18:47:30 -05:00
from sphinx . ext . intersphinx . _load import _get_safe_url , _strip_basic_auth
👌 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
from sphinx . util . console import strip_colors
2020-11-20 12:04:26 -06:00
2024-03-25 05:03:44 -05:00
from tests . test_util . intersphinx_data import INVENTORY_V2 , 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
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 ) :
app . config . intersphinx_mapping = mapping
app . config . intersphinx_cache_limit = 0
2021-10-31 07:56:26 -05:00
app . config . intersphinx_disabled_reftypes = [ ]
2021-07-16 07:34:35 -05:00
2024-04-19 18:47:30 -05:00
@mock.patch ( ' sphinx.ext.intersphinx._load.InventoryFile ' )
@mock.patch ( ' sphinx.ext.intersphinx._load._read_from_url ' )
2023-02-17 17:46:31 -06:00
def test_fetch_inventory_redirection ( _read_from_url , InventoryFile , app , status , warning ) : # NoQA: PT019
2016-09-01 09:30:04 -05:00
intersphinx_setup ( app )
2020-11-24 10:55:22 -06:00
_read_from_url ( ) . readline . return_value = b ' # Sphinx inventory version 2 '
2016-08-10 21:19:42 -05:00
# same uri and inv, not redirected
2024-01-13 22:18:57 -06:00
_read_from_url ( ) . url = ' https://hostname/ ' + INVENTORY_FILENAME
fetch_inventory ( app , ' https://hostname/ ' , ' https://hostname/ ' + INVENTORY_FILENAME )
2016-08-10 21:19:42 -05:00
assert ' intersphinx inventory has moved ' not in status . getvalue ( )
2024-01-13 22:18:57 -06:00
assert InventoryFile . load . call_args [ 0 ] [ 1 ] == ' https://hostname/ '
2016-08-10 21:19:42 -05:00
# same uri and inv, redirected
2016-08-10 23:58:21 -05:00
status . seek ( 0 )
2016-08-10 21:19:42 -05:00
status . truncate ( 0 )
2024-01-13 22:18:57 -06:00
_read_from_url ( ) . url = ' https://hostname/new/ ' + INVENTORY_FILENAME
2016-08-10 21:19:42 -05:00
2024-01-13 22:18:57 -06:00
fetch_inventory ( app , ' https://hostname/ ' , ' https://hostname/ ' + INVENTORY_FILENAME )
2016-08-10 21:19:42 -05:00
assert status . getvalue ( ) == ( ' intersphinx inventory has moved: '
2024-01-13 22:18:57 -06:00
' https://hostname/ %s -> https://hostname/new/ %s \n ' %
2016-08-10 21:19:42 -05:00
( INVENTORY_FILENAME , INVENTORY_FILENAME ) )
2024-01-13 22:18:57 -06:00
assert InventoryFile . load . call_args [ 0 ] [ 1 ] == ' https://hostname/new '
2016-08-10 21:19:42 -05:00
# different uri and inv, not redirected
2016-08-10 23:58:21 -05:00
status . seek ( 0 )
2016-08-10 21:19:42 -05:00
status . truncate ( 0 )
2024-01-13 22:18:57 -06:00
_read_from_url ( ) . url = ' https://hostname/new/ ' + INVENTORY_FILENAME
2016-08-10 21:19:42 -05:00
2024-01-13 22:18:57 -06:00
fetch_inventory ( app , ' https://hostname/ ' , ' https://hostname/new/ ' + INVENTORY_FILENAME )
2016-08-10 21:19:42 -05:00
assert ' intersphinx inventory has moved ' not in status . getvalue ( )
2024-01-13 22:18:57 -06:00
assert InventoryFile . load . call_args [ 0 ] [ 1 ] == ' https://hostname/ '
2016-08-10 21:19:42 -05:00
# different uri and inv, redirected
2016-08-10 23:58:21 -05:00
status . seek ( 0 )
2016-08-10 21:19:42 -05:00
status . truncate ( 0 )
2024-01-13 22:18:57 -06:00
_read_from_url ( ) . url = ' https://hostname/other/ ' + INVENTORY_FILENAME
2016-08-10 21:19:42 -05:00
2024-01-13 22:18:57 -06:00
fetch_inventory ( app , ' https://hostname/ ' , ' https://hostname/new/ ' + INVENTORY_FILENAME )
2016-08-10 21:19:42 -05:00
assert status . getvalue ( ) == ( ' intersphinx inventory has moved: '
2024-01-13 22:18:57 -06:00
' https://hostname/new/ %s -> https://hostname/other/ %s \n ' %
2016-08-10 21:19:42 -05:00
( INVENTORY_FILENAME , INVENTORY_FILENAME ) )
2024-01-13 22:18:57 -06:00
assert InventoryFile . load . call_args [ 0 ] [ 1 ] == ' https://hostname/ '
2016-08-10 21:19:42 -05:00
2023-07-27 18:39:12 -05:00
def test_missing_reference ( tmp_path , app , status , warning ) :
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 , {
2023-07-27 18:39:12 -05:00
' 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
2021-07-16 07:34:35 -05:00
} )
2009-09-09 14:56:53 -05:00
# load the inventory and check if it's done correctly
2018-11-04 05:22:50 -06:00
normalize_intersphinx_mapping ( app , app . config )
2009-09-09 14:56:53 -05:00
load_mappings ( app )
inv = app . env . intersphinx_inventory
assert inv [ ' py:module ' ] [ ' module2 ' ] == \
2015-02-22 22:20:35 -06:00
( ' foo ' , ' 2.0 ' , ' https://docs.python.org/foo.html#module-module2 ' , ' - ' )
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
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
refexplicit = True )
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
2010-08-05 04:58:43 -05:00
node , contnode = fake_node ( ' py ' , ' mod ' , ' py3k:unknown ' , ' py3k:unknown ' ,
refexplicit = False )
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
2010-08-05 04:58:43 -05:00
node , contnode = fake_node ( ' py ' , ' mod ' , ' py3k:unknown ' , ' py3k:unknown ' ,
refexplicit = True )
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 '
rn = reference_check ( app , ' py ' , ' mod ' , ' py3krel:module1 ' , ' foo ' , refdoc = ' sub/dir/test ' )
assert rn [ ' refuri ' ] == ' ../../py3k/foo.html#module-module1 '
rn = reference_check ( app , ' py ' , ' mod ' , ' py3krelparent:module1 ' , ' foo ' ,
refdoc = ' sub/dir/test ' )
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 '
2023-07-27 18:39:12 -05:00
def test_missing_reference_pydomain ( tmp_path , app , status , warning ) :
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 , {
2023-07-27 18:39:12 -05:00
' https://docs.python.org/ ' : str ( inv_file ) ,
2021-07-16 07:34:35 -05:00
} )
2017-04-22 10:21:22 -05:00
# load the inventory and check if it's done correctly
2018-11-04 05:22:50 -06:00
normalize_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
2023-07-27 18:39:12 -05:00
def test_missing_reference_stddomain ( tmp_path , app , status , warning ) :
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 , {
2023-07-27 18:39:12 -05:00
' cmd ' : ( ' https://docs.python.org/ ' , str ( inv_file ) ) ,
2021-07-16 07:34:35 -05:00
} )
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
2018-11-04 05:22:50 -06:00
normalize_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
2017-04-22 21:33:49 -05:00
@pytest.mark.sphinx ( ' html ' , testroot = ' ext-intersphinx-cppdomain ' )
2023-07-27 18:39:12 -05:00
def test_missing_reference_cppdomain ( tmp_path , app , status , warning ) :
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 , {
2023-07-27 18:39:12 -05:00
' https://docs.python.org/ ' : str ( inv_file ) ,
2021-07-16 07:34:35 -05:00
} )
2017-04-22 21:33:49 -05:00
# load the inventory and check if it's done correctly
2018-11-04 05:22:50 -06:00
normalize_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 ' )
2017-04-22 21:33:49 -05:00
assert ( ' <a class= " reference external " '
' href= " https://docs.python.org/index.html#cpp_foo_bar " '
2018-01-13 00:18:09 -06:00
' title= " (in foo v2.0) " > '
' <code class= " xref cpp cpp-class docutils literal notranslate " > '
2017-04-22 21:33:49 -05:00
' <span class= " pre " >Bar</span></code></a> ' in html )
2017-09-28 13:43:46 -05:00
assert ( ' <a class= " reference external " '
' href= " https://docs.python.org/index.html#foons " '
2021-03-20 13:27:13 -05:00
' title= " (in foo v2.0) " ><span class= " n " ><span class= " pre " >foons</span></span></a> ' in html )
2017-09-28 13:43:46 -05:00
assert ( ' <a class= " reference external " '
' href= " https://docs.python.org/index.html#foons_bartype " '
2021-03-20 13:27:13 -05:00
' 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
2023-07-27 18:39:12 -05:00
def test_missing_reference_jsdomain ( tmp_path , app , status , warning ) :
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 , {
2023-07-27 18:39:12 -05:00
' https://docs.python.org/ ' : str ( inv_file ) ,
2021-07-16 07:34:35 -05:00
} )
2017-04-22 11:02:29 -05:00
# load the inventory and check if it's done correctly
2018-11-04 05:22:50 -06:00
normalize_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() '
2023-07-27 18:39:12 -05:00
def test_missing_reference_disabled_domain ( tmp_path , app , status , warning ) :
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 , {
2023-07-27 18:39:12 -05:00
' 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
normalize_intersphinx_mapping ( app , app . config )
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
2023-07-27 18:39:12 -05:00
def test_inventory_not_having_version ( tmp_path , app , status , warning ) :
inv_file = tmp_path / ' inventory '
2024-03-25 05:03:44 -05:00
inv_file . write_bytes ( INVENTORY_V2_NO_VERSION )
2021-07-16 07:34:35 -05:00
set_config ( app , {
2023-07-27 18:39:12 -05:00
' https://docs.python.org/ ' : str ( inv_file ) ,
2021-07-16 07:34:35 -05:00
} )
2018-01-29 09:14:53 -06:00
# load the inventory and check if it's done correctly
2018-11-04 05:22:50 -06:00
normalize_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 '
2023-07-27 18:39:12 -05:00
def test_load_mappings_warnings ( tmp_path , app , status , warning ) :
2011-09-03 14:41:26 -05:00
"""
load_mappings issues a warning if new - style mapping
2016-05-22 22:14:56 -05:00
identifiers are not string
2011-09-03 14:41:26 -05:00
"""
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 , {
2023-07-27 18:39:12 -05:00
' https://docs.python.org/ ' : str ( inv_file ) ,
' py3k ' : ( ' https://docs.python.org/py3k/ ' , str ( inv_file ) ) ,
2024-01-13 22:18:57 -06:00
' repoze.workflow ' : ( ' https://docs.repoze.org/workflow/ ' , str ( inv_file ) ) ,
' django-taggit ' : ( ' https://django-taggit.readthedocs.org/en/latest/ ' ,
2023-07-27 18:39:12 -05:00
str ( inv_file ) ) ,
2024-01-13 22:18:57 -06:00
12345 : ( ' https://www.sphinx-doc.org/en/stable/ ' , str ( inv_file ) ) ,
2021-07-16 07:34:35 -05:00
} )
2011-09-03 14:41:26 -05:00
# load the inventory and check if it's done correctly
2018-11-04 05:22:50 -06:00
normalize_intersphinx_mapping ( app , app . config )
2011-09-03 14:41:26 -05:00
load_mappings ( app )
2023-03-17 11:41:22 -05:00
warnings = warning . getvalue ( ) . splitlines ( )
assert len ( warnings ) == 2
assert " The pre-Sphinx 1.0 ' intersphinx_mapping ' format is " in warnings [ 0 ]
assert ' intersphinx identifier 12345 is not string. Ignored ' in warnings [ 1 ]
2015-10-15 16:37:55 -05:00
2023-07-27 18:39:12 -05:00
def test_load_mappings_fallback ( tmp_path , app , status , warning ) :
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 ' ) ,
}
2018-11-04 05:22:50 -06:00
normalize_intersphinx_mapping ( app , app . config )
2018-01-30 10:21:20 -06:00
load_mappings ( app )
assert " failed to reach any of the inventories " in warning . getvalue ( )
rn = reference_check ( app , ' py ' , ' func ' , ' module1.func ' , ' foo ' )
assert rn is None
# clear messages
status . truncate ( 0 )
warning . truncate ( 0 )
# add fallbacks to mapping
app . config . intersphinx_mapping = {
' fallback ' : ( ' https://docs.python.org/py3k/ ' , ( ' /invalid/inventory/path ' ,
2023-07-27 18:39:12 -05:00
str ( inv_file ) ) ) ,
2018-01-30 10:21:20 -06:00
}
2018-11-04 05:22:50 -06:00
normalize_intersphinx_mapping ( app , app . config )
2018-01-30 10:21:20 -06:00
load_mappings ( app )
assert " encountered some issues with some of the inventories " in status . getvalue ( )
2022-12-29 18:06:11 -06:00
assert 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 )
2023-02-17 17:05:57 -06:00
assert expected == actual
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 )
2023-02-17 17:05:57 -06:00
assert expected == actual
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 )
2023-02-17 17:05:57 -06:00
assert expected == actual
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 )
assert expected == actual
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 )
assert expected == actual
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 )
assert expected == actual
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 = (
" Print out an inventory file. \n "
" Error: must specify local path or URL to an inventory file. "
)
stdout , stderr = capsys . readouterr ( )
assert stdout == " "
assert stderr == expected + " \n "
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 ( )
assert stdout . startswith ( " c:function \n " )
assert stderr == " "
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 """
2020-11-08 14:57:06 -06:00
class InventoryHandler ( http . server . BaseHTTPRequestHandler ) :
def do_GET ( self ) :
self . send_response ( 200 , " OK " )
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 ( )
assert stdout . startswith ( " c:function \n " )
assert stderr == " "
2021-04-04 11:34:45 -05:00
@pytest.mark.sphinx ( ' html ' , testroot = ' ext-intersphinx-role ' )
2021-11-06 07:25:49 -05:00
def test_intersphinx_role ( app , warning ) :
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
normalize_intersphinx_mapping ( app , app . config )
load_mappings ( app )
app . build ( )
2022-04-26 21:04:19 -05:00
content = ( app . outdir / ' index.html ' ) . read_text ( encoding = ' utf8 ' )
👌 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
warnings = strip_colors ( warning . getvalue ( ) ) . splitlines ( )
index_path = app . srcdir / ' index.rst '
assert warnings == [
👌 Improve `external` role warnings (and revert object fallback) (#12193)
The key issue this commit seeks to address, is that existing tools / documentation often lead users to mistakenly use object types and not role names, a classic example being `function` not `func`
Previously, the warning message for using e.g. `` external:function`target` `` (with `py` as the default domain), would be:
```
WARNING: role for external cross-reference not found: function
```
This gives no information to the user on how to fix the issue, even though there is actually quite an easy fix
This commit adds logic to create more specific / helpful warning messages, e.g.
```
WARNING: role for external cross-reference not found in domains 'py', 'std': 'function' (perhaps you meant one of: 'py:func', 'py:obj')
```
This goes through the same original logic but, if the role is not found, it will look if the role name is actually an available object type on the domain(s), and then suggest its related roles.
This commit also reverts #12133, which provided a (silent) fallback to auto convert an object type to a role name.
2024-03-25 08:39:51 -05:00
f " { index_path } :21: WARNING: role for external cross-reference not found in domain ' py ' : ' nope ' " ,
f " { index_path } :28: WARNING: role for external cross-reference not found in domains ' cpp ' , ' std ' : ' nope ' " ,
f " { index_path } :39: WARNING: inventory for external cross-reference not found: ' invNope ' " ,
f " { index_path } :44: WARNING: role for external cross-reference not found in domain ' c ' : ' function ' (perhaps you meant one of: ' func ' , ' identifier ' , ' type ' ) " ,
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 ' ) " ,
👌 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
f ' { index_path } :9: WARNING: external py:mod reference target not found: module3 ' ,
f ' { index_path } :14: WARNING: external py:mod reference target not found: module10 ' ,
f ' { index_path } :19: WARNING: external py:meth reference target not found: inv:Foo.bar ' ,
]
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