Merge branch '4.x' into fix-footnote-in-info

This commit is contained in:
Takeshi KOMIYA
2021-12-11 22:59:56 +09:00
380 changed files with 57314 additions and 25334 deletions

View File

@@ -1,5 +1,5 @@
"""
Test module for napoleon PEP 526 compatiblity with google style
Test module for napoleon PEP 526 compatibility with google style
"""
module_level_var: int = 99

View File

@@ -1,5 +1,5 @@
"""
Test module for napoleon PEP 526 compatiblity with numpy style
Test module for napoleon PEP 526 compatibility with numpy style
"""
module_level_var: int = 99

View File

@@ -114,6 +114,20 @@ class InnerChild(Outer.Inner):
class DocstringSig(object):
def __new__(cls, *new_args, **new_kwargs):
"""__new__(cls, d, e=1) -> DocstringSig
First line of docstring
rest of docstring
"""
def __init__(self, *init_args, **init_kwargs):
"""__init__(self, a, b=1) -> None
First line of docstring
rest of docstring
"""
def meth(self):
"""meth(FOO, BAR=1) -> BAZ
First line of docstring

View File

@@ -29,7 +29,14 @@ class Quux(List[Union[int, float]]):
pass
class Corge(Quux):
pass
Alias = Foo
#: docstring
OtherAlias = Bar
#: docstring
IntAlias = int

View File

@@ -17,6 +17,10 @@ class AsyncClass:
"""A documented coroutine staticmethod"""
pass
async def do_asyncgen(self):
"""A documented async generator"""
yield
async def _other_coro_func():
return "run"

View File

@@ -8,6 +8,10 @@ def func():
async def coroutinefunc():
pass
async def asyncgenerator():
yield
partial_func = partial(func)
partial_coroutinefunc = partial(coroutinefunc)

View File

@@ -22,6 +22,10 @@ def func(arg: missing_module.Class):
class TestAutodoc(object):
"""TestAutodoc docstring."""
#: docstring
Alias = missing_module2.Class
@missing_name
def decoratedMethod(self):
"""TestAutodoc::decoratedMethod docstring"""
@@ -34,3 +38,6 @@ class Inherited(missing_module.Class):
sphinx.missing_module4.missing_function(len(missing_name2))
#: docstring
Alias = missing_module2.Class

View File

@@ -6,14 +6,15 @@ SENTINEL = object()
def foo(name: str = CONSTANT,
sentinal: Any = SENTINEL,
now: datetime = datetime.now()) -> None:
sentinel: Any = SENTINEL,
now: datetime = datetime.now(),
color: int = 0xFFFFFF) -> None:
"""docstring"""
class Class:
"""docstring"""
def meth(self, name: str = CONSTANT, sentinal: Any = SENTINEL,
now: datetime = datetime.now()) -> None:
def meth(self, name: str = CONSTANT, sentinel: Any = SENTINEL,
now: datetime = datetime.now(), color: int = 0xFFFFFF) -> None:
"""docstring"""

View File

@@ -2,5 +2,10 @@ class Foo:
"""docstring"""
@property
def prop(self) -> int:
def prop1(self) -> int:
"""docstring"""
@classmethod
@property
def prop2(self) -> int:
"""docstring"""

View File

@@ -10,6 +10,7 @@ class Bar:
__slots__ = {'attr1': 'docstring of attr1',
'attr2': 'docstring of attr2',
'attr3': None}
__annotations__ = {'attr1': int}
def __init__(self):
self.attr2 = None #: docstring of instance attr2

View File

@@ -1,5 +1,8 @@
from typing import Any, Tuple, Union
CONST1: int
CONST2: int = 1
def incr(a: int, b: int = 1) -> int:
return a + b
@@ -11,6 +14,9 @@ def decr(a, b = 1):
class Math:
CONST1: int
CONST2: int = 1
def __init__(self, s: str, o: Any = None) -> None:
pass
@@ -32,6 +38,10 @@ class Math:
# type: (...) -> None
return
@property
def prop(self) -> int:
return 0
def tuple_args(x: Tuple[int, Union[int, str]]) -> Tuple[int, int]:
pass

View File

@@ -1,6 +1,16 @@
from os import path # NOQA
from typing import Union
__all__ = [
"CONSTANT1",
"Exc",
"Foo",
"_Baz",
"bar",
"qux",
"path",
]
#: module variable
CONSTANT1 = None
CONSTANT2 = None
@@ -48,3 +58,5 @@ class _Exc(Exception):
#: a module-level attribute
qux = 2
#: a module-level attribute that has been excluded from __all__
quuz = 2

View File

@@ -0,0 +1,5 @@
extensions = ['sphinx.ext.extlinks']
extlinks = {
'user': ('https://github.com/%s', '@%s'),
'repo': ('https://github.com/%s', 'project %s'),
}

View File

@@ -0,0 +1,22 @@
test-ext-extlinks-hardcoded-urls
================================
.. Links generated by extlinks extension should not raise any warnings.
.. Only hardcoded URLs are affected.
:user:`octocat`
:repo:`sphinx-doc/sphinx`
.. hardcoded replaceable link which can be replaced as
.. :repo:`octocat` or :user:`octocat`
https://github.com/octocat
`inline replaceable link <https://github.com/octocat>`_
`replaceable link`_
.. hyperlinks
.. _replaceable link: https://github.com/octocat

View File

@@ -0,0 +1,2 @@
extensions = ['sphinx.ext.extlinks']
extlinks = {'issue': ('https://github.com/sphinx-doc/sphinx/issues/%s', 'issue %s')}

View File

@@ -0,0 +1,28 @@
test-ext-extlinks-hardcoded-urls
================================
.. Links generated by extlinks extension should not raise any warnings.
.. Only hardcoded URLs are affected.
:issue:`1`
.. hardcoded replaceable link
https://github.com/sphinx-doc/sphinx/issues/1
`inline replaceable link <https://github.com/sphinx-doc/sphinx/issues/1>`_
`replaceable link`_
.. hardcoded non-replaceable link
https://github.com/sphinx-doc/sphinx/pulls/1
`inline non-replaceable link <https://github.com/sphinx-doc/sphinx/pulls/1>`_
`non-replaceable link`_
.. hyperlinks
.. _replaceable link: https://github.com/sphinx-doc/sphinx/issues/1
.. _non-replaceable link: https://github.com/sphinx-doc/sphinx/pulls/1

View File

@@ -103,7 +103,7 @@ Javascript items
.. js:function:: bar.baz(href, callback[, errback])
:param string href: The location of the resource.
:param callback: Get's called with the data returned by the resource.
:param callback: Gets called with the data returned by the resource.
:throws InvalidHref: If the `href` is invalid.
:returns: `undefined`

View File

@@ -0,0 +1 @@
extensions = ['sphinx.ext.autodoc']

View File

@@ -0,0 +1,4 @@
test-html_signaturereturn_icon
==============================
.. py:function:: foo(a: bool, b: int) -> str

View File

View File

@@ -0,0 +1,4 @@
.. container:: classname
text

View File

@@ -0,0 +1,5 @@
Broken link
===========
Some links are `broken <https://www.sphinx-doc.org/this-is-another-broken-link>`__
but sometimes not worrying about some broken links is a valid strategy.

View File

@@ -0,0 +1,5 @@
Broken link
===========
Some links are `broken <https://www.sphinx-doc.org/this-is-a-broken-link>`__
but sometimes not worrying about some broken links is a valid strategy.

View File

@@ -0,0 +1,5 @@
exclude_patterns = ['_build']
linkcheck_exclude_documents = [
'^broken_link$',
'br[0-9]ken_link',
]

View File

@@ -0,0 +1,3 @@
.. toctree::
broken_link
br0ken_link

View File

@@ -0,0 +1 @@
exclude_patterns = ['_build']

View File

@@ -0,0 +1,2 @@
`local server1 <http://localhost:7777/path1>`_
`local server2 <http://localhost:7777/path2>`_

View File

@@ -13,8 +13,7 @@ Some additional anchors to exercise ignore code
* `Complete nonsense <https://localhost:7777/doesnotexist>`_
* `Example valid local file <conf.py>`_
* `Example invalid local file <path/to/notfound>`_
* https://github.com/sphinx-doc/sphinx#documentation
* https://github.com/sphinx-doc/sphinx#user-content-testing
* https://github.com/sphinx-doc/sphinx/blob/4.x/sphinx/__init__.py#L2
.. image:: https://www.google.com/image.png
.. figure:: https://www.google.com/image2.png

View File

@@ -0,0 +1,4 @@
latex_documents = [
('index', 'test.tex', 'The basic Sphinx documentation for testing', 'Sphinx', 'report')
]
html_logo = "images/img.png"

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

View File

@@ -0,0 +1,32 @@
The basic Sphinx documentation for testing
==========================================
Sphinx is a tool that makes it easy to create intelligent and beautiful
documentation for Python projects (or other documents consisting of multiple
reStructuredText sources), written by Georg Brandl. It was originally created
for the new Python documentation, and has excellent facilities for Python
project documentation, but C/C++ is supported as well, and more languages are
planned.
Sphinx uses reStructuredText as its markup language, and many of its strengths
come from the power and straightforwardness of reStructuredText and its parsing
and translating suite, the Docutils.
features
--------
Among its features are the following:
* Output formats: HTML (including derivative formats such as HTML Help, Epub
and Qt Help), plain text, manual pages and LaTeX or direct PDF output
using rst2pdf
* Extensive cross-references: semantic markup and automatic links
for functions, classes, glossary terms and similar pieces of information
* Hierarchical structure: easy definition of a document tree, with automatic
links to siblings, parents and children
* Automatic indices: general index as well as a module index
* Code handling: automatic highlighting using the Pygments highlighter
* Flexible HTML output using the Jinja 2 templating engine
* Various extensions are available, e.g. for automatic testing of snippets
and inclusion of appropriately formatted docstrings
* Setuptools integration

View File

@@ -0,0 +1,5 @@
latex_documents = [
('index', 'test.tex', 'The basic Sphinx documentation for testing', 'Sphinx', 'report')
]
html_logo = "https://www.python.org/static/img/python-logo.png"
html_favicon = "https://www.python.org/static/favicon.ico"

View File

@@ -0,0 +1,32 @@
The basic Sphinx documentation for testing
==========================================
Sphinx is a tool that makes it easy to create intelligent and beautiful
documentation for Python projects (or other documents consisting of multiple
reStructuredText sources), written by Georg Brandl. It was originally created
for the new Python documentation, and has excellent facilities for Python
project documentation, but C/C++ is supported as well, and more languages are
planned.
Sphinx uses reStructuredText as its markup language, and many of its strengths
come from the power and straightforwardness of reStructuredText and its parsing
and translating suite, the Docutils.
features
--------
Among its features are the following:
* Output formats: HTML (including derivative formats such as HTML Help, Epub
and Qt Help), plain text, manual pages and LaTeX or direct PDF output
using rst2pdf
* Extensive cross-references: semantic markup and automatic links
for functions, classes, glossary terms and similar pieces of information
* Hierarchical structure: easy definition of a document tree, with automatic
links to siblings, parents and children
* Automatic indices: general index as well as a module index
* Code handling: automatic highlighting using the Pygments highlighter
* Flexible HTML output using the Jinja 2 templating engine
* Various extensions are available, e.g. for automatic testing of snippets
and inclusion of appropriately formatted docstrings
* Setuptools integration

View File

@@ -32,7 +32,7 @@ footnotes in table
:header-rows: 1
* - name [#]_
- desription
- description
* - VIDIOC_CROPCAP
- Information about VIDIOC_CROPCAP [#]_

View File

@@ -3,6 +3,7 @@ Testing downloadable files
Download :download:`img.png` here.
Download :download:`this <subdir/img.png>` there.
Download :download:`file with special characters <file_with_special_#_chars.xyz>`.
Test file and literal inclusion
===============================

View File

@@ -243,7 +243,7 @@ Figures
My description paragraph of the figure.
Description paragraph is wraped with legend node.
Description paragraph is wrapped with legend node.
.. figure:: rimg.png
:align: right

View File

@@ -129,7 +129,7 @@ Javascript items
.. js:function:: bar.baz(href, callback[, errback])
:param string href: The location of the resource.
:param callback: Get's called with the data returned by the resource.
:param callback: Gets called with the data returned by the resource.
:throws InvalidHref: If the `href` is invalid.
:returns: `undefined`

View File

@@ -2,3 +2,7 @@ test-smartquotes
================
-- "Sphinx" is a tool that makes it easy ...
.. toctree::
literals

View File

@@ -0,0 +1,12 @@
literals
========
.. role:: python(code)
:language: python
.. default-role:: python
Standard :code:`code role with 'quotes'`
This is a Python :python:`{'code': 'role', 'with': 'quotes'}`.
This is a ``literal with 'quotes'``

View File

@@ -346,7 +346,7 @@ def test_epub_css_files(app):
content = (app.outdir / 'index.xhtml').read_text()
assert '<link rel="stylesheet" type="text/css" href="_static/css/epub.css" />' in content
# files in html_css_files are not outputed
# files in html_css_files are not outputted
assert ('<link rel="stylesheet" type="text/css" href="_static/css/style.css" />'
not in content)
assert ('<link media="print" rel="stylesheet" title="title" type="text/css" '

View File

@@ -10,13 +10,13 @@
import os
import re
from distutils.version import LooseVersion
from itertools import chain, cycle
from unittest.mock import ANY, call, patch
import pygments
import pytest
from html5lib import HTMLParser
from packaging import version
from sphinx.builders.html import validate_html_extra_path, validate_html_static_path
from sphinx.errors import ConfigError
@@ -30,6 +30,9 @@ else:
FIGURE_CAPTION = ".//figure/figcaption/p"
PYGMENTS_VERSION = version.parse(pygments.__version__).release
ENV_WARNINGS = """\
%(root)s/autodoc_fodder.py:docstring of autodoc_fodder.MarkupError:\\d+: \
WARNING: Explicit markup ends without a blank line; unexpected unindent.
@@ -222,9 +225,9 @@ def test_html4_output(app, status, warning):
(".//a[@href='https://www.python.org/dev/peps/pep-0008']"
"[@class='pep reference external']/strong",
'Python Enhancement Proposal #8'),
(".//a[@href='https://tools.ietf.org/html/rfc1.html']"
(".//a[@href='https://datatracker.ietf.org/doc/html/rfc1.html']"
"[@class='rfc reference external']/strong", 'RFC 1'),
(".//a[@href='https://tools.ietf.org/html/rfc1.html']"
(".//a[@href='https://datatracker.ietf.org/doc/html/rfc1.html']"
"[@class='rfc reference external']/strong", 'Request for Comments #1'),
(".//a[@href='objects.html#envvar-HOME']"
"[@class='reference internal']/code/span[@class='pre']", 'HOME'),
@@ -456,6 +459,12 @@ def test_html_download(app):
assert (app.outdir / matched.group(1)).exists()
assert matched.group(1) == filename
pattern = ('<a class="reference download internal" download="" '
'href="(_downloads/.*/)(file_with_special_%23_chars.xyz)">')
matched = re.search(pattern, result)
assert matched
assert (app.outdir / matched.group(1) / "file_with_special_#_chars.xyz").exists()
@pytest.mark.sphinx('html', testroot='roles-download')
def test_html_download_role(app, status, warning):
@@ -1330,6 +1339,25 @@ def test_html_remote_images(app, status, warning):
assert not (app.outdir / 'python-logo.png').exists()
@pytest.mark.sphinx('html', testroot='remote-logo')
def test_html_remote_logo(app, status, warning):
app.builder.build_all()
result = (app.outdir / 'index.html').read_text()
assert ('<img class="logo" src="https://www.python.org/static/img/python-logo.png" alt="Logo"/>' in result)
assert ('<link rel="shortcut icon" href="https://www.python.org/static/favicon.ico"/>' in result)
assert not (app.outdir / 'python-logo.png').exists()
@pytest.mark.sphinx('html', testroot='local-logo')
def test_html_local_logo(app, status, warning):
app.builder.build_all()
result = (app.outdir / 'index.html').read_text()
assert ('<img class="logo" src="_static/img.png" alt="Logo"/>' in result)
assert (app.outdir / '_static/img.png').exists()
@pytest.mark.sphinx('html', testroot='basic')
def test_html_sidebar(app, status, warning):
ctx = {}
@@ -1551,8 +1579,7 @@ def test_html_codeblock_linenos_style_table(app):
app.build()
content = (app.outdir / 'index.html').read_text()
pygments_version = tuple(LooseVersion(pygments.__version__).version)
if pygments_version >= (2, 8):
if PYGMENTS_VERSION >= (2, 8):
assert ('<div class="linenodiv"><pre><span class="normal">1</span>\n'
'<span class="normal">2</span>\n'
'<span class="normal">3</span>\n'
@@ -1567,8 +1594,7 @@ def test_html_codeblock_linenos_style_inline(app):
app.build()
content = (app.outdir / 'index.html').read_text()
pygments_version = tuple(LooseVersion(pygments.__version__).version)
if pygments_version > (2, 7):
if PYGMENTS_VERSION > (2, 7):
assert '<span class="linenos">1</span>' in content
else:
assert '<span class="lineno">1 </span>' in content
@@ -1625,3 +1651,11 @@ def test_html_permalink_icon(app):
assert ('<h1>The basic Sphinx documentation for testing<a class="headerlink" '
'href="#the-basic-sphinx-documentation-for-testing" '
'title="Permalink to this headline"><span>[PERMALINK]</span></a></h1>' in content)
@pytest.mark.sphinx('html', testroot='html_signaturereturn_icon')
def test_html_signaturereturn_icon(app):
app.build()
content = (app.outdir / 'index.html').read_text()
assert ('<span class="sig-return-icon">&#x2192;</span>' in content)

View File

@@ -1599,3 +1599,11 @@ def test_latex_elements_extrapackages(app, status, warning):
def test_latex_nested_tables(app, status, warning):
app.builder.build_all()
assert '' == warning.getvalue()
@pytest.mark.sphinx('latex', testroot='latex-container')
def test_latex_container(app, status, warning):
app.builder.build_all()
result = (app.outdir / 'python.tex').read_text()
assert r'\begin{sphinxuseclass}{classname}' in result
assert r'\end{sphinxuseclass}' in result

View File

@@ -23,6 +23,7 @@ import pytest
import requests
from sphinx.builders.linkcheck import HyperlinkAvailabilityCheckWorker, RateLimit
from sphinx.testing.util import strip_escseq
from sphinx.util.console import strip_colors
from .utils import CERT_FILE, http_server, https_server
@@ -65,8 +66,8 @@ def test_defaults_json(app):
"info"]:
assert attr in row
assert len(content.splitlines()) == 12
assert len(rows) == 12
assert len(content.splitlines()) == 11
assert len(rows) == 11
# the output order of the rows is not stable
# due to possible variance in network latency
rowsby = {row["uri"]: row for row in rows}
@@ -87,7 +88,7 @@ def test_defaults_json(app):
assert dnerow['uri'] == 'https://localhost:7777/doesnotexist'
assert rowsby['https://www.google.com/image2.png'] == {
'filename': 'links.txt',
'lineno': 20,
'lineno': 19,
'status': 'broken',
'code': 0,
'uri': 'https://www.google.com/image2.png',
@@ -101,10 +102,6 @@ def test_defaults_json(app):
# images should fail
assert "Not Found for url: https://www.google.com/image.png" in \
rowsby["https://www.google.com/image.png"]["info"]
# The anchor of the URI for github.com is automatically modified
assert 'https://github.com/sphinx-doc/sphinx#documentation' not in rowsby
assert 'https://github.com/sphinx-doc/sphinx#user-content-documentation' in rowsby
assert 'https://github.com/sphinx-doc/sphinx#user-content-testing' in rowsby
@pytest.mark.sphinx(
@@ -254,7 +251,7 @@ def make_redirect_handler(*, support_head):
@pytest.mark.sphinx('linkcheck', testroot='linkcheck-localserver', freshenv=True)
def test_follows_redirects_on_HEAD(app, capsys):
def test_follows_redirects_on_HEAD(app, capsys, warning):
with http_server(make_redirect_handler(support_head=True)):
app.build()
stdout, stderr = capsys.readouterr()
@@ -269,10 +266,11 @@ def test_follows_redirects_on_HEAD(app, capsys):
127.0.0.1 - - [] "HEAD /?redirected=1 HTTP/1.1" 204 -
"""
)
assert warning.getvalue() == ''
@pytest.mark.sphinx('linkcheck', testroot='linkcheck-localserver', freshenv=True)
def test_follows_redirects_on_GET(app, capsys):
def test_follows_redirects_on_GET(app, capsys, warning):
with http_server(make_redirect_handler(support_head=False)):
app.build()
stdout, stderr = capsys.readouterr()
@@ -288,6 +286,28 @@ def test_follows_redirects_on_GET(app, capsys):
127.0.0.1 - - [] "GET /?redirected=1 HTTP/1.1" 204 -
"""
)
assert warning.getvalue() == ''
@pytest.mark.sphinx('linkcheck', testroot='linkcheck-localserver-warn-redirects',
freshenv=True, confoverrides={
'linkcheck_allowed_redirects': {'http://localhost:7777/.*1': '.*'}
})
def test_linkcheck_allowed_redirects(app, warning):
with http_server(make_redirect_handler(support_head=False)):
app.build()
with open(app.outdir / 'output.json') as fp:
records = [json.loads(l) for l in fp.readlines()]
assert len(records) == 2
result = {r["uri"]: r["status"] for r in records}
assert result["http://localhost:7777/path1"] == "working"
assert result["http://localhost:7777/path2"] == "redirected"
assert ("index.rst:1: WARNING: redirect http://localhost:7777/path2 - with Found to "
"http://localhost:7777/?redirected=1\n" in strip_escseq(warning.getvalue()))
assert len(warning.getvalue().splitlines()) == 1
class OKHandler(http.server.BaseHTTPRequestHandler):
@@ -605,3 +625,30 @@ def test_get_after_head_raises_connection_error(app):
"uri": "http://localhost:7777/",
"info": "",
}
@pytest.mark.sphinx('linkcheck', testroot='linkcheck-documents_exclude', freshenv=True)
def test_linkcheck_exclude_documents(app):
app.build()
with open(app.outdir / 'output.json') as fp:
content = [json.loads(record) for record in fp]
assert content == [
{
'filename': 'broken_link.rst',
'lineno': 4,
'status': 'ignored',
'code': 0,
'uri': 'https://www.sphinx-doc.org/this-is-a-broken-link',
'info': 'broken_link matched ^broken_link$ from linkcheck_exclude_documents',
},
{
'filename': 'br0ken_link.rst',
'lineno': 4,
'status': 'ignored',
'code': 0,
'uri': 'https://www.sphinx-doc.org/this-is-another-broken-link',
'info': 'br0ken_link matched br[0-9]ken_link from linkcheck_exclude_documents',
},
]

View File

@@ -23,6 +23,9 @@ def test_all(app, status, warning):
assert r'\fBprint \fP\fIi\fP\fB\en\fP' in content
assert r'\fBmanpage\en\fP' in content
# heading (title + description)
assert r'sphinxtests \- Sphinx <Tests> 0.6alpha1' in content
# term of definition list including nodes.strong
assert '\n.B term1\n' in content
assert '\nterm2 (\\fBstronged partially\\fP)\n' in content
@@ -35,6 +38,15 @@ def test_all(app, status, warning):
assert 'Footnotes' not in content
@pytest.mark.sphinx('man', testroot='basic',
confoverrides={'man_pages': [('index', 'title', None, [], 1)]})
def test_man_pages_empty_description(app, status, warning):
app.builder.build_all()
content = (app.outdir / 'title.1').read_text()
assert r'title \-' not in content
@pytest.mark.sphinx('man', testroot='basic',
confoverrides={'man_make_section_directory': True})
def test_man_make_section_directory(app, status, warning):

View File

@@ -120,3 +120,28 @@ def test_texinfo_footnote(app, status, warning):
output = (app.outdir / 'python.texi').read_text()
assert 'First footnote: @footnote{First}' in output
@pytest.mark.sphinx('texinfo')
def test_texinfo_xrefs(app, status, warning):
app.builder.build_all()
output = (app.outdir / 'sphinxtests.texi').read_text()
assert re.search(r'@ref{\w+,,--plugin\.option}', output)
# Now rebuild it without xrefs
app.config.texinfo_cross_references = False
app.builder.build_all()
output = (app.outdir / 'sphinxtests.texi').read_text()
assert not re.search(r'@ref{\w+,,--plugin\.option}', output)
assert 'Link to perl +p, --ObjC++, --plugin.option, create-auth-token, arg and -j' in output
@pytest.mark.sphinx('texinfo', testroot='root')
def test_texinfo_samp_with_variable(app, status, warning):
app.build()
output = (app.outdir / 'sphinxtests.texi').read_text()
assert '@code{@var{variable_only}}' in output
assert '@code{@var{variable} and text}' in output
assert '@code{Show @var{variable} in the middle}' in output

View File

@@ -15,16 +15,20 @@ import pytest
from sphinx import addnodes
from sphinx.addnodes import desc
from sphinx.domains.c import DefinitionError, DefinitionParser, Symbol, _id_prefix, _max_id
from sphinx.domains.c import (DefinitionError, DefinitionParser, Symbol, _id_prefix,
_macroKeywords, _max_id)
from sphinx.ext.intersphinx import load_mappings, normalize_intersphinx_mapping
from sphinx.testing import restructuredtext
from sphinx.testing.util import assert_node
class Config:
c_id_attributes = ["id_attr", 'LIGHTGBM_C_EXPORT']
c_paren_attributes = ["paren_attr"]
c_extra_keywords = _macroKeywords
def parse(name, string):
class Config:
c_id_attributes = ["id_attr", 'LIGHTGBM_C_EXPORT']
c_paren_attributes = ["paren_attr"]
parser = DefinitionParser(string, location=None, config=Config())
parser.allowFallbackExpressionParsing = False
ast = parser.parse_declaration(name, name)
@@ -114,9 +118,6 @@ def check(name, input, idDict, output=None, key=None, asTextOutput=None):
def test_domain_c_ast_expressions():
def exprCheck(expr, output=None):
class Config:
c_id_attributes = ["id_attr"]
c_paren_attributes = ["paren_attr"]
parser = DefinitionParser(expr, location=None, config=Config())
parser.allowFallbackExpressionParsing = False
ast = parser.parse_expression()
@@ -274,6 +275,62 @@ def test_domain_c_ast_expressions():
exprCheck('a or_eq 5')
def test_domain_c_ast_fundamental_types():
def types():
def signed(t):
yield t
yield 'signed ' + t
yield 'unsigned ' + t
# integer types
# -------------
yield 'void'
yield from ('_Bool', 'bool')
yield from signed('char')
yield from signed('short')
yield from signed('short int')
yield from signed('int')
yield from ('signed', 'unsigned')
yield from signed('long')
yield from signed('long int')
yield from signed('long long')
yield from signed('long long int')
yield from ('__int128', '__uint128')
# extensions
for t in ('__int8', '__int16', '__int32', '__int64', '__int128'):
yield from signed(t)
# floating point types
# --------------------
yield from ('_Decimal32', '_Decimal64', '_Decimal128')
for f in ('float', 'double', 'long double'):
yield f
yield from (f + " _Complex", f + " complex")
yield from ("_Complex " + f, "complex " + f)
yield from ("_Imaginary " + f, "imaginary " + f)
# extensions
# https://gcc.gnu.org/onlinedocs/gcc/Floating-Types.html#Floating-Types
yield from ('__float80', '_Float64x',
'__float128', '_Float128',
'__ibm128')
# https://gcc.gnu.org/onlinedocs/gcc/Half-Precision.html#Half-Precision
yield '__fp16'
# fixed-point types (extension)
# -----------------------------
# https://gcc.gnu.org/onlinedocs/gcc/Fixed-Point.html#Fixed-Point
for sat in ('', '_Sat '):
for t in ('_Fract', 'fract', '_Accum', 'accum'):
for size in ('short ', '', 'long ', 'long long '):
for tt in signed(size + t):
yield sat + tt
for t in types():
input = "{key}%s foo" % t
output = ' '.join(input.split())
check('type', input, {1: 'foo'}, key='typedef', output=output)
def test_domain_c_ast_type_definitions():
check('type', "{key}T", {1: "T"})
@@ -527,6 +584,16 @@ def test_domain_c_ast_attributes():
check('function', 'LIGHTGBM_C_EXPORT int LGBM_BoosterFree(int handle)',
{1: 'LGBM_BoosterFree'})
def test_extra_keywords():
with pytest.raises(DefinitionError,
match='Expected identifier, got user-defined keyword: complex.'):
parse('function', 'void f(int complex)')
with pytest.raises(DefinitionError,
match='Expected identifier, got user-defined keyword: complex.'):
parse('function', 'void complex(void)')
# def test_print():
# # used for getting all the ids out for checking
# for a in ids:

View File

@@ -123,7 +123,9 @@ def test_domain_cpp_ast_fundamental_types():
def makeIdV1():
if t == 'decltype(auto)':
return None
id = t.replace(" ", "-").replace("long", "l").replace("int", "i")
id = t.replace(" ", "-").replace("long", "l")
if "__int" not in t:
id = id.replace("int", "i")
id = id.replace("bool", "b").replace("char", "c")
id = id.replace("wc_t", "wchar_t").replace("c16_t", "char16_t")
id = id.replace("c8_t", "char8_t")
@@ -135,7 +137,9 @@ def test_domain_cpp_ast_fundamental_types():
if t == "std::nullptr_t":
id = "NSt9nullptr_tE"
return "1f%s" % id
check("function", "void f(%s arg)" % t, {1: makeIdV1(), 2: makeIdV2()})
input = "void f(%s arg)" % t.replace(' ', ' ')
output = "void f(%s arg)" % t
check("function", input, {1: makeIdV1(), 2: makeIdV2()}, output=output)
def test_domain_cpp_ast_expressions():
@@ -635,6 +639,9 @@ def test_domain_cpp_ast_function_definitions():
# from #8960
check('function', 'void f(void (*p)(int, double), int i)', {2: '1fPFvidEi'})
# from #9535 comment
check('function', 'void f(void (*p)(int) = &foo)', {2: '1fPFviE'})
def test_domain_cpp_ast_operators():
check('function', 'void operator new()', {1: "new-operator", 2: "nwv"})

View File

@@ -15,7 +15,8 @@ from docutils import nodes
from sphinx import addnodes
from sphinx.addnodes import (desc, desc_annotation, desc_content, desc_name, desc_parameter,
desc_parameterlist, desc_signature)
desc_parameterlist, desc_sig_keyword, desc_sig_name,
desc_sig_space, desc_signature)
from sphinx.domains.javascript import JavaScriptDomain
from sphinx.testing import restructuredtext
from sphinx.testing.util import assert_node
@@ -184,11 +185,11 @@ def test_js_function(app):
text = ".. js:function:: sum(a, b)"
doctree = restructuredtext.parse(app, text)
assert_node(doctree, (addnodes.index,
[desc, ([desc_signature, ([desc_name, "sum"],
[desc, ([desc_signature, ([desc_name, ([desc_sig_name, "sum"])],
desc_parameterlist)],
[desc_content, ()])]))
assert_node(doctree[1][0][1], [desc_parameterlist, ([desc_parameter, "a"],
[desc_parameter, "b"])])
assert_node(doctree[1][0][1], [desc_parameterlist, ([desc_parameter, ([desc_sig_name, "a"])],
[desc_parameter, ([desc_sig_name, "b"])])])
assert_node(doctree[0], addnodes.index,
entries=[("single", "sum() (built-in function)", "sum", "", None)])
assert_node(doctree[1], addnodes.desc, domain="js", objtype="function", noindex=False)
@@ -198,8 +199,9 @@ def test_js_class(app):
text = ".. js:class:: Application"
doctree = restructuredtext.parse(app, text)
assert_node(doctree, (addnodes.index,
[desc, ([desc_signature, ([desc_annotation, "class "],
[desc_name, "Application"],
[desc, ([desc_signature, ([desc_annotation, ([desc_sig_keyword, 'class'],
desc_sig_space)],
[desc_name, ([desc_sig_name, "Application"])],
[desc_parameterlist, ()])],
[desc_content, ()])]))
assert_node(doctree[0], addnodes.index,
@@ -211,7 +213,7 @@ def test_js_data(app):
text = ".. js:data:: name"
doctree = restructuredtext.parse(app, text)
assert_node(doctree, (addnodes.index,
[desc, ([desc_signature, desc_name, "name"],
[desc, ([desc_signature, ([desc_name, ([desc_sig_name, "name"])])],
[desc_content, ()])]))
assert_node(doctree[0], addnodes.index,
entries=[("single", "name (global variable or constant)", "name", "", None)])

View File

@@ -18,8 +18,10 @@ from docutils import nodes
from sphinx import addnodes
from sphinx.addnodes import (desc, desc_addname, desc_annotation, desc_content, desc_name,
desc_optional, desc_parameter, desc_parameterlist, desc_returns,
desc_sig_name, desc_sig_operator, desc_sig_punctuation,
desc_signature, pending_xref)
desc_sig_keyword, desc_sig_literal_number,
desc_sig_literal_string, desc_sig_name, desc_sig_operator,
desc_sig_punctuation, desc_sig_space, desc_signature,
pending_xref)
from sphinx.domains import IndexEntry
from sphinx.domains.python import (PythonDomain, PythonModuleIndex, _parse_annotation,
_pseudo_parse_arglist, py_sig_re)
@@ -290,7 +292,8 @@ def test_parse_annotation(app):
assert_node(doctree, ([pending_xref, "Tuple"],
[desc_sig_punctuation, "["],
[pending_xref, "int"],
[desc_sig_punctuation, ", "],
[desc_sig_punctuation, ","],
desc_sig_space,
[pending_xref, "int"],
[desc_sig_punctuation, "]"]))
@@ -305,19 +308,33 @@ def test_parse_annotation(app):
assert_node(doctree, ([pending_xref, "Tuple"],
[desc_sig_punctuation, "["],
[pending_xref, "int"],
[desc_sig_punctuation, ", "],
[desc_sig_punctuation, ","],
desc_sig_space,
[desc_sig_punctuation, "..."],
[desc_sig_punctuation, "]"]))
doctree = _parse_annotation("Callable[[int, int], int]", app.env)
print(doctree)
assert_node(doctree, ([pending_xref, "Callable"],
[desc_sig_punctuation, "["],
[desc_sig_punctuation, "["],
[pending_xref, "int"],
[desc_sig_punctuation, ", "],
[desc_sig_punctuation, ","],
desc_sig_space,
[pending_xref, "int"],
[desc_sig_punctuation, "]"],
[desc_sig_punctuation, ", "],
[desc_sig_punctuation, ","],
desc_sig_space,
[pending_xref, "int"],
[desc_sig_punctuation, "]"]))
doctree = _parse_annotation("Callable[[], int]", app.env)
assert_node(doctree, ([pending_xref, "Callable"],
[desc_sig_punctuation, "["],
[desc_sig_punctuation, "["],
[desc_sig_punctuation, "]"],
[desc_sig_punctuation, ","],
desc_sig_space,
[pending_xref, "int"],
[desc_sig_punctuation, "]"]))
@@ -333,6 +350,42 @@ def test_parse_annotation(app):
assert_node(doctree[0], pending_xref, refdomain="py", reftype="obj", reftarget="None")
def test_parse_annotation_suppress(app):
doctree = _parse_annotation("~typing.Dict[str, str]", app.env)
assert_node(doctree, ([pending_xref, "Dict"],
[desc_sig_punctuation, "["],
[pending_xref, "str"],
[desc_sig_punctuation, ","],
desc_sig_space,
[pending_xref, "str"],
[desc_sig_punctuation, "]"]))
assert_node(doctree[0], pending_xref, refdomain="py", reftype="class", reftarget="typing.Dict")
@pytest.mark.skipif(sys.version_info < (3, 8), reason='python 3.8+ is required.')
def test_parse_annotation_Literal(app):
doctree = _parse_annotation("Literal[True, False]", app.env)
assert_node(doctree, ([pending_xref, "Literal"],
[desc_sig_punctuation, "["],
[desc_sig_keyword, "True"],
[desc_sig_punctuation, ","],
desc_sig_space,
[desc_sig_keyword, "False"],
[desc_sig_punctuation, "]"]))
doctree = _parse_annotation("typing.Literal[0, 1, 'abc']", app.env)
assert_node(doctree, ([pending_xref, "typing.Literal"],
[desc_sig_punctuation, "["],
[desc_sig_literal_number, "0"],
[desc_sig_punctuation, ","],
desc_sig_space,
[desc_sig_literal_number, "1"],
[desc_sig_punctuation, ","],
desc_sig_space,
[desc_sig_literal_string, "'abc'"],
[desc_sig_punctuation, "]"]))
def test_pyfunction_signature(app):
text = ".. py:function:: hello(name: str) -> str"
doctree = restructuredtext.parse(app, text)
@@ -346,7 +399,7 @@ def test_pyfunction_signature(app):
assert_node(doctree[1][0][1],
[desc_parameterlist, desc_parameter, ([desc_sig_name, "name"],
[desc_sig_punctuation, ":"],
" ",
desc_sig_space,
[nodes.inline, pending_xref, "str"])])
@@ -364,7 +417,7 @@ def test_pyfunction_signature_full(app):
assert_node(doctree[1][0][1],
[desc_parameterlist, ([desc_parameter, ([desc_sig_name, "a"],
[desc_sig_punctuation, ":"],
" ",
desc_sig_space,
[desc_sig_name, pending_xref, "str"])],
[desc_parameter, ([desc_sig_name, "b"],
[desc_sig_operator, "="],
@@ -372,28 +425,28 @@ def test_pyfunction_signature_full(app):
[desc_parameter, ([desc_sig_operator, "*"],
[desc_sig_name, "args"],
[desc_sig_punctuation, ":"],
" ",
desc_sig_space,
[desc_sig_name, pending_xref, "str"])],
[desc_parameter, ([desc_sig_name, "c"],
[desc_sig_punctuation, ":"],
" ",
desc_sig_space,
[desc_sig_name, pending_xref, "bool"],
" ",
desc_sig_space,
[desc_sig_operator, "="],
" ",
desc_sig_space,
[nodes.inline, "True"])],
[desc_parameter, ([desc_sig_name, "d"],
[desc_sig_punctuation, ":"],
" ",
desc_sig_space,
[desc_sig_name, pending_xref, "tuple"],
" ",
desc_sig_space,
[desc_sig_operator, "="],
" ",
desc_sig_space,
[nodes.inline, "(1, 2)"])],
[desc_parameter, ([desc_sig_operator, "**"],
[desc_sig_name, "kwargs"],
[desc_sig_punctuation, ":"],
" ",
desc_sig_space,
[desc_sig_name, pending_xref, "str"])])])
@@ -452,11 +505,11 @@ def test_pyfunction_with_union_type_operator(app):
assert_node(doctree[1][0][1],
[desc_parameterlist, ([desc_parameter, ([desc_sig_name, "age"],
[desc_sig_punctuation, ":"],
" ",
desc_sig_space,
[desc_sig_name, ([pending_xref, "int"],
" ",
desc_sig_space,
[desc_sig_punctuation, "|"],
" ",
desc_sig_space,
[pending_xref, "None"])])])])
@@ -471,16 +524,16 @@ def test_optional_pyfunction_signature(app):
assert_node(doctree[1], addnodes.desc, desctype="function",
domain="py", objtype="function", noindex=False)
assert_node(doctree[1][0][1],
([desc_parameter, "source"],
[desc_optional, ([desc_parameter, "filename"],
[desc_optional, desc_parameter, "symbol"])]))
([desc_parameter, ([desc_sig_name, "source"])],
[desc_optional, ([desc_parameter, ([desc_sig_name, "filename"])],
[desc_optional, desc_parameter, ([desc_sig_name, "symbol"])])]))
def test_pyexception_signature(app):
text = ".. py:exception:: builtins.IOError"
doctree = restructuredtext.parse(app, text)
assert_node(doctree, (addnodes.index,
[desc, ([desc_signature, ([desc_annotation, "exception "],
[desc, ([desc_signature, ([desc_annotation, ('exception', desc_sig_space)],
[desc_addname, "builtins."],
[desc_name, "IOError"])],
desc_content)]))
@@ -495,9 +548,15 @@ def test_pydata_signature(app):
doctree = restructuredtext.parse(app, text)
assert_node(doctree, (addnodes.index,
[desc, ([desc_signature, ([desc_name, "version"],
[desc_annotation, (": ",
[desc_annotation, ([desc_sig_punctuation, ':'],
desc_sig_space,
[pending_xref, "int"])],
[desc_annotation, " = 1"])],
[desc_annotation, (
desc_sig_space,
[desc_sig_punctuation, '='],
desc_sig_space,
"1")]
)],
desc_content)]))
assert_node(doctree[1], addnodes.desc, desctype="data",
domain="py", objtype="data", noindex=False)
@@ -509,7 +568,8 @@ def test_pydata_signature_old(app):
doctree = restructuredtext.parse(app, text)
assert_node(doctree, (addnodes.index,
[desc, ([desc_signature, ([desc_name, "version"],
[desc_annotation, " = 1"])],
[desc_annotation, (desc_sig_space,
"= 1")])],
desc_content)]))
assert_node(doctree[1], addnodes.desc, desctype="data",
domain="py", objtype="data", noindex=False)
@@ -521,11 +581,12 @@ def test_pydata_with_union_type_operator(app):
doctree = restructuredtext.parse(app, text)
assert_node(doctree[1][0],
([desc_name, "version"],
[desc_annotation, (": ",
[desc_annotation, ([desc_sig_punctuation, ':'],
desc_sig_space,
[pending_xref, "int"],
" ",
desc_sig_space,
[desc_sig_punctuation, "|"],
" ",
desc_sig_space,
[pending_xref, "str"])]))
@@ -536,7 +597,7 @@ def test_pyobject_prefix(app):
" .. py:method:: FooBar.say")
doctree = restructuredtext.parse(app, text)
assert_node(doctree, (addnodes.index,
[desc, ([desc_signature, ([desc_annotation, "class "],
[desc, ([desc_signature, ([desc_annotation, ('class', desc_sig_space)],
[desc_name, "Foo"])],
[desc_content, (addnodes.index,
desc,
@@ -557,10 +618,11 @@ def test_pydata(app):
addnodes.index,
[desc, ([desc_signature, ([desc_addname, "example."],
[desc_name, "var"],
[desc_annotation, (": ",
[desc_annotation, ([desc_sig_punctuation, ':'],
desc_sig_space,
[pending_xref, "int"])])],
[desc_content, ()])]))
assert_node(doctree[3][0][2][1], pending_xref, **{"py:module": "example"})
assert_node(doctree[3][0][2][2], pending_xref, **{"py:module": "example"})
assert 'example.var' in domain.objects
assert domain.objects['example.var'] == ('index', 'example.var', 'data', False)
@@ -579,7 +641,8 @@ def test_pyfunction(app):
nodes.target,
addnodes.index,
addnodes.index,
[desc, ([desc_signature, ([desc_annotation, "async "],
[desc, ([desc_signature, ([desc_annotation, ([desc_sig_keyword, 'async'],
desc_sig_space)],
[desc_addname, "example."],
[desc_name, "func2"],
[desc_parameterlist, ()])],
@@ -604,11 +667,14 @@ def test_pyclass_options(app):
domain = app.env.get_domain('py')
doctree = restructuredtext.parse(app, text)
assert_node(doctree, (addnodes.index,
[desc, ([desc_signature, ([desc_annotation, "class "],
[desc, ([desc_signature, ([desc_annotation, ("class", desc_sig_space)],
[desc_name, "Class1"])],
[desc_content, ()])],
addnodes.index,
[desc, ([desc_signature, ([desc_annotation, "final class "],
[desc, ([desc_signature, ([desc_annotation, ("final",
desc_sig_space,
"class",
desc_sig_space)],
[desc_name, "Class2"])],
[desc_content, ()])]))
@@ -644,7 +710,7 @@ def test_pymethod_options(app):
domain = app.env.get_domain('py')
doctree = restructuredtext.parse(app, text)
assert_node(doctree, (addnodes.index,
[desc, ([desc_signature, ([desc_annotation, "class "],
[desc, ([desc_signature, ([desc_annotation, ("class", desc_sig_space)],
[desc_name, "Class"])],
[desc_content, (addnodes.index,
desc,
@@ -673,7 +739,7 @@ def test_pymethod_options(app):
# :classmethod:
assert_node(doctree[1][1][2], addnodes.index,
entries=[('single', 'meth2() (Class class method)', 'Class.meth2', '', None)])
assert_node(doctree[1][1][3], ([desc_signature, ([desc_annotation, "classmethod "],
assert_node(doctree[1][1][3], ([desc_signature, ([desc_annotation, ("classmethod", desc_sig_space)],
[desc_name, "meth2"],
[desc_parameterlist, ()])],
[desc_content, ()]))
@@ -683,7 +749,7 @@ def test_pymethod_options(app):
# :staticmethod:
assert_node(doctree[1][1][4], addnodes.index,
entries=[('single', 'meth3() (Class static method)', 'Class.meth3', '', None)])
assert_node(doctree[1][1][5], ([desc_signature, ([desc_annotation, "static "],
assert_node(doctree[1][1][5], ([desc_signature, ([desc_annotation, ("static", desc_sig_space)],
[desc_name, "meth3"],
[desc_parameterlist, ()])],
[desc_content, ()]))
@@ -693,7 +759,7 @@ def test_pymethod_options(app):
# :async:
assert_node(doctree[1][1][6], addnodes.index,
entries=[('single', 'meth4() (Class method)', 'Class.meth4', '', None)])
assert_node(doctree[1][1][7], ([desc_signature, ([desc_annotation, "async "],
assert_node(doctree[1][1][7], ([desc_signature, ([desc_annotation, ("async", desc_sig_space)],
[desc_name, "meth4"],
[desc_parameterlist, ()])],
[desc_content, ()]))
@@ -702,8 +768,8 @@ def test_pymethod_options(app):
# :property:
assert_node(doctree[1][1][8], addnodes.index,
entries=[('single', 'meth5() (Class property)', 'Class.meth5', '', None)])
assert_node(doctree[1][1][9], ([desc_signature, ([desc_annotation, "property "],
entries=[('single', 'meth5 (Class property)', 'Class.meth5', '', None)])
assert_node(doctree[1][1][9], ([desc_signature, ([desc_annotation, ("property", desc_sig_space)],
[desc_name, "meth5"])],
[desc_content, ()]))
assert 'Class.meth5' in domain.objects
@@ -712,7 +778,7 @@ def test_pymethod_options(app):
# :abstractmethod:
assert_node(doctree[1][1][10], addnodes.index,
entries=[('single', 'meth6() (Class method)', 'Class.meth6', '', None)])
assert_node(doctree[1][1][11], ([desc_signature, ([desc_annotation, "abstract "],
assert_node(doctree[1][1][11], ([desc_signature, ([desc_annotation, ("abstract", desc_sig_space)],
[desc_name, "meth6"],
[desc_parameterlist, ()])],
[desc_content, ()]))
@@ -722,7 +788,7 @@ def test_pymethod_options(app):
# :final:
assert_node(doctree[1][1][12], addnodes.index,
entries=[('single', 'meth7() (Class method)', 'Class.meth7', '', None)])
assert_node(doctree[1][1][13], ([desc_signature, ([desc_annotation, "final "],
assert_node(doctree[1][1][13], ([desc_signature, ([desc_annotation, ("final", desc_sig_space)],
[desc_name, "meth7"],
[desc_parameterlist, ()])],
[desc_content, ()]))
@@ -737,13 +803,13 @@ def test_pyclassmethod(app):
domain = app.env.get_domain('py')
doctree = restructuredtext.parse(app, text)
assert_node(doctree, (addnodes.index,
[desc, ([desc_signature, ([desc_annotation, "class "],
[desc, ([desc_signature, ([desc_annotation, ("class", desc_sig_space)],
[desc_name, "Class"])],
[desc_content, (addnodes.index,
desc)])]))
assert_node(doctree[1][1][0], addnodes.index,
entries=[('single', 'meth() (Class class method)', 'Class.meth', '', None)])
assert_node(doctree[1][1][1], ([desc_signature, ([desc_annotation, "classmethod "],
assert_node(doctree[1][1][1], ([desc_signature, ([desc_annotation, ("classmethod", desc_sig_space)],
[desc_name, "meth"],
[desc_parameterlist, ()])],
[desc_content, ()]))
@@ -758,13 +824,13 @@ def test_pystaticmethod(app):
domain = app.env.get_domain('py')
doctree = restructuredtext.parse(app, text)
assert_node(doctree, (addnodes.index,
[desc, ([desc_signature, ([desc_annotation, "class "],
[desc, ([desc_signature, ([desc_annotation, ("class", desc_sig_space)],
[desc_name, "Class"])],
[desc_content, (addnodes.index,
desc)])]))
assert_node(doctree[1][1][0], addnodes.index,
entries=[('single', 'meth() (Class static method)', 'Class.meth', '', None)])
assert_node(doctree[1][1][1], ([desc_signature, ([desc_annotation, "static "],
assert_node(doctree[1][1][1], ([desc_signature, ([desc_annotation, ("static", desc_sig_space)],
[desc_name, "meth"],
[desc_parameterlist, ()])],
[desc_content, ()]))
@@ -781,22 +847,27 @@ def test_pyattribute(app):
domain = app.env.get_domain('py')
doctree = restructuredtext.parse(app, text)
assert_node(doctree, (addnodes.index,
[desc, ([desc_signature, ([desc_annotation, "class "],
[desc, ([desc_signature, ([desc_annotation, ("class", desc_sig_space)],
[desc_name, "Class"])],
[desc_content, (addnodes.index,
desc)])]))
assert_node(doctree[1][1][0], addnodes.index,
entries=[('single', 'attr (Class attribute)', 'Class.attr', '', None)])
assert_node(doctree[1][1][1], ([desc_signature, ([desc_name, "attr"],
[desc_annotation, (": ",
[desc_annotation, ([desc_sig_punctuation, ':'],
desc_sig_space,
[pending_xref, "Optional"],
[desc_sig_punctuation, "["],
[pending_xref, "str"],
[desc_sig_punctuation, "]"])],
[desc_annotation, " = ''"])],
[desc_annotation, (desc_sig_space,
[desc_sig_punctuation, '='],
desc_sig_space,
"''")]
)],
[desc_content, ()]))
assert_node(doctree[1][1][1][0][1][1], pending_xref, **{"py:class": "Class"})
assert_node(doctree[1][1][1][0][1][3], pending_xref, **{"py:class": "Class"})
assert_node(doctree[1][1][1][0][1][2], pending_xref, **{"py:class": "Class"})
assert_node(doctree[1][1][1][0][1][4], pending_xref, **{"py:class": "Class"})
assert 'Class.attr' in domain.objects
assert domain.objects['Class.attr'] == ('index', 'Class.attr', 'attribute', False)
@@ -804,24 +875,44 @@ def test_pyattribute(app):
def test_pyproperty(app):
text = (".. py:class:: Class\n"
"\n"
" .. py:property:: prop\n"
" .. py:property:: prop1\n"
" :abstractmethod:\n"
" :type: str\n"
"\n"
" .. py:property:: prop2\n"
" :classmethod:\n"
" :type: str\n")
domain = app.env.get_domain('py')
doctree = restructuredtext.parse(app, text)
assert_node(doctree, (addnodes.index,
[desc, ([desc_signature, ([desc_annotation, "class "],
[desc, ([desc_signature, ([desc_annotation, ("class", desc_sig_space)],
[desc_name, "Class"])],
[desc_content, (addnodes.index,
desc,
addnodes.index,
desc)])]))
assert_node(doctree[1][1][0], addnodes.index,
entries=[('single', 'prop (Class property)', 'Class.prop', '', None)])
assert_node(doctree[1][1][1], ([desc_signature, ([desc_annotation, "abstract property "],
[desc_name, "prop"],
[desc_annotation, ": str"])],
entries=[('single', 'prop1 (Class property)', 'Class.prop1', '', None)])
assert_node(doctree[1][1][1], ([desc_signature, ([desc_annotation, ("abstract", desc_sig_space,
"property", desc_sig_space)],
[desc_name, "prop1"],
[desc_annotation, ([desc_sig_punctuation, ':'],
desc_sig_space,
[pending_xref, "str"])])],
[desc_content, ()]))
assert 'Class.prop' in domain.objects
assert domain.objects['Class.prop'] == ('index', 'Class.prop', 'property', False)
assert_node(doctree[1][1][2], addnodes.index,
entries=[('single', 'prop2 (Class property)', 'Class.prop2', '', None)])
assert_node(doctree[1][1][3], ([desc_signature, ([desc_annotation, ("class", desc_sig_space,
"property", desc_sig_space)],
[desc_name, "prop2"],
[desc_annotation, ([desc_sig_punctuation, ':'],
desc_sig_space,
[pending_xref, "str"])])],
[desc_content, ()]))
assert 'Class.prop1' in domain.objects
assert domain.objects['Class.prop1'] == ('index', 'Class.prop1', 'property', False)
assert 'Class.prop2' in domain.objects
assert domain.objects['Class.prop2'] == ('index', 'Class.prop2', 'property', False)
def test_pydecorator_signature(app):
@@ -860,7 +951,7 @@ def test_canonical(app):
domain = app.env.get_domain('py')
doctree = restructuredtext.parse(app, text)
assert_node(doctree, (addnodes.index,
[desc, ([desc_signature, ([desc_annotation, "class "],
[desc, ([desc_signature, ([desc_annotation, ("class", desc_sig_space)],
[desc_addname, "io."],
[desc_name, "StringIO"])],
desc_content)]))
@@ -918,7 +1009,7 @@ def test_info_field_list(app):
assert_node(doctree, (nodes.target,
addnodes.index,
addnodes.index,
[desc, ([desc_signature, ([desc_annotation, "class "],
[desc, ([desc_signature, ([desc_annotation, ("class", desc_sig_space)],
[desc_addname, "example."],
[desc_name, "Class"])],
[desc_content, nodes.field_list, nodes.field])]))
@@ -1009,7 +1100,7 @@ def test_info_field_list_piped_type(app):
(nodes.target,
addnodes.index,
addnodes.index,
[desc, ([desc_signature, ([desc_annotation, "class "],
[desc, ([desc_signature, ([desc_annotation, ("class", desc_sig_space)],
[desc_addname, "example."],
[desc_name, "Class"])],
[desc_content, nodes.field_list, nodes.field, (nodes.field_name,
@@ -1031,6 +1122,42 @@ def test_info_field_list_piped_type(app):
**{"py:module": "example", "py:class": "Class"})
def test_info_field_list_Literal(app):
text = (".. py:module:: example\n"
".. py:class:: Class\n"
"\n"
" :param age: blah blah\n"
" :type age: Literal['foo', 'bar', 'baz']\n")
doctree = restructuredtext.parse(app, text)
assert_node(doctree,
(nodes.target,
addnodes.index,
addnodes.index,
[desc, ([desc_signature, ([desc_annotation, ("class", desc_sig_space)],
[desc_addname, "example."],
[desc_name, "Class"])],
[desc_content, nodes.field_list, nodes.field, (nodes.field_name,
nodes.field_body)])]))
assert_node(doctree[3][1][0][0][1],
([nodes.paragraph, ([addnodes.literal_strong, "age"],
" (",
[pending_xref, addnodes.literal_emphasis, "Literal"],
[addnodes.literal_emphasis, "["],
[addnodes.literal_emphasis, "'foo'"],
[addnodes.literal_emphasis, ", "],
[addnodes.literal_emphasis, "'bar'"],
[addnodes.literal_emphasis, ", "],
[addnodes.literal_emphasis, "'baz'"],
[addnodes.literal_emphasis, "]"],
")",
" -- ",
"blah blah")],))
assert_node(doctree[3][1][0][0][1][0][2], pending_xref,
refdomain="py", reftype="class", reftarget="Literal",
**{"py:module": "example", "py:class": "Class"})
def test_info_field_list_var(app):
text = (".. py:class:: Class\n"
"\n"
@@ -1055,6 +1182,28 @@ def test_info_field_list_var(app):
refdomain="py", reftype="class", reftarget="int", **{"py:class": "Class"})
def test_type_field(app):
text = (".. py:data:: var1\n"
" :type: .int\n"
".. py:data:: var2\n"
" :type: ~builtins.int\n")
doctree = restructuredtext.parse(app, text)
assert_node(doctree, (addnodes.index,
[desc, ([desc_signature, ([desc_name, "var1"],
[desc_annotation, ([desc_sig_punctuation, ':'],
desc_sig_space,
[pending_xref, "int"])])],
[desc_content, ()])],
addnodes.index,
[desc, ([desc_signature, ([desc_name, "var2"],
[desc_annotation, ([desc_sig_punctuation, ':'],
desc_sig_space,
[pending_xref, "int"])])],
[desc_content, ()])]))
assert_node(doctree[1][0][1][2], pending_xref, reftarget='int', refspecific=True)
assert_node(doctree[3][0][1][2], pending_xref, reftarget='builtins.int', refspecific=False)
@pytest.mark.sphinx(freshenv=True)
def test_module_index(app):
text = (".. py:module:: docutils\n"

View File

@@ -34,7 +34,7 @@ def test_event_allowed_exceptions():
events = EventManager(object()) # pass an dummy object as an app
events.connect('builder-inited', raise_error, priority=500)
# all errors are conveted to ExtensionError
# all errors are converted to ExtensionError
with pytest.raises(ExtensionError):
events.emit('builder-inited')

View File

@@ -635,6 +635,8 @@ def test_namespace_package_file(tempdir):
assert content == ("testpkg namespace\n"
"=================\n"
"\n"
".. py:module:: testpkg\n"
"\n"
"Submodules\n"
"----------\n"
"\n"

View File

@@ -984,7 +984,7 @@ def test_autodoc_inner_class(app):
' .. py:attribute:: Outer.factory',
' :module: target',
'',
' alias of :class:`dict`'
' alias of :py:class:`dict`'
]
actual = do_autodoc(app, 'class', 'target.Outer.Inner', options)
@@ -1009,7 +1009,7 @@ def test_autodoc_inner_class(app):
'',
'.. py:class:: InnerChild()',
' :module: target', '',
' Bases: :class:`target.Outer.Inner`',
' Bases: :py:class:`target.Outer.Inner`',
'',
' InnerChild docstring',
'',
@@ -1084,6 +1084,7 @@ def test_autodoc_cached_property(app):
'',
' .. py:property:: Foo.prop',
' :module: target.cached_property',
' :type: int',
'',
]
@@ -1358,6 +1359,7 @@ def test_slots(app):
'',
' .. py:attribute:: Bar.attr1',
' :module: target.slots',
' :type: int',
'',
' docstring of attr1',
'',
@@ -1399,15 +1401,15 @@ def test_enum_class(app):
options = {"members": None}
actual = do_autodoc(app, 'class', 'target.enums.EnumCls', options)
if sys.version_info < (3, 10):
sig = '(value)'
if sys.version_info > (3, 11):
args = ('(value, names=None, *, module=None, qualname=None, '
'type=None, start=1, boundary=None)')
else:
sig = ('(value, names=None, *, module=None, qualname=None, type=None, start=1, '
'boundary=None)')
args = '(value)'
assert list(actual) == [
'',
'.. py:class:: EnumCls%s' % sig,
'.. py:class:: EnumCls' + args,
' :module: target.enums',
'',
' this is enum class',
@@ -1625,59 +1627,6 @@ def test_bound_method(app):
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_coroutine(app):
actual = do_autodoc(app, 'function', 'target.functions.coroutinefunc')
assert list(actual) == [
'',
'.. py:function:: coroutinefunc()',
' :module: target.functions',
' :async:',
'',
]
options = {"members": None}
actual = do_autodoc(app, 'class', 'target.coroutine.AsyncClass', options)
assert list(actual) == [
'',
'.. py:class:: AsyncClass()',
' :module: target.coroutine',
'',
'',
' .. py:method:: AsyncClass.do_coroutine()',
' :module: target.coroutine',
' :async:',
'',
' A documented coroutine function',
'',
'',
' .. py:method:: AsyncClass.do_coroutine2()',
' :module: target.coroutine',
' :async:',
' :classmethod:',
'',
' A documented coroutine classmethod',
'',
'',
' .. py:method:: AsyncClass.do_coroutine3()',
' :module: target.coroutine',
' :async:',
' :staticmethod:',
'',
' A documented coroutine staticmethod',
'',
]
# force-synchronized wrapper
actual = do_autodoc(app, 'function', 'target.coroutine.sync_func')
assert list(actual) == [
'',
'.. py:function:: sync_func()',
' :module: target.coroutine',
'',
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_partialmethod(app):
expected = [
@@ -1756,7 +1705,7 @@ def test_autodoc_typed_instance_variables(app):
'.. py:attribute:: Alias',
' :module: target.typed_vars',
'',
' alias of :class:`target.typed_vars.Derived`',
' alias of :py:class:`target.typed_vars.Derived`',
'',
'.. py:class:: Class()',
' :module: target.typed_vars',
@@ -1921,12 +1870,15 @@ def test_autodoc_GenericAlias(app):
' .. py:attribute:: Class.T',
' :module: target.genericalias',
'',
' alias of :class:`~typing.List`\\ [:class:`int`]',
' A list of int',
'',
' alias of :py:class:`~typing.List`\\ [:py:class:`int`]',
'',
'.. py:attribute:: T',
' :module: target.genericalias',
'',
' alias of :class:`~typing.List`\\ [:class:`int`]',
' A list of int',
'',
]
else:
assert list(actual) == [
@@ -1943,7 +1895,7 @@ def test_autodoc_GenericAlias(app):
'',
' A list of int',
'',
' alias of :class:`~typing.List`\\ [:class:`int`]',
' alias of :py:class:`~typing.List`\\ [:py:class:`int`]',
'',
'',
'.. py:data:: T',
@@ -1951,7 +1903,7 @@ def test_autodoc_GenericAlias(app):
'',
' A list of int',
'',
' alias of :class:`~typing.List`\\ [:class:`int`]',
' alias of :py:class:`~typing.List`\\ [:py:class:`int`]',
'',
]
@@ -1983,7 +1935,7 @@ def test_autodoc_TypeVar(app):
'',
' T6',
'',
' alias of :class:`int`',
' alias of :py:class:`int`',
'',
'',
'.. py:data:: T1',
@@ -2023,7 +1975,7 @@ def test_autodoc_TypeVar(app):
'',
' T6',
'',
' alias of :class:`int`',
' alias of :py:class:`int`',
'',
'',
'.. py:data:: T7',
@@ -2031,7 +1983,7 @@ def test_autodoc_TypeVar(app):
'',
' T7',
'',
" alias of TypeVar('T7', bound=\\ :class:`int`)",
" alias of TypeVar('T7', bound=\\ :py:class:`int`)",
'',
]
@@ -2165,6 +2117,9 @@ def test_singledispatchmethod_automethod(app):
]
@pytest.mark.skipif(sys.version_info > (3, 11),
reason=('cython does not support python-3.11 yet. '
'see https://github.com/cython/cython/issues/4365'))
@pytest.mark.skipif(pyximport is None, reason='cython is not installed')
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_cython(app):

View File

@@ -129,6 +129,7 @@ def test_autoattribute_slots_variable_dict(app):
'',
'.. py:attribute:: Bar.attr1',
' :module: target.slots',
' :type: int',
'',
' docstring of attr1',
'',
@@ -167,7 +168,7 @@ def test_autoattribute_GenericAlias(app):
'',
' A list of int',
'',
' alias of :class:`~typing.List`\\ [:class:`int`]',
' alias of :py:class:`~typing.List`\\ [:py:class:`int`]',
'',
]
@@ -182,7 +183,7 @@ def test_autoattribute_NewType(app):
'',
' T6',
'',
' alias of :class:`int`',
' alias of :py:class:`int`',
'',
]

View File

@@ -212,12 +212,20 @@ def test_properties(app):
' docstring',
'',
'',
' .. py:property:: Foo.prop',
' .. py:property:: Foo.prop1',
' :module: target.properties',
' :type: int',
'',
' docstring',
'',
'',
' .. py:property:: Foo.prop2',
' :module: target.properties',
' :classmethod:',
' :type: int',
'',
' docstring',
'',
]
@@ -235,6 +243,7 @@ def test_slots_attribute(app):
'',
' .. py:attribute:: Bar.attr1',
' :module: target.slots',
' :type: int',
'',
' docstring of attr1',
'',
@@ -257,14 +266,29 @@ def test_show_inheritance_for_subclass_of_generic_type(app):
'.. py:class:: Quux(iterable=(), /)',
' :module: target.classes',
'',
' Bases: :class:`~typing.List`\\ '
'[:obj:`~typing.Union`\\ [:class:`int`, :class:`float`]]',
' Bases: :py:class:`~typing.List`\\ '
'[:py:obj:`~typing.Union`\\ [:py:class:`int`, :py:class:`float`]]',
'',
' A subclass of List[Union[int, float]]',
'',
]
@pytest.mark.skipif(sys.version_info < (3, 7), reason='python 3.7+ is required.')
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_show_inheritance_for_decendants_of_generic_type(app):
options = {'show-inheritance': None}
actual = do_autodoc(app, 'class', 'target.classes.Corge', options)
assert list(actual) == [
'',
'.. py:class:: Corge(iterable=(), /)',
' :module: target.classes',
'',
' Bases: :py:class:`target.classes.Quux`',
'',
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_process_bases(app):
def autodoc_process_bases(app, name, obj, options, bases):
@@ -288,7 +312,7 @@ def test_autodoc_process_bases(app):
'.. py:class:: Quux(*args, **kwds)',
' :module: target.classes',
'',
' Bases: :class:`int`, :class:`str`',
' Bases: :py:class:`int`, :py:class:`str`',
'',
' A subclass of List[Union[int, float]]',
'',
@@ -299,7 +323,7 @@ def test_autodoc_process_bases(app):
'.. py:class:: Quux(iterable=(), /)',
' :module: target.classes',
'',
' Bases: :class:`int`, :class:`str`',
' Bases: :py:class:`int`, :py:class:`str`',
'',
' A subclass of List[Union[int, float]]',
'',
@@ -367,7 +391,7 @@ def test_class_alias(app):
'.. py:attribute:: Alias',
' :module: target.classes',
'',
' alias of :class:`target.classes.Foo`',
' alias of :py:class:`target.classes.Foo`',
]
@@ -381,3 +405,57 @@ def test_class_alias_having_doccomment(app):
' docstring',
'',
]
def test_class_alias_for_imported_object_having_doccomment(app):
actual = do_autodoc(app, 'class', 'target.classes.IntAlias')
assert list(actual) == [
'',
'.. py:attribute:: IntAlias',
' :module: target.classes',
'',
' docstring',
'',
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_coroutine(app):
options = {"members": None}
actual = do_autodoc(app, 'class', 'target.coroutine.AsyncClass', options)
assert list(actual) == [
'',
'.. py:class:: AsyncClass()',
' :module: target.coroutine',
'',
'',
' .. py:method:: AsyncClass.do_asyncgen()',
' :module: target.coroutine',
' :async:',
'',
' A documented async generator',
'',
'',
' .. py:method:: AsyncClass.do_coroutine()',
' :module: target.coroutine',
' :async:',
'',
' A documented coroutine function',
'',
'',
' .. py:method:: AsyncClass.do_coroutine2()',
' :module: target.coroutine',
' :async:',
' :classmethod:',
'',
' A documented coroutine classmethod',
'',
'',
' .. py:method:: AsyncClass.do_coroutine3()',
' :module: target.coroutine',
' :async:',
' :staticmethod:',
'',
' A documented coroutine staticmethod',
'',
]

View File

@@ -96,7 +96,7 @@ def test_autodata_GenericAlias(app):
'',
' A list of int',
'',
' alias of :class:`~typing.List`\\ [:class:`int`]',
' alias of :py:class:`~typing.List`\\ [:py:class:`int`]',
'',
]
@@ -111,7 +111,7 @@ def test_autodata_NewType(app):
'',
' T6',
'',
' alias of :class:`int`',
' alias of :py:class:`int`',
'',
]

View File

@@ -168,3 +168,38 @@ def test_wrapped_function_contextmanager(app):
" You'll feel better in this context!",
'',
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_coroutine(app):
actual = do_autodoc(app, 'function', 'target.functions.coroutinefunc')
assert list(actual) == [
'',
'.. py:function:: coroutinefunc()',
' :module: target.functions',
' :async:',
'',
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_synchronized_coroutine(app):
actual = do_autodoc(app, 'function', 'target.coroutine.sync_func')
assert list(actual) == [
'',
'.. py:function:: sync_func()',
' :module: target.coroutine',
'',
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_async_generator(app):
actual = do_autodoc(app, 'function', 'target.functions.asyncgenerator')
assert list(actual) == [
'',
'.. py:function:: asyncgenerator()',
' :module: target.functions',
' :async:',
'',
]

View File

@@ -9,6 +9,8 @@
:license: BSD, see LICENSE for details.
"""
import sys
import pytest
from .test_ext_autodoc import do_autodoc
@@ -16,13 +18,41 @@ from .test_ext_autodoc import do_autodoc
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_properties(app):
actual = do_autodoc(app, 'property', 'target.properties.Foo.prop')
actual = do_autodoc(app, 'property', 'target.properties.Foo.prop1')
assert list(actual) == [
'',
'.. py:property:: Foo.prop',
'.. py:property:: Foo.prop1',
' :module: target.properties',
' :type: int',
'',
' docstring',
'',
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_class_properties(app):
actual = do_autodoc(app, 'property', 'target.properties.Foo.prop2')
assert list(actual) == [
'',
'.. py:property:: Foo.prop2',
' :module: target.properties',
' :classmethod:',
' :type: int',
'',
' docstring',
'',
]
@pytest.mark.skipif(sys.version_info < (3, 8), reason='python 3.8+ is required.')
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_cached_properties(app):
actual = do_autodoc(app, 'property', 'target.cached_property.Foo.prop')
assert list(actual) == [
'',
'.. py:property:: Foo.prop',
' :module: target.cached_property',
' :type: int',
'',
]

View File

@@ -287,14 +287,34 @@ def test_autodoc_inherit_docstrings(app):
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_docstring_signature(app):
options = {"members": None}
options = {"members": None, "special-members": "__init__, __new__"}
actual = do_autodoc(app, 'class', 'target.DocstringSig', options)
assert list(actual) == [
'',
'.. py:class:: DocstringSig()',
# FIXME: Ideally this would instead be: `DocstringSig(d, e=1)` but
# currently `ClassDocumenter` does not apply the docstring signature
# logic when extracting a signature from a __new__ or __init__ method.
'.. py:class:: DocstringSig(*new_args, **new_kwargs)',
' :module: target',
'',
'',
' .. py:method:: DocstringSig.__init__(self, a, b=1) -> None',
' :module: target',
'',
' First line of docstring',
'',
' rest of docstring',
'',
'',
' .. py:method:: DocstringSig.__new__(cls, d, e=1) -> DocstringSig',
' :module: target',
' :staticmethod:',
'',
' First line of docstring',
'',
' rest of docstring',
'',
'',
' .. py:method:: DocstringSig.meth(FOO, BAR=1) -> BAZ',
' :module: target',
'',
@@ -331,10 +351,31 @@ def test_autodoc_docstring_signature(app):
actual = do_autodoc(app, 'class', 'target.DocstringSig', options)
assert list(actual) == [
'',
'.. py:class:: DocstringSig()',
'.. py:class:: DocstringSig(*new_args, **new_kwargs)',
' :module: target',
'',
'',
' .. py:method:: DocstringSig.__init__(*init_args, **init_kwargs)',
' :module: target',
'',
' __init__(self, a, b=1) -> None',
' First line of docstring',
'',
' rest of docstring',
'',
'',
'',
' .. py:method:: DocstringSig.__new__(cls, *new_args, **new_kwargs)',
' :module: target',
' :staticmethod:',
'',
' __new__(cls, d, e=1) -> DocstringSig',
' First line of docstring',
'',
' rest of docstring',
'',
'',
'',
' .. py:method:: DocstringSig.meth()',
' :module: target',
'',
@@ -495,7 +536,7 @@ def test_mocked_module_imports(app, warning):
sys.modules.pop('target', None) # unload target module to clear the module cache
# no autodoc_mock_imports
options = {"members": 'TestAutodoc,decoratedFunction,func'}
options = {"members": 'TestAutodoc,decoratedFunction,func,Alias'}
actual = do_autodoc(app, 'module', 'target.need_mocks', options)
assert list(actual) == []
assert "autodoc: failed to import module 'need_mocks'" in warning.getvalue()
@@ -516,12 +557,24 @@ def test_mocked_module_imports(app, warning):
'.. py:module:: target.need_mocks',
'',
'',
'.. py:data:: Alias',
' :module: target.need_mocks',
'',
' docstring',
'',
'',
'.. py:class:: TestAutodoc()',
' :module: target.need_mocks',
'',
' TestAutodoc docstring.',
'',
'',
' .. py:attribute:: TestAutodoc.Alias',
' :module: target.need_mocks',
'',
' docstring',
'',
'',
' .. py:method:: TestAutodoc.decoratedMethod()',
' :module: target.need_mocks',
'',
@@ -554,10 +607,26 @@ def test_autodoc_typehints_signature(app):
'.. py:module:: target.typehints',
'',
'',
'.. py:data:: CONST1',
' :module: target.typehints',
' :type: int',
'',
'',
'.. py:class:: Math(s: str, o: Optional[Any] = None)',
' :module: target.typehints',
'',
'',
' .. py:attribute:: Math.CONST1',
' :module: target.typehints',
' :type: int',
'',
'',
' .. py:attribute:: Math.CONST2',
' :module: target.typehints',
' :type: int',
' :value: 1',
'',
'',
' .. py:method:: Math.decr(a: int, b: int = 1) -> int',
' :module: target.typehints',
'',
@@ -574,6 +643,11 @@ def test_autodoc_typehints_signature(app):
' :module: target.typehints',
'',
'',
' .. py:property:: Math.prop',
' :module: target.typehints',
' :type: int',
'',
'',
'.. py:class:: NewAnnotation(i: int)',
' :module: target.typehints',
'',
@@ -620,10 +694,23 @@ def test_autodoc_typehints_none(app):
'.. py:module:: target.typehints',
'',
'',
'.. py:data:: CONST1',
' :module: target.typehints',
'',
'',
'.. py:class:: Math(s, o=None)',
' :module: target.typehints',
'',
'',
' .. py:attribute:: Math.CONST1',
' :module: target.typehints',
'',
'',
' .. py:attribute:: Math.CONST2',
' :module: target.typehints',
' :value: 1',
'',
'',
' .. py:method:: Math.decr(a, b=1)',
' :module: target.typehints',
'',
@@ -640,6 +727,10 @@ def test_autodoc_typehints_none(app):
' :module: target.typehints',
'',
'',
' .. py:property:: Math.prop',
' :module: target.typehints',
'',
'',
'.. py:class:: NewAnnotation(i)',
' :module: target.typehints',
'',
@@ -746,7 +837,7 @@ def test_autodoc_typehints_description(app):
' Tuple[int, int]\n'
in context)
# Overloads still get displyed in the signature
# Overloads still get displayed in the signature
assert ('target.overload.sum(x: int, y: int = 0) -> int\n'
'target.overload.sum(x: float, y: float = 0.0) -> float\n'
'target.overload.sum(x: str, y: str = None) -> str\n'
@@ -765,6 +856,10 @@ def test_autodoc_typehints_description_no_undoc(app):
(app.srcdir / 'index.rst').write_text(
'.. autofunction:: target.typehints.incr\n'
'\n'
'.. autofunction:: target.typehints.decr\n'
'\n'
' :returns: decremented number\n'
'\n'
'.. autofunction:: target.typehints.tuple_args\n'
'\n'
' :param x: arg\n'
@@ -773,6 +868,14 @@ def test_autodoc_typehints_description_no_undoc(app):
app.build()
context = (app.outdir / 'index.txt').read_text()
assert ('target.typehints.incr(a, b=1)\n'
'\n'
'target.typehints.decr(a, b=1)\n'
'\n'
' Returns:\n'
' decremented number\n'
'\n'
' Return type:\n'
' int\n'
'\n'
'target.typehints.tuple_args(x)\n'
'\n'
@@ -877,7 +980,7 @@ def test_autodoc_typehints_both(app):
' Tuple[int, int]\n'
in context)
# Overloads still get displyed in the signature
# Overloads still get displayed in the signature
assert ('target.overload.sum(x: int, y: int = 0) -> int\n'
'target.overload.sum(x: float, y: float = 0.0) -> float\n'
'target.overload.sum(x: str, y: str = None) -> str\n'
@@ -1039,6 +1142,99 @@ def test_autodoc_typehints_description_and_type_aliases(app):
' myint\n' == context)
@pytest.mark.sphinx('html', testroot='ext-autodoc',
confoverrides={'autodoc_unqualified_typehints': True})
def test_autodoc_unqualified_typehints(app):
if sys.version_info < (3, 7):
Any = 'Any'
else:
Any = '~typing.Any'
options = {"members": None,
"undoc-members": None}
actual = do_autodoc(app, 'module', 'target.typehints', options)
assert list(actual) == [
'',
'.. py:module:: target.typehints',
'',
'',
'.. py:data:: CONST1',
' :module: target.typehints',
' :type: int',
'',
'',
'.. py:class:: Math(s: str, o: ~typing.Optional[%s] = None)' % Any,
' :module: target.typehints',
'',
'',
' .. py:attribute:: Math.CONST1',
' :module: target.typehints',
' :type: int',
'',
'',
' .. py:attribute:: Math.CONST2',
' :module: target.typehints',
' :type: int',
' :value: 1',
'',
'',
' .. py:method:: Math.decr(a: int, b: int = 1) -> int',
' :module: target.typehints',
'',
'',
' .. py:method:: Math.horse(a: str, b: int) -> None',
' :module: target.typehints',
'',
'',
' .. py:method:: Math.incr(a: int, b: int = 1) -> int',
' :module: target.typehints',
'',
'',
' .. py:method:: Math.nothing() -> None',
' :module: target.typehints',
'',
'',
' .. py:property:: Math.prop',
' :module: target.typehints',
' :type: int',
'',
'',
'.. py:class:: NewAnnotation(i: int)',
' :module: target.typehints',
'',
'',
'.. py:class:: NewComment(i: int)',
' :module: target.typehints',
'',
'',
'.. py:class:: SignatureFromMetaclass(a: int)',
' :module: target.typehints',
'',
'',
'.. py:function:: complex_func(arg1: str, arg2: List[int], arg3: Tuple[int, '
'Union[str, Unknown]] = None, *args: str, **kwargs: str) -> None',
' :module: target.typehints',
'',
'',
'.. py:function:: decr(a: int, b: int = 1) -> int',
' :module: target.typehints',
'',
'',
'.. py:function:: incr(a: int, b: int = 1) -> int',
' :module: target.typehints',
'',
'',
'.. py:function:: missing_attr(c, a: str, b: Optional[str] = None) -> str',
' :module: target.typehints',
'',
'',
'.. py:function:: tuple_args(x: ~typing.Tuple[int, ~typing.Union[int, str]]) '
'-> ~typing.Tuple[int, int]',
' :module: target.typehints',
'',
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_default_options(app):
# no settings

View File

@@ -146,6 +146,7 @@ def test_ismock():
assert ismock(mod1) is True
assert ismock(mod1.Class) is True
assert ismock(mod1.submod.Class) is True
assert ismock(Inherited) is False
assert ismock(mod2) is False

View File

@@ -8,6 +8,8 @@
:license: BSD, see LICENSE for details.
"""
import sys
import pytest
from .test_ext_autodoc import do_autodoc
@@ -16,6 +18,11 @@ from .test_ext_autodoc import do_autodoc
@pytest.mark.sphinx('html', testroot='ext-autodoc',
confoverrides={'autodoc_preserve_defaults': True})
def test_preserve_defaults(app):
if sys.version_info < (3, 8):
color = "16777215"
else:
color = "0xFFFFFF"
options = {"members": None}
actual = do_autodoc(app, 'module', 'target.preserve_defaults', options)
assert list(actual) == [
@@ -29,15 +36,15 @@ def test_preserve_defaults(app):
' docstring',
'',
'',
' .. py:method:: Class.meth(name: str = CONSTANT, sentinal: Any = SENTINEL, '
'now: datetime.datetime = datetime.now()) -> None',
' .. py:method:: Class.meth(name: str = CONSTANT, sentinel: Any = SENTINEL, '
'now: datetime.datetime = datetime.now(), color: int = %s) -> None' % color,
' :module: target.preserve_defaults',
'',
' docstring',
'',
'',
'.. py:function:: foo(name: str = CONSTANT, sentinal: Any = SENTINEL, now: '
'datetime.datetime = datetime.now()) -> None',
'.. py:function:: foo(name: str = CONSTANT, sentinel: Any = SENTINEL, now: '
'datetime.datetime = datetime.now(), color: int = %s) -> None' % color,
' :module: target.preserve_defaults',
'',
' docstring',

View File

@@ -63,7 +63,7 @@ def test_mangle_signature():
(a=1, b=2, c=3) :: ([a, b, c])
(a=1, b=<SomeClass: a, b, c>, c=3) :: ([a, b, c])
(a=1, b=T(a=1, b=2), c=3) :: ([a, b, c])
(a: int, b: int) -> str :: (a, b)
(a: Tuple[int, str], b: int) -> str :: (a, b)
"""
TEST = [[y.strip() for y in x.split("::")] for x in TEST.split("\n")
@@ -109,6 +109,11 @@ def test_extract_summary(capsys):
'=========']
assert extract_summary(doc, document) == 'blah blah'
doc = ['=========',
'blah blah',
'=========']
assert extract_summary(doc, document) == 'blah blah'
# hyperlink target
doc = ['Do `this <https://www.sphinx-doc.org/>`_ and that. '
'blah blah blah.']
@@ -211,14 +216,42 @@ def test_autosummary_generate_content_for_module(app):
context = template.render.call_args[0][1]
assert context['members'] == ['CONSTANT1', 'CONSTANT2', 'Exc', 'Foo', '_Baz', '_Exc',
'__builtins__', '__cached__', '__doc__', '__file__',
'__name__', '__package__', '_quux', 'bar', 'qux']
'__all__', '__builtins__', '__cached__', '__doc__',
'__file__', '__name__', '__package__', '_quux', 'bar',
'quuz', 'qux']
assert context['functions'] == ['bar']
assert context['all_functions'] == ['_quux', 'bar']
assert context['classes'] == ['Foo']
assert context['all_classes'] == ['Foo', '_Baz']
assert context['exceptions'] == ['Exc']
assert context['all_exceptions'] == ['Exc', '_Exc']
assert context['attributes'] == ['CONSTANT1', 'qux', 'quuz']
assert context['all_attributes'] == ['CONSTANT1', 'qux', 'quuz']
assert context['fullname'] == 'autosummary_dummy_module'
assert context['module'] == 'autosummary_dummy_module'
assert context['objname'] == ''
assert context['name'] == ''
assert context['objtype'] == 'module'
@pytest.mark.sphinx(testroot='ext-autosummary')
def test_autosummary_generate_content_for_module___all__(app):
import autosummary_dummy_module
template = Mock()
app.config.autosummary_ignore_module_all = False
generate_autosummary_content('autosummary_dummy_module', autosummary_dummy_module, None,
template, None, False, app, False, {})
assert template.render.call_args[0][0] == 'module'
context = template.render.call_args[0][1]
assert context['members'] == ['CONSTANT1', 'Exc', 'Foo', '_Baz', 'bar', 'qux', 'path']
assert context['functions'] == ['bar']
assert context['all_functions'] == ['bar']
assert context['classes'] == ['Foo']
assert context['all_classes'] == ['Foo', '_Baz']
assert context['exceptions'] == ['Exc']
assert context['all_exceptions'] == ['Exc']
assert context['attributes'] == ['CONSTANT1', 'qux']
assert context['all_attributes'] == ['CONSTANT1', 'qux']
assert context['fullname'] == 'autosummary_dummy_module'
@@ -241,9 +274,9 @@ def test_autosummary_generate_content_for_module_skipped(app):
generate_autosummary_content('autosummary_dummy_module', autosummary_dummy_module, None,
template, None, False, app, False, {})
context = template.render.call_args[0][1]
assert context['members'] == ['CONSTANT1', 'CONSTANT2', '_Baz', '_Exc', '__builtins__',
'__cached__', '__doc__', '__file__', '__name__',
'__package__', '_quux', 'qux']
assert context['members'] == ['CONSTANT1', 'CONSTANT2', '_Baz', '_Exc', '__all__',
'__builtins__', '__cached__', '__doc__', '__file__',
'__name__', '__package__', '_quux', 'quuz', 'qux']
assert context['functions'] == []
assert context['classes'] == []
assert context['exceptions'] == []
@@ -260,17 +293,17 @@ def test_autosummary_generate_content_for_module_imported_members(app):
context = template.render.call_args[0][1]
assert context['members'] == ['CONSTANT1', 'CONSTANT2', 'Exc', 'Foo', 'Union', '_Baz',
'_Exc', '__builtins__', '__cached__', '__doc__',
'_Exc', '__all__', '__builtins__', '__cached__', '__doc__',
'__file__', '__loader__', '__name__', '__package__',
'__spec__', '_quux', 'bar', 'path', 'qux']
'__spec__', '_quux', 'bar', 'path', 'quuz', 'qux']
assert context['functions'] == ['bar']
assert context['all_functions'] == ['_quux', 'bar']
assert context['classes'] == ['Foo']
assert context['all_classes'] == ['Foo', '_Baz']
assert context['exceptions'] == ['Exc']
assert context['all_exceptions'] == ['Exc', '_Exc']
assert context['attributes'] == ['CONSTANT1', 'qux']
assert context['all_attributes'] == ['CONSTANT1', 'qux']
assert context['attributes'] == ['CONSTANT1', 'qux', 'quuz']
assert context['all_attributes'] == ['CONSTANT1', 'qux', 'quuz']
assert context['fullname'] == 'autosummary_dummy_module'
assert context['module'] == 'autosummary_dummy_module'
assert context['objname'] == ''
@@ -308,6 +341,7 @@ def test_autosummary_generate(app, status, warning):
assert doctree[3][0][0][2][5].astext() == 'autosummary_dummy_module.qux\n\na module-level attribute'
module = (app.srcdir / 'generated' / 'autosummary_dummy_module.rst').read_text()
assert (' .. autosummary::\n'
' \n'
' Foo\n'
@@ -316,6 +350,7 @@ def test_autosummary_generate(app, status, warning):
' \n'
' CONSTANT1\n'
' qux\n'
' quuz\n'
' \n' in module)
Foo = (app.srcdir / 'generated' / 'autosummary_dummy_module.Foo.rst').read_text()

View File

@@ -0,0 +1,36 @@
import pytest
@pytest.mark.sphinx('html', testroot='ext-extlinks-hardcoded-urls')
def test_replaceable_uris_emit_extlinks_warnings(app, warning):
app.build()
warning_output = warning.getvalue()
# there should be exactly three warnings for replaceable URLs
message = (
"WARNING: hardcoded link 'https://github.com/sphinx-doc/sphinx/issues/1' "
"could be replaced by an extlink (try using ':issue:`1`' instead)"
)
assert f"index.rst:11: {message}" in warning_output
assert f"index.rst:13: {message}" in warning_output
assert f"index.rst:15: {message}" in warning_output
@pytest.mark.sphinx('html', testroot='ext-extlinks-hardcoded-urls-multiple-replacements')
def test_all_replacements_suggested_if_multiple_replacements_possible(app, warning):
app.build()
warning_output = warning.getvalue()
# there should be six warnings for replaceable URLs, three pairs per link
message = (
"WARNING: hardcoded link 'https://github.com/octocat' "
"could be replaced by an extlink (try using ':user:`octocat`' instead)"
)
assert f"index.rst:14: {message}" in warning_output
assert f"index.rst:16: {message}" in warning_output
assert f"index.rst:18: {message}" in warning_output
message = (
"WARNING: hardcoded link 'https://github.com/octocat' "
"could be replaced by an extlink (try using ':repo:`octocat`' instead)"
)
assert f"index.rst:14: {message}" in warning_output
assert f"index.rst:16: {message}" in warning_output
assert f"index.rst:18: {message}" in warning_output

View File

@@ -42,6 +42,12 @@ def reference_check(app, *args, **kwds):
return missing_reference(app, app.env, node, contnode)
def set_config(app, mapping):
app.config.intersphinx_mapping = mapping
app.config.intersphinx_cache_limit = 0
app.config.intersphinx_disabled_reftypes = []
@mock.patch('sphinx.ext.intersphinx.InventoryFile')
@mock.patch('sphinx.ext.intersphinx._read_from_url')
def test_fetch_inventory_redirection(_read_from_url, InventoryFile, app, status, warning):
@@ -90,13 +96,12 @@ def test_fetch_inventory_redirection(_read_from_url, InventoryFile, app, status,
def test_missing_reference(tempdir, app, status, warning):
inv_file = tempdir / 'inventory'
inv_file.write_bytes(inventory_v2)
app.config.intersphinx_mapping = {
set_config(app, {
'https://docs.python.org/': inv_file,
'py3k': ('https://docs.python.org/py3k/', inv_file),
'py3krel': ('py3k', inv_file), # relative path
'py3krelparent': ('../../py3k', inv_file), # relative path, parent dir
}
app.config.intersphinx_cache_limit = 0
})
# load the inventory and check if it's done correctly
normalize_intersphinx_mapping(app, app.config)
@@ -133,12 +138,12 @@ def test_missing_reference(tempdir, app, status, warning):
refexplicit=True)
assert rn[0].astext() == 'py3k:module2'
# prefix given, target not found and nonexplicit title: prefix is stripped
# prefix given, target not found and nonexplicit title: prefix is not stripped
node, contnode = fake_node('py', 'mod', 'py3k:unknown', 'py3k:unknown',
refexplicit=False)
rn = missing_reference(app, app.env, node, contnode)
assert rn is None
assert contnode[0].astext() == 'unknown'
assert contnode[0].astext() == 'py3k:unknown'
# prefix given, target not found and explicit title: nothing is changed
node, contnode = fake_node('py', 'mod', 'py3k:unknown', 'py3k:unknown',
@@ -169,10 +174,9 @@ def test_missing_reference(tempdir, app, status, warning):
def test_missing_reference_pydomain(tempdir, app, status, warning):
inv_file = tempdir / 'inventory'
inv_file.write_bytes(inventory_v2)
app.config.intersphinx_mapping = {
set_config(app, {
'https://docs.python.org/': inv_file,
}
app.config.intersphinx_cache_limit = 0
})
# load the inventory and check if it's done correctly
normalize_intersphinx_mapping(app, app.config)
@@ -196,14 +200,23 @@ def test_missing_reference_pydomain(tempdir, app, status, warning):
rn = missing_reference(app, app.env, node, contnode)
assert rn.astext() == 'Foo.bar'
# term reference (normal)
node, contnode = fake_node('std', 'term', 'a term', 'a term')
rn = missing_reference(app, app.env, node, contnode)
assert rn.astext() == 'a term'
# term reference (case insensitive)
node, contnode = fake_node('std', 'term', 'A TERM', 'A TERM')
rn = missing_reference(app, app.env, node, contnode)
assert rn.astext() == 'A TERM'
def test_missing_reference_stddomain(tempdir, app, status, warning):
inv_file = tempdir / 'inventory'
inv_file.write_bytes(inventory_v2)
app.config.intersphinx_mapping = {
set_config(app, {
'cmd': ('https://docs.python.org/', inv_file),
}
app.config.intersphinx_cache_limit = 0
})
# load the inventory and check if it's done correctly
normalize_intersphinx_mapping(app, app.config)
@@ -232,10 +245,9 @@ def test_missing_reference_stddomain(tempdir, app, status, warning):
def test_missing_reference_cppdomain(tempdir, app, status, warning):
inv_file = tempdir / 'inventory'
inv_file.write_bytes(inventory_v2)
app.config.intersphinx_mapping = {
set_config(app, {
'https://docs.python.org/': inv_file,
}
app.config.intersphinx_cache_limit = 0
})
# load the inventory and check if it's done correctly
normalize_intersphinx_mapping(app, app.config)
@@ -259,10 +271,9 @@ def test_missing_reference_cppdomain(tempdir, app, status, warning):
def test_missing_reference_jsdomain(tempdir, app, status, warning):
inv_file = tempdir / 'inventory'
inv_file.write_bytes(inventory_v2)
app.config.intersphinx_mapping = {
set_config(app, {
'https://docs.python.org/': inv_file,
}
app.config.intersphinx_cache_limit = 0
})
# load the inventory and check if it's done correctly
normalize_intersphinx_mapping(app, app.config)
@@ -281,14 +292,75 @@ def test_missing_reference_jsdomain(tempdir, app, status, warning):
assert rn.astext() == 'baz()'
def test_missing_reference_disabled_domain(tempdir, app, status, warning):
inv_file = tempdir / 'inventory'
inv_file.write_bytes(inventory_v2)
set_config(app, {
'inv': ('https://docs.python.org/', inv_file),
})
# load the inventory and check if it's done correctly
normalize_intersphinx_mapping(app, app.config)
load_mappings(app)
def case(*, term, doc, py):
def assert_(rn, expected):
if expected is None:
assert rn is None
else:
assert rn.astext() == expected
kwargs = {}
node, contnode = fake_node('std', 'term', 'a term', 'a term', **kwargs)
rn = missing_reference(app, app.env, node, contnode)
assert_(rn, 'a term' if term else None)
node, contnode = fake_node('std', 'term', 'inv:a term', 'a term', **kwargs)
rn = missing_reference(app, app.env, node, contnode)
assert_(rn, 'a term')
node, contnode = fake_node('std', 'doc', 'docname', 'docname', **kwargs)
rn = missing_reference(app, app.env, node, contnode)
assert_(rn, 'docname' if doc else None)
node, contnode = fake_node('std', 'doc', 'inv:docname', 'docname', **kwargs)
rn = missing_reference(app, app.env, node, contnode)
assert_(rn, 'docname')
# an arbitrary ref in another domain
node, contnode = fake_node('py', 'func', 'module1.func', 'func()', **kwargs)
rn = missing_reference(app, app.env, node, contnode)
assert_(rn, 'func()' if py else None)
node, contnode = fake_node('py', 'func', 'inv:module1.func', 'func()', **kwargs)
rn = missing_reference(app, app.env, node, contnode)
assert_(rn, 'func()')
# the base case, everything should resolve
assert app.config.intersphinx_disabled_reftypes == []
case(term=True, doc=True, py=True)
# disabled a single ref type
app.config.intersphinx_disabled_reftypes = ['std:doc']
case(term=True, doc=False, py=True)
# disabled a whole domain
app.config.intersphinx_disabled_reftypes = ['std:*']
case(term=False, doc=False, py=True)
# disabled all domains
app.config.intersphinx_disabled_reftypes = ['*']
case(term=False, doc=False, py=False)
@pytest.mark.xfail(os.name != 'posix', reason="Path separator mismatch issue")
def test_inventory_not_having_version(tempdir, app, status, warning):
inv_file = tempdir / 'inventory'
inv_file.write_bytes(inventory_v2_not_having_version)
app.config.intersphinx_mapping = {
set_config(app, {
'https://docs.python.org/': inv_file,
}
app.config.intersphinx_cache_limit = 0
})
# load the inventory and check if it's done correctly
normalize_intersphinx_mapping(app, app.config)
@@ -308,16 +380,15 @@ def test_load_mappings_warnings(tempdir, app, status, warning):
"""
inv_file = tempdir / 'inventory'
inv_file.write_bytes(inventory_v2)
app.config.intersphinx_mapping = {
set_config(app, {
'https://docs.python.org/': inv_file,
'py3k': ('https://docs.python.org/py3k/', inv_file),
'repoze.workflow': ('http://docs.repoze.org/workflow/', inv_file),
'django-taggit': ('http://django-taggit.readthedocs.org/en/latest/',
inv_file),
12345: ('http://www.sphinx-doc.org/en/stable/', inv_file),
}
})
app.config.intersphinx_cache_limit = 0
# load the inventory and check if it's done correctly
normalize_intersphinx_mapping(app, app.config)
load_mappings(app)
@@ -327,7 +398,7 @@ def test_load_mappings_warnings(tempdir, app, status, warning):
def test_load_mappings_fallback(tempdir, app, status, warning):
inv_file = tempdir / 'inventory'
inv_file.write_bytes(inventory_v2)
app.config.intersphinx_cache_limit = 0
set_config(app, {})
# connect to invalid path
app.config.intersphinx_mapping = {

View File

@@ -221,6 +221,7 @@ def test_mathjax3_config(app, status, warning):
content = (app.outdir / 'index.html').read_text()
assert MATHJAX_URL in content
assert ('<script defer="defer" src="%s">' % MATHJAX_URL in content)
assert ('<script>window.MathJax = {"extensions": ["tex2jax.js"]}</script>' in content)
@@ -231,12 +232,35 @@ def test_mathjax2_config(app, status, warning):
app.builder.build_all()
content = (app.outdir / 'index.html').read_text()
assert MATHJAX_URL in content
assert ('<script async="async" src="%s">' % MATHJAX_URL in content)
assert ('<script type="text/x-mathjax-config">'
'MathJax.Hub.Config({"extensions": ["tex2jax.js"]})'
'</script>' in content)
@pytest.mark.sphinx('html', testroot='ext-math',
confoverrides={'extensions': ['sphinx.ext.mathjax'],
'mathjax_options': {'async': 'async'},
'mathjax3_config': {'extensions': ['tex2jax.js']}})
def test_mathjax_options_async_for_mathjax3(app, status, warning):
app.builder.build_all()
content = (app.outdir / 'index.html').read_text()
assert MATHJAX_URL in content
assert ('<script async="async" src="%s">' % MATHJAX_URL in content)
@pytest.mark.sphinx('html', testroot='ext-math',
confoverrides={'extensions': ['sphinx.ext.mathjax'],
'mathjax_options': {'defer': 'defer'},
'mathjax2_config': {'extensions': ['tex2jax.js']}})
def test_mathjax_options_defer_for_mathjax2(app, status, warning):
app.builder.build_all()
content = (app.outdir / 'index.html').read_text()
assert ('<script defer="defer" src="%s">' % MATHJAX_URL in content)
@pytest.mark.sphinx('html', testroot='ext-math',
confoverrides={'extensions': ['sphinx.ext.mathjax']})
def test_mathjax_is_installed_only_if_document_having_math(app, status, warning):
@@ -256,3 +280,16 @@ def test_mathjax_is_not_installed_if_no_equations(app, status, warning):
content = (app.outdir / 'index.html').read_text()
assert 'MathJax.js' not in content
@pytest.mark.sphinx('html', testroot='ext-math',
confoverrides={'extensions': ['sphinx.ext.mathjax']})
def test_mathjax_is_installed_if_no_equations_when_forced(app, status, warning):
app.set_html_assets_policy('always')
app.builder.build_all()
content = (app.outdir / 'index.html').read_text()
assert MATHJAX_URL in content
content = (app.outdir / 'nomath.html').read_text()
assert MATHJAX_URL in content

31
tests/test_extension.py Normal file
View File

@@ -0,0 +1,31 @@
"""
test_extension
~~~~~~~~~~~~~~
Test sphinx.extension module.
:copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
import pytest
from sphinx.errors import VersionRequirementError
from sphinx.extension import Extension, verify_needs_extensions
def test_needs_extensions(app):
# empty needs_extensions
assert app.config.needs_extensions == {}
verify_needs_extensions(app, app.config)
# needs_extensions fulfilled
app.config.needs_extensions = {'test.extension': '3.9'}
app.extensions['test.extension'] = Extension('test.extension', 'test.extension', version='3.10')
verify_needs_extensions(app, app.config)
# needs_extensions not fulfilled
app.config.needs_extensions = {'test.extension': '3.11'}
app.extensions['test.extension'] = Extension('test.extension', 'test.extension', version='3.10')
with pytest.raises(VersionRequirementError):
verify_needs_extensions(app, app.config)

View File

@@ -12,6 +12,7 @@
import os
import re
import pygments
import pytest
from babel.messages import mofile, pofile
from babel.messages.catalog import Catalog
@@ -30,6 +31,8 @@ sphinx_intl = pytest.mark.sphinx(
},
)
pygments_version = tuple(int(v) for v in pygments.__version__.split('.'))
def read_po(pathname):
with pathname.open() as f:
@@ -622,7 +625,7 @@ def test_html_meta(app):
assert expected_expr in result
expected_expr = '<meta content="I18N, SPHINX, MARKUP" name="keywords" />'
assert expected_expr in result
expected_expr = '<p class="caption"><span class="caption-text">HIDDEN TOC</span></p>'
expected_expr = '<p class="caption" role="heading"><span class="caption-text">HIDDEN TOC</span></p>'
assert expected_expr in result
@@ -1060,8 +1063,13 @@ def test_additional_targets_should_not_be_translated(app):
assert_count(expected_expr, result, 1)
# C code block with lang should not be translated but be *C* highlighted
expected_expr = ("""<span class="cp">#include</span> """
"""<span class="cpf">&lt;stdio.h&gt;</span>""")
if pygments_version < (2, 10, 0):
expected_expr = ("""<span class="cp">#include</span> """
"""<span class="cpf">&lt;stdio.h&gt;</span>""")
else:
expected_expr = ("""<span class="cp">#include</span>"""
"""<span class="w"> </span>"""
"""<span class="cpf">&lt;stdio.h&gt;</span>""")
assert_count(expected_expr, result, 1)
# literal block in list item should not be translated
@@ -1138,8 +1146,13 @@ def test_additional_targets_should_be_translated(app):
assert_count(expected_expr, result, 1)
# C code block with lang should be translated and be *C* highlighted
expected_expr = ("""<span class="cp">#include</span> """
"""<span class="cpf">&lt;STDIO.H&gt;</span>""")
if pygments_version < (2, 10, 0):
expected_expr = ("""<span class="cp">#include</span> """
"""<span class="cpf">&lt;STDIO.H&gt;</span>""")
else:
expected_expr = ("""<span class="cp">#include</span>"""
"""<span class="w"> </span>"""
"""<span class="cpf">&lt;STDIO.H&gt;</span>""")
assert_count(expected_expr, result, 1)
# literal block in list item should be translated
@@ -1288,6 +1301,44 @@ def getwarning(warnings):
return strip_escseq(warnings.getvalue().replace(os.sep, '/'))
@pytest.mark.sphinx('html', testroot='basic',
srcdir='gettext_allow_fuzzy_translations',
confoverrides={
'language': 'de',
'gettext_allow_fuzzy_translations': True
})
def test_gettext_allow_fuzzy_translations(app):
locale_dir = app.srcdir / 'locales' / 'de' / 'LC_MESSAGES'
locale_dir.makedirs()
with (locale_dir / 'index.po').open('wb') as f:
catalog = Catalog()
catalog.add('features', 'FEATURES', flags=('fuzzy',))
pofile.write_po(f, catalog)
app.build()
content = (app.outdir / 'index.html').read_text()
assert 'FEATURES' in content
@pytest.mark.sphinx('html', testroot='basic',
srcdir='gettext_disallow_fuzzy_translations',
confoverrides={
'language': 'de',
'gettext_allow_fuzzy_translations': False
})
def test_gettext_disallow_fuzzy_translations(app):
locale_dir = app.srcdir / 'locales' / 'de' / 'LC_MESSAGES'
locale_dir.makedirs()
with (locale_dir / 'index.po').open('wb') as f:
catalog = Catalog()
catalog.add('features', 'FEATURES', flags=('fuzzy',))
pofile.write_po(f, catalog)
app.build()
content = (app.outdir / 'index.html').read_text()
assert 'FEATURES' not in content
@pytest.mark.sphinx('html', testroot='basic', confoverrides={'language': 'de'})
def test_customize_system_message(make_app, app_params, sphinx_test_tempdir):
try:

View File

@@ -36,7 +36,7 @@ def settings(app):
settings.env = app.builder.env
settings.env.temp_data['docname'] = 'dummy'
settings.contentsname = 'dummy'
settings.rfc_base_url = 'http://tools.ietf.org/html/'
settings.rfc_base_url = 'http://datatracker.ietf.org/doc/html/'
domain_context = sphinx_domains(settings.env)
domain_context.enable()
yield settings
@@ -181,10 +181,10 @@ def get_verifier(verify, verify_re):
'verify',
':rfc:`2324`',
('<p><span class="target" id="index-0"></span><a class="rfc reference external" '
'href="http://tools.ietf.org/html/rfc2324.html"><strong>RFC 2324</strong></a></p>'),
'href="http://datatracker.ietf.org/doc/html/rfc2324.html"><strong>RFC 2324</strong></a></p>'),
('\\sphinxAtStartPar\n'
'\\index{RFC@\\spxentry{RFC}!RFC 2324@\\spxentry{RFC 2324}}'
'\\sphinxhref{http://tools.ietf.org/html/rfc2324.html}'
'\\sphinxhref{http://datatracker.ietf.org/doc/html/rfc2324.html}'
'{\\sphinxstylestrong{RFC 2324}}')
),
(
@@ -192,11 +192,11 @@ def get_verifier(verify, verify_re):
'verify',
':rfc:`2324#id1`',
('<p><span class="target" id="index-0"></span><a class="rfc reference external" '
'href="http://tools.ietf.org/html/rfc2324.html#id1">'
'href="http://datatracker.ietf.org/doc/html/rfc2324.html#id1">'
'<strong>RFC 2324#id1</strong></a></p>'),
('\\sphinxAtStartPar\n'
'\\index{RFC@\\spxentry{RFC}!RFC 2324\\#id1@\\spxentry{RFC 2324\\#id1}}'
'\\sphinxhref{http://tools.ietf.org/html/rfc2324.html\\#id1}'
'\\sphinxhref{http://datatracker.ietf.org/doc/html/rfc2324.html\\#id1}'
'{\\sphinxstylestrong{RFC 2324\\#id1}}')
),
(

View File

@@ -53,8 +53,9 @@ from sphinx.pycode import ast
("+ a", "+ a"), # UAdd
("- 1", "- 1"), # UnaryOp
("- a", "- a"), # USub
("(1, 2, 3)", "(1, 2, 3)"), # Tuple
("(1, 2, 3)", "(1, 2, 3)"), # Tuple
("()", "()"), # Tuple (empty)
("(1,)", "(1,)"), # Tuple (single item)
])
def test_unparse(source, expected):
module = ast.parse(source)

View File

@@ -224,7 +224,7 @@ def test_class():
'\n'
' def __init__(self):\n'
' self.a = 1 + 1 #: comment3\n'
' self.attr2 = 1 + 1 #: overrided\n'
' self.attr2 = 1 + 1 #: overridden\n'
' b = 1 + 1 #: comment5\n'
'\n'
' def some_method(self):\n'
@@ -233,7 +233,7 @@ def test_class():
parser.parse()
assert parser.comments == {('Foo', 'attr1'): 'comment1',
('Foo', 'a'): 'comment3',
('Foo', 'attr2'): 'overrided'}
('Foo', 'attr2'): 'overridden'}
assert parser.definitions == {'Foo': ('class', 1, 11),
'Foo.__init__': ('def', 5, 8),
'Foo.some_method': ('def', 10, 11)}

View File

@@ -10,6 +10,7 @@
import time
from io import StringIO
from os import path
import pytest
@@ -250,3 +251,18 @@ def test_extensions(tempdir):
ns = {}
exec(conffile.read_text(), ns)
assert ns['extensions'] == ['foo', 'bar', 'baz']
def test_exits_when_existing_confpy(monkeypatch):
# The code detects existing conf.py with path.isfile()
# so we mock it as True with pytest's monkeypatch
def mock_isfile(path):
return True
monkeypatch.setattr(path, 'isfile', mock_isfile)
qs.term_input = mock_input({
'Please enter a new root path (or just Enter to exit)': ''
})
d = {}
with pytest.raises(SystemExit):
qs.ask_user(d)

View File

@@ -66,7 +66,11 @@ test that non-comments are indexed: fermion
def test_objects_are_escaped(app, status, warning):
app.builder.build_all()
index = jsload(app.outdir / 'searchindex.js')
assert 'n::Array&lt;T, d&gt;' in index.get('objects').get('') # n::Array<T,d> is escaped
for item in index.get('objects').get(''):
if item[-1] == 'n::Array&lt;T, d&gt;': # n::Array<T,d> is escaped
break
else:
assert False, index.get('objects').get('')
@pytest.mark.sphinx(testroot='search')
@@ -129,50 +133,58 @@ def test_term_in_raw_directive(app, status, warning):
def test_IndexBuilder():
domain = DummyDomain([('objname', 'objdispname', 'objtype', 'docname', '#anchor', 1),
('objname2', 'objdispname2', 'objtype2', 'docname2', '', -1)])
env = DummyEnvironment('1.0', {'dummy': domain})
domain1 = DummyDomain([('objname1', 'objdispname1', 'objtype1', 'docname1_1', '#anchor', 1),
('objname2', 'objdispname2', 'objtype2', 'docname1_2', '', -1)])
domain2 = DummyDomain([('objname1', 'objdispname1', 'objtype1', 'docname2_1', '#anchor', 1),
('objname2', 'objdispname2', 'objtype2', 'docname2_2', '', -1)])
env = DummyEnvironment('1.0', {'dummy1': domain1, 'dummy2': domain2})
doc = utils.new_document(b'test data', settings)
doc['file'] = 'dummy'
parser.parse(FILE_CONTENTS, doc)
# feed
index = IndexBuilder(env, 'en', {}, None)
index.feed('docname', 'filename', 'title', doc)
index.feed('docname2', 'filename2', 'title2', doc)
assert index._titles == {'docname': 'title', 'docname2': 'title2'}
assert index._filenames == {'docname': 'filename', 'docname2': 'filename2'}
index.feed('docname1_1', 'filename1_1', 'title1_1', doc)
index.feed('docname1_2', 'filename1_2', 'title1_2', doc)
index.feed('docname2_1', 'filename2_1', 'title2_1', doc)
index.feed('docname2_2', 'filename2_2', 'title2_2', doc)
assert index._titles == {'docname1_1': 'title1_1', 'docname1_2': 'title1_2',
'docname2_1': 'title2_1', 'docname2_2': 'title2_2'}
assert index._filenames == {'docname1_1': 'filename1_1', 'docname1_2': 'filename1_2',
'docname2_1': 'filename2_1', 'docname2_2': 'filename2_2'}
assert index._mapping == {
'ar': {'docname', 'docname2'},
'fermion': {'docname', 'docname2'},
'comment': {'docname', 'docname2'},
'non': {'docname', 'docname2'},
'index': {'docname', 'docname2'},
'test': {'docname', 'docname2'}
'ar': {'docname1_1', 'docname1_2', 'docname2_1', 'docname2_2'},
'fermion': {'docname1_1', 'docname1_2', 'docname2_1', 'docname2_2'},
'comment': {'docname1_1', 'docname1_2', 'docname2_1', 'docname2_2'},
'non': {'docname1_1', 'docname1_2', 'docname2_1', 'docname2_2'},
'index': {'docname1_1', 'docname1_2', 'docname2_1', 'docname2_2'},
'test': {'docname1_1', 'docname1_2', 'docname2_1', 'docname2_2'}
}
assert index._title_mapping == {'section_titl': {'docname', 'docname2'}}
assert index._title_mapping == {'section_titl': {'docname1_1', 'docname1_2', 'docname2_1', 'docname2_2'}}
assert index._objtypes == {}
assert index._objnames == {}
# freeze
assert index.freeze() == {
'docnames': ('docname', 'docname2'),
'docnames': ('docname1_1', 'docname1_2', 'docname2_1', 'docname2_2'),
'envversion': '1.0',
'filenames': ['filename', 'filename2'],
'objects': {'': {'objdispname': (0, 0, 1, '#anchor')}},
'objnames': {0: ('dummy', 'objtype', 'objtype')},
'objtypes': {0: 'dummy:objtype'},
'terms': {'ar': [0, 1],
'comment': [0, 1],
'fermion': [0, 1],
'index': [0, 1],
'non': [0, 1],
'test': [0, 1]},
'titles': ('title', 'title2'),
'titleterms': {'section_titl': [0, 1]}
'filenames': ['filename1_1', 'filename1_2', 'filename2_1', 'filename2_2'],
'objects': {'': [(0, 0, 1, '#anchor', 'objdispname1'),
(2, 1, 1, '#anchor', 'objdispname1')]},
'objnames': {0: ('dummy1', 'objtype1', 'objtype1'), 1: ('dummy2', 'objtype1', 'objtype1')},
'objtypes': {0: 'dummy1:objtype1', 1: 'dummy2:objtype1'},
'terms': {'ar': [0, 1, 2, 3],
'comment': [0, 1, 2, 3],
'fermion': [0, 1, 2, 3],
'index': [0, 1, 2, 3],
'non': [0, 1, 2, 3],
'test': [0, 1, 2, 3]},
'titles': ('title1_1', 'title1_2', 'title2_1', 'title2_2'),
'titleterms': {'section_titl': [0, 1, 2, 3]}
}
assert index._objtypes == {('dummy', 'objtype'): 0}
assert index._objnames == {0: ('dummy', 'objtype', 'objtype')}
assert index._objtypes == {('dummy1', 'objtype1'): 0, ('dummy2', 'objtype1'): 1}
assert index._objnames == {0: ('dummy1', 'objtype1', 'objtype1'),
1: ('dummy2', 'objtype1', 'objtype1')}
# dump / load
stream = BytesIO()
@@ -195,40 +207,41 @@ def test_IndexBuilder():
assert index2._objnames == index._objnames
# prune
index.prune(['docname2'])
assert index._titles == {'docname2': 'title2'}
assert index._filenames == {'docname2': 'filename2'}
index.prune(['docname1_2', 'docname2_2'])
assert index._titles == {'docname1_2': 'title1_2', 'docname2_2': 'title2_2'}
assert index._filenames == {'docname1_2': 'filename1_2', 'docname2_2': 'filename2_2'}
assert index._mapping == {
'ar': {'docname2'},
'fermion': {'docname2'},
'comment': {'docname2'},
'non': {'docname2'},
'index': {'docname2'},
'test': {'docname2'}
'ar': {'docname1_2', 'docname2_2'},
'fermion': {'docname1_2', 'docname2_2'},
'comment': {'docname1_2', 'docname2_2'},
'non': {'docname1_2', 'docname2_2'},
'index': {'docname1_2', 'docname2_2'},
'test': {'docname1_2', 'docname2_2'}
}
assert index._title_mapping == {'section_titl': {'docname2'}}
assert index._objtypes == {('dummy', 'objtype'): 0}
assert index._objnames == {0: ('dummy', 'objtype', 'objtype')}
assert index._title_mapping == {'section_titl': {'docname1_2', 'docname2_2'}}
assert index._objtypes == {('dummy1', 'objtype1'): 0, ('dummy2', 'objtype1'): 1}
assert index._objnames == {0: ('dummy1', 'objtype1', 'objtype1'), 1: ('dummy2', 'objtype1', 'objtype1')}
# freeze after prune
assert index.freeze() == {
'docnames': ('docname2',),
'docnames': ('docname1_2', 'docname2_2'),
'envversion': '1.0',
'filenames': ['filename2'],
'filenames': ['filename1_2', 'filename2_2'],
'objects': {},
'objnames': {0: ('dummy', 'objtype', 'objtype')},
'objtypes': {0: 'dummy:objtype'},
'terms': {'ar': 0,
'comment': 0,
'fermion': 0,
'index': 0,
'non': 0,
'test': 0},
'titles': ('title2',),
'titleterms': {'section_titl': 0}
'objnames': {0: ('dummy1', 'objtype1', 'objtype1'), 1: ('dummy2', 'objtype1', 'objtype1')},
'objtypes': {0: 'dummy1:objtype1', 1: 'dummy2:objtype1'},
'terms': {'ar': [0, 1],
'comment': [0, 1],
'fermion': [0, 1],
'index': [0, 1],
'non': [0, 1],
'test': [0, 1]},
'titles': ('title1_2', 'title2_2'),
'titleterms': {'section_titl': [0, 1]}
}
assert index._objtypes == {('dummy', 'objtype'): 0}
assert index._objnames == {0: ('dummy', 'objtype', 'objtype')}
assert index._objtypes == {('dummy1', 'objtype1'): 0, ('dummy2', 'objtype1'): 1}
assert index._objnames == {0: ('dummy1', 'objtype1', 'objtype1'),
1: ('dummy2', 'objtype1', 'objtype1')}
def test_IndexBuilder_lookup():

View File

@@ -9,6 +9,7 @@
"""
import pytest
from html5lib import HTMLParser
@pytest.mark.sphinx(buildername='html', testroot='smartquotes', freshenv=True)
@@ -19,6 +20,24 @@ def test_basic(app, status, warning):
assert '<p> “Sphinx” is a tool that makes it easy …</p>' in content
@pytest.mark.sphinx(buildername='html', testroot='smartquotes', freshenv=True)
def test_literals(app, status, warning):
app.build()
with (app.outdir / 'literals.html').open() as html_file:
etree = HTMLParser(namespaceHTMLElements=False).parse(html_file)
for code_element in etree.iter('code'):
code_text = ''.join(code_element.itertext())
if code_text.startswith('code role'):
assert "'quotes'" in code_text
elif code_text.startswith('{'):
assert code_text == "{'code': 'role', 'with': 'quotes'}"
elif code_text.startswith('literal'):
assert code_text == "literal with 'quotes'"
@pytest.mark.sphinx(buildername='text', testroot='smartquotes', freshenv=True)
def test_text_builder(app, status, warning):
app.build()

View File

@@ -26,7 +26,7 @@ def test_separate_metadata():
assert docstring == ':param baz:\n'
assert metadata == {'foo': 'bar'}
# field_list like text following just after paragaph is not a field_list
# field_list like text following just after paragraph is not a field_list
text = ("blah blah blah\n"
":meta foo: bar\n"
":meta baz:\n")

View File

@@ -45,7 +45,7 @@ def test_SphinxFileOutput(tmpdir):
output.write(content)
os.utime(filename, (0, 0))
# overrite it again
# overwrite it again
output.write(content)
assert os.stat(filename).st_mtime != 0 # updated
@@ -55,11 +55,11 @@ def test_SphinxFileOutput(tmpdir):
output.write(content)
os.utime(filename, (0, 0))
# overrite it again
# overwrite it again
output.write(content)
assert os.stat(filename).st_mtime == 0 # not updated
# overrite it again (content changed)
# overwrite it again (content changed)
output.write(content + "; content change")
assert os.stat(filename).st_mtime != 0 # updated

View File

@@ -19,7 +19,7 @@ class DummyTemplateLoader(BuiltinTemplateLoader):
super().__init__()
builder = mock.Mock()
builder.config.templates_path = []
builder.app.translater = None
builder.app.translator = None
self.init(builder)

View File

@@ -16,7 +16,6 @@ import sys
import types
from inspect import Parameter
import _testcapi
import pytest
from sphinx.util import inspect
@@ -215,11 +214,8 @@ def test_signature_annotations():
# optional union
sig = inspect.signature(f20)
if sys.version_info < (3, 7):
assert stringify_signature(sig) in ('() -> Optional[Union[int, str]]',
'() -> Optional[Union[str, int]]')
else:
assert stringify_signature(sig) == '() -> Optional[Union[int, str]]'
assert stringify_signature(sig) in ('() -> Optional[Union[int, str]]',
'() -> Optional[Union[str, int]]')
# Any
sig = inspect.signature(f14)
@@ -263,6 +259,10 @@ def test_signature_annotations():
sig = inspect.signature(f7)
assert stringify_signature(sig, show_return_annotation=False) == '(x: Optional[int] = None, y: dict = {})'
# unqualified_typehints is True
sig = inspect.signature(f7)
assert stringify_signature(sig, unqualified_typehints=True) == '(x: ~typing.Optional[int] = None, y: dict = {}) -> None'
@pytest.mark.skipif(sys.version_info < (3, 8), reason='python 3.8+ is required.')
@pytest.mark.sphinx(testroot='ext-autodoc')
@@ -630,8 +630,6 @@ def test_isattributedescriptor(app):
def __get__(self, obj, typ=None):
pass
testinstancemethod = _testcapi.instancemethod(str.__repr__)
assert inspect.isattributedescriptor(Base.prop) is True # property
assert inspect.isattributedescriptor(Base.meth) is False # method
assert inspect.isattributedescriptor(Base.staticmeth) is False # staticmethod
@@ -642,7 +640,16 @@ def test_isattributedescriptor(app):
assert inspect.isattributedescriptor(dict.__dict__['fromkeys']) is False # ClassMethodDescriptorType # NOQA
assert inspect.isattributedescriptor(types.FrameType.f_locals) is True # GetSetDescriptorType # NOQA
assert inspect.isattributedescriptor(datetime.timedelta.days) is True # MemberDescriptorType # NOQA
assert inspect.isattributedescriptor(testinstancemethod) is False # instancemethod (C-API) # NOQA
try:
# _testcapi module cannot be importable in some distro
# refs: https://github.com/sphinx-doc/sphinx/issues/9868
import _testcapi
testinstancemethod = _testcapi.instancemethod(str.__repr__)
assert inspect.isattributedescriptor(testinstancemethod) is False # instancemethod (C-API) # NOQA
except ImportError:
pass
def test_isproperty(app):
@@ -680,6 +687,25 @@ def test_unpartial():
assert inspect.unpartial(func3) is func1
def test_getdoc_inherited_classmethod():
class Foo:
@classmethod
def meth(self):
"""
docstring
indented text
"""
class Bar(Foo):
@classmethod
def meth(self):
# inherited classmethod
pass
assert inspect.getdoc(Bar.meth, getattr, False, Bar, "meth") is None
assert inspect.getdoc(Bar.meth, getattr, True, Bar, "meth") == Foo.meth.__doc__
def test_getdoc_inherited_decorated_method():
class Foo:
def meth(self):

View File

@@ -10,8 +10,6 @@
import codecs
import os
import platform
import sys
import pytest
from docutils import nodes
@@ -131,6 +129,7 @@ def test_is_suppressed_warning():
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", "syntax", suppress_warnings) is False
assert is_suppressed_warning("rest", "duplicated_labels", suppress_warnings) is True
@@ -143,33 +142,39 @@ def test_suppress_warnings(app, status, warning):
app.config.suppress_warnings = []
warning.truncate(0)
logger.warning('message0', type='test')
logger.warning('message1', type='test', subtype='logging')
logger.warning('message2', type='test', subtype='crash')
logger.warning('message3', type='actual', subtype='logging')
assert 'message0' in warning.getvalue()
assert 'message1' in warning.getvalue()
assert 'message2' in warning.getvalue()
assert 'message3' in warning.getvalue()
assert app._warncount == 3
assert app._warncount == 4
app.config.suppress_warnings = ['test']
warning.truncate(0)
logger.warning('message0', type='test')
logger.warning('message1', type='test', subtype='logging')
logger.warning('message2', type='test', subtype='crash')
logger.warning('message3', type='actual', subtype='logging')
assert 'message0' not in warning.getvalue()
assert 'message1' not in warning.getvalue()
assert 'message2' not in warning.getvalue()
assert 'message3' in warning.getvalue()
assert app._warncount == 4
assert app._warncount == 5
app.config.suppress_warnings = ['test.logging']
warning.truncate(0)
logger.warning('message0', type='test')
logger.warning('message1', type='test', subtype='logging')
logger.warning('message2', type='test', subtype='crash')
logger.warning('message3', type='actual', subtype='logging')
assert 'message0' in warning.getvalue()
assert 'message1' not in warning.getvalue()
assert 'message2' in warning.getvalue()
assert 'message3' in warning.getvalue()
assert app._warncount == 6
assert app._warncount == 8
def test_warningiserror(app, status, warning):
@@ -272,7 +277,7 @@ def test_pending_warnings(app, status, warning):
logger.warning('message1')
with logging.pending_warnings():
# not logged yet (bufferred) in here
# not logged yet (buffered) in here
logger.warning('message2')
logger.warning('message3')
assert 'WARNING: message1' in warning.getvalue()
@@ -311,8 +316,6 @@ def test_colored_logs(app, status, warning):
@pytest.mark.xfail(os.name != 'posix', reason="Not working on windows")
@pytest.mark.xfail(platform.system() == 'Darwin' and sys.version_info > (3, 8),
reason="Not working on macOS and py38")
def test_logging_in_ParallelTasks(app, status, warning):
logging.setup(app, status, warning)
logger = logging.getLogger(__name__)

View File

@@ -60,31 +60,31 @@ def test_NodeMatcher():
# search by node class
matcher = NodeMatcher(nodes.paragraph)
assert len(doctree.traverse(matcher)) == 3
assert len(list(doctree.traverse(matcher))) == 3
# search by multiple node classes
matcher = NodeMatcher(nodes.paragraph, nodes.literal_block)
assert len(doctree.traverse(matcher)) == 4
assert len(list(doctree.traverse(matcher))) == 4
# search by node attribute
matcher = NodeMatcher(block=1)
assert len(doctree.traverse(matcher)) == 1
assert len(list(doctree.traverse(matcher))) == 1
# search by node attribute (Any)
matcher = NodeMatcher(block=Any)
assert len(doctree.traverse(matcher)) == 3
assert len(list(doctree.traverse(matcher))) == 3
# search by both class and attribute
matcher = NodeMatcher(nodes.paragraph, block=Any)
assert len(doctree.traverse(matcher)) == 2
assert len(list(doctree.traverse(matcher))) == 2
# mismatched
matcher = NodeMatcher(nodes.title)
assert len(doctree.traverse(matcher)) == 0
assert len(list(doctree.traverse(matcher))) == 0
# search with Any does not match to Text node
matcher = NodeMatcher(blah=Any)
assert len(doctree.traverse(matcher)) == 0
assert len(list(doctree.traverse(matcher))) == 0
@pytest.mark.parametrize(

View File

@@ -17,6 +17,7 @@ from typing import (Any, Callable, Dict, Generator, List, NewType, Optional, Tup
import pytest
from sphinx.ext.autodoc import mock
from sphinx.util.typing import restify, stringify
@@ -41,65 +42,70 @@ class BrokenType:
def test_restify():
assert restify(int) == ":class:`int`"
assert restify(str) == ":class:`str`"
assert restify(None) == ":obj:`None`"
assert restify(Integral) == ":class:`numbers.Integral`"
assert restify(Struct) == ":class:`struct.Struct`"
assert restify(TracebackType) == ":class:`types.TracebackType`"
assert restify(Any) == ":obj:`~typing.Any`"
assert restify(int) == ":py:class:`int`"
assert restify(str) == ":py:class:`str`"
assert restify(None) == ":py:obj:`None`"
assert restify(Integral) == ":py:class:`numbers.Integral`"
assert restify(Struct) == ":py:class:`struct.Struct`"
assert restify(TracebackType) == ":py:class:`types.TracebackType`"
assert restify(Any) == ":py:obj:`~typing.Any`"
assert restify('str') == "str"
def test_restify_type_hints_containers():
assert restify(List) == ":class:`~typing.List`"
assert restify(Dict) == ":class:`~typing.Dict`"
assert restify(List[int]) == ":class:`~typing.List`\\ [:class:`int`]"
assert restify(List[str]) == ":class:`~typing.List`\\ [:class:`str`]"
assert restify(Dict[str, float]) == (":class:`~typing.Dict`\\ "
"[:class:`str`, :class:`float`]")
assert restify(Tuple[str, str, str]) == (":class:`~typing.Tuple`\\ "
"[:class:`str`, :class:`str`, :class:`str`]")
assert restify(Tuple[str, ...]) == ":class:`~typing.Tuple`\\ [:class:`str`, ...]"
assert restify(List[Dict[str, Tuple]]) == (":class:`~typing.List`\\ "
"[:class:`~typing.Dict`\\ "
"[:class:`str`, :class:`~typing.Tuple`]]")
assert restify(MyList[Tuple[int, int]]) == (":class:`tests.test_util_typing.MyList`\\ "
"[:class:`~typing.Tuple`\\ "
"[:class:`int`, :class:`int`]]")
assert restify(Generator[None, None, None]) == (":class:`~typing.Generator`\\ "
"[:obj:`None`, :obj:`None`, :obj:`None`]")
assert restify(List) == ":py:class:`~typing.List`"
assert restify(Dict) == ":py:class:`~typing.Dict`"
assert restify(List[int]) == ":py:class:`~typing.List`\\ [:py:class:`int`]"
assert restify(List[str]) == ":py:class:`~typing.List`\\ [:py:class:`str`]"
assert restify(Dict[str, float]) == (":py:class:`~typing.Dict`\\ "
"[:py:class:`str`, :py:class:`float`]")
assert restify(Tuple[str, str, str]) == (":py:class:`~typing.Tuple`\\ "
"[:py:class:`str`, :py:class:`str`, "
":py:class:`str`]")
assert restify(Tuple[str, ...]) == ":py:class:`~typing.Tuple`\\ [:py:class:`str`, ...]"
assert restify(Tuple[()]) == ":py:class:`~typing.Tuple`\\ [()]"
assert restify(List[Dict[str, Tuple]]) == (":py:class:`~typing.List`\\ "
"[:py:class:`~typing.Dict`\\ "
"[:py:class:`str`, :py:class:`~typing.Tuple`]]")
assert restify(MyList[Tuple[int, int]]) == (":py:class:`tests.test_util_typing.MyList`\\ "
"[:py:class:`~typing.Tuple`\\ "
"[:py:class:`int`, :py:class:`int`]]")
assert restify(Generator[None, None, None]) == (":py:class:`~typing.Generator`\\ "
"[:py:obj:`None`, :py:obj:`None`, "
":py:obj:`None`]")
def test_restify_type_hints_Callable():
assert restify(Callable) == ":class:`~typing.Callable`"
assert restify(Callable) == ":py:class:`~typing.Callable`"
if sys.version_info >= (3, 7):
assert restify(Callable[[str], int]) == (":class:`~typing.Callable`\\ "
"[[:class:`str`], :class:`int`]")
assert restify(Callable[..., int]) == (":class:`~typing.Callable`\\ "
"[[...], :class:`int`]")
assert restify(Callable[[str], int]) == (":py:class:`~typing.Callable`\\ "
"[[:py:class:`str`], :py:class:`int`]")
assert restify(Callable[..., int]) == (":py:class:`~typing.Callable`\\ "
"[[...], :py:class:`int`]")
else:
assert restify(Callable[[str], int]) == (":class:`~typing.Callable`\\ "
"[:class:`str`, :class:`int`]")
assert restify(Callable[..., int]) == (":class:`~typing.Callable`\\ "
"[..., :class:`int`]")
assert restify(Callable[[str], int]) == (":py:class:`~typing.Callable`\\ "
"[:py:class:`str`, :py:class:`int`]")
assert restify(Callable[..., int]) == (":py:class:`~typing.Callable`\\ "
"[..., :py:class:`int`]")
def test_restify_type_hints_Union():
assert restify(Optional[int]) == ":obj:`~typing.Optional`\\ [:class:`int`]"
assert restify(Union[str, None]) == ":obj:`~typing.Optional`\\ [:class:`str`]"
assert restify(Union[int, str]) == ":obj:`~typing.Union`\\ [:class:`int`, :class:`str`]"
assert restify(Optional[int]) == ":py:obj:`~typing.Optional`\\ [:py:class:`int`]"
assert restify(Union[str, None]) == ":py:obj:`~typing.Optional`\\ [:py:class:`str`]"
assert restify(Union[int, str]) == (":py:obj:`~typing.Union`\\ "
"[:py:class:`int`, :py:class:`str`]")
if sys.version_info >= (3, 7):
assert restify(Union[int, Integral]) == (":obj:`~typing.Union`\\ "
"[:class:`int`, :class:`numbers.Integral`]")
assert restify(Union[int, Integral]) == (":py:obj:`~typing.Union`\\ "
"[:py:class:`int`, :py:class:`numbers.Integral`]")
assert (restify(Union[MyClass1, MyClass2]) ==
(":obj:`~typing.Union`\\ "
"[:class:`tests.test_util_typing.MyClass1`, "
":class:`tests.test_util_typing.<MyClass2>`]"))
(":py:obj:`~typing.Union`\\ "
"[:py:class:`tests.test_util_typing.MyClass1`, "
":py:class:`tests.test_util_typing.<MyClass2>`]"))
else:
assert restify(Union[int, Integral]) == ":class:`numbers.Integral`"
assert restify(Union[MyClass1, MyClass2]) == ":class:`tests.test_util_typing.MyClass1`"
assert restify(Union[int, Integral]) == ":py:class:`numbers.Integral`"
assert restify(Union[MyClass1, MyClass2]) == ":py:class:`tests.test_util_typing.MyClass1`"
@pytest.mark.skipif(sys.version_info < (3, 7), reason='python 3.7+ is required.')
@@ -108,108 +114,220 @@ def test_restify_type_hints_typevars():
T_co = TypeVar('T_co', covariant=True)
T_contra = TypeVar('T_contra', contravariant=True)
assert restify(T) == ":obj:`tests.test_util_typing.T`"
assert restify(T_co) == ":obj:`tests.test_util_typing.T_co`"
assert restify(T_contra) == ":obj:`tests.test_util_typing.T_contra`"
assert restify(List[T]) == ":class:`~typing.List`\\ [:obj:`tests.test_util_typing.T`]"
assert restify(MyInt) == ":class:`MyInt`"
assert restify(T) == ":py:obj:`tests.test_util_typing.T`"
assert restify(T_co) == ":py:obj:`tests.test_util_typing.T_co`"
assert restify(T_contra) == ":py:obj:`tests.test_util_typing.T_contra`"
assert restify(List[T]) == ":py:class:`~typing.List`\\ [:py:obj:`tests.test_util_typing.T`]"
if sys.version_info >= (3, 10):
assert restify(MyInt) == ":py:class:`tests.test_util_typing.MyInt`"
else:
assert restify(MyInt) == ":py:class:`MyInt`"
def test_restify_type_hints_custom_class():
assert restify(MyClass1) == ":class:`tests.test_util_typing.MyClass1`"
assert restify(MyClass2) == ":class:`tests.test_util_typing.<MyClass2>`"
assert restify(MyClass1) == ":py:class:`tests.test_util_typing.MyClass1`"
assert restify(MyClass2) == ":py:class:`tests.test_util_typing.<MyClass2>`"
def test_restify_type_hints_alias():
MyStr = str
MyTuple = Tuple[str, str]
assert restify(MyStr) == ":class:`str`"
assert restify(MyTuple) == ":class:`~typing.Tuple`\\ [:class:`str`, :class:`str`]"
assert restify(MyStr) == ":py:class:`str`"
assert restify(MyTuple) == ":py:class:`~typing.Tuple`\\ [:py:class:`str`, :py:class:`str`]"
@pytest.mark.skipif(sys.version_info < (3, 7), reason='python 3.7+ is required.')
def test_restify_type_ForwardRef():
from typing import ForwardRef # type: ignore
assert restify(ForwardRef("myint")) == ":class:`myint`"
assert restify(ForwardRef("myint")) == ":py:class:`myint`"
@pytest.mark.skipif(sys.version_info < (3, 8), reason='python 3.8+ is required.')
def test_restify_type_Literal():
from typing import Literal # type: ignore
assert restify(Literal[1, "2", "\r"]) == ":obj:`~typing.Literal`\\ [1, '2', '\\r']"
assert restify(Literal[1, "2", "\r"]) == ":py:obj:`~typing.Literal`\\ [1, '2', '\\r']"
@pytest.mark.skipif(sys.version_info < (3, 9), reason='python 3.9+ is required.')
def test_restify_pep_585():
assert restify(list[str]) == ":py:class:`list`\\ [:py:class:`str`]" # type: ignore
assert restify(dict[str, str]) == (":py:class:`dict`\\ " # type: ignore
"[:py:class:`str`, :py:class:`str`]")
assert restify(dict[str, tuple[int, ...]]) == (":py:class:`dict`\\ " # type: ignore
"[:py:class:`str`, :py:class:`tuple`\\ "
"[:py:class:`int`, ...]]")
@pytest.mark.skipif(sys.version_info < (3, 10), reason='python 3.10+ is required.')
def test_restify_type_union_operator():
assert restify(int | None) == "Optional[:class:`int`]" # type: ignore
assert restify(int | str) == ":class:`int` | :class:`str`" # type: ignore
assert restify(int | str | None) == "Optional[:class:`int` | :class:`str`]" # type: ignore
assert restify(int | None) == ":py:class:`int` | :py:obj:`None`" # type: ignore
assert restify(int | str) == ":py:class:`int` | :py:class:`str`" # type: ignore
assert restify(int | str | None) == (":py:class:`int` | :py:class:`str` | " # type: ignore
":py:obj:`None`")
def test_restify_broken_type_hints():
assert restify(BrokenType) == ':class:`tests.test_util_typing.BrokenType`'
assert restify(BrokenType) == ':py:class:`tests.test_util_typing.BrokenType`'
def test_restify_mock():
with mock(['unknown']):
import unknown
assert restify(unknown.secret.Class) == ':py:class:`unknown.secret.Class`'
def test_stringify():
assert stringify(int) == "int"
assert stringify(str) == "str"
assert stringify(None) == "None"
assert stringify(Integral) == "numbers.Integral"
assert stringify(Struct) == "struct.Struct"
assert stringify(TracebackType) == "types.TracebackType"
assert stringify(Any) == "Any"
assert stringify(int, False) == "int"
assert stringify(int, True) == "int"
assert stringify(str, False) == "str"
assert stringify(str, True) == "str"
assert stringify(None, False) == "None"
assert stringify(None, True) == "None"
assert stringify(Integral, False) == "numbers.Integral"
assert stringify(Integral, True) == "~numbers.Integral"
assert stringify(Struct, False) == "struct.Struct"
assert stringify(Struct, True) == "~struct.Struct"
assert stringify(TracebackType, False) == "types.TracebackType"
assert stringify(TracebackType, True) == "~types.TracebackType"
assert stringify(Any, False) == "Any"
assert stringify(Any, True) == "~typing.Any"
def test_stringify_type_hints_containers():
assert stringify(List) == "List"
assert stringify(Dict) == "Dict"
assert stringify(List[int]) == "List[int]"
assert stringify(List[str]) == "List[str]"
assert stringify(Dict[str, float]) == "Dict[str, float]"
assert stringify(Tuple[str, str, str]) == "Tuple[str, str, str]"
assert stringify(Tuple[str, ...]) == "Tuple[str, ...]"
assert stringify(List[Dict[str, Tuple]]) == "List[Dict[str, Tuple]]"
assert stringify(MyList[Tuple[int, int]]) == "tests.test_util_typing.MyList[Tuple[int, int]]"
assert stringify(Generator[None, None, None]) == "Generator[None, None, None]"
assert stringify(List, False) == "List"
assert stringify(List, True) == "~typing.List"
assert stringify(Dict, False) == "Dict"
assert stringify(Dict, True) == "~typing.Dict"
assert stringify(List[int], False) == "List[int]"
assert stringify(List[int], True) == "~typing.List[int]"
assert stringify(List[str], False) == "List[str]"
assert stringify(List[str], True) == "~typing.List[str]"
assert stringify(Dict[str, float], False) == "Dict[str, float]"
assert stringify(Dict[str, float], True) == "~typing.Dict[str, float]"
assert stringify(Tuple[str, str, str], False) == "Tuple[str, str, str]"
assert stringify(Tuple[str, str, str], True) == "~typing.Tuple[str, str, str]"
assert stringify(Tuple[str, ...], False) == "Tuple[str, ...]"
assert stringify(Tuple[str, ...], True) == "~typing.Tuple[str, ...]"
assert stringify(Tuple[()], False) == "Tuple[()]"
assert stringify(Tuple[()], True) == "~typing.Tuple[()]"
assert stringify(List[Dict[str, Tuple]], False) == "List[Dict[str, Tuple]]"
assert stringify(List[Dict[str, Tuple]], True) == "~typing.List[~typing.Dict[str, ~typing.Tuple]]"
assert stringify(MyList[Tuple[int, int]], False) == "tests.test_util_typing.MyList[Tuple[int, int]]"
assert stringify(MyList[Tuple[int, int]], True) == "~tests.test_util_typing.MyList[~typing.Tuple[int, int]]"
assert stringify(Generator[None, None, None], False) == "Generator[None, None, None]"
assert stringify(Generator[None, None, None], True) == "~typing.Generator[None, None, None]"
@pytest.mark.skipif(sys.version_info < (3, 9), reason='python 3.9+ is required.')
def test_stringify_type_hints_pep_585():
assert stringify(list[int], False) == "list[int]"
assert stringify(list[int], True) == "list[int]"
assert stringify(list[str], False) == "list[str]"
assert stringify(list[str], True) == "list[str]"
assert stringify(dict[str, float], False) == "dict[str, float]"
assert stringify(dict[str, float], True) == "dict[str, float]"
assert stringify(tuple[str, str, str], False) == "tuple[str, str, str]"
assert stringify(tuple[str, str, str], True) == "tuple[str, str, str]"
assert stringify(tuple[str, ...], False) == "tuple[str, ...]"
assert stringify(tuple[str, ...], True) == "tuple[str, ...]"
assert stringify(tuple[()], False) == "tuple[()]"
assert stringify(tuple[()], True) == "tuple[()]"
assert stringify(list[dict[str, tuple]], False) == "list[dict[str, tuple]]"
assert stringify(list[dict[str, tuple]], True) == "list[dict[str, tuple]]"
assert stringify(type[int], False) == "type[int]"
assert stringify(type[int], True) == "type[int]"
@pytest.mark.skipif(sys.version_info < (3, 9), reason='python 3.9+ is required.')
def test_stringify_Annotated():
from typing import Annotated # type: ignore
assert stringify(Annotated[str, "foo", "bar"]) == "str" # NOQA
assert stringify(Annotated[str, "foo", "bar"], False) == "str" # NOQA
assert stringify(Annotated[str, "foo", "bar"], True) == "str" # NOQA
def test_stringify_type_hints_string():
assert stringify("int") == "int"
assert stringify("str") == "str"
assert stringify(List["int"]) == "List[int]"
assert stringify("Tuple[str]") == "Tuple[str]"
assert stringify("unknown") == "unknown"
assert stringify("int", False) == "int"
assert stringify("int", True) == "int"
assert stringify("str", False) == "str"
assert stringify("str", True) == "str"
assert stringify(List["int"], False) == "List[int]"
assert stringify(List["int"], True) == "~typing.List[int]"
assert stringify("Tuple[str]", False) == "Tuple[str]"
assert stringify("Tuple[str]", True) == "Tuple[str]"
assert stringify("unknown", False) == "unknown"
assert stringify("unknown", True) == "unknown"
def test_stringify_type_hints_Callable():
assert stringify(Callable) == "Callable"
assert stringify(Callable, False) == "Callable"
assert stringify(Callable, True) == "~typing.Callable"
if sys.version_info >= (3, 7):
assert stringify(Callable[[str], int]) == "Callable[[str], int]"
assert stringify(Callable[..., int]) == "Callable[[...], int]"
assert stringify(Callable[[str], int], False) == "Callable[[str], int]"
assert stringify(Callable[[str], int], True) == "~typing.Callable[[str], int]"
assert stringify(Callable[..., int], False) == "Callable[[...], int]"
assert stringify(Callable[..., int], True) == "~typing.Callable[[...], int]"
else:
assert stringify(Callable[[str], int]) == "Callable[str, int]"
assert stringify(Callable[..., int]) == "Callable[..., int]"
assert stringify(Callable[[str], int], False) == "Callable[str, int]"
assert stringify(Callable[[str], int], True) == "~typing.Callable[str, int]"
assert stringify(Callable[..., int], False) == "Callable[..., int]"
assert stringify(Callable[..., int], True) == "~typing.Callable[..., int]"
def test_stringify_type_hints_Union():
assert stringify(Optional[int]) == "Optional[int]"
assert stringify(Union[str, None]) == "Optional[str]"
assert stringify(Union[int, str]) == "Union[int, str]"
assert stringify(Optional[int], False) == "Optional[int]"
assert stringify(Optional[int], True) == "~typing.Optional[int]"
assert stringify(Union[str, None], False) == "Optional[str]"
assert stringify(Union[str, None], True) == "~typing.Optional[str]"
assert stringify(Union[int, str], False) == "Union[int, str]"
assert stringify(Union[int, str], True) == "~typing.Union[int, str]"
if sys.version_info >= (3, 7):
assert stringify(Union[int, Integral]) == "Union[int, numbers.Integral]"
assert (stringify(Union[MyClass1, MyClass2]) ==
assert stringify(Union[int, Integral], False) == "Union[int, numbers.Integral]"
assert stringify(Union[int, Integral], True) == "~typing.Union[int, ~numbers.Integral]"
assert (stringify(Union[MyClass1, MyClass2], False) ==
"Union[tests.test_util_typing.MyClass1, tests.test_util_typing.<MyClass2>]")
assert (stringify(Union[MyClass1, MyClass2], True) ==
"~typing.Union[~tests.test_util_typing.MyClass1, ~tests.test_util_typing.<MyClass2>]")
else:
assert stringify(Union[int, Integral]) == "numbers.Integral"
assert stringify(Union[MyClass1, MyClass2]) == "tests.test_util_typing.MyClass1"
assert stringify(Union[int, Integral], False) == "numbers.Integral"
assert stringify(Union[int, Integral], True) == "~numbers.Integral"
assert stringify(Union[MyClass1, MyClass2], False) == "tests.test_util_typing.MyClass1"
assert stringify(Union[MyClass1, MyClass2], True) == "~tests.test_util_typing.MyClass1"
def test_stringify_type_hints_typevars():
@@ -218,43 +336,83 @@ def test_stringify_type_hints_typevars():
T_contra = TypeVar('T_contra', contravariant=True)
if sys.version_info < (3, 7):
assert stringify(T) == "T"
assert stringify(T_co) == "T_co"
assert stringify(T_contra) == "T_contra"
assert stringify(List[T]) == "List[T]"
else:
assert stringify(T) == "tests.test_util_typing.T"
assert stringify(T_co) == "tests.test_util_typing.T_co"
assert stringify(T_contra) == "tests.test_util_typing.T_contra"
assert stringify(List[T]) == "List[tests.test_util_typing.T]"
assert stringify(T, False) == "T"
assert stringify(T, True) == "T"
assert stringify(MyInt) == "MyInt"
assert stringify(T_co, False) == "T_co"
assert stringify(T_co, True) == "T_co"
assert stringify(T_contra, False) == "T_contra"
assert stringify(T_contra, True) == "T_contra"
assert stringify(List[T], False) == "List[T]"
assert stringify(List[T], True) == "~typing.List[T]"
else:
assert stringify(T, False) == "tests.test_util_typing.T"
assert stringify(T, True) == "~tests.test_util_typing.T"
assert stringify(T_co, False) == "tests.test_util_typing.T_co"
assert stringify(T_co, True) == "~tests.test_util_typing.T_co"
assert stringify(T_contra, False) == "tests.test_util_typing.T_contra"
assert stringify(T_contra, True) == "~tests.test_util_typing.T_contra"
assert stringify(List[T], False) == "List[tests.test_util_typing.T]"
assert stringify(List[T], True) == "~typing.List[~tests.test_util_typing.T]"
if sys.version_info >= (3, 10):
assert stringify(MyInt, False) == "tests.test_util_typing.MyInt"
assert stringify(MyInt, True) == "~tests.test_util_typing.MyInt"
else:
assert stringify(MyInt, False) == "MyInt"
assert stringify(MyInt, True) == "MyInt"
def test_stringify_type_hints_custom_class():
assert stringify(MyClass1) == "tests.test_util_typing.MyClass1"
assert stringify(MyClass2) == "tests.test_util_typing.<MyClass2>"
assert stringify(MyClass1, False) == "tests.test_util_typing.MyClass1"
assert stringify(MyClass1, True) == "~tests.test_util_typing.MyClass1"
assert stringify(MyClass2, False) == "tests.test_util_typing.<MyClass2>"
assert stringify(MyClass2, True) == "~tests.test_util_typing.<MyClass2>"
def test_stringify_type_hints_alias():
MyStr = str
MyTuple = Tuple[str, str]
assert stringify(MyStr) == "str"
assert stringify(MyTuple) == "Tuple[str, str]" # type: ignore
assert stringify(MyStr, False) == "str"
assert stringify(MyStr, True) == "str"
assert stringify(MyTuple, False) == "Tuple[str, str]" # type: ignore
assert stringify(MyTuple, True) == "~typing.Tuple[str, str]" # type: ignore
@pytest.mark.skipif(sys.version_info < (3, 8), reason='python 3.8+ is required.')
def test_stringify_type_Literal():
from typing import Literal # type: ignore
assert stringify(Literal[1, "2", "\r"]) == "Literal[1, '2', '\\r']"
assert stringify(Literal[1, "2", "\r"], False) == "Literal[1, '2', '\\r']"
assert stringify(Literal[1, "2", "\r"], True) == "~typing.Literal[1, '2', '\\r']"
@pytest.mark.skipif(sys.version_info < (3, 10), reason='python 3.10+ is required.')
def test_stringify_type_union_operator():
assert stringify(int | None) == "Optional[int]" # type: ignore
assert stringify(int | str) == "int | str" # type: ignore
assert stringify(int | str | None) == "Optional[int | str]" # type: ignore
assert stringify(int | None, False) == "int | None" # type: ignore
assert stringify(int | None, True) == "int | None" # type: ignore
assert stringify(int | str, False) == "int | str" # type: ignore
assert stringify(int | str, True) == "int | str" # type: ignore
assert stringify(int | str | None, False) == "int | str | None" # type: ignore
assert stringify(int | str | None, True) == "int | str | None" # type: ignore
def test_stringify_broken_type_hints():
assert stringify(BrokenType) == 'tests.test_util_typing.BrokenType'
assert stringify(BrokenType, False) == 'tests.test_util_typing.BrokenType'
assert stringify(BrokenType, True) == '~tests.test_util_typing.BrokenType'
def test_stringify_mock():
with mock(['unknown']):
import unknown
assert stringify(unknown.secret.Class, False) == 'unknown.secret.Class'
assert stringify(unknown.secret.Class, True) == 'unknown.secret.Class'

View File

@@ -11,12 +11,18 @@
import pickle
import pytest
from docutils.parsers.rst.directives.html import MetaBody
from sphinx import addnodes
from sphinx.testing.util import SphinxTestApp
from sphinx.versioning import add_uids, get_ratio, merge_doctrees
try:
from docutils.nodes import meta
except ImportError:
# docutils-0.18.0 or older
from docutils.parsers.rst.directives.html import MetaBody
meta = MetaBody.meta
app = original = original_uids = None
@@ -64,7 +70,7 @@ def test_picklablility():
copy.settings.warning_stream = None
copy.settings.env = None
copy.settings.record_dependencies = None
for metanode in copy.traverse(MetaBody.meta):
for metanode in copy.traverse(meta):
metanode.__class__ = addnodes.meta
loaded = pickle.loads(pickle.dumps(copy, pickle.HIGHEST_PROTOCOL))
assert all(getattr(n, 'uid', False) for n in loaded.traverse(is_paragraph))