mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
[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:
parent
9e239729d4
commit
885818bb7f
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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()
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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')
|
||||
|
@ -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"), [
|
||||
|
8
tests/test_builders/xpath_data.py
Normal file
8
tests/test_builders/xpath_data.py
Normal 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"
|
79
tests/test_builders/xpath_util.py
Normal file
79
tests/test_builders/xpath_util.py
Normal 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)
|
@ -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
|
||||
|
33
tests/test_extensions/autodoc_util.py
Normal file
33
tests/test_extensions/autodoc_util.py
Normal 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
|
@ -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)
|
||||
|
||||
|
@ -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')
|
||||
|
@ -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')
|
||||
|
@ -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')
|
||||
|
@ -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')
|
||||
|
@ -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')
|
||||
|
@ -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')
|
||||
|
@ -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'
|
||||
|
||||
|
@ -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')
|
||||
|
@ -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',
|
||||
|
@ -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')
|
||||
|
@ -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)),
|
||||
}
|
||||
|
@ -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}'
|
||||
|
@ -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())
|
||||
|
||||
|
52
tests/test_util/intersphinx_data.py
Normal file
52
tests/test_util/intersphinx_data.py
Normal 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
|
||||
''')
|
@ -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, /)'
|
||||
|
||||
|
||||
|
@ -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):
|
||||
|
Loading…
Reference in New Issue
Block a user