Merge remote-tracking branch 'upstream/master' into productionlist

# Conflicts:
#	CHANGES.rst
This commit is contained in:
Adam Turner 2025-02-12 18:43:22 +00:00
commit 1f7ed77b2a
53 changed files with 314 additions and 301 deletions

View File

@ -27,6 +27,8 @@ Deprecated
* #13037: Deprecate the ``SingleHTMLBuilder.fix_refuris`` method.
Patch by James Addison.
* #13083, #13330: Un-deprecate ``sphinx.util.import_object``.
Patch by Matthias Geier.
Features added
--------------
@ -104,6 +106,8 @@ Features added
* #9169: Add the :confval:`intersphinx_resolve_self` option
to resolve an intersphinx reference to the current project.
Patch by Jakob Lykke Andersen and Adam Turner.
* #11280: Add ability to skip a particular section using the ``no-search`` class.
Patch by Will Lachance.
* #13326: Remove hardcoding from handling :class:`~sphinx.addnodes.productionlist`
nodes in all writers, to improve flexibility.
Patch by Adam Turner.

View File

@ -37,11 +37,6 @@ The following is a list of deprecated interfaces.
- 10.0
- N/A
* - ``sphinx.util.import_object``
- 8.1
- 10.0
- ``importlib.import_module``
* - ``sphinx.ext.intersphinx.normalize_intersphinx_mapping``
- 8.0
- 10.0

View File

@ -81,7 +81,7 @@ docs = [
"sphinxcontrib-websupport",
]
lint = [
"ruff==0.9.5",
"ruff==0.9.6",
"mypy==1.15.0",
"sphinx-lint>=0.9",
"types-colorama==0.4.15.20240311",

View File

@ -11,6 +11,8 @@ __display_version__ = __version__ # used for command line version
import os
import warnings
from sphinx.util._pathlib import _StrPath
# by default, all DeprecationWarning under sphinx package will be emit.
# Users can avoid this by using environment variable: PYTHONWARNINGS=
if 'PYTHONWARNINGS' not in os.environ:
@ -34,7 +36,7 @@ warnings.filterwarnings(
#: Before version 1.2, check the string ``sphinx.__version__``.
version_info = (8, 2, 0, 'beta', 0)
package_dir = os.path.abspath(os.path.dirname(__file__))
package_dir = _StrPath(__file__).resolve().parent
_in_development = True
if _in_development:

View File

@ -5,6 +5,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING
from docutils import nodes
from docutils.nodes import document # NoQA: F401
if TYPE_CHECKING:
from collections.abc import Sequence
@ -16,22 +17,6 @@ if TYPE_CHECKING:
from sphinx.util.typing import ExtensionMetadata
class document(nodes.document):
"""The document root element patched by Sphinx.
This fixes that document.set_id() does not support a node having multiple node Ids.
see https://sourceforge.net/p/docutils/patches/167/
.. important:: This is only for Sphinx internal use. Please don't use this
in your extensions. It will be removed without deprecation period.
"""
def set_id(
self, node: Element, msgnode: Element | None = None, suggested_prefix: str = ''
) -> str:
return super().set_id(node, msgnode, suggested_prefix)
class translatable(nodes.Node):
"""Node which supports translation.

View File

@ -361,7 +361,7 @@ class Sphinx:
locale_dirs: list[_StrPath | None] = list(repo.locale_dirs)
locale_dirs += [None]
locale_dirs += [_StrPath(package_dir, 'locale')]
locale_dirs += [package_dir / 'locale']
self.translator, has_translation = locale.init(
locale_dirs, self.config.language

View File

@ -33,7 +33,7 @@ from sphinx.util.build_phase import BuildPhase
from sphinx.util.display import progress_message, status_iterator
from sphinx.util.docutils import sphinx_domains
from sphinx.util.i18n import CatalogRepository, docname_to_domain
from sphinx.util.osutil import canon_path, ensuredir, relative_uri, relpath
from sphinx.util.osutil import ensuredir, relative_uri, relpath
from sphinx.util.parallel import (
ParallelTasks,
SerialTasks,
@ -518,7 +518,7 @@ class Builder:
from sphinx.util.matching import _translate_pattern
master_doc_path = self.env.doc2path(self.config.master_doc)
master_doc_canon = canon_path(master_doc_path)
master_doc_canon = master_doc_path.as_posix()
for pat in EXCLUDE_PATHS:
if not re.match(_translate_pattern(pat), master_doc_canon):
continue

View File

@ -3,7 +3,6 @@
from __future__ import annotations
import html
from pathlib import Path
from typing import TYPE_CHECKING
from sphinx import package_dir
@ -148,14 +147,14 @@ class ChangesBuilder(Builder):
'theme_' + key: val for (key, val) in self.theme.get_options({}).items()
}
copy_asset_file(
Path(package_dir, 'themes', 'default', 'static', 'default.css.jinja'),
package_dir.joinpath('themes', 'default', 'static', 'default.css.jinja'),
self.outdir,
context=themectx,
renderer=self.templates,
force=True,
)
copy_asset_file(
Path(package_dir, 'themes', 'basic', 'static', 'basic.css'),
package_dir.joinpath('themes', 'basic', 'static', 'basic.css'),
self.outdir / 'basic.css',
force=True,
)

View File

@ -17,7 +17,6 @@ from sphinx.builders import _epub_base
from sphinx.config import ENUM
from sphinx.locale import __
from sphinx.util import logging
from sphinx.util._pathlib import _StrPath
from sphinx.util.fileutil import copy_asset_file
from sphinx.util.osutil import make_filename
@ -85,7 +84,7 @@ class Epub3Builder(_epub_base.EpubBuilder):
epilog = __('The ePub file is in %(outdir)s.')
supported_remote_images = False
template_dir = _StrPath(package_dir, 'templates', 'epub3')
template_dir = package_dir.joinpath('templates', 'epub3')
doctype = DOCTYPE
html_tag = HTML_TAG
use_meta_charset = True

View File

@ -39,7 +39,7 @@ if TYPE_CHECKING:
from sphinx.util.i18n import CatalogInfo
from sphinx.util.typing import ExtensionMetadata
DEFAULT_TEMPLATE_PATH = Path(package_dir, 'templates', 'gettext')
DEFAULT_TEMPLATE_PATH = package_dir.joinpath('templates', 'gettext')
logger = logging.getLogger(__name__)

View File

@ -199,8 +199,8 @@ class StandaloneHTMLBuilder(Builder):
if js_file.is_file():
return js_file
js_file = Path(
package_dir, 'locale', self.config.language, 'LC_MESSAGES', 'sphinx.js'
js_file = package_dir.joinpath(
'locale', self.config.language, 'LC_MESSAGES', 'sphinx.js'
)
if js_file.is_file():
return js_file

View File

@ -442,7 +442,7 @@ class LaTeXBuilder(Builder):
'xindy_lang_option': xindy_lang_option,
'xindy_cyrillic': xindy_cyrillic,
}
static_dir_name = Path(package_dir, 'texinputs')
static_dir_name = package_dir / 'texinputs'
for filename in Path(static_dir_name).iterdir():
if not filename.name.startswith('.'):
copy_asset_file(
@ -454,7 +454,7 @@ class LaTeXBuilder(Builder):
# use pre-1.6.x Makefile for make latexpdf on Windows
if os.name == 'nt':
static_dir_name = Path(package_dir, 'texinputs_win')
static_dir_name = package_dir / 'texinputs_win'
copy_asset_file(
static_dir_name / 'Makefile.jinja',
self.outdir,
@ -522,7 +522,7 @@ class LaTeXBuilder(Builder):
context['addtocaptions'] = r'\addto\captions%s' % self.babel.get_language()
copy_asset_file(
Path(package_dir, 'templates', 'latex', 'sphinxmessages.sty.jinja'),
package_dir.joinpath('templates', 'latex', 'sphinxmessages.sty.jinja'),
self.outdir,
context=context,
renderer=LaTeXRenderer(),

View File

@ -2,10 +2,8 @@
from __future__ import annotations
import os
import os.path
import warnings
from pathlib import Path
from typing import TYPE_CHECKING
from docutils import nodes
@ -36,7 +34,7 @@ if TYPE_CHECKING:
from sphinx.util.typing import ExtensionMetadata
logger = logging.getLogger(__name__)
template_dir = Path(package_dir, 'templates', 'texinfo')
template_dir = package_dir.joinpath('templates', 'texinfo')
class TexinfoBuilder(Builder):

View File

@ -7,6 +7,7 @@ import os
import pickle
from collections import defaultdict
from copy import deepcopy
from pathlib import Path
from typing import TYPE_CHECKING
from sphinx import addnodes
@ -28,7 +29,7 @@ from sphinx.util._timestamps import _format_rfc3339_microseconds
from sphinx.util.docutils import LoggingReporter
from sphinx.util.i18n import CatalogRepository, docname_to_domain
from sphinx.util.nodes import is_translatable
from sphinx.util.osutil import _last_modified_time, _relative_path, canon_path
from sphinx.util.osutil import _last_modified_time, _relative_path
if TYPE_CHECKING:
from collections.abc import Callable, Iterable, Iterator, Mapping
@ -415,7 +416,9 @@ class BuildEnvironment:
"""
return self.project.doc2path(docname, absolute=base)
def relfn2path(self, filename: str, docname: str | None = None) -> tuple[str, str]:
def relfn2path(
self, filename: str | Path, docname: str | None = None
) -> tuple[str, str]:
"""Return paths to a file referenced from a document, relative to
documentation root and absolute.
@ -423,9 +426,9 @@ class BuildEnvironment:
source dir, while relative filenames are relative to the dir of the
containing document.
"""
filename = canon_path(filename)
if filename.startswith('/'):
abs_fn = (self.srcdir / filename[1:]).resolve()
file_name = Path(filename)
if file_name.parts[:1] in {('/',), ('\\',)}:
abs_fn = self.srcdir.joinpath(*file_name.parts[1:]).resolve()
else:
if not docname:
if self.docname:
@ -434,10 +437,10 @@ class BuildEnvironment:
msg = 'docname'
raise KeyError(msg)
doc_dir = self.doc2path(docname, base=False).parent
abs_fn = (self.srcdir / doc_dir / filename).resolve()
abs_fn = self.srcdir.joinpath(doc_dir, file_name).resolve()
rel_fn = _relative_path(abs_fn, self.srcdir)
return canon_path(rel_fn), os.fspath(abs_fn)
return rel_fn.as_posix(), os.fspath(abs_fn)
@property
def found_docs(self) -> set[str]:

View File

@ -33,7 +33,7 @@ else:
PY_SUFFIXES = ('.py', '.pyx', *EXTENSION_SUFFIXES)
template_dir = Path(package_dir, 'templates', 'apidoc')
template_dir = package_dir.joinpath('templates', 'apidoc')
def is_initpy(filename: str | Path) -> bool:

View File

@ -132,7 +132,9 @@ class AutosummaryRenderer:
msg = 'Expected a Sphinx application object!'
raise TypeError(msg)
system_templates_path = [Path(package_dir, 'ext', 'autosummary', 'templates')]
system_templates_path = [
package_dir.joinpath('ext', 'autosummary', 'templates')
]
loader = SphinxTemplateLoader(
app.srcdir, app.config.templates_path, system_templates_path
)

View File

@ -8,7 +8,6 @@ import subprocess
import xml.etree.ElementTree as ET
from hashlib import sha1
from itertools import chain
from pathlib import Path
from subprocess import CalledProcessError
from typing import TYPE_CHECKING
from urllib.parse import urlsplit, urlunsplit
@ -506,7 +505,7 @@ def man_visit_graphviz(self: ManualPageTranslator, node: graphviz) -> None:
def on_config_inited(_app: Sphinx, config: Config) -> None:
css_path = Path(sphinx.package_dir, 'templates', 'graphviz', 'graphviz.css')
css_path = sphinx.package_dir.joinpath('templates', 'graphviz', 'graphviz.css')
config.html_static_path.append(str(css_path))

View File

@ -40,7 +40,7 @@ if TYPE_CHECKING:
logger = logging.getLogger(__name__)
templates_path = Path(package_dir, 'templates', 'imgmath')
templates_path = package_dir.joinpath('templates', 'imgmath')
class MathExtError(SphinxError):

View File

@ -10,7 +10,6 @@ from docutils.readers import standalone
from docutils.transforms.references import DanglingReferences
from docutils.writers import UnfilteredWriter
from sphinx import addnodes
from sphinx.transforms import AutoIndexUpgrader, DoctreeReadEvent, SphinxTransformer
from sphinx.transforms.i18n import (
Locale,
@ -76,7 +75,6 @@ class SphinxBaseReader(standalone.Reader): # type: ignore[misc]
for logging.
"""
document = super().new_document()
document.__class__ = addnodes.document # replace the class with patched version
# substitute transformer
document.transformer = SphinxTransformer(document)

View File

@ -10,7 +10,6 @@ import os
import pickle
import re
from importlib import import_module
from pathlib import Path
from typing import TYPE_CHECKING
from docutils import nodes
@ -39,8 +38,8 @@ if TYPE_CHECKING:
def write(self, s: _T_contra, /) -> object: ...
_NON_MINIFIED_JS_PATH = Path(package_dir, 'search', 'non-minified-js')
_MINIFIED_JS_PATH = Path(package_dir, 'search', 'minified-js')
_NON_MINIFIED_JS_PATH = package_dir.joinpath('search', 'non-minified-js')
_MINIFIED_JS_PATH = package_dir.joinpath('search', 'minified-js')
class SearchLanguage:
@ -227,6 +226,9 @@ class WordCollector(nodes.NodeVisitor):
def dispatch_visit(self, node: Node) -> None:
if isinstance(node, nodes.comment):
raise nodes.SkipNode
elif isinstance(node, nodes.Element) and 'no-search' in node['classes']:
# skip nodes marked with a 'no-search' class
raise nodes.SkipNode
elif isinstance(node, nodes.raw):
if 'html' in node.get('format', '').split():
# Some people might put content in raw HTML that should be searched,
@ -603,6 +605,9 @@ def _feed_visit_nodes(
) -> None:
if isinstance(node, nodes.comment):
return
elif isinstance(node, nodes.Element) and 'no-search' in node['classes']:
# skip nodes marked with a 'no-search' class
return
elif isinstance(node, nodes.raw):
if 'html' in node.get('format', '').split():
# Some people might put content in raw HTML that should be searched,

View File

@ -159,7 +159,7 @@ class HTMLThemeFactory:
def _load_builtin_themes(self) -> None:
"""Load built-in themes."""
themes = self._find_themes(Path(package_dir, 'themes'))
themes = self._find_themes(package_dir / 'themes')
for name, theme in themes.items():
self._themes[name] = _StrPath(theme)

View File

@ -5,10 +5,8 @@ from __future__ import annotations
import os
import posixpath
import re
from typing import TYPE_CHECKING
from sphinx.errors import FiletypeNotFoundError
TYPE_CHECKING = False
if TYPE_CHECKING:
import hashlib
from collections.abc import Callable
@ -23,12 +21,14 @@ url_re: re.Pattern[str] = re.compile(r'(?P<schema>.+)://.*')
def docname_join(basedocname: str, docname: str) -> str:
return posixpath.normpath(posixpath.join('/' + basedocname, '..', docname))[1:]
return posixpath.normpath(posixpath.join(f'/{basedocname}', '..', docname))[1:]
def get_filetype(
source_suffix: dict[str, str], filename: str | os.PathLike[str]
) -> str:
from sphinx.errors import FiletypeNotFoundError
for suffix, filetype in source_suffix.items():
if os.fspath(filename).endswith(suffix):
# If default filetype (None), considered as restructuredtext.
@ -98,12 +98,6 @@ def __getattr__(name: str) -> Any:
_deprecation_warning(__name__, name, '', remove=(10, 0))
return obj
if name == 'import_object':
from sphinx.util._importer import import_object
_deprecation_warning(__name__, name, '', remove=(10, 0))
return import_object
# Re-exported for backwards compatibility,
# but not currently deprecated
@ -112,6 +106,11 @@ def __getattr__(name: str) -> Any:
return encode_uri
if name == 'import_object':
from sphinx.util._importer import import_object
return import_object
if name == 'isurl':
from sphinx.util._uri import is_url

View File

@ -813,8 +813,6 @@ def new_document(source_path: str, settings: Any = None) -> nodes.document:
settings = copy(cached_settings)
# Create a new instance of nodes.document using cached reporter
from sphinx import addnodes
document = addnodes.document(settings, reporter, source=source_path)
document = nodes.document(settings, reporter, source=source_path)
document.note_source(source_path, -1)
return document

View File

@ -16,11 +16,7 @@ from sphinx.errors import SphinxError
from sphinx.locale import __
from sphinx.util import logging
from sphinx.util._pathlib import _StrPath
from sphinx.util.osutil import (
SEP,
_last_modified_time,
canon_path,
)
from sphinx.util.osutil import SEP, _last_modified_time
if TYPE_CHECKING:
import datetime as dt
@ -163,7 +159,7 @@ class CatalogRepository:
@property
def catalogs(self) -> Iterator[CatalogInfo]:
for basedir, filename in self.pofiles:
domain = canon_path(os.path.splitext(filename)[0])
domain = filename.with_suffix('').as_posix()
yield CatalogInfo(basedir, domain, self.encoding)

View File

@ -4,9 +4,9 @@ from __future__ import annotations
import logging
import logging.handlers
import os.path
from collections import defaultdict
from contextlib import contextmanager, nullcontext
from os.path import abspath
from typing import TYPE_CHECKING
from docutils import nodes
@ -554,9 +554,9 @@ class WarningLogRecordTranslator(SphinxLogRecordTranslator):
def get_node_location(node: Node) -> str | None:
source, line = get_source_line(node)
if source and line:
return f'{abspath(source)}:{line}'
return f'{os.path.abspath(source)}:{line}'
if source:
return f'{abspath(source)}:'
return f'{os.path.abspath(source)}:'
if line:
return f'<unknown>:{line}'
return None

View File

@ -22,7 +22,7 @@ if TYPE_CHECKING:
from jinja2.environment import Environment
_TEMPLATES_PATH = Path(package_dir, 'templates')
_TEMPLATES_PATH = package_dir / 'templates'
_LATEX_TEMPLATES_PATH = _TEMPLATES_PATH / 'latex'

View File

@ -89,3 +89,14 @@ def _http_teapot(monkeypatch: pytest.MonkeyPatch) -> Iterator[None]:
with monkeypatch.context() as m:
m.setattr('sphinx.util.requests._Session.request', _request)
yield
@pytest.fixture
def make_app_with_empty_project(make_app, tmp_path):
(tmp_path / 'conf.py').touch()
def _make_app(*args, **kw):
kw.setdefault('srcdir', Path(tmp_path))
return make_app(*args, **kw)
return _make_app

View File

View File

View File

@ -17,6 +17,10 @@ textinheading
International
.. tip::
:class: no-search
bat cat
.. toctree::
tocitem

View File

@ -2,72 +2,11 @@
from __future__ import annotations
import os
import shutil
from contextlib import contextmanager
from unittest import mock
import pytest
from docutils import nodes
from sphinx.cmd.build import build_main
from sphinx.errors import SphinxError
from tests.utils import TESTS_ROOT
def request_session_head(url, **kwargs):
response = mock.Mock()
response.status_code = 200
response.url = url
return response
@pytest.fixture
def nonascii_srcdir(request, rootdir, sphinx_test_tempdir):
# Build in a non-ASCII source dir
test_name = '\u65e5\u672c\u8a9e'
basedir = sphinx_test_tempdir / request.node.originalname
srcdir = basedir / test_name
if not srcdir.exists():
shutil.copytree(rootdir / 'test-root', srcdir)
# add a doc with a non-ASCII file name to the source dir
(srcdir / (test_name + '.txt')).write_text(
"""
nonascii file name page
=======================
""",
encoding='utf8',
)
root_doc = srcdir / 'index.txt'
root_doc.write_text(
root_doc.read_text(encoding='utf8')
+ f"""
.. toctree::
{test_name}/{test_name}
""",
encoding='utf8',
)
return srcdir
# note: this test skips building docs for some builders because they have independent testcase.
# (html, changes, epub, latex, texinfo and manpage)
@pytest.mark.parametrize(
'buildername',
['dirhtml', 'singlehtml', 'text', 'xml', 'pseudoxml', 'linkcheck'],
)
@mock.patch(
'sphinx.builders.linkcheck.requests.head',
side_effect=request_session_head,
)
def test_build_all(requests_head, make_app, nonascii_srcdir, buildername):
app = make_app(buildername, srcdir=nonascii_srcdir)
app.build()
def test_root_doc_not_found(tmp_path, make_app):
(tmp_path / 'conf.py').touch()
@ -165,29 +104,3 @@ def test_image_glob(app):
'image/svg+xml': 'subdir/svgimg.svg',
}
assert doctree[0][3][0]['uri'] == 'subdir/svgimg.*'
@contextmanager
def force_colors():
forcecolor = os.environ.get('FORCE_COLOR', None)
try:
os.environ['FORCE_COLOR'] = '1'
yield
finally:
if forcecolor is None:
os.environ.pop('FORCE_COLOR', None)
else:
os.environ['FORCE_COLOR'] = forcecolor
def test_log_no_ansi_colors(tmp_path):
with force_colors():
wfile = tmp_path / 'warnings.txt'
srcdir = TESTS_ROOT / 'roots' / 'test-nitpicky-warnings'
argv = list(map(str, ['-b', 'html', srcdir, tmp_path, '-n', '-w', wfile]))
retcode = build_main(argv)
assert retcode == 0
content = wfile.read_text(encoding='utf8')
assert '\x1b[91m' not in content

View File

@ -0,0 +1,97 @@
"""Test all builders.
This test skips building docs for some builders that have independent testcases.
(html, changes, epub, latex, texinfo and manpage)
"""
from __future__ import annotations
import shutil
from typing import TYPE_CHECKING
from unittest import mock
import pytest
if TYPE_CHECKING:
from collections.abc import Callable
from pathlib import Path
from unittest.mock import MagicMock
from sphinx.testing.util import SphinxTestApp
def request_session_head(url: str, **kwargs: object) -> mock.Mock:
response = mock.Mock()
response.status_code = 200
response.url = url
return response
@pytest.fixture
def nonascii_srcdir(rootdir: Path, sphinx_test_tempdir: Path) -> Path:
# Build in a non-ASCII source dir
test_name = '\u65e5\u672c\u8a9e'
srcdir = sphinx_test_tempdir / 'test_build_all' / test_name
if not srcdir.exists():
shutil.copytree(rootdir / 'test-root', srcdir)
# add a doc with a non-ASCII file name to the source dir
(srcdir / f'{test_name}.txt').write_text(
'non-ascii file name page\n========================\n', encoding='utf8'
)
with srcdir.joinpath('index.txt').open('a', encoding='utf8') as f:
f.write(f"""
.. toctree::
{test_name}/{test_name}
""")
return srcdir
def test_build_dirhtml(
make_app: Callable[..., SphinxTestApp], nonascii_srcdir: Path
) -> None:
app = make_app('dirhtml', srcdir=nonascii_srcdir)
app.build(force_all=True)
def test_build_singlehtml(
make_app: Callable[..., SphinxTestApp], nonascii_srcdir: Path
) -> None:
app = make_app('singlehtml', srcdir=nonascii_srcdir)
app.build(force_all=True)
def test_build_text(
make_app: Callable[..., SphinxTestApp], nonascii_srcdir: Path
) -> None:
app = make_app('text', srcdir=nonascii_srcdir)
app.build(force_all=True)
def test_build_xml(
make_app: Callable[..., SphinxTestApp], nonascii_srcdir: Path
) -> None:
app = make_app('xml', srcdir=nonascii_srcdir)
app.build(force_all=True)
def test_build_pseudoxml(
make_app: Callable[..., SphinxTestApp], nonascii_srcdir: Path
) -> None:
app = make_app('pseudoxml', srcdir=nonascii_srcdir)
app.build(force_all=True)
@mock.patch(
'sphinx.builders.linkcheck.requests.head',
side_effect=request_session_head,
)
def test_build_linkcheck(
requests_head: MagicMock,
make_app: Callable[..., SphinxTestApp],
nonascii_srcdir: Path,
) -> None:
app = make_app('linkcheck', srcdir=nonascii_srcdir)
app.build(force_all=True)

View File

@ -77,20 +77,14 @@ def compile_latex_document(app, filename='projectnamenotset.tex', docclass='manu
raise AssertionError(msg) from exc
def skip_if_requested(testfunc):
if 'SKIP_LATEX_BUILD' in os.environ:
msg = 'Skip LaTeX builds because SKIP_LATEX_BUILD is set'
return pytest.mark.skipif(True, reason=msg)(testfunc)
else:
return testfunc
def skip_if_stylefiles_notfound(testfunc):
if kpsetest(*STYLEFILES) is False:
msg = 'not running latex, the required styles do not seem to be installed'
return pytest.mark.skipif(True, reason=msg)(testfunc)
else:
return testfunc
skip_if_requested = pytest.mark.skipif(
'SKIP_LATEX_BUILD' in os.environ,
reason='Skip LaTeX builds because SKIP_LATEX_BUILD is set',
)
skip_if_stylefiles_notfound = pytest.mark.skipif(
not kpsetest(*STYLEFILES),
reason='not running latex, the required styles do not seem to be installed',
)
class RemoteImageHandler(http.server.BaseHTTPRequestHandler):

View File

@ -4,7 +4,6 @@ from __future__ import annotations
import pickle
from collections import Counter
from pathlib import Path
from typing import TYPE_CHECKING, Any
from unittest import mock
@ -411,17 +410,6 @@ def test_errors_if_setup_is_not_callable(tmp_path, make_app):
assert 'callable' in str(excinfo.value)
@pytest.fixture
def make_app_with_empty_project(make_app, tmp_path):
(tmp_path / 'conf.py').touch()
def _make_app(*args, **kw):
kw.setdefault('srcdir', Path(tmp_path))
return make_app(*args, **kw)
return _make_app
@mock.patch.object(sphinx, '__display_version__', '1.6.4')
def test_needs_sphinx(make_app_with_empty_project):
make_app = make_app_with_empty_project

View File

@ -57,7 +57,7 @@ def test_object_description_sections(app):
assert doctree[1][1][0][1][0] == 'Lorem ipsum dolar sit amet'
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_object_description_content_line_number(app):
text = '.. py:function:: foo(bar)\n\n Some link here: :ref:`abc`\n'
doc = restructuredtext.parse(app, text)

View File

@ -9,7 +9,7 @@ from sphinx.testing import restructuredtext
from sphinx.testing.util import assert_node
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_code_directive(app):
# normal case
text = '.. code::\n\n print("hello world")\n'
@ -89,7 +89,7 @@ def test_csv_table_directive(app):
)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_math_directive(app):
# normal case
text = '.. math:: E = mc^2'

View File

@ -765,7 +765,7 @@ def test_domain_c_build_anon_dup_decl(app):
assert 'WARNING: c:identifier reference target not found: @b' in ws[1]
@pytest.mark.sphinx('html', testroot='root', confoverrides={'nitpicky': True})
@pytest.mark.sphinx('html', testroot='_blank', confoverrides={'nitpicky': True})
def test_domain_c_build_semicolon(app):
text = """
.. c:member:: int member;
@ -875,7 +875,7 @@ _var c:member 1 index.html#c.$ -
assert len(ws) == 0
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_domain_c_parse_cfunction(app):
text = (
'.. c:function:: PyObject* '
@ -895,7 +895,7 @@ def test_domain_c_parse_cfunction(app):
assert entry == ('index', 'c.PyType_GenericAlloc', 'function')
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_domain_c_parse_cmember(app):
text = '.. c:member:: PyObject* PyTypeObject.tp_bases'
doctree = restructuredtext.parse(app, text)
@ -912,7 +912,7 @@ def test_domain_c_parse_cmember(app):
assert entry == ('index', 'c.PyTypeObject.tp_bases', 'member')
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_domain_c_parse_cvar(app):
text = '.. c:var:: PyObject* PyClass_Type'
doctree = restructuredtext.parse(app, text)
@ -929,7 +929,7 @@ def test_domain_c_parse_cvar(app):
assert entry == ('index', 'c.PyClass_Type', 'member')
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_domain_c_parse_no_index_entry(app):
text = '.. c:function:: void f()\n.. c:function:: void g()\n :no-index-entry:\n'
doctree = restructuredtext.parse(app, text)
@ -944,7 +944,7 @@ def test_domain_c_parse_no_index_entry(app):
@pytest.mark.sphinx(
'html',
testroot='root',
testroot='_blank',
confoverrides={
'c_maximum_signature_line_length': len('str hello(str name)'),
},
@ -1005,7 +1005,7 @@ def test_cfunction_signature_with_c_maximum_signature_line_length_equal(app):
@pytest.mark.sphinx(
'html',
testroot='root',
testroot='_blank',
confoverrides={
'c_maximum_signature_line_length': len('str hello(str name)'),
},
@ -1066,7 +1066,7 @@ def test_cfunction_signature_with_c_maximum_signature_line_length_force_single(a
@pytest.mark.sphinx(
'html',
testroot='root',
testroot='_blank',
confoverrides={
'c_maximum_signature_line_length': len('str hello(str name)'),
},
@ -1125,7 +1125,7 @@ def test_cfunction_signature_with_c_maximum_signature_line_length_break(app):
@pytest.mark.sphinx(
'html',
testroot='root',
testroot='_blank',
confoverrides={
'maximum_signature_line_length': len('str hello(str name)'),
},
@ -1186,7 +1186,7 @@ def test_cfunction_signature_with_maximum_signature_line_length_equal(app):
@pytest.mark.sphinx(
'html',
testroot='root',
testroot='_blank',
confoverrides={
'maximum_signature_line_length': len('str hello(str name)'),
},
@ -1247,7 +1247,7 @@ def test_cfunction_signature_with_maximum_signature_line_length_force_single(app
@pytest.mark.sphinx(
'html',
testroot='root',
testroot='_blank',
confoverrides={
'maximum_signature_line_length': len('str hello(str name)'),
},
@ -1306,7 +1306,7 @@ def test_cfunction_signature_with_maximum_signature_line_length_break(app):
@pytest.mark.sphinx(
'html',
testroot='root',
testroot='_blank',
confoverrides={
'c_maximum_signature_line_length': len('str hello(str name)'),
'maximum_signature_line_length': 1,

View File

@ -1909,7 +1909,7 @@ _var cpp:member 1 index.html#_CPPv44$ -
assert len(ws) == 0
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_domain_cpp_parse_no_index_entry(app):
text = (
'.. cpp:function:: void f()\n.. cpp:function:: void g()\n :no-index-entry:\n'
@ -1924,7 +1924,7 @@ def test_domain_cpp_parse_no_index_entry(app):
assert_node(doctree[2], addnodes.index, entries=[])
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_domain_cpp_parse_mix_decl_duplicate(app):
# Issue 8270
text = '.. cpp:struct:: A\n.. cpp:function:: void A()\n.. cpp:struct:: A\n'

View File

@ -201,7 +201,7 @@ def test_get_full_qualified_name():
assert domain.get_full_qualified_name(node) == 'module1.Class.func'
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_js_module(app):
text = '.. js:module:: sphinx'
doctree = restructuredtext.parse(app, text)
@ -214,7 +214,7 @@ def test_js_module(app):
assert_node(doctree[1], nodes.target, ids=['module-sphinx'])
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_js_function(app):
text = '.. js:function:: sum(a, b)'
doctree = restructuredtext.parse(app, text)
@ -254,7 +254,7 @@ def test_js_function(app):
)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_js_class(app):
text = '.. js:class:: Application'
doctree = restructuredtext.parse(app, text)
@ -289,7 +289,7 @@ def test_js_class(app):
assert_node(doctree[1], addnodes.desc, domain='js', objtype='class', no_index=False)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_js_data(app):
text = '.. js:data:: name'
doctree = restructuredtext.parse(app, text)
@ -314,7 +314,7 @@ def test_js_data(app):
assert_node(doctree[1], addnodes.desc, domain='js', objtype='data', no_index=False)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_no_index_entry(app):
text = '.. js:function:: f()\n.. js:function:: g()\n :no-index-entry:\n'
doctree = restructuredtext.parse(app, text)
@ -346,7 +346,7 @@ def test_no_index_entry(app):
)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_module_content_line_number(app):
text = '.. js:module:: foo\n\n Some link here: :ref:`abc`\n'
doc = restructuredtext.parse(app, text)

View File

@ -344,7 +344,7 @@ def test_get_full_qualified_name():
assert domain.get_full_qualified_name(node) == 'module1.Class.func'
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_parse_annotation(app):
doctree = _parse_annotation('int', app.env)
assert_node(doctree, ([pending_xref, 'int'],))
@ -504,7 +504,7 @@ def test_parse_annotation(app):
)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_parse_annotation_suppress(app):
doctree = _parse_annotation('~typing.Dict[str, str]', app.env)
assert_node(
@ -524,7 +524,7 @@ def test_parse_annotation_suppress(app):
)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_parse_annotation_Literal(app):
doctree = _parse_annotation('Literal[True, False]', app.env)
assert_node(
@ -814,7 +814,7 @@ def test_modindex_common_prefix(app):
)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_no_index_entry(app):
text = '.. py:function:: f()\n.. py:function:: g()\n :no-index-entry:\n'
doctree = restructuredtext.parse(app, text)
@ -1209,7 +1209,7 @@ def test_domain_py_python_trailing_comma_in_multi_line_signatures_in_text(app):
assert expected_parameter_list_foo in content
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_module_content_line_number(app):
text = '.. py:module:: foo\n\n Some link here: :ref:`abc`\n'
doc = restructuredtext.parse(app, text)
@ -1325,7 +1325,7 @@ def test_short_literal_types(app):
)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_function_pep_695(app):
text = """.. py:function:: func[\
S,\
@ -1452,7 +1452,7 @@ def test_function_pep_695(app):
)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_class_def_pep_695(app):
# Non-concrete unbound generics are allowed at runtime but type checkers
# should fail (https://peps.python.org/pep-0695/#type-parameter-scopes)
@ -1508,7 +1508,7 @@ def test_class_def_pep_695(app):
)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_class_def_pep_696(app):
# test default values for type variables without using PEP 696 AST parser
text = """.. py:class:: Class[\

View File

@ -36,7 +36,7 @@ def test_domain_py_canonical(app):
assert app.warning.getvalue() == ''
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_canonical(app):
text = '.. py:class:: io.StringIO\n :canonical: _io.StringIO'
domain = app.env.domains.python_domain
@ -69,7 +69,7 @@ def test_canonical(app):
assert domain.objects['_io.StringIO'] == ('index', 'io.StringIO', 'class', True)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_canonical_definition_overrides(app):
text = (
'.. py:class:: io.StringIO\n'
@ -83,7 +83,7 @@ def test_canonical_definition_overrides(app):
assert domain.objects['_io.StringIO'] == ('index', 'id0', 'class', False)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_canonical_definition_skip(app):
text = (
'.. py:class:: _io.StringIO\n'
@ -98,7 +98,7 @@ def test_canonical_definition_skip(app):
assert domain.objects['_io.StringIO'] == ('index', 'io.StringIO', 'class', False)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_canonical_duplicated(app):
text = (
'.. py:class:: mypackage.StringIO\n'

View File

@ -22,7 +22,7 @@ from sphinx.testing import restructuredtext
from sphinx.testing.util import assert_node
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_info_field_list(app):
text = (
'.. py:module:: example\n'
@ -201,7 +201,7 @@ def test_info_field_list(app):
)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_info_field_list_piped_type(app):
text = (
'.. py:module:: example\n'
@ -278,7 +278,7 @@ def test_info_field_list_piped_type(app):
)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_info_field_list_Literal(app):
text = (
'.. py:module:: example\n'
@ -352,7 +352,7 @@ def test_info_field_list_Literal(app):
)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_info_field_list_var(app):
text = '.. py:class:: Class\n\n :var int attr: blah blah\n'
doctree = restructuredtext.parse(app, text)
@ -391,7 +391,7 @@ def test_info_field_list_var(app):
)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_info_field_list_napoleon_deliminator_of(app):
text = (
'.. py:module:: example\n'
@ -437,7 +437,7 @@ def test_info_field_list_napoleon_deliminator_of(app):
)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_info_field_list_napoleon_deliminator_or(app):
text = (
'.. py:module:: example\n'
@ -483,7 +483,7 @@ def test_info_field_list_napoleon_deliminator_or(app):
)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_type_field(app):
text = (
'.. py:data:: var1\n'

View File

@ -28,7 +28,7 @@ from sphinx.testing import restructuredtext
from sphinx.testing.util import assert_node
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_pyfunction(app):
text = (
'.. py:function:: func1\n'
@ -99,7 +99,7 @@ def test_pyfunction(app):
)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_pyfunction_signature(app):
text = '.. py:function:: hello(name: str) -> str'
doctree = restructuredtext.parse(app, text)
@ -146,7 +146,7 @@ def test_pyfunction_signature(app):
)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_pyfunction_signature_full(app):
text = (
'.. py:function:: hello(a: str, b = 1, *args: str, '
@ -311,7 +311,7 @@ def test_pyfunction_signature_full(app):
)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_pyfunction_with_unary_operators(app):
text = '.. py:function:: menu(egg=+1, bacon=-1, sausage=~1, spam=not spam)'
doctree = restructuredtext.parse(app, text)
@ -357,7 +357,7 @@ def test_pyfunction_with_unary_operators(app):
)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_pyfunction_with_binary_operators(app):
text = '.. py:function:: menu(spam=2**64)'
doctree = restructuredtext.parse(app, text)
@ -377,7 +377,7 @@ def test_pyfunction_with_binary_operators(app):
)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_pyfunction_with_number_literals(app):
text = '.. py:function:: hello(age=0x10, height=1_6_0)'
doctree = restructuredtext.parse(app, text)
@ -407,7 +407,7 @@ def test_pyfunction_with_number_literals(app):
)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_pyfunction_with_union_type_operator(app):
text = '.. py:function:: hello(age: int | None)'
doctree = restructuredtext.parse(app, text)
@ -437,7 +437,7 @@ def test_pyfunction_with_union_type_operator(app):
)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_optional_pyfunction_signature(app):
text = '.. py:function:: compile(source [, filename [, symbol]]) -> ast object'
doctree = restructuredtext.parse(app, text)

View File

@ -23,7 +23,7 @@ from sphinx.testing import restructuredtext
from sphinx.testing.util import assert_node
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_pyexception_signature(app):
text = '.. py:exception:: builtins.IOError'
doctree = restructuredtext.parse(app, text)
@ -60,7 +60,7 @@ def test_pyexception_signature(app):
)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_pydata_signature(app):
text = '.. py:data:: version\n :type: int\n :value: 1\n'
doctree = restructuredtext.parse(app, text)
@ -109,7 +109,7 @@ def test_pydata_signature(app):
)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_pydata_signature_old(app):
text = '.. py:data:: version\n :annotation: = 1\n'
doctree = restructuredtext.parse(app, text)
@ -142,7 +142,7 @@ def test_pydata_signature_old(app):
)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_pydata_with_union_type_operator(app):
text = '.. py:data:: version\n :type: int | str'
doctree = restructuredtext.parse(app, text)
@ -166,7 +166,7 @@ def test_pydata_with_union_type_operator(app):
)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_pyobject_prefix(app):
text = (
'.. py:class:: Foo\n\n .. py:method:: Foo.say\n .. py:method:: FooBar.say'
@ -200,7 +200,7 @@ def test_pyobject_prefix(app):
assert doctree[1][1][3].astext().strip() == 'FooBar.say()'
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_pydata(app):
text = '.. py:module:: example\n.. py:data:: var\n :type: int\n'
domain = app.env.domains.python_domain
@ -239,7 +239,7 @@ def test_pydata(app):
assert domain.objects['example.var'] == ('index', 'example.var', 'data', False)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_pyclass_options(app):
text = '.. py:class:: Class1\n.. py:class:: Class2\n :final:\n'
domain = app.env.domains.python_domain
@ -308,7 +308,7 @@ def test_pyclass_options(app):
assert domain.objects['Class2'] == ('index', 'Class2', 'class', False)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_pymethod_options(app):
text = (
'.. py:class:: Class\n'
@ -504,7 +504,7 @@ def test_pymethod_options(app):
assert domain.objects['Class.meth6'] == ('index', 'Class.meth6', 'method', False)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_pyclassmethod(app):
text = '.. py:class:: Class\n\n .. py:classmethod:: meth\n'
domain = app.env.domains.python_domain
@ -557,7 +557,7 @@ def test_pyclassmethod(app):
assert domain.objects['Class.meth'] == ('index', 'Class.meth', 'method', False)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_pystaticmethod(app):
text = '.. py:class:: Class\n\n .. py:staticmethod:: meth\n'
domain = app.env.domains.python_domain
@ -607,7 +607,7 @@ def test_pystaticmethod(app):
assert domain.objects['Class.meth'] == ('index', 'Class.meth', 'method', False)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_pyattribute(app):
text = (
'.. py:class:: Class\n'
@ -684,7 +684,7 @@ def test_pyattribute(app):
assert domain.objects['Class.attr'] == ('index', 'Class.attr', 'attribute', False)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_pyproperty(app):
text = (
'.. py:class:: Class\n'
@ -795,7 +795,7 @@ def test_pyproperty(app):
assert domain.objects['Class.prop2'] == ('index', 'Class.prop2', 'property', False)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_py_type_alias(app):
text = (
'.. py:module:: example\n'
@ -940,7 +940,7 @@ def test_domain_py_type_alias(app):
assert app.warning.getvalue() == ''
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_pydecorator_signature(app):
text = '.. py:decorator:: deco'
domain = app.env.domains.python_domain
@ -971,7 +971,7 @@ def test_pydecorator_signature(app):
assert domain.objects['deco'] == ('index', 'deco', 'function', False)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_pydecoratormethod_signature(app):
text = '.. py:decoratormethod:: deco'
domain = app.env.domains.python_domain
@ -1002,7 +1002,7 @@ def test_pydecoratormethod_signature(app):
assert domain.objects['deco'] == ('index', 'deco', 'method', False)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_pycurrentmodule(app):
text = (
'.. py:module:: Other\n'

View File

@ -32,7 +32,7 @@ def test_parse_directive():
assert s == ('.. :: bar', '')
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_rst_directive(app):
# bare
text = '.. rst:directive:: toctree'
@ -81,7 +81,7 @@ def test_rst_directive(app):
)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_rst_directive_with_argument(app):
text = '.. rst:directive:: .. toctree:: foo bar baz'
doctree = restructuredtext.parse(app, text)
@ -115,7 +115,7 @@ def test_rst_directive_with_argument(app):
)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_rst_directive_option(app):
text = '.. rst:directive:option:: foo'
doctree = restructuredtext.parse(app, text)
@ -142,7 +142,7 @@ def test_rst_directive_option(app):
)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_rst_directive_option_with_argument(app):
text = '.. rst:directive:option:: foo: bar baz'
doctree = restructuredtext.parse(app, text)
@ -178,7 +178,7 @@ def test_rst_directive_option_with_argument(app):
)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_rst_directive_option_type(app):
text = '.. rst:directive:option:: foo\n :type: directives.flags\n'
doctree = restructuredtext.parse(app, text)
@ -217,7 +217,7 @@ def test_rst_directive_option_type(app):
)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_rst_directive_and_directive_option(app):
text = '.. rst:directive:: foo\n\n .. rst:directive:option:: bar\n'
doctree = restructuredtext.parse(app, text)
@ -259,7 +259,7 @@ def test_rst_directive_and_directive_option(app):
)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_rst_role(app):
text = '.. rst:role:: ref'
doctree = restructuredtext.parse(app, text)

View File

@ -89,7 +89,7 @@ def test_get_full_qualified_name():
assert domain.get_full_qualified_name(node) == 'ls.-l'
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_cmd_option_with_optional_value(app):
text = '.. option:: -j[=N]'
doctree = restructuredtext.parse(app, text)
@ -116,7 +116,7 @@ def test_cmd_option_with_optional_value(app):
assert ('-j', '-j', 'cmdoption', 'index', 'cmdoption-j', 1) in objects
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_cmd_option_starting_with_bracket(app):
text = '.. option:: [enable=]PATTERN'
doctree = restructuredtext.parse(app, text)
@ -147,7 +147,7 @@ def test_cmd_option_starting_with_bracket(app):
) in objects
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_glossary(app):
text = (
'.. glossary::\n'
@ -243,7 +243,7 @@ def test_glossary(app):
assert_node(refnode, nodes.reference, refid='term-TERM2')
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_glossary_warning(app):
# empty line between terms
text = '.. glossary::\n\n term1\n\n term2\n'
@ -275,7 +275,7 @@ def test_glossary_warning(app):
) in app.warning.getvalue()
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_glossary_comment(app):
text = (
'.. glossary::\n'
@ -301,7 +301,7 @@ def test_glossary_comment(app):
assert_node(doctree[0][0][0][1], [nodes.definition, nodes.paragraph, 'description'])
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_glossary_comment2(app):
text = (
'.. glossary::\n'
@ -335,7 +335,7 @@ def test_glossary_comment2(app):
)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_glossary_sorted(app):
text = (
'.. glossary::\n'
@ -373,7 +373,7 @@ def test_glossary_sorted(app):
assert_node(doctree[0][0][1][1], [nodes.definition, nodes.paragraph, 'description'])
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_glossary_alphanumeric(app):
text = '.. glossary::\n\n 1\n /\n'
restructuredtext.parse(app, text)
@ -382,7 +382,7 @@ def test_glossary_alphanumeric(app):
assert ('/', '/', 'term', 'index', 'term-0', -1) in objects
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_glossary_conflicted_labels(app):
text = '.. _term-foo:\n.. glossary::\n\n foo\n'
restructuredtext.parse(app, text)
@ -390,7 +390,7 @@ def test_glossary_conflicted_labels(app):
assert ('foo', 'foo', 'term', 'index', 'term-0', -1) in objects
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_cmdoption(app):
text = '.. program:: ls\n\n.. option:: -l\n'
domain = app.env.domains.standard_domain
@ -417,7 +417,7 @@ def test_cmdoption(app):
assert domain.progoptions['ls', '-l'] == ('index', 'cmdoption-ls-l')
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_cmdoption_for_None(app):
text = '.. program:: ls\n.. program:: None\n\n.. option:: -l\n'
domain = app.env.domains.standard_domain
@ -444,7 +444,7 @@ def test_cmdoption_for_None(app):
assert domain.progoptions[None, '-l'] == ('index', 'cmdoption-l')
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_multiple_cmdoptions(app):
text = '.. program:: cmd\n\n.. option:: -o directory, --output directory\n'
domain = app.env.domains.standard_domain
@ -485,7 +485,7 @@ def test_multiple_cmdoptions(app):
assert domain.progoptions['cmd', '--output'] == ('index', 'cmdoption-cmd-o')
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_disabled_docref(app):
text = ':doc:`index`\n:doc:`!index`\n'
doctree = restructuredtext.parse(app, text)
@ -500,7 +500,7 @@ def test_disabled_docref(app):
)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_labeled_rubric(app):
text = '.. _label:\n.. rubric:: blah *blah* blah\n'
restructuredtext.parse(app, text)
@ -510,7 +510,7 @@ def test_labeled_rubric(app):
assert domain.labels['label'] == ('index', 'label', 'blah blah blah')
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_labeled_definition(app):
text = (
'.. _label1:\n'
@ -533,7 +533,7 @@ def test_labeled_definition(app):
assert domain.labels['label2'] == ('index', 'label2', 'Bar blah blah blah')
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_labeled_field(app):
text = (
'.. _label1:\n'

View File

@ -17,7 +17,7 @@ def test_ifconfig(app):
assert 'ham' not in result
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_ifconfig_content_line_number(app):
app.setup_extension('sphinx.ext.ifconfig')
text = '.. ifconfig:: confval1\n\n Some link here: :ref:`abc`\n'

View File

@ -147,15 +147,7 @@ def verify(verify_re_html, verify_re_latex):
@pytest.fixture
def get_verifier(verify, verify_re):
v = {
'verify': verify,
'verify_re': verify_re,
}
def get(name):
return v[name]
return get
return {'verify': verify, 'verify_re': verify_re}.__getitem__
@pytest.mark.parametrize(

View File

@ -16,8 +16,8 @@ SPHINX_MODULE_PATH = Path(sphinx.__file__).resolve().with_suffix('.py')
def test_ModuleAnalyzer_get_module_source():
assert ModuleAnalyzer.get_module_source('sphinx') == (
sphinx.__file__,
sphinx.__loader__.get_source('sphinx'),
Path(sphinx.__file__),
sphinx.__spec__.loader.get_source('sphinx'),
)
# failed to obtain source information from builtin modules

View File

@ -398,10 +398,13 @@ def test_nosearch(app):
app.build()
index = load_searchindex(app.outdir / 'searchindex.js')
assert index['docnames'] == ['index', 'nosearch', 'tocitem']
# latex is in 'nosearch.rst', and nowhere else
assert 'latex' not in index['terms']
assert 'bat' in index['terms']
# cat is in 'index.rst' but is marked with the 'no-search' class
assert 'cat' not in index['terms']
# bat is indexed from 'index.rst' and 'tocitem.rst' (document IDs 0, 2), and
# not from 'nosearch.rst' (document ID 1)
assert 'bat' in index['terms']
assert index['terms']['bat'] == [0, 2]

View File

@ -10,7 +10,7 @@ from sphinx.testing import restructuredtext
from sphinx.testing.util import assert_node
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_transforms_reorder_consecutive_target_and_index_nodes_preserve_order(app):
text = '.. index:: abc\n.. index:: def\n.. index:: ghi\n.. index:: jkl\n\ntext\n'
doctree = restructuredtext.parse(app, text)
@ -47,7 +47,7 @@ def test_transforms_reorder_consecutive_target_and_index_nodes_preserve_order(ap
# assert_node(doctree[8], nodes.paragraph)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_transforms_reorder_consecutive_target_and_index_nodes_no_merge_across_other_nodes(
app,
):
@ -98,7 +98,7 @@ def test_transforms_reorder_consecutive_target_and_index_nodes_no_merge_across_o
# assert_node(doctree[9], nodes.paragraph)
@pytest.mark.sphinx('html', testroot='root')
@pytest.mark.sphinx('html', testroot='_blank')
def test_transforms_reorder_consecutive_target_and_index_nodes_merge_with_labels(app):
text = (
'.. _abc:\n'

View File

@ -69,12 +69,11 @@ def test_exported_attributes():
assert sphinx.util.FilenameUniqDict is FilenameUniqDict
with pytest.warns(RemovedInSphinx10Warning, match=r'deprecated.'):
assert sphinx.util.DownloadFiles is DownloadFiles
with pytest.warns(RemovedInSphinx10Warning, match=r'deprecated.'):
assert sphinx.util.import_object is import_object
# Re-exported for backwards compatibility,
# but not currently deprecated
assert sphinx.util.encode_uri is encode_uri
assert sphinx.util.import_object is import_object
assert sphinx.util.isurl is is_url
assert sphinx.util.parselinenos is parse_line_num_spec
assert sphinx.util.patfilter is patfilter

View File

@ -4,17 +4,21 @@ from __future__ import annotations
import codecs
import os
from contextlib import contextmanager
from pathlib import Path
import pytest
from docutils import nodes
from sphinx._cli.util.errors import strip_escape_sequences
from sphinx.cmd.build import build_main
from sphinx.util import logging
from sphinx.util.console import colorize
from sphinx.util.logging import is_suppressed_warning, prefixed_warnings
from sphinx.util.parallel import ParallelTasks
from tests.utils import TESTS_ROOT
@pytest.mark.sphinx('html', testroot='root')
def test_info_and_warning(app):
@ -276,6 +280,32 @@ def test_pending_warnings(app):
assert 'WARNING: message2\nWARNING: message3' in warnings
@contextmanager
def force_colors():
forcecolor = os.environ.get('FORCE_COLOR', None)
try:
os.environ['FORCE_COLOR'] = '1'
yield
finally:
if forcecolor is None:
os.environ.pop('FORCE_COLOR', None)
else:
os.environ['FORCE_COLOR'] = forcecolor
def test_log_no_ansi_colors(tmp_path):
with force_colors():
wfile = tmp_path / 'warnings.txt'
srcdir = TESTS_ROOT / 'roots' / 'test-nitpicky-warnings'
argv = list(map(str, ['-b', 'html', srcdir, tmp_path, '-n', '-w', wfile]))
retcode = build_main(argv)
assert retcode == 0
content = wfile.read_text(encoding='utf8')
assert '\x1b[91m' not in content
@pytest.mark.sphinx('html', testroot='root')
def test_colored_logs(app):
app.verbosity = 2