From b9650d5d26273aba9fc49a78b14a085235f8ebd2 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Date: Mon, 15 Dec 2025 19:55:28 +0000 Subject: [PATCH] Remove some entries from the mypy whitelist (#14182) --- pyproject.toml | 7 - sphinx/ext/autodoc/_event_listeners.py | 2 +- sphinx/ext/doctest.py | 4 +- sphinx/util/i18n.py | 2 +- tests/test_domains/test_domain_rst.py | 2 +- .../test_ext_autodoc_events.py | 39 +- tests/test_extensions/test_ext_doctest.py | 46 +- tests/test_intl/test_intl.py | 461 +++++++++++------- tests/test_util/test_util_i18n.py | 27 +- tests/test_util/test_util_inspect.py | 2 +- tests/test_util/test_util_logging.py | 31 +- tests/test_util/test_util_nodes.py | 2 +- 12 files changed, 376 insertions(+), 249 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 56b2ebd270..3e9b464dc0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -230,13 +230,11 @@ module = [ "tests.test_domains.test_domain_py_fields", "tests.test_domains.test_domain_py_pyfunction", "tests.test_domains.test_domain_py_pyobject", - "tests.test_domains.test_domain_rst", "tests.test_domains.test_domain_std", # tests/test_environment "tests.test_environment.test_environment_toctree", # tests/test_ext_autodoc "tests.test_ext_autodoc.test_ext_autodoc", - "tests.test_ext_autodoc.test_ext_autodoc_events", "tests.test_ext_autodoc.test_ext_autodoc_mock", # tests/test_ext_autosummary "tests.test_ext_autosummary.test_ext_autosummary", @@ -246,14 +244,9 @@ module = [ "tests.test_ext_napoleon.test_ext_napoleon_docstring", # tests/test_extensions "tests.test_extensions.test_ext_apidoc", - "tests.test_extensions.test_ext_doctest", "tests.test_extensions.test_ext_inheritance_diagram", - # tests/test_intl - "tests.test_intl.test_intl", # tests/test_util - "tests.test_util.test_util_i18n", "tests.test_util.test_util_inspect", - "tests.test_util.test_util_logging", "tests.test_util.test_util_nodes", "tests.test_util.test_util_typing", ] diff --git a/sphinx/ext/autodoc/_event_listeners.py b/sphinx/ext/autodoc/_event_listeners.py index 4196630d91..ec04fbec2f 100644 --- a/sphinx/ext/autodoc/_event_listeners.py +++ b/sphinx/ext/autodoc/_event_listeners.py @@ -74,7 +74,7 @@ if TYPE_CHECKING: skip: bool, options: Any, /, - ) -> bool: ... + ) -> bool | None: ... def cut_lines( diff --git a/sphinx/ext/doctest.py b/sphinx/ext/doctest.py index 9036e6ef56..9da6c4318d 100644 --- a/sphinx/ext/doctest.py +++ b/sphinx/ext/doctest.py @@ -336,11 +336,11 @@ class DocTestBuilder(Builder): if hasattr(self, 'outfile'): self.outfile.close() - def _out(self, text: str) -> None: + def _out(self, text: str, /) -> None: logger.info(text, nonl=True) self.outfile.write(text) - def _warn_out(self, text: str) -> None: + def _warn_out(self, text: str, /) -> None: if self.config.verbosity < 0: logger.warning(text) else: diff --git a/sphinx/util/i18n.py b/sphinx/util/i18n.py index ed8c2363f8..06bae4b886 100644 --- a/sphinx/util/i18n.py +++ b/sphinx/util/i18n.py @@ -218,7 +218,7 @@ date_format_re = re.compile('(%s)' % '|'.join(date_format_mappings)) def babel_format_date( - date: datetime, + date: dt.datetime, format: str, locale: str, formatter: Formatter = babel.dates.format_date, diff --git a/tests/test_domains/test_domain_rst.py b/tests/test_domains/test_domain_rst.py index 60e9268512..08d5ccebd7 100644 --- a/tests/test_domains/test_domain_rst.py +++ b/tests/test_domains/test_domain_rst.py @@ -225,7 +225,7 @@ def test_rst_directive_option_type(app: SphinxTestApp) -> None: @pytest.mark.sphinx('html', testroot='_blank') -def test_rst_directive_and_directive_option(app): +def test_rst_directive_and_directive_option(app: SphinxTestApp) -> None: text = '.. rst:directive:: foo\n\n .. rst:directive:option:: bar\n' doctree = restructuredtext.parse(app, text) assert_node( diff --git a/tests/test_ext_autodoc/test_ext_autodoc_events.py b/tests/test_ext_autodoc/test_ext_autodoc_events.py index af57488f34..7b6b6e679d 100644 --- a/tests/test_ext_autodoc/test_ext_autodoc_events.py +++ b/tests/test_ext_autodoc/test_ext_autodoc_events.py @@ -2,17 +2,32 @@ from __future__ import annotations +from typing import TYPE_CHECKING + import pytest from sphinx.ext.autodoc import between, cut_lines from tests.test_ext_autodoc.autodoc_util import FakeEvents, do_autodoc +if TYPE_CHECKING: + from typing import Any + + from sphinx.application import Sphinx + from sphinx.ext.autodoc._property_types import _AutodocObjType + pytestmark = pytest.mark.usefixtures('inject_autodoc_root_into_sys_path') def test_process_docstring() -> None: - def on_process_docstring(app, what, name, obj, options, lines): + def on_process_docstring( + app: Sphinx, + what: _AutodocObjType, + name: str, + obj: Any, + options: Any, + lines: list[str], + ) -> None: lines.clear() lines.append('my docstring') @@ -31,7 +46,14 @@ def test_process_docstring() -> None: def test_process_docstring_for_nondatadescriptor() -> None: - def on_process_docstring(app, what, name, obj, options, lines): + def on_process_docstring( + app: Sphinx, + what: _AutodocObjType, + name: str, + obj: Any, + options: Any, + lines: list[str], + ) -> None: raise RuntimeError events = FakeEvents() @@ -62,7 +84,7 @@ def test_cut_lines() -> None: ] -def test_cut_lines_no_objtype(): +def test_cut_lines_no_objtype() -> None: docstring_lines = [ 'first line', '---', @@ -73,7 +95,7 @@ def test_cut_lines_no_objtype(): ] process = cut_lines(2) - process(None, 'function', 'func', None, {}, docstring_lines) + process(None, 'function', 'func', None, {}, docstring_lines) # type: ignore[arg-type] assert docstring_lines == [ 'second line', '---', @@ -116,7 +138,14 @@ def test_between_exclude() -> None: def test_skip_module_member() -> None: - def autodoc_skip_member(app, what, name, obj, skip, options): + def autodoc_skip_member( + app: Sphinx, + what: _AutodocObjType, + name: str, + obj: Any, + skip: bool, + options: Any, + ) -> bool | None: if name == 'Class': return True # Skip "Class" class in __all__ elif name == 'raises': diff --git a/tests/test_extensions/test_ext_doctest.py b/tests/test_extensions/test_ext_doctest.py index 89d2b8e49a..c8a12c24de 100644 --- a/tests/test_extensions/test_ext_doctest.py +++ b/tests/test_extensions/test_ext_doctest.py @@ -11,7 +11,7 @@ from docutils import nodes from packaging.specifiers import InvalidSpecifier from packaging.version import InvalidVersion -from sphinx.ext.doctest import is_allowed_version +from sphinx.ext.doctest import DocTestBuilder, is_allowed_version if TYPE_CHECKING: from sphinx.testing.util import SphinxTestApp @@ -74,12 +74,12 @@ def test_is_allowed_version() -> None: is_allowed_version('>3.4', 'Sphinx') -def cleanup_call(): +def cleanup_call() -> None: global cleanup_called # NoQA: PLW0603 cleanup_called += 1 -recorded_calls: Counter[tuple[str, str, int]] = Counter() +recorded_calls: Counter[tuple[str, str, bool]] = Counter() @pytest.mark.sphinx('doctest', testroot='ext-doctest-skipif') @@ -125,16 +125,17 @@ def test_skipif(app: SphinxTestApp) -> None: } -def record(directive, part, should_skip): +def record(directive: str, part: str, should_skip: bool) -> str: recorded_calls[directive, part, should_skip] += 1 return f'Recorded {directive} {part} {should_skip}' @pytest.mark.sphinx('doctest', testroot='ext-doctest-with-autodoc') -def test_reporting_with_autodoc(app, capfd): +def test_reporting_with_autodoc(app: SphinxTestApp) -> None: # Patch builder to get a copy of the output - written = [] - app.builder._warn_out = written.append + written: list[str] = [] + assert isinstance(app.builder, DocTestBuilder) + app.builder._warn_out = written.append # type: ignore[method-assign] app.build(force_all=True) failures = [ @@ -151,36 +152,43 @@ def test_reporting_with_autodoc(app, capfd): @pytest.mark.sphinx('doctest', testroot='ext-doctest-fail-fast') @pytest.mark.parametrize('fail_fast', [False, True, None]) -def test_fail_fast(app, fail_fast, capsys): +def test_fail_fast(app: SphinxTestApp, fail_fast: bool | None) -> None: if fail_fast is not None: app.config.doctest_fail_fast = fail_fast # Patch builder to get a copy of the output - written = [] - app.builder._out = written.append + written: list[str] = [] + assert isinstance(app.builder, DocTestBuilder) + app.builder._out = written.append # type: ignore[method-assign] app.build(force_all=True) assert app.statuscode - written = ''.join(written) + written = ''.join(written).split('\n') if fail_fast: - assert 'Doctest summary (exiting after first failed test)' in written - assert '1 failure in tests' in written + assert written[10] == 'Doctest summary (exiting after first failed test)' + assert written[13] == ' 1 failure in tests' else: - assert 'Doctest summary\n' in written - assert '2 failures in tests' in written + assert written[10] == 'Doctest summary' + assert written[13] == ' 2 failures in tests' @pytest.mark.sphinx('doctest', testroot='ext-doctest-with-autodoc') @pytest.mark.parametrize( ('test_doctest_blocks', 'group_name'), - [(None, 'default'), ('CustomGroupName', 'CustomGroupName')], + [ + (None, 'default'), + ('CustomGroupName', 'CustomGroupName'), + ], ) -def test_doctest_block_group_name(app, test_doctest_blocks, group_name, capfd): +def test_doctest_block_group_name( + app: SphinxTestApp, test_doctest_blocks: str | None, group_name: str +) -> None: if test_doctest_blocks is not None: app.config.doctest_test_doctest_blocks = test_doctest_blocks # Patch builder to get a copy of the output - written = [] - app.builder._warn_out = written.append + written: list[str] = [] + assert isinstance(app.builder, DocTestBuilder) + app.builder._warn_out = written.append # type: ignore[method-assign] app.build(force_all=True) failures = [ diff --git a/tests/test_intl/test_intl.py b/tests/test_intl/test_intl.py index 7899f42ea1..c823a6a3b4 100644 --- a/tests/test_intl/test_intl.py +++ b/tests/test_intl/test_intl.py @@ -22,11 +22,17 @@ from sphinx._cli.util.errors import strip_escape_sequences from sphinx.testing.util import assert_node, etree_parse from sphinx.util.nodes import NodeMatcher +from tests.utils import extract_element, extract_node + if TYPE_CHECKING: - from collections.abc import Iterator + import xml.etree.ElementTree as ET + from collections.abc import Callable, Iterator from io import StringIO from pathlib import Path + from sphinx.testing.fixtures import _app_params + from sphinx.testing.util import SphinxTestApp + _CATALOG_LOCALE = 'xx' sphinx_intl = pytest.mark.sphinx( @@ -39,14 +45,14 @@ sphinx_intl = pytest.mark.sphinx( ) -def read_po(pathname): +def read_po(pathname: Path) -> Catalog: with open(pathname, encoding='utf-8') as f: return pofile.read_po(f) -def write_mo(pathname, po): +def write_mo(pathname: Path, po: Catalog) -> None: with open(pathname, 'wb') as f: - return mofile.write_mo(f, po) + mofile.write_mo(f, po) def _set_mtime_ns(target: Path, value: int) -> int: @@ -54,33 +60,39 @@ def _set_mtime_ns(target: Path, value: int) -> int: return target.stat().st_mtime_ns -def _get_bom_intl_path(app): - basedir = app.srcdir / _CATALOG_LOCALE / 'LC_MESSAGES' +def _get_bom_intl_path(srcdir: Path) -> tuple[Path, Path]: + basedir = srcdir / _CATALOG_LOCALE / 'LC_MESSAGES' return basedir / 'bom.po', basedir / 'bom.mo' -def _get_update_targets(app): +def _get_update_targets(app: SphinxTestApp) -> tuple[set[str], set[str], set[str]]: app.env.find_files(app.config, app.builder) added, changed, removed = app.env.get_outdated_files(config_changed=False) return added, changed, removed @pytest.fixture(autouse=True) -def _info(app): +def _info(app: SphinxTestApp) -> Iterator[None]: yield - print('# language:', app.config.language) - print('# locale_dirs:', app.config.locale_dirs) + print(f'# language: {app.config.language}') + print(f'# locale_dirs: {app.config.locale_dirs}') -def elem_gettexts(elem): +def elem_gettexts(elem: ET.Element) -> list[str]: return list(filter(None, map(str.strip, elem.itertext()))) -def elem_getref(elem): +def elem_getref(elem: ET.Element) -> str | None: return elem.attrib.get('refid') or elem.attrib.get('refuri') -def assert_elem(elem, texts=None, refs=None, names=None): +def assert_elem( + elem: ET.Element, + *, + texts: list[str] | None = None, + refs: list[str] | None = None, + names: list[str] | None = None, +) -> None: if texts is not None: _texts = elem_gettexts(elem) assert _texts == texts @@ -88,11 +100,11 @@ def assert_elem(elem, texts=None, refs=None, names=None): _refs = [elem_getref(x) for x in elem.findall('reference')] assert _refs == refs if names is not None: - _names = elem.attrib.get('names').split() + _names = elem.attrib['names'].split() assert _names == names -def assert_count(expected_expr, result, count): +def assert_count(expected_expr: str, result: str, count: int) -> None: find_pair = (expected_expr, result) assert len(re.findall(*find_pair)) == count, find_pair @@ -100,7 +112,7 @@ def assert_count(expected_expr, result, count): @sphinx_intl @pytest.mark.sphinx('text', testroot='intl') @pytest.mark.test_params(shared_result='test_intl_basic') -def test_text_emit_warnings(app): +def test_text_emit_warnings(app: SphinxTestApp) -> None: app.build() # test warnings in translation warnings = getwarning(app.warning) @@ -116,7 +128,7 @@ def test_text_emit_warnings(app): @sphinx_intl @pytest.mark.sphinx('text', testroot='intl') @pytest.mark.test_params(shared_result='test_intl_basic') -def test_text_warning_node(app): +def test_text_warning_node(app: SphinxTestApp) -> None: app.build() # test warnings in translation result = (app.outdir / 'warnings.txt').read_text(encoding='utf8') @@ -131,7 +143,7 @@ def test_text_warning_node(app): @sphinx_intl @pytest.mark.sphinx('text', testroot='intl') @pytest.mark.test_params(shared_result='test_intl_basic') -def test_text_title_underline(app): +def test_text_title_underline(app: SphinxTestApp) -> None: app.build() # --- simple translation; check title underlines result = (app.outdir / 'bom.txt').read_text(encoding='utf8') @@ -146,7 +158,7 @@ def test_text_title_underline(app): @sphinx_intl @pytest.mark.sphinx('text', testroot='intl') @pytest.mark.test_params(shared_result='test_intl_basic') -def test_text_subdirs(app): +def test_text_subdirs(app: SphinxTestApp) -> None: app.build() # --- check translation in subdirs result = (app.outdir / 'subdir' / 'index.txt').read_text(encoding='utf8') @@ -156,7 +168,7 @@ def test_text_subdirs(app): @sphinx_intl @pytest.mark.sphinx('text', testroot='intl') @pytest.mark.test_params(shared_result='test_intl_basic') -def test_text_inconsistency_warnings(app): +def test_text_inconsistency_warnings(app: SphinxTestApp) -> None: app.build() # --- check warnings for inconsistency in number of references result = (app.outdir / 'refs_inconsistency.txt').read_text(encoding='utf8') @@ -234,7 +246,7 @@ def test_text_inconsistency_warnings(app): @sphinx_intl @pytest.mark.sphinx('text', testroot='intl') @pytest.mark.test_params(shared_result='test_intl_basic') -def test_noqa(app): +def test_noqa(app: SphinxTestApp) -> None: app.build() result = (app.outdir / 'noqa.txt').read_text(encoding='utf8') expect = r"""FIRST SECTION @@ -260,7 +272,7 @@ TO TEST BARE noqa. @sphinx_intl @pytest.mark.sphinx('text', testroot='intl') @pytest.mark.test_params(shared_result='test_intl_basic') -def test_text_literalblock_warnings(app): +def test_text_literalblock_warnings(app: SphinxTestApp) -> None: app.build() # --- check warning for literal block result = (app.outdir / 'literalblock.txt').read_text(encoding='utf8') @@ -287,7 +299,7 @@ def test_text_literalblock_warnings(app): @sphinx_intl @pytest.mark.sphinx('text', testroot='intl') @pytest.mark.test_params(shared_result='test_intl_basic') -def test_text_definition_terms(app): +def test_text_definition_terms(app: SphinxTestApp) -> None: app.build() # --- definition terms: regression test for # https://github.com/sphinx-doc/sphinx/issues/975, @@ -312,7 +324,7 @@ def test_text_definition_terms(app): @sphinx_intl @pytest.mark.sphinx('text', testroot='intl') @pytest.mark.test_params(shared_result='test_intl_basic') -def test_text_glossary_term(app): +def test_text_glossary_term(app: SphinxTestApp) -> None: app.build() # --- glossary terms: regression test for # https://github.com/sphinx-doc/sphinx/issues/1090 @@ -348,7 +360,7 @@ VVV @sphinx_intl @pytest.mark.sphinx('text', testroot='intl') @pytest.mark.test_params(shared_result='test_intl_basic') -def test_text_glossary_term_inconsistencies(app): +def test_text_glossary_term_inconsistencies(app: SphinxTestApp) -> None: app.build() # --- glossary term inconsistencies: regression test for # https://github.com/sphinx-doc/sphinx/issues/1090 @@ -385,7 +397,7 @@ def test_text_glossary_term_inconsistencies(app): @sphinx_intl @pytest.mark.sphinx('text', testroot='intl') @pytest.mark.test_params(shared_result='test_intl_basic') -def test_text_refs_reordered_no_warning(app): +def test_text_refs_reordered_no_warning(app: SphinxTestApp) -> None: app.build() # --- refs_reordered: verify no inconsistency warnings result = (app.outdir / 'refs_reordered.txt').read_text(encoding='utf8') @@ -404,7 +416,7 @@ def test_text_refs_reordered_no_warning(app): @sphinx_intl @pytest.mark.sphinx('gettext', testroot='intl') @pytest.mark.test_params(shared_result='test_intl_gettext') -def test_gettext_section(app): +def test_gettext_section(app: SphinxTestApp) -> None: app.build() # --- section expect = read_po(app.srcdir / _CATALOG_LOCALE / 'LC_MESSAGES' / 'section.po') @@ -417,19 +429,22 @@ def test_gettext_section(app): @sphinx_intl @pytest.mark.sphinx('text', testroot='intl') @pytest.mark.test_params(shared_result='test_intl_basic') -def test_text_section(app): +def test_text_section(app: SphinxTestApp) -> None: app.build() # --- section result = (app.outdir / 'section.txt').read_text(encoding='utf8') expect = read_po(app.srcdir / _CATALOG_LOCALE / 'LC_MESSAGES' / 'section.po') - for expect_msg in (msg for msg in expect if msg.id): + for expect_msg in expect: + if not expect_msg.id: + continue + assert isinstance(expect_msg.string, str) assert expect_msg.string in result @sphinx_intl @pytest.mark.sphinx('text', testroot='intl') @pytest.mark.test_params(shared_result='test_intl_basic') -def test_text_seealso(app): +def test_text_seealso(app: SphinxTestApp) -> None: app.build() # --- seealso result = (app.outdir / 'seealso.txt').read_text(encoding='utf8') @@ -448,7 +463,7 @@ def test_text_seealso(app): @sphinx_intl @pytest.mark.sphinx('text', testroot='intl') @pytest.mark.test_params(shared_result='test_intl_basic') -def test_text_figure_captions(app): +def test_text_figure_captions(app: SphinxTestApp) -> None: app.build() # --- figure captions: regression test for # https://github.com/sphinx-doc/sphinx/issues/940 @@ -495,7 +510,7 @@ def test_text_figure_captions(app): @sphinx_intl @pytest.mark.sphinx('text', testroot='intl') @pytest.mark.test_params(shared_result='test_intl_basic') -def test_text_rubric(app): +def test_text_rubric(app: SphinxTestApp) -> None: app.build() # --- rubric: regression test for pull request # https://github.com/sphinx-doc/sphinx/issues/190 @@ -516,7 +531,7 @@ def test_text_rubric(app): @sphinx_intl @pytest.mark.sphinx('text', testroot='intl') @pytest.mark.test_params(shared_result='test_intl_basic') -def test_text_docfields(app): +def test_text_docfields(app: SphinxTestApp) -> None: app.build() # --- docfields result = (app.outdir / 'docfields.txt').read_text(encoding='utf8') @@ -547,7 +562,7 @@ def test_text_docfields(app): @sphinx_intl @pytest.mark.sphinx('text', testroot='intl') @pytest.mark.test_params(shared_result='test_intl_basic') -def test_text_admonitions(app): +def test_text_admonitions(app: SphinxTestApp) -> None: app.build() # --- admonitions # https://github.com/sphinx-doc/sphinx/issues/1206: @@ -578,7 +593,7 @@ def test_text_admonitions(app): @sphinx_intl @pytest.mark.sphinx('gettext', testroot='intl') @pytest.mark.test_params(shared_result='test_intl_gettext') -def test_gettext_toctree(app): +def test_gettext_toctree(app: SphinxTestApp) -> None: app.build() # --- toctree (index.rst) expect = read_po(app.srcdir / _CATALOG_LOCALE / 'LC_MESSAGES' / 'index.po') @@ -597,7 +612,7 @@ def test_gettext_toctree(app): @sphinx_intl @pytest.mark.sphinx('gettext', testroot='intl') @pytest.mark.test_params(shared_result='test_intl_gettext') -def test_gettext_table(app): +def test_gettext_table(app: SphinxTestApp) -> None: app.build() # --- toctree expect = read_po(app.srcdir / _CATALOG_LOCALE / 'LC_MESSAGES' / 'table.po') @@ -610,19 +625,22 @@ def test_gettext_table(app): @sphinx_intl @pytest.mark.sphinx('text', testroot='intl') @pytest.mark.test_params(shared_result='test_intl_basic') -def test_text_table(app): +def test_text_table(app: SphinxTestApp) -> None: app.build() # --- toctree result = (app.outdir / 'table.txt').read_text(encoding='utf8') expect = read_po(app.srcdir / _CATALOG_LOCALE / 'LC_MESSAGES' / 'table.po') - for expect_msg in (msg for msg in expect if msg.id): + for expect_msg in expect: + if not expect_msg.id: + continue + assert isinstance(expect_msg.string, str) assert expect_msg.string in result @sphinx_intl @pytest.mark.sphinx('text', testroot='intl') @pytest.mark.test_params(shared_result='test_intl_basic') -def test_text_toctree(app): +def test_text_toctree(app: SphinxTestApp) -> None: app.build() # --- toctree (index.rst) # Note: index.rst contains contents that is not shown in text. @@ -632,14 +650,17 @@ def test_text_toctree(app): # --- toctree (toctree.rst) result = (app.outdir / 'toctree.txt').read_text(encoding='utf8') expect = read_po(app.srcdir / _CATALOG_LOCALE / 'LC_MESSAGES' / 'toctree.po') - for expect_msg in (m for m in expect if m.id): + for expect_msg in expect: + if not expect_msg.id: + continue + assert isinstance(expect_msg.string, str) assert expect_msg.string in result @sphinx_intl @pytest.mark.sphinx('gettext', testroot='intl') @pytest.mark.test_params(shared_result='test_intl_gettext') -def test_gettext_topic(app): +def test_gettext_topic(app: SphinxTestApp) -> None: app.build() # --- topic expect = read_po(app.srcdir / _CATALOG_LOCALE / 'LC_MESSAGES' / 'topic.po') @@ -652,19 +673,22 @@ def test_gettext_topic(app): @sphinx_intl @pytest.mark.sphinx('text', testroot='intl') @pytest.mark.test_params(shared_result='test_intl_basic') -def test_text_topic(app): +def test_text_topic(app: SphinxTestApp) -> None: app.build() # --- topic result = (app.outdir / 'topic.txt').read_text(encoding='utf8') expect = read_po(app.srcdir / _CATALOG_LOCALE / 'LC_MESSAGES' / 'topic.po') - for expect_msg in (msg for msg in expect if msg.id): + for expect_msg in expect: + if not expect_msg.id: + continue + assert isinstance(expect_msg.string, str) assert expect_msg.string in result @sphinx_intl @pytest.mark.sphinx('gettext', testroot='intl') @pytest.mark.test_params(shared_result='test_intl_gettext') -def test_gettext_definition_terms(app): +def test_gettext_definition_terms(app: SphinxTestApp) -> None: app.build() # --- definition terms: regression test for # https://github.com/sphinx-doc/sphinx/issues/2198, @@ -681,7 +705,7 @@ def test_gettext_definition_terms(app): @sphinx_intl @pytest.mark.sphinx('gettext', testroot='intl') @pytest.mark.test_params(shared_result='test_intl_gettext') -def test_gettext_glossary_terms(app): +def test_gettext_glossary_terms(app: SphinxTestApp) -> None: app.build() # --- glossary terms: regression test for # https://github.com/sphinx-doc/sphinx/issues/1090 @@ -697,7 +721,7 @@ def test_gettext_glossary_terms(app): @sphinx_intl @pytest.mark.sphinx('gettext', testroot='intl') @pytest.mark.test_params(shared_result='test_intl_gettext') -def test_gettext_glossary_term_inconsistencies(app): +def test_gettext_glossary_term_inconsistencies(app: SphinxTestApp) -> None: app.build() # --- glossary term inconsistencies: regression test for # https://github.com/sphinx-doc/sphinx/issues/1090 @@ -713,13 +737,16 @@ def test_gettext_glossary_term_inconsistencies(app): @sphinx_intl @pytest.mark.sphinx('gettext', testroot='intl') @pytest.mark.test_params(shared_result='test_intl_gettext') -def test_gettext_literalblock(app): +def test_gettext_literalblock(app: SphinxTestApp) -> None: app.build() # --- gettext builder always ignores ``only`` directive expect = read_po(app.srcdir / _CATALOG_LOCALE / 'LC_MESSAGES' / 'literalblock.po') actual = read_po(app.outdir / 'literalblock.pot') actual_msg_ids = {msg.id for msg in actual if msg.id} # pyright: ignore[reportUnhashable] - for expect_msg in (msg for msg in expect if msg.id): + for expect_msg in expect: + if not expect_msg.id: + continue + assert isinstance(expect_msg.id, str) if len(expect_msg.id.splitlines()) == 1: # compare translations only labels assert expect_msg.id in actual_msg_ids @@ -730,7 +757,7 @@ def test_gettext_literalblock(app): @sphinx_intl @pytest.mark.sphinx('gettext', testroot='intl') @pytest.mark.test_params(shared_result='test_intl_gettext') -def test_gettext_buildr_ignores_only_directive(app): +def test_gettext_buildr_ignores_only_directive(app: SphinxTestApp) -> None: app.build() # --- gettext builder always ignores ``only`` directive expect = read_po(app.srcdir / _CATALOG_LOCALE / 'LC_MESSAGES' / 'only.po') @@ -742,7 +769,7 @@ def test_gettext_buildr_ignores_only_directive(app): @sphinx_intl @pytest.mark.sphinx('html', testroot='intl', copy_test_root=True) -def test_node_translated_attribute(app): +def test_node_translated_attribute(app: SphinxTestApp) -> None: app.build(filenames=[app.srcdir / 'translation_progress.txt']) doctree = app.env.get_doctree('translation_progress') @@ -756,12 +783,13 @@ def test_node_translated_attribute(app): @sphinx_intl @pytest.mark.sphinx('html', testroot='intl', copy_test_root=True) -def test_translation_progress_substitution(app): +def test_translation_progress_substitution(app: SphinxTestApp) -> None: app.build(filenames=[app.srcdir / 'translation_progress.txt']) doctree = app.env.get_doctree('translation_progress') - assert doctree[0][19][0] == '68.75%' # 11 out of 16 lines are translated + # 11 out of 16 lines are translated + assert extract_node(doctree, 0, 19, 0) == '68.75%' @pytest.mark.sphinx( @@ -776,47 +804,48 @@ def test_translation_progress_substitution(app): }, copy_test_root=True, ) -def test_translation_progress_classes_true(app): +def test_translation_progress_classes_true(app: SphinxTestApp) -> None: app.build(filenames=[app.srcdir / 'translation_progress.txt']) doctree = app.env.get_doctree('translation_progress') # title - assert 'translated' in doctree[0][0]['classes'] + assert 'translated' in extract_element(doctree, 0, 0)['classes'] # translated lines - assert 'translated' in doctree[0][1]['classes'] - assert 'translated' in doctree[0][2]['classes'] - assert 'translated' in doctree[0][3]['classes'] - assert 'translated' in doctree[0][4]['classes'] - assert 'translated' in doctree[0][5]['classes'] - assert 'translated' in doctree[0][6]['classes'] - assert 'translated' in doctree[0][7]['classes'] - assert 'translated' in doctree[0][8]['classes'] + assert 'translated' in extract_element(doctree, 0, 1)['classes'] + assert 'translated' in extract_element(doctree, 0, 2)['classes'] + assert 'translated' in extract_element(doctree, 0, 3)['classes'] + assert 'translated' in extract_element(doctree, 0, 4)['classes'] + assert 'translated' in extract_element(doctree, 0, 5)['classes'] + assert 'translated' in extract_element(doctree, 0, 6)['classes'] + assert 'translated' in extract_element(doctree, 0, 7)['classes'] + assert 'translated' in extract_element(doctree, 0, 8)['classes'] - assert doctree[0][9]['classes'] == [] # comment node + assert extract_element(doctree, 0, 9)['classes'] == [] # comment node # idempotent - assert 'translated' in doctree[0][10]['classes'] - assert 'translated' in doctree[0][11]['classes'] + assert 'translated' in extract_element(doctree, 0, 10)['classes'] + assert 'translated' in extract_element(doctree, 0, 11)['classes'] - assert doctree[0][12]['classes'] == [] # comment node + assert extract_element(doctree, 0, 12)['classes'] == [] # comment node # untranslated - assert 'untranslated' in doctree[0][13]['classes'] - assert 'untranslated' in doctree[0][14]['classes'] + assert 'untranslated' in extract_element(doctree, 0, 13)['classes'] + assert 'untranslated' in extract_element(doctree, 0, 14)['classes'] - assert doctree[0][15]['classes'] == [] # comment node + assert extract_element(doctree, 0, 15)['classes'] == [] # comment node # missing - assert 'untranslated' in doctree[0][16]['classes'] - assert 'untranslated' in doctree[0][17]['classes'] + assert 'untranslated' in extract_element(doctree, 0, 16)['classes'] + assert 'untranslated' in extract_element(doctree, 0, 17)['classes'] - assert doctree[0][18]['classes'] == [] # comment node + assert extract_element(doctree, 0, 18)['classes'] == [] # comment node # substitution reference - assert 'untranslated' in doctree[0][19]['classes'] + assert 'untranslated' in extract_element(doctree, 0, 19)['classes'] + assert isinstance(doctree[0], nodes.Element) assert len(doctree[0]) == 20 @@ -875,7 +904,7 @@ def mock_time_and_i18n() -> Iterator[tuple[pytest.MonkeyPatch, _MockClock]]: # save the 'original' definition catalog_write_mo = CatalogInfo.write_mo - def mock_write_mo(self, locale, use_fuzzy=False): + def mock_write_mo(self: CatalogInfo, locale: str, use_fuzzy: bool = False) -> None: catalog_write_mo(self, locale, use_fuzzy) # ensure that the .mo file being written has a correct fake timestamp _set_mtime_ns(self.mo_path, time.time_ns()) @@ -908,13 +937,17 @@ def mock_time_and_i18n() -> Iterator[tuple[pytest.MonkeyPatch, _MockClock]]: freshenv=True, copy_test_root=True, ) -def test_dummy_should_rebuild_mo(mock_time_and_i18n, make_app, app_params): +def test_dummy_should_rebuild_mo( + mock_time_and_i18n: tuple[pytest.MonkeyPatch, _MockClock], + make_app: Callable[..., SphinxTestApp], + app_params: _app_params, +) -> None: mock, clock = mock_time_and_i18n assert os.name == 'posix' or clock.time() == 0 args, kwargs = app_params app = make_app(*args, **kwargs) - po_path, mo_path = _get_bom_intl_path(app) + po_path, mo_path = _get_bom_intl_path(app.srcdir) # creation time of the those files (order does not matter) bom_rst = app.srcdir / 'bom.rst' @@ -971,7 +1004,9 @@ def test_dummy_should_rebuild_mo(mock_time_and_i18n, make_app, app_params): freshenv=True, copy_test_root=True, ) -def test_gettext_dont_rebuild_mo(mock_time_and_i18n, app): +def test_gettext_dont_rebuild_mo( + mock_time_and_i18n: tuple[pytest.MonkeyPatch, _MockClock], app: SphinxTestApp +) -> None: mock, clock = mock_time_and_i18n assert os.name == 'posix' or clock.time() == 0 @@ -987,7 +1022,7 @@ def test_gettext_dont_rebuild_mo(mock_time_and_i18n, app): assert _set_mtime_ns(index_rst, index_rst_time) == index_rst_time # phase 1: create fake MO file in the src directory - po_path, mo_path = _get_bom_intl_path(app) + po_path, mo_path = _get_bom_intl_path(app.srcdir) write_mo(mo_path, read_po(po_path)) po_time = time.time_ns() assert _set_mtime_ns(po_path, po_time) == po_time @@ -1017,7 +1052,7 @@ def test_gettext_dont_rebuild_mo(mock_time_and_i18n, app): @sphinx_intl @pytest.mark.sphinx('html', testroot='intl') @pytest.mark.test_params(shared_result='test_intl_basic') -def test_html_meta(app): +def test_html_meta(app: SphinxTestApp) -> None: app.build() # --- test for meta result = (app.outdir / 'index.html').read_text(encoding='utf8') @@ -1036,7 +1071,7 @@ def test_html_meta(app): @sphinx_intl @pytest.mark.sphinx('html', testroot='intl') @pytest.mark.test_params(shared_result='test_intl_basic') -def test_html_footnotes(app): +def test_html_footnotes(app: SphinxTestApp) -> None: app.build() # --- test for # https://github.com/sphinx-doc/sphinx/issues/955 @@ -1048,7 +1083,7 @@ def test_html_footnotes(app): @sphinx_intl @pytest.mark.sphinx('html', testroot='intl') @pytest.mark.test_params(shared_result='test_intl_basic') -def test_html_undefined_refs(app): +def test_html_undefined_refs(app: SphinxTestApp) -> None: app.build() # --- links to undefined reference result = (app.outdir / 'refs_inconsistency.html').read_text(encoding='utf8') @@ -1072,20 +1107,20 @@ def test_html_undefined_refs(app): @sphinx_intl @pytest.mark.sphinx('html', testroot='intl') @pytest.mark.test_params(shared_result='test_intl_basic') -def test_html_index_entries(app): +def test_html_index_entries(app: SphinxTestApp) -> None: app.build() # --- index entries: regression test for # https://github.com/sphinx-doc/sphinx/issues/976 result = (app.outdir / 'genindex.html').read_text(encoding='utf8') - def wrap(tag, keyword): + def wrap(tag: str, keyword: str) -> str: start_tag = '<%s[^>]*>' % tag end_tag = '%s>' % tag return rf'{start_tag}\s*{keyword}\s*{end_tag}' - def wrap_nest(parenttag, childtag, keyword): - start_tag1 = '<%s[^>]*>' % parenttag - start_tag2 = '<%s[^>]*>' % childtag + def wrap_nest(parenttag: str, childtag: str, keyword: str) -> str: + start_tag1 = f'<{parenttag}[^>]*>' + start_tag2 = f'<{childtag}[^>]*>' return rf'{start_tag1}\s*{keyword}\s*{start_tag2}' expected_exprs = [ @@ -1117,17 +1152,16 @@ def test_html_index_entries(app): @sphinx_intl @pytest.mark.sphinx('html', testroot='intl') @pytest.mark.test_params(shared_result='test_intl_basic') -def test_html_versionchanges(app): +def test_html_versionchanges(app: SphinxTestApp) -> None: app.build() # --- versionchanges result = (app.outdir / 'versionchange.html').read_text(encoding='utf8') - def get_content(result, name): - matched = re.search(r'
Deprecated since version 1.0: """
@@ -1162,7 +1196,7 @@ def test_html_versionchanges(app):
@sphinx_intl
@pytest.mark.sphinx('html', testroot='intl')
@pytest.mark.test_params(shared_result='test_intl_basic')
-def test_html_docfields(app):
+def test_html_docfields(app: SphinxTestApp) -> None:
app.build()
# --- docfields
# expect no error by build
@@ -1172,7 +1206,7 @@ def test_html_docfields(app):
@sphinx_intl
@pytest.mark.sphinx('html', testroot='intl')
@pytest.mark.test_params(shared_result='test_intl_basic')
-def test_html_template(app):
+def test_html_template(app: SphinxTestApp) -> None:
app.build()
# --- gettext template
result = (app.outdir / 'contents.html').read_text(encoding='utf8')
@@ -1183,14 +1217,14 @@ def test_html_template(app):
@sphinx_intl
@pytest.mark.sphinx('html', testroot='intl')
@pytest.mark.test_params(shared_result='test_intl_basic')
-def test_html_rebuild_mo(app):
+def test_html_rebuild_mo(app: SphinxTestApp) -> None:
app.build()
# --- rebuild by .mo mtime
app.build()
_, updated, _ = _get_update_targets(app)
assert updated == set()
- _, bom_file = _get_bom_intl_path(app)
+ _, bom_file = _get_bom_intl_path(app.srcdir)
old_mtime = bom_file.stat().st_mtime
new_mtime = old_mtime + (dt := 5)
os.utime(bom_file, (new_mtime, new_mtime))
@@ -1202,7 +1236,7 @@ def test_html_rebuild_mo(app):
@sphinx_intl
@pytest.mark.sphinx('xml', testroot='intl')
@pytest.mark.test_params(shared_result='test_intl_basic')
-def test_xml_footnotes(app):
+def test_xml_footnotes(app: SphinxTestApp) -> None:
app.build()
# --- footnotes: regression test for fix
# https://github.com/sphinx-doc/sphinx/issues/955,
@@ -1213,7 +1247,7 @@ def test_xml_footnotes(app):
para0 = secs[0].findall('paragraph')
assert_elem(
para0[0],
- [
+ texts=[
'I18N WITH FOOTNOTE',
'INCLUDE THIS CONTENTS',
'2',
@@ -1225,7 +1259,7 @@ def test_xml_footnotes(app):
'100',
'.',
],
- ['i18n-with-footnote', 'ref'],
+ refs=['i18n-with-footnote', 'ref'],
)
# check node_id for footnote_references which refer same footnote
@@ -1234,15 +1268,38 @@ def test_xml_footnotes(app):
assert para0[0][4].attrib['ids'] != para0[0][6].attrib['ids']
footnote0 = secs[0].findall('footnote')
- assert_elem(footnote0[0], ['1', 'THIS IS A AUTO NUMBERED FOOTNOTE.'], None, ['1'])
- assert_elem(footnote0[1], ['100', 'THIS IS A NUMBERED FOOTNOTE.'], None, ['100'])
assert_elem(
- footnote0[2], ['2', 'THIS IS A AUTO NUMBERED NAMED FOOTNOTE.'], None, ['named']
+ footnote0[0],
+ texts=['1', 'THIS IS A AUTO NUMBERED FOOTNOTE.'],
+ refs=None,
+ names=['1'],
+ )
+ assert_elem(
+ footnote0[1],
+ texts=['100', 'THIS IS A NUMBERED FOOTNOTE.'],
+ refs=None,
+ names=['100'],
+ )
+ assert_elem(
+ footnote0[2],
+ texts=['2', 'THIS IS A AUTO NUMBERED NAMED FOOTNOTE.'],
+ refs=None,
+ names=['named'],
+ )
+ assert_elem(
+ footnote0[3],
+ texts=['*', 'THIS IS A AUTO SYMBOL FOOTNOTE.'],
+ refs=None,
+ names=None,
)
- assert_elem(footnote0[3], ['*', 'THIS IS A AUTO SYMBOL FOOTNOTE.'], None, None)
citation0 = secs[0].findall('citation')
- assert_elem(citation0[0], ['ref', 'THIS IS A NAMED FOOTNOTE.'], None, ['ref'])
+ assert_elem(
+ citation0[0],
+ texts=['ref', 'THIS IS A NAMED FOOTNOTE.'],
+ refs=None,
+ names=['ref'],
+ )
warnings = getwarning(app.warning)
warning_expr = '.*/footnote.xml:\\d*: SEVERE: Duplicate ID: ".*".\n'
@@ -1254,7 +1311,7 @@ def test_xml_footnotes(app):
@sphinx_intl
@pytest.mark.sphinx('xml', testroot='intl')
@pytest.mark.test_params(shared_result='test_intl_basic')
-def test_xml_footnote_backlinks(app):
+def test_xml_footnote_backlinks(app: SphinxTestApp) -> None:
app.build()
# --- footnote backlinks: i18n test for
# https://github.com/sphinx-doc/sphinx/issues/1058
@@ -1268,14 +1325,14 @@ def test_xml_footnote_backlinks(app):
footnote0 = secs[0].findall('footnote')
for footnote in footnote0:
ids = footnote.attrib.get('ids')
- backrefs = footnote.attrib.get('backrefs').split()
+ backrefs = footnote.attrib['backrefs'].split()
assert refid2id[ids] in backrefs
@sphinx_intl
@pytest.mark.sphinx('xml', testroot='intl')
@pytest.mark.test_params(shared_result='test_intl_basic')
-def test_xml_refs_in_python_domain(app):
+def test_xml_refs_in_python_domain(app: SphinxTestApp) -> None:
app.build()
# --- refs in the Python domain
et = etree_parse(app.outdir / 'refs_python_domain.xml')
@@ -1286,15 +1343,15 @@ def test_xml_refs_in_python_domain(app):
para0 = secs[0].findall('paragraph')
assert_elem(
para0[0],
- ['SEE THIS DECORATOR:', 'sensitive_variables()', '.'],
- ['sensitive.sensitive_variables'],
+ texts=['SEE THIS DECORATOR:', 'sensitive_variables()', '.'],
+ refs=['sensitive.sensitive_variables'],
)
@sphinx_intl
@pytest.mark.sphinx('xml', testroot='intl')
@pytest.mark.test_params(shared_result='test_intl_basic')
-def test_xml_keep_external_links(app):
+def test_xml_keep_external_links(app: SphinxTestApp) -> None:
app.build()
# --- keep external links: regression test for
# https://github.com/sphinx-doc/sphinx/issues/1044
@@ -1304,40 +1361,50 @@ def test_xml_keep_external_links(app):
para0 = secs[0].findall('paragraph')
# external link check
assert_elem(
- para0[0], ['EXTERNAL LINK TO', 'Python', '.'], ['https://python.org/index.html']
+ para0[0],
+ texts=['EXTERNAL LINK TO', 'Python', '.'],
+ refs=['https://python.org/index.html'],
)
# internal link check
assert_elem(
- para0[1], ['EXTERNAL LINKS', 'IS INTERNAL LINK.'], ['i18n-with-external-links']
+ para0[1],
+ texts=['EXTERNAL LINKS', 'IS INTERNAL LINK.'],
+ refs=['i18n-with-external-links'],
)
# inline link check
assert_elem(
- para0[2], ['INLINE LINK BY', 'THE SPHINX SITE', '.'], ['https://sphinx-doc.org']
+ para0[2],
+ texts=['INLINE LINK BY', 'THE SPHINX SITE', '.'],
+ refs=['https://sphinx-doc.org'],
)
# unnamed link check
- assert_elem(para0[3], ['UNNAMED', 'LINK', '.'], ['https://google.com'])
+ assert_elem(
+ para0[3],
+ texts=['UNNAMED', 'LINK', '.'],
+ refs=['https://google.com'],
+ )
# link target swapped translation
para1 = secs[1].findall('paragraph')
assert_elem(
para1[0],
- ['LINK TO', 'external2', 'AND', 'external1', '.'],
- ['https://www.google.com/external2', 'https://www.google.com/external1'],
+ texts=['LINK TO', 'external2', 'AND', 'external1', '.'],
+ refs=['https://www.google.com/external2', 'https://www.google.com/external1'],
)
assert_elem(
para1[1],
- ['LINK TO', 'THE PYTHON SITE', 'AND', 'THE SPHINX SITE', '.'],
- ['https://python.org', 'https://sphinx-doc.org'],
+ texts=['LINK TO', 'THE PYTHON SITE', 'AND', 'THE SPHINX SITE', '.'],
+ refs=['https://python.org', 'https://sphinx-doc.org'],
)
# multiple references in the same line
para2 = secs[2].findall('paragraph')
assert_elem(
para2[0],
- [
+ texts=[
'LINK TO',
'EXTERNAL LINKS',
',',
@@ -1350,7 +1417,7 @@ def test_xml_keep_external_links(app):
'THE PYTHON SITE',
'.',
],
- [
+ refs=[
'i18n-with-external-links',
'https://python.org/index.html',
'https://sphinx-doc.org',
@@ -1363,7 +1430,7 @@ def test_xml_keep_external_links(app):
@sphinx_intl
@pytest.mark.sphinx('xml', testroot='intl')
@pytest.mark.test_params(shared_result='test_intl_basic')
-def test_xml_role_xref(app):
+def test_xml_role_xref(app: SphinxTestApp) -> None:
app.build()
# --- role xref: regression test for
# https://github.com/sphinx-doc/sphinx/issues/1090,
@@ -1374,7 +1441,7 @@ def test_xml_role_xref(app):
(para1,) = sec1.findall('paragraph')
assert_elem(
para1,
- [
+ texts=[
'LINK TO',
"I18N ROCK'N ROLE XREF",
',',
@@ -1383,14 +1450,14 @@ def test_xml_role_xref(app):
'SOME NEW TERM',
'.',
],
- ['i18n-role-xref', 'index', 'glossary_terms#term-Some-term'],
+ refs=['i18n-role-xref', 'index', 'glossary_terms#term-Some-term'],
)
(sec1_1,) = sec1.findall('section')
(title,) = sec1_1.findall('title')
assert_elem(
title,
- [
+ texts=[
'LINK TO',
"I18N ROCK'N ROLE XREF",
',',
@@ -1399,48 +1466,60 @@ def test_xml_role_xref(app):
'SOME NEW TERM',
'.',
],
- ['i18n-role-xref', 'index', 'glossary_terms#term-Some-term'],
+ refs=['i18n-role-xref', 'index', 'glossary_terms#term-Some-term'],
)
para2 = sec2.findall('paragraph')
assert_elem(
para2[0],
- ['LINK TO', 'SOME OTHER NEW TERM', 'AND', 'SOME NEW TERM', '.'],
- ['glossary_terms#term-Some-other-term', 'glossary_terms#term-Some-term'],
+ texts=['LINK TO', 'SOME OTHER NEW TERM', 'AND', 'SOME NEW TERM', '.'],
+ refs=['glossary_terms#term-Some-other-term', 'glossary_terms#term-Some-term'],
)
assert_elem(
para2[1],
- ['LINK TO', 'LABEL', 'AND', 'SAME TYPE LINKS', 'AND', 'SAME TYPE LINKS', '.'],
- ['i18n-role-xref', 'same-type-links', 'same-type-links'],
+ texts=[
+ 'LINK TO',
+ 'LABEL',
+ 'AND',
+ 'SAME TYPE LINKS',
+ 'AND',
+ 'SAME TYPE LINKS',
+ '.',
+ ],
+ refs=['i18n-role-xref', 'same-type-links', 'same-type-links'],
)
assert_elem(
para2[2],
- ['LINK TO', 'I18N WITH GLOSSARY TERMS', 'AND', 'CONTENTS', '.'],
- ['glossary_terms', 'index'],
+ texts=['LINK TO', 'I18N WITH GLOSSARY TERMS', 'AND', 'CONTENTS', '.'],
+ refs=['glossary_terms', 'index'],
)
assert_elem(
para2[3],
- ['LINK TO', '--module', 'AND', '-m', '.'],
- ['cmdoption-module', 'cmdoption-m'],
+ texts=['LINK TO', '--module', 'AND', '-m', '.'],
+ refs=['cmdoption-module', 'cmdoption-m'],
)
assert_elem(
para2[4],
- ['LINK TO', 'env2', 'AND', 'env1', '.'],
- ['envvar-env2', 'envvar-env1'],
+ texts=['LINK TO', 'env2', 'AND', 'env1', '.'],
+ refs=['envvar-env2', 'envvar-env1'],
)
# TODO: how do I link token role to productionlist?
- assert_elem(para2[5], ['LINK TO', 'token2', 'AND', 'token1', '.'], [])
+ assert_elem(
+ para2[5],
+ texts=['LINK TO', 'token2', 'AND', 'token1', '.'],
+ refs=[],
+ )
assert_elem(
para2[6],
- ['LINK TO', 'same-type-links', 'AND', 'i18n-role-xref', '.'],
- ['same-type-links', 'i18n-role-xref'],
+ texts=['LINK TO', 'same-type-links', 'AND', 'i18n-role-xref', '.'],
+ refs=['same-type-links', 'i18n-role-xref'],
)
@sphinx_intl
@pytest.mark.sphinx('xml', testroot='intl')
@pytest.mark.test_params(shared_result='test_intl_basic')
-def test_xml_warnings(app):
+def test_xml_warnings(app: SphinxTestApp) -> None:
app.build()
# warnings
warnings = getwarning(app.warning)
@@ -1452,7 +1531,7 @@ def test_xml_warnings(app):
@sphinx_intl
@pytest.mark.sphinx('xml', testroot='intl')
@pytest.mark.test_params(shared_result='test_intl_basic')
-def test_xml_label_targets(app):
+def test_xml_label_targets(app: SphinxTestApp) -> None:
app.build()
# --- label targets: regression test for
# https://github.com/sphinx-doc/sphinx/issues/1193,
@@ -1463,7 +1542,7 @@ def test_xml_label_targets(app):
para0 = secs[0].findall('paragraph')
assert_elem(
para0[0],
- [
+ texts=[
'X SECTION AND LABEL',
'POINT TO',
'implicit-target',
@@ -1473,13 +1552,13 @@ def test_xml_label_targets(app):
'section-and-label',
'.',
],
- ['implicit-target', 'section-and-label'],
+ refs=['implicit-target', 'section-and-label'],
)
para1 = secs[1].findall('paragraph')
assert_elem(
para1[0],
- [
+ texts=[
'X EXPLICIT-TARGET',
'POINT TO',
'explicit-target',
@@ -1489,34 +1568,38 @@ def test_xml_label_targets(app):
'id1',
'.',
],
- ['explicit-target', 'id1'],
+ refs=['explicit-target', 'id1'],
)
para2 = secs[2].findall('paragraph')
assert_elem(
para2[0],
- ['X IMPLICIT SECTION NAME', 'POINT TO', 'implicit-section-name', '.'],
- ['implicit-section-name'],
+ texts=['X IMPLICIT SECTION NAME', 'POINT TO', 'implicit-section-name', '.'],
+ refs=['implicit-section-name'],
)
sec2 = secs[2].findall('section')
para2_0 = sec2[0].findall('paragraph')
- assert_elem(para2_0[0], ['`X DUPLICATED SUB SECTION`_', 'IS BROKEN LINK.'], [])
+ assert_elem(
+ para2_0[0],
+ texts=['`X DUPLICATED SUB SECTION`_', 'IS BROKEN LINK.'],
+ refs=[],
+ )
para3 = secs[3].findall('paragraph')
assert_elem(
para3[0],
- [
+ texts=[
'X',
'bridge label',
'IS NOT TRANSLATABLE BUT LINKED TO TRANSLATED SECTION TITLE.',
],
- ['label-bridged-target-section'],
+ refs=['label-bridged-target-section'],
)
assert_elem(
para3[1],
- [
+ texts=[
'X',
'bridge label',
'POINT TO',
@@ -1529,27 +1612,27 @@ def test_xml_label_targets(app):
'bridge label2',
'POINT TO CORRECT TARGET.',
],
- ['label-bridged-target-section', 'section-and-label', 'section-and-label'],
+ refs=['label-bridged-target-section', 'section-and-label', 'section-and-label'],
)
@sphinx_intl
@pytest.mark.sphinx('xml', testroot='intl')
@pytest.mark.test_params(shared_result='test_intl_basic')
-def test_xml_strange_markup(app):
+def test_xml_strange_markup(app: SphinxTestApp) -> None:
app.build()
et = etree_parse(app.outdir / 'markup.xml')
secs = et.findall('section')
(subsec1,) = secs[0].findall('section')
(title1,) = subsec1.findall('title')
- assert_elem(title1, ['1. TITLE STARTING WITH 1.'])
+ assert_elem(title1, texts=['1. TITLE STARTING WITH 1.'])
@sphinx_intl
@pytest.mark.sphinx('html', testroot='intl')
@pytest.mark.test_params(shared_result='test_intl_basic')
-def test_additional_targets_should_not_be_translated(app):
+def test_additional_targets_should_not_be_translated(app: SphinxTestApp) -> None:
if tuple(map(int, pygments.__version__.split('.')[:2])) >= (2, 19):
sp = ' '
else:
@@ -1637,7 +1720,7 @@ def test_additional_targets_should_not_be_translated(app):
],
},
)
-def test_additional_targets_should_be_translated(app):
+def test_additional_targets_should_be_translated(app: SphinxTestApp) -> None:
if tuple(map(int, pygments.__version__.split('.')[:2])) >= (2, 19):
sp = ' '
else:
@@ -1738,7 +1821,9 @@ def test_additional_targets_should_be_translated(app):
},
copy_test_root=True,
)
-def test_additional_targets_should_be_translated_substitution_definitions(app):
+def test_additional_targets_should_be_translated_substitution_definitions(
+ app: SphinxTestApp,
+) -> None:
app.build(force_all=True)
# [prolog_epilog_substitution.txt]
@@ -1757,7 +1842,7 @@ def test_additional_targets_should_be_translated_substitution_definitions(app):
@sphinx_intl
@pytest.mark.sphinx('text', testroot='intl')
@pytest.mark.test_params(shared_result='test_intl_basic')
-def test_text_references(app):
+def test_text_references(app: SphinxTestApp) -> None:
app.build(filenames=[app.srcdir / 'refs.txt'])
warnings = app.warning.getvalue().replace(os.sep, '/')
@@ -1775,7 +1860,7 @@ def test_text_references(app):
},
copy_test_root=True,
)
-def test_text_prolog_epilog_substitution(app):
+def test_text_prolog_epilog_substitution(app: SphinxTestApp) -> None:
app.build()
result = (app.outdir / 'prolog_epilog_substitution.txt').read_text(encoding='utf8')
@@ -1803,25 +1888,28 @@ SUBSTITUTED IMAGE [image: SUBST_EPILOG_2 TRANSLATED][image] HERE.
srcdir='test_intl_images',
confoverrides={'language': _CATALOG_LOCALE},
)
-def test_image_glob_intl(app):
+def test_image_glob_intl(app: SphinxTestApp) -> None:
app.build()
# index.rst
doctree = app.env.get_doctree('index')
assert_node(
- doctree[0][1], nodes.image, uri='rimg.xx.png', candidates={'*': 'rimg.xx.png'}
+ extract_node(doctree, 0, 1),
+ nodes.image,
+ uri='rimg.xx.png',
+ candidates={'*': 'rimg.xx.png'},
)
- assert isinstance(doctree[0][2], nodes.figure)
+ assert isinstance(extract_node(doctree, 0, 2), nodes.figure)
assert_node(
- doctree[0][2][0],
+ extract_node(doctree, 0, 2, 0),
nodes.image,
uri='rimg.xx.png',
candidates={'*': 'rimg.xx.png'},
)
assert_node(
- doctree[0][3],
+ extract_node(doctree, 0, 3),
nodes.image,
uri='img.*',
candidates={
@@ -1831,9 +1919,9 @@ def test_image_glob_intl(app):
},
)
- assert isinstance(doctree[0][4], nodes.figure)
+ assert isinstance(extract_node(doctree, 0, 4), nodes.figure)
assert_node(
- doctree[0][4][0],
+ extract_node(doctree, 0, 4, 0),
nodes.image,
uri='img.*',
candidates={
@@ -1846,14 +1934,14 @@ def test_image_glob_intl(app):
# subdir/index.rst
doctree = app.env.get_doctree('subdir/index')
assert_node(
- doctree[0][1],
+ extract_node(doctree, 0, 1),
nodes.image,
uri='subdir/rimg.xx.png',
candidates={'*': 'subdir/rimg.xx.png'},
)
assert_node(
- doctree[0][2],
+ extract_node(doctree, 0, 2),
nodes.image,
uri='subdir/svgimg.*',
candidates={
@@ -1862,9 +1950,9 @@ def test_image_glob_intl(app):
},
)
- assert isinstance(doctree[0][3], nodes.figure)
+ assert isinstance(extract_node(doctree, 0, 3), nodes.figure)
assert_node(
- doctree[0][3][0],
+ extract_node(doctree, 0, 3, 0),
nodes.image,
uri='subdir/svgimg.*',
candidates={
@@ -1884,25 +1972,28 @@ def test_image_glob_intl(app):
'figure_language_filename': '{root}{ext}.{language}',
},
)
-def test_image_glob_intl_using_figure_language_filename(app):
+def test_image_glob_intl_using_figure_language_filename(app: SphinxTestApp) -> None:
app.build()
# index.rst
doctree = app.env.get_doctree('index')
assert_node(
- doctree[0][1], nodes.image, uri='rimg.png.xx', candidates={'*': 'rimg.png.xx'}
+ extract_node(doctree, 0, 1),
+ nodes.image,
+ uri='rimg.png.xx',
+ candidates={'*': 'rimg.png.xx'},
)
- assert isinstance(doctree[0][2], nodes.figure)
+ assert isinstance(extract_node(doctree, 0, 2), nodes.figure)
assert_node(
- doctree[0][2][0],
+ extract_node(doctree, 0, 2, 0),
nodes.image,
uri='rimg.png.xx',
candidates={'*': 'rimg.png.xx'},
)
assert_node(
- doctree[0][3],
+ extract_node(doctree, 0, 3),
nodes.image,
uri='img.*',
candidates={
@@ -1912,9 +2003,9 @@ def test_image_glob_intl_using_figure_language_filename(app):
},
)
- assert isinstance(doctree[0][4], nodes.figure)
+ assert isinstance(extract_node(doctree, 0, 4), nodes.figure)
assert_node(
- doctree[0][4][0],
+ extract_node(doctree, 0, 4, 0),
nodes.image,
uri='img.*',
candidates={
@@ -1927,14 +2018,14 @@ def test_image_glob_intl_using_figure_language_filename(app):
# subdir/index.rst
doctree = app.env.get_doctree('subdir/index')
assert_node(
- doctree[0][1],
+ extract_node(doctree, 0, 1),
nodes.image,
uri='subdir/rimg.png',
candidates={'*': 'subdir/rimg.png'},
)
assert_node(
- doctree[0][2],
+ extract_node(doctree, 0, 2),
nodes.image,
uri='subdir/svgimg.*',
candidates={
@@ -1943,9 +2034,9 @@ def test_image_glob_intl_using_figure_language_filename(app):
},
)
- assert isinstance(doctree[0][3], nodes.figure)
+ assert isinstance(extract_node(doctree, 0, 3), nodes.figure)
assert_node(
- doctree[0][3][0],
+ extract_node(doctree, 0, 3, 0),
nodes.image,
uri='subdir/svgimg.*',
candidates={
@@ -1968,7 +2059,7 @@ def getwarning(warnings: StringIO) -> str:
'gettext_allow_fuzzy_translations': True,
},
)
-def test_gettext_allow_fuzzy_translations(app):
+def test_gettext_allow_fuzzy_translations(app: SphinxTestApp) -> None:
locale_dir = app.srcdir / 'locales' / 'de' / 'LC_MESSAGES'
locale_dir.mkdir(parents=True, exist_ok=True)
with (locale_dir / 'index.po').open('wb') as f:
@@ -1990,7 +2081,7 @@ def test_gettext_allow_fuzzy_translations(app):
'gettext_allow_fuzzy_translations': False,
},
)
-def test_gettext_disallow_fuzzy_translations(app):
+def test_gettext_disallow_fuzzy_translations(app: SphinxTestApp) -> None:
locale_dir = app.srcdir / 'locales' / 'de' / 'LC_MESSAGES'
locale_dir.mkdir(parents=True, exist_ok=True)
with (locale_dir / 'index.po').open('wb') as f:
@@ -2009,7 +2100,9 @@ def test_gettext_disallow_fuzzy_translations(app):
confoverrides={'language': 'de', 'html_sidebars': {'**': ['searchbox.html']}},
copy_test_root=True,
)
-def test_customize_system_message(make_app, app_params):
+def test_customize_system_message(
+ make_app: Callable[..., SphinxTestApp], app_params: _app_params
+) -> None:
try:
# clear translators cache
locale.translators.clear()
@@ -2040,7 +2133,9 @@ def test_customize_system_message(make_app, app_params):
testroot='intl',
confoverrides={'today_fmt': '%Y-%m-%d'},
)
-def test_customize_today_date_format(app, monkeypatch):
+def test_customize_today_date_format(
+ app: SphinxTestApp, monkeypatch: pytest.MonkeyPatch
+) -> None:
with monkeypatch.context() as m:
m.setenv('SOURCE_DATE_EPOCH', '1439131307')
app.build()
diff --git a/tests/test_util/test_util_i18n.py b/tests/test_util/test_util_i18n.py
index 31e774b793..7aeeace369 100644
--- a/tests/test_util/test_util_i18n.py
+++ b/tests/test_util/test_util_i18n.py
@@ -34,7 +34,7 @@ def test_catalog_info_for_sub_domain_file_and_path() -> None:
assert cat.mo_path == Path('path', 'sub', 'domain.mo')
-def test_catalog_outdated(tmp_path):
+def test_catalog_outdated(tmp_path: Path) -> None:
(tmp_path / 'test.po').write_text('#', encoding='utf8')
cat = i18n.CatalogInfo(tmp_path, 'test', 'utf-8')
assert cat.is_outdated() # if mo is not exist
@@ -48,7 +48,7 @@ def test_catalog_outdated(tmp_path):
assert cat.is_outdated() # if mo is exist and older than po
-def test_catalog_write_mo(tmp_path):
+def test_catalog_write_mo(tmp_path: Path) -> None:
(tmp_path / 'test.po').write_text('#', encoding='utf8')
cat = i18n.CatalogInfo(tmp_path, 'test', 'utf-8')
cat.write_mo('en')
@@ -58,8 +58,8 @@ def test_catalog_write_mo(tmp_path):
assert read_mo(f) is not None
-def test_format_date():
- date = datetime.date(2016, 2, 7)
+def test_format_date() -> None:
+ date = datetime.datetime(2016, 2, 7, 5, 11, 17, 0) # NoQA: DTZ001
# strftime format
format = '%B %d, %Y'
@@ -75,28 +75,25 @@ def test_format_date():
assert i18n.format_date(format, date=date, language='en') == format
format = '%B %d, %Y, %H:%M:%S %I %p'
- datet = datetime.datetime(2016, 2, 7, 5, 11, 17, 0) # NoQA: DTZ001
- formatted = i18n.format_date(format, date=datet, language='en')
+ formatted = i18n.format_date(format, date=date, language='en')
assert formatted == 'February 07, 2016, 05:11:17 05 AM'
format = '%B %-d, %Y, %-H:%-M:%-S %-I %p'
- formatted = i18n.format_date(format, date=datet, language='en')
+ formatted = i18n.format_date(format, date=date, language='en')
assert formatted == 'February 7, 2016, 5:11:17 5 AM'
format = '%x'
- assert i18n.format_date(format, date=datet, language='en') == 'Feb 7, 2016'
+ assert i18n.format_date(format, date=date, language='en') == 'Feb 7, 2016'
format = '%X'
- assert i18n.format_date(format, date=datet, language='en') == '5:11:17\u202fAM'
- assert i18n.format_date(format, date=date, language='en') == 'Feb 7, 2016'
+ assert i18n.format_date(format, date=date, language='en') == '5:11:17\u202fAM'
format = '%c'
- formatted = i18n.format_date(format, date=datet, language='en')
+ formatted = i18n.format_date(format, date=date, language='en')
assert formatted == 'Feb 7, 2016, 5:11:17\u202fAM'
- assert i18n.format_date(format, date=date, language='en') == 'Feb 7, 2016'
# timezone
format = '%Z'
- assert i18n.format_date(format, date=datet, language='en') == 'UTC'
+ assert i18n.format_date(format, date=date, language='en') == 'UTC'
format = '%z'
- assert i18n.format_date(format, date=datet, language='en') == '+0000'
+ assert i18n.format_date(format, date=date, language='en') == '+0000'
def test_format_date_timezone() -> None:
@@ -163,7 +160,7 @@ def test_get_filename_for_language(app: SphinxTestApp) -> None:
assert get_filename('foo.png', app.env) == '/subdir/en/foo.png'
-def test_CatalogRepository(tmp_path):
+def test_CatalogRepository(tmp_path: Path) -> None:
for po_file in (
(tmp_path / 'loc1' / 'xx' / 'LC_MESSAGES' / 'test1.po'),
(tmp_path / 'loc1' / 'xx' / 'LC_MESSAGES' / 'test2.po'),
diff --git a/tests/test_util/test_util_inspect.py b/tests/test_util/test_util_inspect.py
index 577ae6f3f8..4018a9632a 100644
--- a/tests/test_util/test_util_inspect.py
+++ b/tests/test_util/test_util_inspect.py
@@ -999,7 +999,7 @@ def test_getdoc_inherited_decorated_method():
def test_is_builtin_class_method() -> None:
class MyInt(int):
- def my_method(self):
+ def my_method(self) -> None:
pass
assert inspect.is_builtin_class_method(MyInt, 'to_bytes')
diff --git a/tests/test_util/test_util_logging.py b/tests/test_util/test_util_logging.py
index 2eee35ce84..cd2a2eb90b 100644
--- a/tests/test_util/test_util_logging.py
+++ b/tests/test_util/test_util_logging.py
@@ -2,7 +2,6 @@
from __future__ import annotations
-import codecs
import os
from contextlib import contextmanager
from pathlib import Path
@@ -21,6 +20,9 @@ from sphinx.util.parallel import ParallelTasks
from tests.utils import TEST_ROOTS_DIR
if TYPE_CHECKING:
+ import io
+ from collections.abc import Iterator
+
from sphinx.testing.util import SphinxTestApp
@@ -124,16 +126,16 @@ def test_once_warning_log(app: SphinxTestApp) -> None:
assert 'WARNING: message: 1\nWARNING: message: 2\n' in warnings
-def test_is_suppressed_warning():
+def test_is_suppressed_warning() -> None:
suppress_warnings = ['ref', 'files.*', 'rest.duplicated_labels']
- assert is_suppressed_warning(None, None, suppress_warnings) is False
- assert is_suppressed_warning('ref', None, suppress_warnings) is True
+ assert is_suppressed_warning(None, None, suppress_warnings) is False # type: ignore[arg-type]
+ assert is_suppressed_warning('ref', None, suppress_warnings) is True # type: ignore[arg-type]
assert is_suppressed_warning('ref', 'numref', suppress_warnings) is True
assert is_suppressed_warning('ref', 'option', suppress_warnings) is True
assert is_suppressed_warning('files', 'image', suppress_warnings) is True
assert is_suppressed_warning('files', 'stylesheet', suppress_warnings) is True
- assert is_suppressed_warning('rest', None, suppress_warnings) is False
+ assert is_suppressed_warning('rest', None, suppress_warnings) is False # type: ignore[arg-type]
assert is_suppressed_warning('rest', 'syntax', suppress_warnings) is False
assert is_suppressed_warning('rest', 'duplicated_labels', suppress_warnings) is True
@@ -281,7 +283,7 @@ def test_pending_warnings(app: SphinxTestApp) -> None:
@contextmanager
-def force_colors():
+def force_colors() -> Iterator[None]:
forcecolor = os.environ.get('FORCE_COLOR', None)
try:
@@ -294,7 +296,7 @@ def force_colors():
os.environ['FORCE_COLOR'] = forcecolor
-def test_log_no_ansi_colors(tmp_path):
+def test_log_no_ansi_colors(tmp_path: Path) -> None:
with force_colors():
wfile = tmp_path / 'warnings.txt'
srcdir = TEST_ROOTS_DIR / 'test-nitpicky-warnings'
@@ -341,7 +343,7 @@ def test_logging_in_ParallelTasks(app: SphinxTestApp) -> None:
logging.setup(app, app.status, app.warning)
logger = logging.getLogger(__name__)
- def child_process():
+ def child_process() -> None:
logger.info('message1')
logger.warning('message2', location='index')
@@ -353,12 +355,15 @@ def test_logging_in_ParallelTasks(app: SphinxTestApp) -> None:
@pytest.mark.sphinx('html', testroot='root')
-def test_output_with_unencodable_char(app):
- class StreamWriter(codecs.StreamWriter):
- def write(self, object):
+def test_output_with_unencodable_char(app: SphinxTestApp) -> None:
+ class StreamWriter:
+ def __init__(self, stream: io.StringIO) -> None:
+ self.stream = stream
+
+ def write(self, object: str) -> None:
self.stream.write(object.encode('cp1252').decode('cp1252'))
- logging.setup(app, StreamWriter(app.status), app.warning, verbosity=0)
+ logging.setup(app, StreamWriter(app.status), app.warning, verbosity=0) # type: ignore[arg-type]
logger = logging.getLogger(__name__)
# info with UnicodeEncodeError
@@ -388,7 +393,7 @@ def test_prefixed_warnings(app: SphinxTestApp) -> None:
assert 'WARNING: message5' in app.warning.getvalue()
-def test_get_node_location_abspath():
+def test_get_node_location_abspath() -> None:
# Ensure that node locations are reported as an absolute path,
# even if the source attribute is a relative path.
diff --git a/tests/test_util/test_util_nodes.py b/tests/test_util/test_util_nodes.py
index c1121a8c91..7588fcbbbc 100644
--- a/tests/test_util/test_util_nodes.py
+++ b/tests/test_util/test_util_nodes.py
@@ -251,7 +251,7 @@ def test_make_id_sequential(app):
('hello