[tests] move utilities and static data into dedicated modules and remove `html5lib` (#12173)

Since #12168, HTML files are now XML compliant, hence ``html5lib`` is no more needed as a testing dependencies.
This commit is contained in:
Bénédikt Tran 2024-03-25 11:03:44 +01:00 committed by GitHub
parent 9e239729d4
commit 885818bb7f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 289 additions and 237 deletions

View File

@ -91,7 +91,6 @@ lint = [
]
test = [
"pytest>=6.0",
"html5lib",
"defusedxml>=0.7.1", # for secure XML/HTML parsing
"cython>=3.0",
"setuptools>=67.0", # for Cython compilation

View File

@ -7,12 +7,11 @@ __all__ = ('SphinxTestApp', 'SphinxTestAppWrapperForSkipBuilding')
import contextlib
import os
import sys
import warnings
from io import StringIO
from types import MappingProxyType
from typing import TYPE_CHECKING
from xml.etree import ElementTree
from defusedxml.ElementTree import parse as xml_parse
from docutils import nodes
from docutils.parsers.rst import directives, roles
@ -26,6 +25,7 @@ if TYPE_CHECKING:
from collections.abc import Mapping
from pathlib import Path
from typing import Any, Final
from xml.etree.ElementTree import ElementTree
from docutils.nodes import Node
@ -70,10 +70,10 @@ def assert_node(node: Node, cls: Any = None, xpath: str = "", **kwargs: Any) ->
f'The node{xpath}[{key}] is not {value!r}: {node[key]!r}'
def etree_parse(path: str) -> Any:
with warnings.catch_warnings(record=False):
warnings.filterwarnings("ignore", category=DeprecationWarning)
return ElementTree.parse(path) # NoQA: S314 # using known data in tests
# keep this to restrict the API usage and to have a correct return type
def etree_parse(path: str | os.PathLike[str]) -> ElementTree:
"""Parse a file into a (safe) XML element tree."""
return xml_parse(path)
class SphinxTestApp(sphinx.application.Sphinx):

View File

@ -3,26 +3,26 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import pytest
from html5lib import HTMLParser
from sphinx.testing.util import etree_parse
if TYPE_CHECKING:
from collections.abc import Callable, Generator
from pathlib import Path
from xml.etree.ElementTree import Element
from xml.etree.ElementTree import ElementTree
etree_cache: dict[Path, Element] = {}
_etree_cache: dict[Path, ElementTree] = {}
def _parse(fname: Path) -> Element:
if fname in etree_cache:
return etree_cache[fname]
with fname.open('rb') as fp:
etree = HTMLParser(namespaceHTMLElements=False).parse(fp)
etree_cache[fname] = etree
return etree
def _parse(path: Path) -> ElementTree:
if path in _etree_cache:
return _etree_cache[path]
_etree_cache[path] = tree = etree_parse(path)
return tree
@pytest.fixture(scope='package')
def cached_etree_parse() -> Generator[Callable[[Path], Element], None, None]:
def cached_etree_parse() -> Generator[Callable[[Path], ElementTree], None, None]:
yield _parse
etree_cache.clear()
_etree_cache.clear()

View File

@ -12,43 +12,8 @@ from sphinx.errors import ConfigError
from sphinx.util.console import strip_colors
from sphinx.util.inventory import InventoryFile
FIGURE_CAPTION = ".//figure/figcaption/p"
def check_xpath(etree, fname, path, check, be_found=True):
nodes = list(etree.findall(path))
if check is None:
assert nodes == [], ('found any nodes matching xpath '
f'{path!r} in file {fname}')
return
else:
assert nodes != [], ('did not find any node matching xpath '
f'{path!r} in file {fname}')
if callable(check):
check(nodes)
elif not check:
# only check for node presence
pass
else:
def get_text(node):
if node.text is not None:
# the node has only one text
return node.text
else:
# the node has tags and text; gather texts just under the node
return ''.join(n.tail or '' for n in node)
rex = re.compile(check)
if be_found:
if any(rex.search(get_text(node)) for node in nodes):
return
else:
if all(not rex.search(get_text(node)) for node in nodes):
return
msg = (f'{check!r} not found in any node matching '
f'{path!r} in file {fname}: {[node.text for node in nodes]!r}')
raise AssertionError(msg)
from tests.test_builders.xpath_data import FIGURE_CAPTION
from tests.test_builders.xpath_util import check_xpath
def test_html4_error(make_app, tmp_path):

View File

@ -4,7 +4,7 @@ import re
import pytest
from tests.test_builders.test_build_html import check_xpath
from tests.test_builders.xpath_util import check_xpath
def tail_check(check):
@ -128,7 +128,7 @@ def tail_check(check):
# ``seealso`` directive
('markup.html', ".//div/p[@class='admonition-title']", 'See also'),
# a ``hlist`` directive
('markup.html', ".//table[@class='hlist']/tbody/tr/td/ul/li/p", '^This$'),
('markup.html', ".//table[@class='hlist']/tr/td/ul/li/p", '^This$'),
# a ``centered`` directive
('markup.html', ".//p[@class='centered']/strong", 'LICENSE'),
# a glossary

View File

@ -5,7 +5,8 @@ import re
import pytest
from tests.test_builders.test_build_html import FIGURE_CAPTION, check_xpath
from tests.test_builders.xpath_data import FIGURE_CAPTION
from tests.test_builders.xpath_util import check_xpath
@pytest.mark.sphinx('html', testroot='numfig')

View File

@ -2,7 +2,7 @@
import pytest
from tests.test_builders.test_build_html import check_xpath
from tests.test_builders.xpath_util import check_xpath
@pytest.mark.parametrize(("fname", "path", "check", "be_found"), [

View File

@ -0,0 +1,8 @@
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Final
FIGURE_CAPTION: Final[str] = ".//figure/figcaption/p"

View File

@ -0,0 +1,79 @@
from __future__ import annotations
import re
import textwrap
from typing import TYPE_CHECKING
from xml.etree.ElementTree import tostring
if TYPE_CHECKING:
import os
from collections.abc import Callable, Iterable, Sequence
from xml.etree.ElementTree import Element, ElementTree
def _get_text(node: Element) -> str:
if node.text is not None:
# the node has only one text
return node.text
# the node has tags and text; gather texts just under the node
return ''.join(n.tail or '' for n in node)
def _prettify(nodes: Iterable[Element]) -> str:
def pformat(node: Element) -> str:
return tostring(node, encoding='unicode', method='html')
return ''.join(f'(i={index}) {pformat(node)}\n' for index, node in enumerate(nodes))
def check_xpath(
etree: ElementTree,
filename: str | os.PathLike[str],
xpath: str,
check: str | re.Pattern[str] | Callable[[Sequence[Element]], None] | None,
be_found: bool = True,
*,
min_count: int = 1,
) -> None:
"""Check that one or more nodes satisfy a predicate.
:param etree: The element tree.
:param filename: The element tree source name (for errors only).
:param xpath: An XPath expression to use.
:param check: Optional regular expression or a predicate the nodes must validate.
:param be_found: If false, negate the predicate.
:param min_count: Minimum number of nodes expected to satisfy the predicate.
* If *check* is empty (``''``), only the minimum count is checked.
* If *check* is ``None``, no node should satisfy the XPath expression.
"""
nodes = etree.findall(xpath)
assert isinstance(nodes, list)
if check is None:
# use == to have a nice pytest diff
assert nodes == [], f'found nodes matching xpath {xpath!r} in file {filename}'
return
assert len(nodes) >= min_count, (f'expecting at least {min_count} node(s) '
f'to satisfy {xpath!r} in file {filename}')
if check == '':
return
if callable(check):
check(nodes)
return
rex = re.compile(check)
if be_found:
if any(rex.search(_get_text(node)) for node in nodes):
return
else:
if all(not rex.search(_get_text(node)) for node in nodes):
return
ctx = textwrap.indent(_prettify(nodes), ' ' * 2)
msg = f'{check!r} not found in any node matching {xpath!r} in file {filename}:\n{ctx}'
raise AssertionError(msg)

View File

@ -5,7 +5,6 @@ from unittest import mock
import pytest
from docutils import nodes
from docutils.nodes import definition, definition_list, definition_list_item, term
from html5lib import HTMLParser
from sphinx import addnodes
from sphinx.addnodes import (
@ -20,7 +19,7 @@ from sphinx.addnodes import (
)
from sphinx.domains.std import StandardDomain
from sphinx.testing import restructuredtext
from sphinx.testing.util import assert_node
from sphinx.testing.util import assert_node, etree_parse
def test_process_doc_handle_figure_caption():
@ -375,9 +374,11 @@ def test_productionlist(app, status, warning):
assert warnings[-1] == ''
assert "Dup2.rst:4: WARNING: duplicate token description of Dup, other instance in Dup1" in warnings[0]
with (app.outdir / 'index.html').open('rb') as f:
etree = HTMLParser(namespaceHTMLElements=False).parse(f)
ul = list(etree.iter('ul'))[1]
etree = etree_parse(app.outdir / 'index.html')
nodes = list(etree.iter('ul'))
assert len(nodes) >= 2
ul = nodes[1]
cases = []
for li in list(ul):
assert len(list(li)) == 1

View File

@ -0,0 +1,33 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from unittest.mock import Mock
# NEVER import those objects from sphinx.ext.autodoc directly
from sphinx.ext.autodoc.directive import DocumenterBridge, process_documenter_options
from sphinx.util.docutils import LoggingReporter
if TYPE_CHECKING:
from typing import Any
from docutils.statemachine import StringList
from sphinx.application import Sphinx
def do_autodoc(
app: Sphinx,
objtype: str,
name: str,
options: dict[str, Any] | None = None,
) -> StringList:
options = {} if options is None else options.copy()
app.env.temp_data.setdefault('docname', 'index') # set dummy docname
doccls = app.registry.documenters[objtype]
docoptions = process_documenter_options(doccls, app.config, options)
state = Mock()
state.document.settings.tab_width = 8
bridge = DocumenterBridge(app.env, LoggingReporter(''), docoptions, 1, state)
documenter = doccls(bridge, name)
documenter.generate()
return bridge.result

View File

@ -20,8 +20,8 @@ from docutils.statemachine import ViewList
from sphinx import addnodes
from sphinx.ext.autodoc import ALL, ModuleLevelDocumenter, Options
from sphinx.ext.autodoc.directive import DocumenterBridge, process_documenter_options
from sphinx.util.docutils import LoggingReporter
from tests.test_extensions.autodoc_util import do_autodoc
try:
# Enable pyximport to test cython module
@ -34,20 +34,6 @@ if TYPE_CHECKING:
from typing import Any
def do_autodoc(app, objtype, name, options=None):
options = {} if options is None else options.copy()
app.env.temp_data.setdefault('docname', 'index') # set dummy docname
doccls = app.registry.documenters[objtype]
docoptions = process_documenter_options(doccls, app.config, options)
state = Mock()
state.document.settings.tab_width = 8
bridge = DocumenterBridge(app.env, LoggingReporter(''), docoptions, 1, state)
documenter = doccls(bridge, name)
documenter.generate()
return bridge.result
def make_directive_bridge(env):
options = Options(
inherited_members=False,
@ -82,23 +68,6 @@ def make_directive_bridge(env):
processed_signatures = []
def process_signature(app, what, name, obj, options, args, retann):
processed_signatures.append((what, name))
if name == 'bar':
return '42', None
return None
def skip_member(app, what, name, obj, skip, options):
if name in ('__special1__', '__special2__'):
return skip
if name.startswith('__'):
return True
if name == 'skipmeth':
return True
return None
def test_parse_name(app):
def verify(objtype, name, result):
inst = app.registry.documenters[objtype](directive, name)
@ -139,6 +108,21 @@ def test_parse_name(app):
def test_format_signature(app):
def process_signature(app, what, name, obj, options, args, retann):
processed_signatures.append((what, name))
if name == 'bar':
return '42', None
return None
def skip_member(app, what, name, obj, skip, options):
if name in ('__special1__', '__special2__'):
return skip
if name.startswith('__'):
return True
if name == 'skipmeth':
return True
return None
app.connect('autodoc-process-signature', process_signature)
app.connect('autodoc-skip-member', skip_member)

View File

@ -6,7 +6,7 @@ source file translated by test_build.
import pytest
from tests.test_extensions.test_ext_autodoc import do_autodoc
from tests.test_extensions.autodoc_util import do_autodoc
@pytest.mark.sphinx('html', testroot='ext-autodoc')

View File

@ -11,7 +11,7 @@ from typing import Union
import pytest
from tests.test_extensions.test_ext_autodoc import do_autodoc
from tests.test_extensions.autodoc_util import do_autodoc
@pytest.mark.sphinx('html', testroot='ext-autodoc')

View File

@ -6,7 +6,7 @@ source file translated by test_build.
import pytest
from tests.test_extensions.test_ext_autodoc import do_autodoc
from tests.test_extensions.autodoc_util import do_autodoc
@pytest.mark.sphinx('html', testroot='ext-autodoc')

View File

@ -6,7 +6,7 @@ source file translated by test_build.
import pytest
from tests.test_extensions.test_ext_autodoc import do_autodoc
from tests.test_extensions.autodoc_util import do_autodoc
@pytest.mark.sphinx('html', testroot='ext-autodoc')

View File

@ -8,7 +8,7 @@ import sys
import pytest
from tests.test_extensions.test_ext_autodoc import do_autodoc
from tests.test_extensions.autodoc_util import do_autodoc
@pytest.mark.sphinx('html', testroot='ext-autodoc')

View File

@ -6,7 +6,7 @@ source file translated by test_build.
import pytest
from tests.test_extensions.test_ext_autodoc import do_autodoc
from tests.test_extensions.autodoc_util import do_autodoc
@pytest.mark.sphinx('html', testroot='ext-autodoc')

View File

@ -8,7 +8,7 @@ import pytest
from sphinx.testing import restructuredtext
from tests.test_extensions.test_ext_autodoc import do_autodoc
from tests.test_extensions.autodoc_util import do_autodoc
IS_PYPY = platform.python_implementation() == 'PyPy'

View File

@ -4,7 +4,7 @@ import pytest
from sphinx.ext.autodoc import between, cut_lines
from tests.test_extensions.test_ext_autodoc import do_autodoc
from tests.test_extensions.autodoc_util import do_autodoc
@pytest.mark.sphinx('html', testroot='ext-autodoc')

View File

@ -2,7 +2,7 @@
import pytest
from tests.test_extensions.test_ext_autodoc import do_autodoc
from tests.test_extensions.autodoc_util import do_autodoc
@pytest.mark.sphinx('html', testroot='ext-autodoc',

View File

@ -3,7 +3,7 @@
import pytest
from tests.test_extensions.test_ext_autodoc import do_autodoc
from tests.test_extensions.autodoc_util import do_autodoc
@pytest.mark.sphinx('html', testroot='ext-autodoc')

View File

@ -20,7 +20,7 @@ from sphinx.ext.intersphinx import (
from sphinx.ext.intersphinx import setup as intersphinx_setup
from sphinx.util.console import strip_colors
from tests.test_util.test_util_inventory import inventory_v2, inventory_v2_not_having_version
from tests.test_util.intersphinx_data import INVENTORY_V2, INVENTORY_V2_NO_VERSION
from tests.utils import http_server
@ -92,7 +92,7 @@ def test_fetch_inventory_redirection(_read_from_url, InventoryFile, app, status,
def test_missing_reference(tmp_path, app, status, warning):
inv_file = tmp_path / 'inventory'
inv_file.write_bytes(inventory_v2)
inv_file.write_bytes(INVENTORY_V2)
set_config(app, {
'https://docs.python.org/': str(inv_file),
'py3k': ('https://docs.python.org/py3k/', str(inv_file)),
@ -170,7 +170,7 @@ def test_missing_reference(tmp_path, app, status, warning):
def test_missing_reference_pydomain(tmp_path, app, status, warning):
inv_file = tmp_path / 'inventory'
inv_file.write_bytes(inventory_v2)
inv_file.write_bytes(INVENTORY_V2)
set_config(app, {
'https://docs.python.org/': str(inv_file),
})
@ -200,7 +200,7 @@ def test_missing_reference_pydomain(tmp_path, app, status, warning):
def test_missing_reference_stddomain(tmp_path, app, status, warning):
inv_file = tmp_path / 'inventory'
inv_file.write_bytes(inventory_v2)
inv_file.write_bytes(INVENTORY_V2)
set_config(app, {
'cmd': ('https://docs.python.org/', str(inv_file)),
})
@ -251,7 +251,7 @@ def test_missing_reference_stddomain(tmp_path, app, status, warning):
@pytest.mark.sphinx('html', testroot='ext-intersphinx-cppdomain')
def test_missing_reference_cppdomain(tmp_path, app, status, warning):
inv_file = tmp_path / 'inventory'
inv_file.write_bytes(inventory_v2)
inv_file.write_bytes(INVENTORY_V2)
set_config(app, {
'https://docs.python.org/': str(inv_file),
})
@ -277,7 +277,7 @@ def test_missing_reference_cppdomain(tmp_path, app, status, warning):
def test_missing_reference_jsdomain(tmp_path, app, status, warning):
inv_file = tmp_path / 'inventory'
inv_file.write_bytes(inventory_v2)
inv_file.write_bytes(INVENTORY_V2)
set_config(app, {
'https://docs.python.org/': str(inv_file),
})
@ -301,7 +301,7 @@ def test_missing_reference_jsdomain(tmp_path, app, status, warning):
def test_missing_reference_disabled_domain(tmp_path, app, status, warning):
inv_file = tmp_path / 'inventory'
inv_file.write_bytes(inventory_v2)
inv_file.write_bytes(INVENTORY_V2)
set_config(app, {
'inv': ('https://docs.python.org/', str(inv_file)),
})
@ -363,7 +363,7 @@ def test_missing_reference_disabled_domain(tmp_path, app, status, warning):
def test_inventory_not_having_version(tmp_path, app, status, warning):
inv_file = tmp_path / 'inventory'
inv_file.write_bytes(inventory_v2_not_having_version)
inv_file.write_bytes(INVENTORY_V2_NO_VERSION)
set_config(app, {
'https://docs.python.org/': str(inv_file),
})
@ -385,7 +385,7 @@ def test_load_mappings_warnings(tmp_path, app, status, warning):
identifiers are not string
"""
inv_file = tmp_path / 'inventory'
inv_file.write_bytes(inventory_v2)
inv_file.write_bytes(INVENTORY_V2)
set_config(app, {
'https://docs.python.org/': str(inv_file),
'py3k': ('https://docs.python.org/py3k/', str(inv_file)),
@ -406,7 +406,7 @@ def test_load_mappings_warnings(tmp_path, app, status, warning):
def test_load_mappings_fallback(tmp_path, app, status, warning):
inv_file = tmp_path / 'inventory'
inv_file.write_bytes(inventory_v2)
inv_file.write_bytes(INVENTORY_V2)
set_config(app, {})
# connect to invalid path
@ -505,7 +505,7 @@ def test_inspect_main_noargs(capsys):
def test_inspect_main_file(capsys, tmp_path):
"""inspect_main interface, with file argument"""
inv_file = tmp_path / 'inventory'
inv_file.write_bytes(inventory_v2)
inv_file.write_bytes(INVENTORY_V2)
inspect_main([str(inv_file)])
@ -520,7 +520,7 @@ def test_inspect_main_url(capsys):
def do_GET(self):
self.send_response(200, "OK")
self.end_headers()
self.wfile.write(inventory_v2)
self.wfile.write(INVENTORY_V2)
def log_message(*args, **kwargs):
# Silenced.
@ -539,7 +539,7 @@ def test_inspect_main_url(capsys):
@pytest.mark.sphinx('html', testroot='ext-intersphinx-role')
def test_intersphinx_role(app, warning):
inv_file = app.srcdir / 'inventory'
inv_file.write_bytes(inventory_v2)
inv_file.write_bytes(INVENTORY_V2)
app.config.intersphinx_mapping = {
'inv': ('https://example.org/', str(inv_file)),
}

View File

@ -9,7 +9,6 @@ from textwrap import dedent
from unittest import mock
import pytest
from html5lib import HTMLParser
from sphinx.ext.intersphinx import load_mappings, normalize_intersphinx_mapping
from sphinx.ext.napoleon import Config
@ -21,6 +20,7 @@ from sphinx.ext.napoleon.docstring import (
_token_type,
_tokenize_type_spec,
)
from sphinx.testing.util import etree_parse
from tests.test_extensions.ext_napoleon_pep526_data_google import PEP526GoogleClass
from tests.test_extensions.ext_napoleon_pep526_data_numpy import PEP526NumpyClass
@ -2684,8 +2684,7 @@ int py:class 1 int.html -
app.build(force_all=True)
buffer = (app.outdir / 'index.html').read_bytes()
etree = HTMLParser(namespaceHTMLElements=False).parse(buffer)
etree = etree_parse(app.outdir / 'index.html')
for name, typename in product(('keyword', 'kwarg', 'kwparam'), ('paramtype', 'kwtype')):
param = f'{name}_{typename}'

View File

@ -1,7 +1,8 @@
"""Test smart quotes."""
import pytest
from html5lib import HTMLParser
from sphinx.testing.util import etree_parse
@pytest.mark.sphinx(buildername='html', testroot='smartquotes', freshenv=True)
@ -16,9 +17,7 @@ def test_basic(app, status, warning):
def test_literals(app, status, warning):
app.build()
with (app.outdir / 'literals.html').open(encoding='utf-8') as html_file:
etree = HTMLParser(namespaceHTMLElements=False).parse(html_file)
etree = etree_parse(app.outdir / 'literals.html')
for code_element in etree.iter('code'):
code_text = ''.join(code_element.itertext())

View File

@ -0,0 +1,52 @@
from __future__ import annotations
import zlib
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Final
INVENTORY_V1: Final[bytes] = b'''\
# Sphinx inventory version 1
# Project: foo
# Version: 1.0
module mod foo.html
module.cls class foo.html
'''
INVENTORY_V2: Final[bytes] = b'''\
# Sphinx inventory version 2
# Project: foo
# Version: 2.0
# The remainder of this file is compressed with zlib.
''' + zlib.compress(b'''\
module1 py:module 0 foo.html#module-module1 Long Module desc
module2 py:module 0 foo.html#module-$ -
module1.func py:function 1 sub/foo.html#$ -
module1.Foo.bar py:method 1 index.html#foo.Bar.baz -
CFunc c:function 2 cfunc.html#CFunc -
std cpp:type 1 index.html#std -
std::uint8_t cpp:type 1 index.html#std_uint8_t -
foo::Bar cpp:class 1 index.html#cpp_foo_bar -
foo::Bar::baz cpp:function 1 index.html#cpp_foo_bar_baz -
foons cpp:type 1 index.html#foons -
foons::bartype cpp:type 1 index.html#foons_bartype -
a term std:term -1 glossary.html#term-a-term -
ls.-l std:cmdoption 1 index.html#cmdoption-ls-l -
docname std:doc -1 docname.html -
foo js:module 1 index.html#foo -
foo.bar js:class 1 index.html#foo.bar -
foo.bar.baz js:method 1 index.html#foo.bar.baz -
foo.bar.qux js:data 1 index.html#foo.bar.qux -
a term including:colon std:term -1 glossary.html#term-a-term-including-colon -
The-Julia-Domain std:label -1 write_inventory/#$ The Julia Domain
''')
INVENTORY_V2_NO_VERSION: Final[bytes] = b'''\
# Sphinx inventory version 2
# Project: foo
# Version:
# The remainder of this file is compressed with zlib.
''' + zlib.compress(b'''\
module1 py:module 0 foo.html#module-module1 Long Module desc
''')

View File

@ -223,168 +223,140 @@ def test_signature_partialmethod():
def test_signature_annotations():
from tests.test_util.typing_test_data import (
Node,
f0,
f1,
f2,
f3,
f4,
f5,
f6,
f7,
f8,
f9,
f10,
f11,
f12,
f13,
f14,
f15,
f16,
f17,
f18,
f19,
f20,
f21,
f22,
f23,
f24,
f25,
)
import tests.test_util.typing_test_data as mod
# Class annotations
sig = inspect.signature(f0)
sig = inspect.signature(mod.f0)
assert stringify_signature(sig) == '(x: int, y: numbers.Integral) -> None'
# Generic types with concrete parameters
sig = inspect.signature(f1)
sig = inspect.signature(mod.f1)
assert stringify_signature(sig) == '(x: list[int]) -> typing.List[int]'
# TypeVars and generic types with TypeVars
sig = inspect.signature(f2)
sig = inspect.signature(mod.f2)
assert stringify_signature(sig) == ('(x: typing.List[tests.test_util.typing_test_data.T],'
' y: typing.List[tests.test_util.typing_test_data.T_co],'
' z: tests.test_util.typing_test_data.T'
') -> typing.List[tests.test_util.typing_test_data.T_contra]')
# Union types
sig = inspect.signature(f3)
sig = inspect.signature(mod.f3)
assert stringify_signature(sig) == '(x: str | numbers.Integral) -> None'
# Quoted annotations
sig = inspect.signature(f4)
sig = inspect.signature(mod.f4)
assert stringify_signature(sig) == '(x: str, y: str) -> None'
# Keyword-only arguments
sig = inspect.signature(f5)
sig = inspect.signature(mod.f5)
assert stringify_signature(sig) == '(x: int, *, y: str, z: str) -> None'
# Keyword-only arguments with varargs
sig = inspect.signature(f6)
sig = inspect.signature(mod.f6)
assert stringify_signature(sig) == '(x: int, *args, y: str, z: str) -> None'
# Space around '=' for defaults
sig = inspect.signature(f7)
sig = inspect.signature(mod.f7)
if sys.version_info[:2] <= (3, 10):
assert stringify_signature(sig) == '(x: int | None = None, y: dict = {}) -> None'
else:
assert stringify_signature(sig) == '(x: int = None, y: dict = {}) -> None'
# Callable types
sig = inspect.signature(f8)
sig = inspect.signature(mod.f8)
assert stringify_signature(sig) == '(x: typing.Callable[[int, str], int]) -> None'
sig = inspect.signature(f9)
sig = inspect.signature(mod.f9)
assert stringify_signature(sig) == '(x: typing.Callable) -> None'
# Tuple types
sig = inspect.signature(f10)
sig = inspect.signature(mod.f10)
assert stringify_signature(sig) == '(x: typing.Tuple[int, str], y: typing.Tuple[int, ...]) -> None'
# Instance annotations
sig = inspect.signature(f11)
sig = inspect.signature(mod.f11)
assert stringify_signature(sig) == '(x: CustomAnnotation, y: 123) -> None'
# tuple with more than two items
sig = inspect.signature(f12)
sig = inspect.signature(mod.f12)
assert stringify_signature(sig) == '() -> typing.Tuple[int, str, int]'
# optional
sig = inspect.signature(f13)
sig = inspect.signature(mod.f13)
assert stringify_signature(sig) == '() -> str | None'
# optional union
sig = inspect.signature(f20)
sig = inspect.signature(mod.f20)
assert stringify_signature(sig) in ('() -> int | str | None',
'() -> str | int | None')
# Any
sig = inspect.signature(f14)
sig = inspect.signature(mod.f14)
assert stringify_signature(sig) == '() -> typing.Any'
# ForwardRef
sig = inspect.signature(f15)
sig = inspect.signature(mod.f15)
assert stringify_signature(sig) == '(x: Unknown, y: int) -> typing.Any'
# keyword only arguments (1)
sig = inspect.signature(f16)
sig = inspect.signature(mod.f16)
assert stringify_signature(sig) == '(arg1, arg2, *, arg3=None, arg4=None)'
# keyword only arguments (2)
sig = inspect.signature(f17)
sig = inspect.signature(mod.f17)
assert stringify_signature(sig) == '(*, arg3, arg4)'
sig = inspect.signature(f18)
sig = inspect.signature(mod.f18)
assert stringify_signature(sig) == ('(self, arg1: int | typing.Tuple = 10) -> '
'typing.List[typing.Dict]')
# annotations for variadic and keyword parameters
sig = inspect.signature(f19)
sig = inspect.signature(mod.f19)
assert stringify_signature(sig) == '(*args: int, **kwargs: str)'
# default value is inspect.Signature.empty
sig = inspect.signature(f21)
sig = inspect.signature(mod.f21)
assert stringify_signature(sig) == "(arg1='whatever', arg2)"
# type hints by string
sig = inspect.signature(Node.children)
sig = inspect.signature(mod.Node.children)
assert stringify_signature(sig) == '(self) -> typing.List[tests.test_util.typing_test_data.Node]'
sig = inspect.signature(Node.__init__)
sig = inspect.signature(mod.Node.__init__)
assert stringify_signature(sig) == '(self, parent: tests.test_util.typing_test_data.Node | None) -> None'
# show_annotation is False
sig = inspect.signature(f7)
sig = inspect.signature(mod.f7)
assert stringify_signature(sig, show_annotation=False) == '(x=None, y={})'
# show_return_annotation is False
sig = inspect.signature(f7)
sig = inspect.signature(mod.f7)
if sys.version_info[:2] <= (3, 10):
assert stringify_signature(sig, show_return_annotation=False) == '(x: int | None = None, y: dict = {})'
else:
assert stringify_signature(sig, show_return_annotation=False) == '(x: int = None, y: dict = {})'
# unqualified_typehints is True
sig = inspect.signature(f7)
sig = inspect.signature(mod.f7)
if sys.version_info[:2] <= (3, 10):
assert stringify_signature(sig, unqualified_typehints=True) == '(x: int | None = None, y: dict = {}) -> None'
else:
assert stringify_signature(sig, unqualified_typehints=True) == '(x: int = None, y: dict = {}) -> None'
# case: separator at head
sig = inspect.signature(f22)
sig = inspect.signature(mod.f22)
assert stringify_signature(sig) == '(*, a, b)'
# case: separator in the middle
sig = inspect.signature(f23)
sig = inspect.signature(mod.f23)
assert stringify_signature(sig) == '(a, b, /, c, d)'
sig = inspect.signature(f24)
sig = inspect.signature(mod.f24)
assert stringify_signature(sig) == '(a, /, *, b)'
# case: separator at tail
sig = inspect.signature(f25)
sig = inspect.signature(mod.f25)
assert stringify_signature(sig) == '(a, b, /)'

View File

@ -1,61 +1,21 @@
"""Test inventory util functions."""
import os
import posixpath
import zlib
from io import BytesIO
import sphinx.locale
from sphinx.testing.util import SphinxTestApp
from sphinx.util.inventory import InventoryFile
inventory_v1 = b'''\
# Sphinx inventory version 1
# Project: foo
# Version: 1.0
module mod foo.html
module.cls class foo.html
'''
inventory_v2 = b'''\
# Sphinx inventory version 2
# Project: foo
# Version: 2.0
# The remainder of this file is compressed with zlib.
''' + zlib.compress(b'''\
module1 py:module 0 foo.html#module-module1 Long Module desc
module2 py:module 0 foo.html#module-$ -
module1.func py:function 1 sub/foo.html#$ -
module1.Foo.bar py:method 1 index.html#foo.Bar.baz -
CFunc c:function 2 cfunc.html#CFunc -
std cpp:type 1 index.html#std -
std::uint8_t cpp:type 1 index.html#std_uint8_t -
foo::Bar cpp:class 1 index.html#cpp_foo_bar -
foo::Bar::baz cpp:function 1 index.html#cpp_foo_bar_baz -
foons cpp:type 1 index.html#foons -
foons::bartype cpp:type 1 index.html#foons_bartype -
a term std:term -1 glossary.html#term-a-term -
ls.-l std:cmdoption 1 index.html#cmdoption-ls-l -
docname std:doc -1 docname.html -
foo js:module 1 index.html#foo -
foo.bar js:class 1 index.html#foo.bar -
foo.bar.baz js:method 1 index.html#foo.bar.baz -
foo.bar.qux js:data 1 index.html#foo.bar.qux -
a term including:colon std:term -1 glossary.html#term-a-term-including-colon -
The-Julia-Domain std:label -1 write_inventory/#$ The Julia Domain
''')
inventory_v2_not_having_version = b'''\
# Sphinx inventory version 2
# Project: foo
# Version:
# The remainder of this file is compressed with zlib.
''' + zlib.compress(b'''\
module1 py:module 0 foo.html#module-module1 Long Module desc
''')
from tests.test_util.intersphinx_data import (
INVENTORY_V1,
INVENTORY_V2,
INVENTORY_V2_NO_VERSION,
)
def test_read_inventory_v1():
f = BytesIO(inventory_v1)
f = BytesIO(INVENTORY_V1)
invdata = InventoryFile.load(f, '/util', posixpath.join)
assert invdata['py:module']['module'] == \
('foo', '1.0', '/util/foo.html#module-module', '-')
@ -64,7 +24,7 @@ def test_read_inventory_v1():
def test_read_inventory_v2():
f = BytesIO(inventory_v2)
f = BytesIO(INVENTORY_V2)
invdata = InventoryFile.load(f, '/util', posixpath.join)
assert len(invdata['py:module']) == 2
@ -82,7 +42,7 @@ def test_read_inventory_v2():
def test_read_inventory_v2_not_having_version():
f = BytesIO(inventory_v2_not_having_version)
f = BytesIO(INVENTORY_V2_NO_VERSION)
invdata = InventoryFile.load(f, '/util', posixpath.join)
assert invdata['py:module']['module1'] == \
('foo', '', '/util/foo.html#module-module1', 'Long Module desc')
@ -102,7 +62,7 @@ def _build_inventory(srcdir):
app = SphinxTestApp(srcdir=srcdir)
app.build()
sphinx.locale.translators.clear()
return (app.outdir / 'objects.inv')
return app.outdir / 'objects.inv'
def test_inventory_localization(tmp_path):