diff options
Diffstat (limited to 'tests')
24 files changed, 402 insertions, 139 deletions
diff --git a/tests/roots/test-domain-c/field-role.rst b/tests/roots/test-domain-c/field-role.rst new file mode 100644 index 000000000..5452db5be --- /dev/null +++ b/tests/roots/test-domain-c/field-role.rst @@ -0,0 +1,4 @@ +.. c:function:: void f(int a, int *b) + + :param int a: + :param int* b: diff --git a/tests/roots/test-domain-cpp/field-role.rst b/tests/roots/test-domain-cpp/field-role.rst new file mode 100644 index 000000000..1711a889c --- /dev/null +++ b/tests/roots/test-domain-cpp/field-role.rst @@ -0,0 +1,5 @@ +.. cpp:function:: void f() + + :throws int: + :throws int*: + diff --git a/tests/roots/test-domain-py-python_use_unqualified_type_names/index.rst b/tests/roots/test-domain-py-python_use_unqualified_type_names/index.rst index 599206d8c..a6850a0f4 100644 --- a/tests/roots/test-domain-py-python_use_unqualified_type_names/index.rst +++ b/tests/roots/test-domain-py-python_use_unqualified_type_names/index.rst @@ -4,5 +4,9 @@ domain-py-smart_reference .. py:class:: Name :module: foo + :param name: blah blah + :type name: foo.Name + :param age: blah blah + :type age: foo.Age .. py:function:: hello(name: foo.Name, age: foo.Age) diff --git a/tests/roots/test-ext-autodoc/target/instance_variable.py b/tests/roots/test-ext-autodoc/target/instance_variable.py index ae86d1edb..1d393bc87 100644 --- a/tests/roots/test-ext-autodoc/target/instance_variable.py +++ b/tests/roots/test-ext-autodoc/target/instance_variable.py @@ -8,3 +8,4 @@ class Bar(Foo): def __init__(self): self.attr2 = None #: docstring bar self.attr3 = None #: docstring bar + self.attr4 = None diff --git a/tests/roots/test-ext-autodoc/target/typehints.py b/tests/roots/test-ext-autodoc/target/typehints.py index bb56054c3..9e1504399 100644 --- a/tests/roots/test-ext-autodoc/target/typehints.py +++ b/tests/roots/test-ext-autodoc/target/typehints.py @@ -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 diff --git a/tests/roots/test-latex-container/conf.py b/tests/roots/test-latex-container/conf.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/tests/roots/test-latex-container/conf.py diff --git a/tests/roots/test-latex-container/index.rst b/tests/roots/test-latex-container/index.rst new file mode 100644 index 000000000..899788bd5 --- /dev/null +++ b/tests/roots/test-latex-container/index.rst @@ -0,0 +1,4 @@ +.. container:: classname + + text +
\ No newline at end of file diff --git a/tests/roots/test-linkcheck/links.txt b/tests/roots/test-linkcheck/links.txt index b389414c9..c21968250 100644 --- a/tests/roots/test-linkcheck/links.txt +++ b/tests/roots/test-linkcheck/links.txt @@ -13,6 +13,8 @@ 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 .. image:: https://www.google.com/image.png .. figure:: https://www.google.com/image2.png diff --git a/tests/roots/test-remote-logo/conf.py b/tests/roots/test-remote-logo/conf.py new file mode 100644 index 000000000..07949ba91 --- /dev/null +++ b/tests/roots/test-remote-logo/conf.py @@ -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" diff --git a/tests/roots/test-remote-logo/index.rst b/tests/roots/test-remote-logo/index.rst new file mode 100644 index 000000000..48407e643 --- /dev/null +++ b/tests/roots/test-remote-logo/index.rst @@ -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 diff --git a/tests/roots/test-root/lists.txt b/tests/roots/test-root/lists.txt index 1fa2d11c5..0b5445461 100644 --- a/tests/roots/test-root/lists.txt +++ b/tests/roots/test-root/lists.txt @@ -61,3 +61,10 @@ term1 term2 (**stronged partially**) description + +Samp tests +---------- + +:samp:`{variable_only}` +:samp:`{variable} and text` +:samp:`Show {variable} in the middle` diff --git a/tests/test_build_html.py b/tests/test_build_html.py index c74552d9e..2e53bdc54 100644 --- a/tests/test_build_html.py +++ b/tests/test_build_html.py @@ -1330,6 +1330,16 @@ 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='basic') def test_html_sidebar(app, status, warning): ctx = {} diff --git a/tests/test_build_latex.py b/tests/test_build_latex.py index bb1904d2c..e0cfce953 100644 --- a/tests/test_build_latex.py +++ b/tests/test_build_latex.py @@ -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 diff --git a/tests/test_build_linkcheck.py b/tests/test_build_linkcheck.py index bc85e402f..6db0e7512 100644 --- a/tests/test_build_linkcheck.py +++ b/tests/test_build_linkcheck.py @@ -66,8 +66,8 @@ def test_defaults_json(app): "info"]: assert attr in row - assert len(content.splitlines()) == 10 - assert len(rows) == 10 + assert len(content.splitlines()) == 12 + assert len(rows) == 12 # the output order of the rows is not stable # due to possible variance in network latency rowsby = {row["uri"]: row for row in rows} @@ -88,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': 18, + 'lineno': 20, 'status': 'broken', 'code': 0, 'uri': 'https://www.google.com/image2.png', @@ -102,6 +102,10 @@ 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( @@ -599,3 +603,29 @@ def test_limit_rate_bails_out_after_waiting_max_time(app): rate_limits) next_check = worker.limit_rate(FakeResponse()) assert next_check is None + + +class ConnectionResetHandler(http.server.BaseHTTPRequestHandler): + def do_HEAD(self): + self.connection.close() + + def do_GET(self): + self.send_response(200, "OK") + self.end_headers() + + +@pytest.mark.sphinx('linkcheck', testroot='linkcheck-localserver', freshenv=True) +def test_get_after_head_raises_connection_error(app): + with http_server(ConnectionResetHandler): + app.build() + content = (app.outdir / 'output.txt').read_text() + assert not content + content = (app.outdir / 'output.json').read_text() + assert json.loads(content) == { + "filename": "index.rst", + "lineno": 1, + "status": "working", + "code": 0, + "uri": "http://localhost:7777/", + "info": "", + } diff --git a/tests/test_build_manpage.py b/tests/test_build_manpage.py index 0b7ce2396..3680d8651 100644 --- a/tests/test_build_manpage.py +++ b/tests/test_build_manpage.py @@ -27,6 +27,11 @@ def test_all(app, status, warning): assert '\n.B term1\n' in content assert '\nterm2 (\\fBstronged partially\\fP)\n' in content + # test samp with braces + assert '\n\\fIvariable_only\\fP\n' in content + assert '\n\\fIvariable\\fP\\fB and text\\fP\n' in content + assert '\n\\fBShow \\fP\\fIvariable\\fP\\fB in the middle\\fP\n' in content + assert 'Footnotes' not in content diff --git a/tests/test_domain_c.py b/tests/test_domain_c.py index ef4858786..d59c4fc1c 100644 --- a/tests/test_domain_c.py +++ b/tests/test_domain_c.py @@ -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) @@ -112,11 +116,8 @@ def check(name, input, idDict, output=None, key=None, asTextOutput=None): asTextOutput + ';' if asTextOutput is not None else None) -def test_expressions(): +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() @@ -155,7 +156,8 @@ def test_expressions(): # primary exprCheck('true') exprCheck('false') - ints = ['5', '0', '075', '0x0123456789ABCDEF', '0XF', '0b1', '0B1'] + ints = ['5', '0', '075', '0x0123456789ABCDEF', '0XF', '0b1', '0B1', + "0b0'1'0", "00'1'2", "0x0'1'2", "1'2'3"] unsignedSuffix = ['', 'u', 'U'] longSuffix = ['', 'l', 'L', 'll', 'LL'] for i in ints: @@ -170,14 +172,18 @@ def test_expressions(): '5e42', '5e+42', '5e-42', '5.', '5.e42', '5.e+42', '5.e-42', '.5', '.5e42', '.5e+42', '.5e-42', - '5.0', '5.0e42', '5.0e+42', '5.0e-42']: + '5.0', '5.0e42', '5.0e+42', '5.0e-42', + "1'2'3e7'8'9", "1'2'3.e7'8'9", + ".4'5'6e7'8'9", "1'2'3.4'5'6e7'8'9"]: expr = e + suffix exprCheck(expr) for e in [ 'ApF', 'Ap+F', 'Ap-F', 'A.', 'A.pF', 'A.p+F', 'A.p-F', '.A', '.ApF', '.Ap+F', '.Ap-F', - 'A.B', 'A.BpF', 'A.Bp+F', 'A.Bp-F']: + 'A.B', 'A.BpF', 'A.Bp+F', 'A.Bp-F', + "A'B'Cp1'2'3", "A'B'C.p1'2'3", + ".D'E'Fp1'2'3", "A'B'C.D'E'Fp1'2'3"]: expr = "0x" + e + suffix exprCheck(expr) exprCheck('"abc\\"cba"') # string @@ -269,7 +275,7 @@ def test_expressions(): exprCheck('a or_eq 5') -def test_type_definitions(): +def test_domain_c_ast_type_definitions(): check('type', "{key}T", {1: "T"}) check('type', "{key}bool *b", {1: 'b'}, key='typedef') @@ -290,7 +296,7 @@ def test_type_definitions(): {1: 'gpio_callback_t'}, key='typedef') -def test_macro_definitions(): +def test_domain_c_ast_macro_definitions(): check('macro', 'M', {1: 'M'}) check('macro', 'M()', {1: 'M'}) check('macro', 'M(arg)', {1: 'M'}) @@ -306,7 +312,7 @@ def test_macro_definitions(): check('macro', 'M(arg1, arg2..., arg3)', {1: 'M'}) -def test_member_definitions(): +def test_domain_c_ast_member_definitions(): check('member', 'void a', {1: 'a'}) check('member', '_Bool a', {1: 'a'}) check('member', 'bool a', {1: 'a'}) @@ -364,7 +370,7 @@ def test_member_definitions(): check('member', 'int b : 3', {1: 'b'}) -def test_function_definitions(): +def test_domain_c_ast_function_definitions(): check('function', 'void f()', {1: 'f'}) check('function', 'void f(int)', {1: 'f'}) check('function', 'void f(int i)', {1: 'f'}) @@ -424,29 +430,29 @@ def test_function_definitions(): check('function', 'void f(void (*p)(int, double), int i)', {1: 'f'}) -def test_nested_name(): +def test_domain_c_ast_nested_name(): check('struct', '{key}.A', {1: "A"}) check('struct', '{key}.A.B', {1: "A.B"}) check('function', 'void f(.A a)', {1: "f"}) check('function', 'void f(.A.B a)', {1: "f"}) -def test_struct_definitions(): +def test_domain_c_ast_struct_definitions(): check('struct', '{key}A', {1: 'A'}) -def test_union_definitions(): +def test_domain_c_ast_union_definitions(): check('union', '{key}A', {1: 'A'}) -def test_enum_definitions(): +def test_domain_c_ast_enum_definitions(): check('enum', '{key}A', {1: 'A'}) check('enumerator', '{key}A', {1: 'A'}) check('enumerator', '{key}A = 42', {1: 'A'}) -def test_anon_definitions(): +def test_domain_c_ast_anon_definitions(): check('struct', '@a', {1: "@a"}, asTextOutput='struct [anonymous]') check('union', '@a', {1: "@a"}, asTextOutput='union [anonymous]') check('enum', '@a', {1: "@a"}, asTextOutput='enum [anonymous]') @@ -454,7 +460,7 @@ def test_anon_definitions(): check('struct', '@a.A', {1: "@a.A"}, asTextOutput='struct [anonymous].A') -def test_initializers(): +def test_domain_c_ast_initializers(): idsMember = {1: 'v'} idsFunction = {1: 'f'} # no init @@ -473,7 +479,7 @@ def test_initializers(): # TODO: designator-list -def test_attributes(): +def test_domain_c_ast_attributes(): # style: C++ check('member', '[[]] int f', {1: 'f'}) check('member', '[ [ ] ] int f', {1: 'f'}, @@ -522,6 +528,16 @@ def test_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: @@ -566,14 +582,14 @@ def extract_role_links(app, filename): @pytest.mark.sphinx(testroot='domain-c', confoverrides={'nitpicky': True}) -def test_build_domain_c(app, status, warning): +def test_domain_c_build(app, status, warning): app.builder.build_all() ws = filter_warnings(warning, "index") assert len(ws) == 0 @pytest.mark.sphinx(testroot='domain-c', confoverrides={'nitpicky': True}) -def test_build_domain_c_namespace(app, status, warning): +def test_domain_c_build_namespace(app, status, warning): app.builder.build_all() ws = filter_warnings(warning, "namespace") assert len(ws) == 0 @@ -583,7 +599,7 @@ def test_build_domain_c_namespace(app, status, warning): @pytest.mark.sphinx(testroot='domain-c', confoverrides={'nitpicky': True}) -def test_build_domain_c_anon_dup_decl(app, status, warning): +def test_domain_c_build_anon_dup_decl(app, status, warning): app.builder.build_all() ws = filter_warnings(warning, "anon-dup-decl") assert len(ws) == 2 @@ -592,7 +608,7 @@ def test_build_domain_c_anon_dup_decl(app, status, warning): @pytest.mark.sphinx(confoverrides={'nitpicky': True}) -def test_build_domain_c_semicolon(app, warning): +def test_domain_c_build_semicolon(app, warning): text = """ .. c:member:: int member; .. c:var:: int var; @@ -611,7 +627,7 @@ def test_build_domain_c_semicolon(app, warning): @pytest.mark.sphinx(testroot='domain-c', confoverrides={'nitpicky': True}) -def test_build_function_param_target(app, warning): +def test_domain_c_build_function_param_target(app, warning): # the anchor for function parameters should be the function app.builder.build_all() ws = filter_warnings(warning, "function_param_target") @@ -624,12 +640,19 @@ def test_build_function_param_target(app, warning): @pytest.mark.sphinx(testroot='domain-c', confoverrides={'nitpicky': True}) -def test_build_ns_lookup(app, warning): +def test_domain_c_build_ns_lookup(app, warning): app.builder.build_all() ws = filter_warnings(warning, "ns_lookup") assert len(ws) == 0 +@pytest.mark.sphinx(testroot='domain-c', confoverrides={'nitpicky': True}) +def test_domain_c_build_field_role(app, status, warning): + app.builder.build_all() + ws = filter_warnings(warning, "field-role") + assert len(ws) == 0 + + def _get_obj(app, queryName): domain = app.env.get_domain('c') for name, dispname, objectType, docname, anchor, prio in domain.get_objects(): @@ -638,49 +661,8 @@ def _get_obj(app, queryName): return (queryName, "not", "found") -def test_cfunction(app): - text = (".. c:function:: PyObject* " - "PyType_GenericAlloc(PyTypeObject *type, Py_ssize_t nitems)") - doctree = restructuredtext.parse(app, text) - assert_node(doctree[1], addnodes.desc, desctype="function", - domain="c", objtype="function", noindex=False) - - entry = _get_obj(app, 'PyType_GenericAlloc') - assert entry == ('index', 'c.PyType_GenericAlloc', 'function') - - -def test_cmember(app): - text = ".. c:member:: PyObject* PyTypeObject.tp_bases" - doctree = restructuredtext.parse(app, text) - assert_node(doctree[1], addnodes.desc, desctype="member", - domain="c", objtype="member", noindex=False) - - entry = _get_obj(app, 'PyTypeObject.tp_bases') - assert entry == ('index', 'c.PyTypeObject.tp_bases', 'member') - - -def test_cvar(app): - text = ".. c:var:: PyObject* PyClass_Type" - doctree = restructuredtext.parse(app, text) - assert_node(doctree[1], addnodes.desc, desctype="var", - domain="c", objtype="var", noindex=False) - - entry = _get_obj(app, 'PyClass_Type') - assert entry == ('index', 'c.PyClass_Type', 'member') - - -def test_noindexentry(app): - text = (".. c:function:: void f()\n" - ".. c:function:: void g()\n" - " :noindexentry:\n") - doctree = restructuredtext.parse(app, text) - assert_node(doctree, (addnodes.index, desc, addnodes.index, desc)) - assert_node(doctree[0], addnodes.index, entries=[('single', 'f (C function)', 'c.f', '', None)]) - assert_node(doctree[2], addnodes.index, entries=[]) - - @pytest.mark.sphinx(testroot='domain-c-intersphinx', confoverrides={'nitpicky': True}) -def test_intersphinx(tempdir, app, status, warning): +def test_domain_c_build_intersphinx(tempdir, app, status, warning): # a splitting of test_ids_vs_tags0 into the primary directives in a remote project, # and then the references in the test project origSource = """\ @@ -728,3 +710,44 @@ _var c:member 1 index.html#c.$ - app.builder.build_all() ws = filter_warnings(warning, "index") assert len(ws) == 0 + + +def test_domain_c_parse_cfunction(app): + text = (".. c:function:: PyObject* " + "PyType_GenericAlloc(PyTypeObject *type, Py_ssize_t nitems)") + doctree = restructuredtext.parse(app, text) + assert_node(doctree[1], addnodes.desc, desctype="function", + domain="c", objtype="function", noindex=False) + + entry = _get_obj(app, 'PyType_GenericAlloc') + assert entry == ('index', 'c.PyType_GenericAlloc', 'function') + + +def test_domain_c_parse_cmember(app): + text = ".. c:member:: PyObject* PyTypeObject.tp_bases" + doctree = restructuredtext.parse(app, text) + assert_node(doctree[1], addnodes.desc, desctype="member", + domain="c", objtype="member", noindex=False) + + entry = _get_obj(app, 'PyTypeObject.tp_bases') + assert entry == ('index', 'c.PyTypeObject.tp_bases', 'member') + + +def test_domain_c_parse_cvar(app): + text = ".. c:var:: PyObject* PyClass_Type" + doctree = restructuredtext.parse(app, text) + assert_node(doctree[1], addnodes.desc, desctype="var", + domain="c", objtype="var", noindex=False) + + entry = _get_obj(app, 'PyClass_Type') + assert entry == ('index', 'c.PyClass_Type', 'member') + + +def test_domain_c_parse_noindexentry(app): + text = (".. c:function:: void f()\n" + ".. c:function:: void g()\n" + " :noindexentry:\n") + doctree = restructuredtext.parse(app, text) + assert_node(doctree, (addnodes.index, desc, addnodes.index, desc)) + assert_node(doctree[0], addnodes.index, entries=[('single', 'f (C function)', 'c.f', '', None)]) + assert_node(doctree[2], addnodes.index, entries=[]) diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py index 52aaad850..e02cd8c1c 100644 --- a/tests/test_domain_cpp.py +++ b/tests/test_domain_cpp.py @@ -117,7 +117,7 @@ def check(name, input, idDict, output=None, key=None, asTextOutput=None): asTextOutput + ';' if asTextOutput is not None else None) -def test_fundamental_types(): +def test_domain_cpp_ast_fundamental_types(): # see https://en.cppreference.com/w/cpp/language/types for t, id_v2 in cppDomain._id_fundamental_v2.items(): def makeIdV1(): @@ -126,6 +126,7 @@ def test_fundamental_types(): id = t.replace(" ", "-").replace("long", "l").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") id = id.replace("c32_t", "char32_t") return "f__%s" % id @@ -137,7 +138,7 @@ def test_fundamental_types(): check("function", "void f(%s arg)" % t, {1: makeIdV1(), 2: makeIdV2()}) -def test_expressions(): +def test_domain_cpp_ast_expressions(): def exprCheck(expr, id, id4=None): ids = 'IE1CIA%s_1aE' # call .format() on the expr to unescape double curly braces @@ -173,31 +174,36 @@ def test_expressions(): exprCheck('nullptr', 'LDnE') exprCheck('true', 'L1E') exprCheck('false', 'L0E') - ints = ['5', '0', '075', '0x0123456789ABCDEF', '0XF', '0b1', '0B1'] + ints = ['5', '0', '075', '0x0123456789ABCDEF', '0XF', '0b1', '0B1', + "0b0'1'0", "00'1'2", "0x0'1'2", "1'2'3"] unsignedSuffix = ['', 'u', 'U'] longSuffix = ['', 'l', 'L', 'll', 'LL'] for i in ints: for u in unsignedSuffix: for l in longSuffix: expr = i + u + l - exprCheck(expr, 'L' + expr + 'E') + exprCheck(expr, 'L' + expr.replace("'", "") + 'E') expr = i + l + u - exprCheck(expr, 'L' + expr + 'E') + exprCheck(expr, 'L' + expr.replace("'", "") + 'E') decimalFloats = ['5e42', '5e+42', '5e-42', '5.', '5.e42', '5.e+42', '5.e-42', '.5', '.5e42', '.5e+42', '.5e-42', - '5.0', '5.0e42', '5.0e+42', '5.0e-42'] + '5.0', '5.0e42', '5.0e+42', '5.0e-42', + "1'2'3e7'8'9", "1'2'3.e7'8'9", + ".4'5'6e7'8'9", "1'2'3.4'5'6e7'8'9"] hexFloats = ['ApF', 'Ap+F', 'Ap-F', 'A.', 'A.pF', 'A.p+F', 'A.p-F', '.A', '.ApF', '.Ap+F', '.Ap-F', - 'A.B', 'A.BpF', 'A.Bp+F', 'A.Bp-F'] + 'A.B', 'A.BpF', 'A.Bp+F', 'A.Bp-F', + "A'B'Cp1'2'3", "A'B'C.p1'2'3", + ".D'E'Fp1'2'3", "A'B'C.D'E'Fp1'2'3"] for suffix in ['', 'f', 'F', 'l', 'L']: for e in decimalFloats: expr = e + suffix - exprCheck(expr, 'L' + expr + 'E') + exprCheck(expr, 'L' + expr.replace("'", "") + 'E') for e in hexFloats: expr = "0x" + e + suffix - exprCheck(expr, 'L' + expr + 'E') + exprCheck(expr, 'L' + expr.replace("'", "") + 'E') exprCheck('"abc\\"cba"', 'LA8_KcE') # string exprCheck('this', 'fpT') # character literals @@ -210,13 +216,13 @@ def test_expressions(): exprCheck("{}'{}'".format(p, c), t + val) # user-defined literals for i in ints: - exprCheck(i + '_udl', 'clL_Zli4_udlEL' + i + 'EE') - exprCheck(i + 'uludl', 'clL_Zli5uludlEL' + i + 'EE') + exprCheck(i + '_udl', 'clL_Zli4_udlEL' + i.replace("'", "") + 'EE') + exprCheck(i + 'uludl', 'clL_Zli5uludlEL' + i.replace("'", "") + 'EE') for f in decimalFloats: - exprCheck(f + '_udl', 'clL_Zli4_udlEL' + f + 'EE') - exprCheck(f + 'fudl', 'clL_Zli4fudlEL' + f + 'EE') + exprCheck(f + '_udl', 'clL_Zli4_udlEL' + f.replace("'", "") + 'EE') + exprCheck(f + 'fudl', 'clL_Zli4fudlEL' + f.replace("'", "") + 'EE') for f in hexFloats: - exprCheck('0x' + f + '_udl', 'clL_Zli4_udlEL0x' + f + 'EE') + exprCheck('0x' + f + '_udl', 'clL_Zli4_udlEL0x' + f.replace("'", "") + 'EE') for p, t in charPrefixAndIds: for c, val in chars: exprCheck("{}'{}'_udl".format(p, c), 'clL_Zli4_udlE' + t + val + 'E') @@ -351,7 +357,7 @@ def test_expressions(): exprCheck('a(b(c, 1 + d...)..., e(f..., g))', 'cl1aspcl1b1cspplL1E1dEcl1esp1f1gEE') -def test_type_definitions(): +def test_domain_cpp_ast_type_definitions(): check("type", "public bool b", {1: "b", 2: "1b"}, "{key}bool b", key='typedef') check("type", "{key}bool A::b", {1: "A::b", 2: "N1A1bE"}, key='typedef') check("type", "{key}bool *b", {1: "b", 2: "1b"}, key='typedef') @@ -396,7 +402,7 @@ def test_type_definitions(): check('type', '{key}T = Q<A::operator bool>', {2: '1T'}, key='using') -def test_concept_definitions(): +def test_domain_cpp_ast_concept_definitions(): check('concept', 'template<typename Param> {key}A::B::Concept', {2: 'I0EN1A1B7ConceptE'}) check('concept', 'template<typename A, typename B, typename ...C> {key}Foo', @@ -407,7 +413,7 @@ def test_concept_definitions(): parse('concept', 'template<typename T> template<typename U> {key}Foo') -def test_member_definitions(): +def test_domain_cpp_ast_member_definitions(): check('member', ' const std::string & name = 42', {1: "name__ssCR", 2: "4name"}, output='const std::string &name = 42') check('member', ' const std::string & name', {1: "name__ssCR", 2: "4name"}, @@ -435,8 +441,11 @@ def test_member_definitions(): # check('member', 'int b : (true ? 8 : a) = 42', {1: 'b__i', 2: '1b'}) check('member', 'int b : 1 || new int{0}', {1: 'b__i', 2: '1b'}) + check('member', 'inline int n', {1: 'n__i', 2: '1n'}) + check('member', 'constinit int n', {1: 'n__i', 2: '1n'}) -def test_function_definitions(): + +def test_domain_cpp_ast_function_definitions(): check('function', 'void f(volatile int)', {1: "f__iV", 2: "1fVi"}) check('function', 'void f(std::size_t)', {1: "f__std::s", 2: "1fNSt6size_tE"}) check('function', 'operator bool() const', {1: "castto-b-operatorC", 2: "NKcvbEv"}) @@ -565,6 +574,9 @@ def test_function_definitions(): check("function", "void f(int *volatile const p)", {1: "f__iPVC", 2: "1fPVCi"}) check('function', 'extern int f()', {1: 'f', 2: '1fv'}) + check('function', 'consteval int f()', {1: 'f', 2: '1fv'}) + + check('function', 'explicit(true) void f()', {1: 'f', 2: '1fv'}) check('function', 'decltype(auto) f()', {1: 'f', 2: "1fv"}) @@ -624,7 +636,7 @@ def test_function_definitions(): check('function', 'void f(void (*p)(int, double), int i)', {2: '1fPFvidEi'}) -def test_operators(): +def test_domain_cpp_ast_operators(): check('function', 'void operator new()', {1: "new-operator", 2: "nwv"}) check('function', 'void operator new[]()', {1: "new-array-operator", 2: "nav"}) check('function', 'void operator delete()', {1: "delete-operator", 2: "dlv"}) @@ -684,14 +696,14 @@ def test_operators(): check('function', 'void operator[]()', {1: "subscript-operator", 2: "ixv"}) -def test_nested_name(): +def test_domain_cpp_ast_nested_name(): check('class', '{key}::A', {1: "A", 2: "1A"}) check('class', '{key}::A::B', {1: "A::B", 2: "N1A1BE"}) check('function', 'void f(::A a)', {1: "f__A", 2: "1f1A"}) check('function', 'void f(::A::B a)', {1: "f__A::B", 2: "1fN1A1BE"}) -def test_class_definitions(): +def test_domain_cpp_ast_class_definitions(): check('class', 'public A', {1: "A", 2: "1A"}, output='{key}A') check('class', 'private {key}A', {1: "A", 2: "1A"}) check('class', '{key}A final', {1: 'A', 2: '1A'}) @@ -722,11 +734,11 @@ def test_class_definitions(): {2: 'I_DpiE1TIJX(Is)EEE', 3: 'I_DpiE1TIJX2IsEEE'}) -def test_union_definitions(): +def test_domain_cpp_ast_union_definitions(): check('union', '{key}A', {2: "1A"}) -def test_enum_definitions(): +def test_domain_cpp_ast_enum_definitions(): check('enum', '{key}A', {2: "1A"}) check('enum', '{key}A : std::underlying_type<B>::type', {2: "1A"}) check('enum', '{key}A : unsigned int', {2: "1A"}) @@ -737,7 +749,7 @@ def test_enum_definitions(): check('enumerator', '{key}A = std::numeric_limits<unsigned long>::max()', {2: "1A"}) -def test_anon_definitions(): +def test_domain_cpp_ast_anon_definitions(): check('class', '@a', {3: "Ut1_a"}, asTextOutput='class [anonymous]') check('union', '@a', {3: "Ut1_a"}, asTextOutput='union [anonymous]') check('enum', '@a', {3: "Ut1_a"}, asTextOutput='enum [anonymous]') @@ -748,7 +760,7 @@ def test_anon_definitions(): asTextOutput='int f(int [anonymous])') -def test_templates(): +def test_domain_cpp_ast_templates(): check('class', "A<T>", {2: "IE1AI1TE"}, output="template<> {key}A<T>") # first just check which objects support templating check('class', "template<> {key}A", {2: "IE1A"}) @@ -854,7 +866,16 @@ def test_templates(): check('type', 'template<C T = int&> {key}A', {2: 'I_1CE1A'}, key='using') -def test_requires_clauses(): +def test_domain_cpp_ast_placeholder_types(): + check('function', 'void f(Sortable auto &v)', {1: 'f__SortableR', 2: '1fR8Sortable'}) + check('function', 'void f(const Sortable auto &v)', {1: 'f__SortableCR', 2: '1fRK8Sortable'}) + check('function', 'void f(Sortable decltype(auto) &v)', {1: 'f__SortableR', 2: '1fR8Sortable'}) + check('function', 'void f(const Sortable decltype(auto) &v)', {1: 'f__SortableCR', 2: '1fRK8Sortable'}) + check('function', 'void f(Sortable decltype ( auto ) &v)', {1: 'f__SortableR', 2: '1fR8Sortable'}, + output='void f(Sortable decltype(auto) &v)') + + +def test_domain_cpp_ast_requires_clauses(): check('function', 'template<typename T> requires A auto f() -> void requires B', {4: 'I0EIQaa1A1BE1fvv'}) check('function', 'template<typename T> requires A || B or C void f()', @@ -863,7 +884,7 @@ def test_requires_clauses(): {4: 'I0EIQooaa1A1Baa1C1DE1fvv'}) -def test_template_args(): +def test_domain_cpp_ast_template_args(): # from breathe#218 check('function', "template<typename F> " @@ -878,7 +899,7 @@ def test_template_args(): key='using') -def test_initializers(): +def test_domain_cpp_ast_initializers(): idsMember = {1: 'v__T', 2: '1v'} idsFunction = {1: 'f__T', 2: '1f1T'} idsTemplate = {2: 'I_1TE1fv', 4: 'I_1TE1fvv'} @@ -912,7 +933,7 @@ def test_initializers(): check('member', 'T v = T{}', idsMember) -def test_attributes(): +def test_domain_cpp_ast_attributes(): # style: C++ check('member', '[[]] int f', {1: 'f__i', 2: '1f'}) check('member', '[ [ ] ] int f', {1: 'f__i', 2: '1f'}, @@ -960,7 +981,7 @@ def test_attributes(): check('function', 'void f() [[attr1]] [[attr2]]', {1: 'f', 2: '1fv'}) -def test_xref_parsing(): +def test_domain_cpp_ast_xref_parsing(): def check(target): class Config: cpp_id_attributes = ["id_attr"] @@ -993,7 +1014,7 @@ def filter_warnings(warning, file): @pytest.mark.sphinx(testroot='domain-cpp', confoverrides={'nitpicky': True}) -def test_build_domain_cpp_multi_decl_lookup(app, status, warning): +def test_domain_cpp_build_multi_decl_lookup(app, status, warning): app.builder.build_all() ws = filter_warnings(warning, "lookup-key-overload") assert len(ws) == 0 @@ -1003,7 +1024,7 @@ def test_build_domain_cpp_multi_decl_lookup(app, status, warning): @pytest.mark.sphinx(testroot='domain-cpp', confoverrides={'nitpicky': True}) -def test_build_domain_cpp_warn_template_param_qualified_name(app, status, warning): +def test_domain_cpp_build_warn_template_param_qualified_name(app, status, warning): app.builder.build_all() ws = filter_warnings(warning, "warn-template-param-qualified-name") assert len(ws) == 2 @@ -1012,14 +1033,14 @@ def test_build_domain_cpp_warn_template_param_qualified_name(app, status, warnin @pytest.mark.sphinx(testroot='domain-cpp', confoverrides={'nitpicky': True}) -def test_build_domain_cpp_backslash_ok_true(app, status, warning): +def test_domain_cpp_build_backslash_ok_true(app, status, warning): app.builder.build_all() ws = filter_warnings(warning, "backslash") assert len(ws) == 0 @pytest.mark.sphinx(testroot='domain-cpp', confoverrides={'nitpicky': True}) -def test_build_domain_cpp_semicolon(app, status, warning): +def test_domain_cpp_build_semicolon(app, status, warning): app.builder.build_all() ws = filter_warnings(warning, "semicolon") assert len(ws) == 0 @@ -1027,7 +1048,7 @@ def test_build_domain_cpp_semicolon(app, status, warning): @pytest.mark.sphinx(testroot='domain-cpp', confoverrides={'nitpicky': True, 'strip_signature_backslash': True}) -def test_build_domain_cpp_backslash_ok_false(app, status, warning): +def test_domain_cpp_build_backslash_ok_false(app, status, warning): app.builder.build_all() ws = filter_warnings(warning, "backslash") assert len(ws) == 1 @@ -1035,7 +1056,7 @@ def test_build_domain_cpp_backslash_ok_false(app, status, warning): @pytest.mark.sphinx(testroot='domain-cpp', confoverrides={'nitpicky': True}) -def test_build_domain_cpp_anon_dup_decl(app, status, warning): +def test_domain_cpp_build_anon_dup_decl(app, status, warning): app.builder.build_all() ws = filter_warnings(warning, "anon-dup-decl") assert len(ws) == 2 @@ -1044,7 +1065,7 @@ def test_build_domain_cpp_anon_dup_decl(app, status, warning): @pytest.mark.sphinx(testroot='domain-cpp') -def test_build_domain_cpp_misuse_of_roles(app, status, warning): +def test_domain_cpp_build_misuse_of_roles(app, status, warning): app.builder.build_all() ws = filter_warnings(warning, "roles-targets-ok") assert len(ws) == 0 @@ -1092,7 +1113,7 @@ def test_build_domain_cpp_misuse_of_roles(app, status, warning): @pytest.mark.sphinx(testroot='domain-cpp', confoverrides={'add_function_parentheses': True}) -def test_build_domain_cpp_with_add_function_parentheses_is_True(app, status, warning): +def test_domain_cpp_build_with_add_function_parentheses_is_True(app, status, warning): app.builder.build_all() def check(spec, text, file): @@ -1133,7 +1154,7 @@ def test_build_domain_cpp_with_add_function_parentheses_is_True(app, status, war @pytest.mark.sphinx(testroot='domain-cpp', confoverrides={'add_function_parentheses': False}) -def test_build_domain_cpp_with_add_function_parentheses_is_False(app, status, warning): +def test_domain_cpp_build_with_add_function_parentheses_is_False(app, status, warning): app.builder.build_all() def check(spec, text, file): @@ -1174,7 +1195,7 @@ def test_build_domain_cpp_with_add_function_parentheses_is_False(app, status, wa @pytest.mark.sphinx(testroot='domain-cpp') -def test_xref_consistency(app, status, warning): +def test_domain_cpp_build_xref_consistency(app, status, warning): app.builder.build_all() test = 'xref_consistency.html' @@ -1237,33 +1258,15 @@ not found in `{test}` assert any_role.classes == texpr_role.content_classes['a'], expect -def test_noindexentry(app): - text = (".. cpp:function:: void f()\n" - ".. cpp:function:: void g()\n" - " :noindexentry:\n") - doctree = restructuredtext.parse(app, text) - assert_node(doctree, (addnodes.index, desc, addnodes.index, desc)) - assert_node(doctree[0], addnodes.index, entries=[('single', 'f (C++ function)', '_CPPv41fv', '', None)]) - assert_node(doctree[2], addnodes.index, entries=[]) - - -def test_mix_decl_duplicate(app, warning): - # Issue 8270 - text = (".. cpp:struct:: A\n" - ".. cpp:function:: void A()\n" - ".. cpp:struct:: A\n") - restructuredtext.parse(app, text) - ws = warning.getvalue().split("\n") - assert len(ws) == 5 - assert "index.rst:2: WARNING: Duplicate C++ declaration, also defined at index:1." in ws[0] - assert "Declaration is '.. cpp:function:: void A()'." in ws[1] - assert "index.rst:3: WARNING: Duplicate C++ declaration, also defined at index:1." in ws[2] - assert "Declaration is '.. cpp:struct:: A'." in ws[3] - assert ws[4] == "" +@pytest.mark.sphinx(testroot='domain-cpp', confoverrides={'nitpicky': True}) +def test_domain_cpp_build_field_role(app, status, warning): + app.builder.build_all() + ws = filter_warnings(warning, "field-role") + assert len(ws) == 0 @pytest.mark.sphinx(testroot='domain-cpp-intersphinx', confoverrides={'nitpicky': True}) -def test_intersphinx(tempdir, app, status, warning): +def test_domain_cpp_build_intersphinx(tempdir, app, status, warning): origSource = """\ .. cpp:class:: _class .. cpp:struct:: _struct @@ -1323,3 +1326,28 @@ _var cpp:member 1 index.html#_CPPv44$ - app.builder.build_all() ws = filter_warnings(warning, "index") assert len(ws) == 0 + + +def test_domain_cpp_parse_noindexentry(app): + text = (".. cpp:function:: void f()\n" + ".. cpp:function:: void g()\n" + " :noindexentry:\n") + doctree = restructuredtext.parse(app, text) + assert_node(doctree, (addnodes.index, desc, addnodes.index, desc)) + assert_node(doctree[0], addnodes.index, entries=[('single', 'f (C++ function)', '_CPPv41fv', '', None)]) + assert_node(doctree[2], addnodes.index, entries=[]) + + +def test_domain_cpp_parse_mix_decl_duplicate(app, warning): + # Issue 8270 + text = (".. cpp:struct:: A\n" + ".. cpp:function:: void A()\n" + ".. cpp:struct:: A\n") + restructuredtext.parse(app, text) + ws = warning.getvalue().split("\n") + assert len(ws) == 5 + assert "index.rst:2: WARNING: Duplicate C++ declaration, also defined at index:1." in ws[0] + assert "Declaration is '.. cpp:function:: void A()'." in ws[1] + assert "index.rst:3: WARNING: Duplicate C++ declaration, also defined at index:1." in ws[2] + assert "Declaration is '.. cpp:struct:: A'." in ws[3] + assert ws[4] == "" diff --git a/tests/test_domain_py.py b/tests/test_domain_py.py index e66f066d4..2ee2d5f2d 100644 --- a/tests/test_domain_py.py +++ b/tests/test_domain_py.py @@ -1147,6 +1147,9 @@ def test_python_python_use_unqualified_type_names(app, status, warning): assert ('<span class="n"><a class="reference internal" href="#foo.Name" title="foo.Name">' '<span class="pre">Name</span></a></span>' in content) assert '<span class="n"><span class="pre">foo.Age</span></span>' in content + assert ('<p><strong>name</strong> (<a class="reference internal" href="#foo.Name" ' + 'title="foo.Name"><em>Name</em></a>) – blah blah</p>' in content) + assert '<p><strong>age</strong> (<em>foo.Age</em>) – blah blah</p>' in content @pytest.mark.sphinx('html', testroot='domain-py-python_use_unqualified_type_names', @@ -1157,6 +1160,9 @@ def test_python_python_use_unqualified_type_names_disabled(app, status, warning) assert ('<span class="n"><a class="reference internal" href="#foo.Name" title="foo.Name">' '<span class="pre">foo.Name</span></a></span>' in content) assert '<span class="n"><span class="pre">foo.Age</span></span>' in content + assert ('<p><strong>name</strong> (<a class="reference internal" href="#foo.Name" ' + 'title="foo.Name"><em>foo.Name</em></a>) – blah blah</p>' in content) + assert '<p><strong>age</strong> (<em>foo.Age</em>) – blah blah</p>' in content @pytest.mark.sphinx('dummy', testroot='domain-py-xref-warning') diff --git a/tests/test_ext_autodoc_autoattribute.py b/tests/test_ext_autodoc_autoattribute.py index 5e7220234..20317b8da 100644 --- a/tests/test_ext_autodoc_autoattribute.py +++ b/tests/test_ext_autodoc_autoattribute.py @@ -101,6 +101,17 @@ def test_autoattribute_instance_variable_in_alias(app): @pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autoattribute_instance_variable_without_comment(app): + actual = do_autodoc(app, 'attribute', 'target.instance_variable.Bar.attr4') + assert list(actual) == [ + '', + '.. py:attribute:: Bar.attr4', + ' :module: target.instance_variable', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') def test_autoattribute_slots_variable_list(app): actual = do_autodoc(app, 'attribute', 'target.slots.Foo.attr') assert list(actual) == [ diff --git a/tests/test_ext_autodoc_configs.py b/tests/test_ext_autodoc_configs.py index e0f08ea77..0f0580404 100644 --- a/tests/test_ext_autodoc_configs.py +++ b/tests/test_ext_autodoc_configs.py @@ -554,10 +554,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 +590,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 +641,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 +674,10 @@ def test_autodoc_typehints_none(app): ' :module: target.typehints', '', '', + ' .. py:property:: Math.prop', + ' :module: target.typehints', + '', + '', '.. py:class:: NewAnnotation(i)', ' :module: target.typehints', '', diff --git a/tests/test_ext_math.py b/tests/test_ext_math.py index ebe2c0f38..973fc3699 100644 --- a/tests/test_ext_math.py +++ b/tests/test_ext_math.py @@ -256,3 +256,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 diff --git a/tests/test_intl.py b/tests/test_intl.py index 73d94166e..7791b4aee 100644 --- a/tests/test_intl.py +++ b/tests/test_intl.py @@ -622,7 +622,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 diff --git a/tests/test_pycode_ast.py b/tests/test_pycode_ast.py index e80062351..6ae7050da 100644 --- a/tests/test_pycode_ast.py +++ b/tests/test_pycode_ast.py @@ -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) diff --git a/tests/test_quickstart.py b/tests/test_quickstart.py index 94144ef22..6c2f70ec4 100644 --- a/tests/test_quickstart.py +++ b/tests/test_quickstart.py @@ -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) |