sphinx/tests/test_ext_autosummary.py

400 lines
14 KiB
Python
Raw Normal View History

"""
test_autosummary
~~~~~~~~~~~~~~~~
Test the autosummary extension.
2019-01-02 01:00:30 -06:00
:copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
import sys
from io import StringIO
import os
from unittest.mock import Mock
import pytest
from docutils import nodes
from sphinx import addnodes
from sphinx.ext.autosummary import (
autosummary_table, autosummary_toc, mangle_signature, import_by_name, extract_summary
)
from sphinx.testing.util import assert_node, etree_parse
from sphinx.util.docutils import new_document
html_warnfile = StringIO()
2014-04-11 10:24:53 -05:00
default_kw = {
'testroot': 'autosummary',
'confoverrides': {
'extensions': ['sphinx.ext.autosummary'],
'autosummary_generate': True,
'source_suffix': '.rst'
2014-04-11 10:24:53 -05:00
}
}
2014-04-11 10:24:53 -05:00
def test_mangle_signature():
TEST = """
() :: ()
(a, b, c, d, e) :: (a, b, c, d, e)
(a, b, c=1, d=2, e=3) :: (a, b[, c, d, e])
(a, b, aaa=1, bbb=1, ccc=1, eee=1, fff=1, ggg=1, hhh=1, iii=1, jjj=1)\
2009-05-31 12:50:29 -05:00
:: (a, b[, aaa, bbb, ccc, ...])
(a, b, c=(), d=<foo>) :: (a, b[, c, d])
(a, b, c='foobar()', d=123) :: (a, b[, c, d])
(a, b[, c]) :: (a, b[, c])
(a, b[, cxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx]) :: (a, b[, ...)
(a, b='c=d, e=f, g=h', c=3) :: (a[, b, c])
(a, b="c=d, e=f, g=h", c=3) :: (a[, b, c])
(a, b='c=d, \\'e=f,\\' g=h', c=3) :: (a[, b, c])
(a, b='c=d, ', e='\\\\' g=h, c=3) :: (a[, b, e, c])
(a, b={'c=d, ': 3, '\\\\': 3}) :: (a[, b])
(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)
"""
TEST = [[y.strip() for y in x.split("::")] for x in TEST.split("\n")
if '::' in x]
for inp, outp in TEST:
2018-12-15 08:02:28 -06:00
res = mangle_signature(inp).strip().replace("\u00a0", " ")
assert res == outp, ("'%s' -> '%s' != '%s'" % (inp, res, outp))
def test_extract_summary(capsys):
settings = Mock(language_code='',
id_prefix='',
auto_id_prefix='',
pep_reference=False,
rfc_reference=False)
document = new_document('', settings)
# normal case
doc = ['',
'This is a first sentence. And second one.',
'',
'Second block is here']
assert extract_summary(doc, document) == 'This is a first sentence.'
# inliner case
doc = ['This sentence contains *emphasis text having dots.*,',
'it does not break sentence.']
assert extract_summary(doc, document) == ' '.join(doc)
# abbreviations
doc = ['Blabla, i.e. bla.']
assert extract_summary(doc, document) == 'Blabla, i.e.'
# literal
doc = ['blah blah::']
assert extract_summary(doc, document) == 'blah blah.'
# heading
doc = ['blah blah',
'=========']
assert extract_summary(doc, document) == 'blah blah'
_, err = capsys.readouterr()
assert err == ''
@pytest.mark.sphinx('dummy', **default_kw)
def test_get_items_summary(make_app, app_params):
import sphinx.ext.autosummary
import sphinx.ext.autosummary.generate
args, kwargs = app_params
app = make_app(*args, **kwargs)
2019-02-24 05:13:04 -06:00
sphinx.ext.autosummary.generate.setup_documenters(app)
# monkey-patch Autosummary.get_items so we can easily get access to it's
# results..
orig_get_items = sphinx.ext.autosummary.Autosummary.get_items
autosummary_items = {}
def new_get_items(self, names, *args, **kwargs):
results = orig_get_items(self, names, *args, **kwargs)
for name, result in zip(names, results):
autosummary_items[name] = result
return results
def handler(app, what, name, obj, options, lines):
assert isinstance(lines, list)
# ensure no docstring is processed twice:
assert 'THIS HAS BEEN HANDLED' not in lines
lines.append('THIS HAS BEEN HANDLED')
app.connect('autodoc-process-docstring', handler)
sphinx.ext.autosummary.Autosummary.get_items = new_get_items
try:
2014-04-11 10:24:53 -05:00
app.builder.build_all()
finally:
sphinx.ext.autosummary.Autosummary.get_items = orig_get_items
html_warnings = app._warning.getvalue()
assert html_warnings == ''
expected_values = {
'withSentence': 'I have a sentence which spans multiple lines.',
'noSentence': "this doesn't start with a capital.",
'emptyLine': "This is the real summary",
'module_attr': 'This is a module attribute',
'C.class_attr': 'This is a class attribute',
'C.prop_attr1': 'This is a function docstring',
'C.prop_attr2': 'This is a attribute docstring',
'C.C2': 'This is a nested inner class docstring',
}
for key, expected in expected_values.items():
assert autosummary_items[key][2] == expected, 'Summary for %s was %r -'\
' expected %r' % (key, autosummary_items[key], expected)
# check an item in detail
assert 'func' in autosummary_items
func_attrs = ('func',
'(arg_, *args, **kwargs)',
'Test function take an argument ended with underscore.',
'dummy_module.func')
assert autosummary_items['func'] == func_attrs
2017-05-09 07:57:36 -05:00
def str_content(elem):
if elem.text is not None:
return elem.text
else:
return ''.join(str_content(e) for e in elem)
2017-05-09 07:57:36 -05:00
@pytest.mark.sphinx('xml', **default_kw)
def test_escaping(app, status, warning):
app.builder.build_all()
outdir = app.builder.outdir
docpage = outdir / 'underscore_module_.xml'
assert docpage.exists()
title = etree_parse(docpage).find('section/title')
assert str_content(title) == 'underscore_module_'
@pytest.mark.sphinx('dummy', testroot='ext-autosummary')
def test_autosummary_generate(app, status, warning):
app.builder.build_all()
doctree = app.env.get_doctree('index')
assert_node(doctree, (nodes.paragraph,
nodes.paragraph,
addnodes.tabular_col_spec,
autosummary_table,
autosummary_toc))
assert_node(doctree[3],
[autosummary_table, nodes.table, nodes.tgroup, (nodes.colspec,
nodes.colspec,
[nodes.tbody, (nodes.row,
nodes.row,
nodes.row,
nodes.row)])])
assert doctree[3][0][0][2][0].astext() == 'autosummary_dummy_module\n\n'
assert doctree[3][0][0][2][1].astext() == 'autosummary_dummy_module.Foo()\n\n'
assert doctree[3][0][0][2][2].astext() == 'autosummary_dummy_module.bar(x[, y])\n\n'
assert doctree[3][0][0][2][3].astext() == 'autosummary_importfail\n\n'
2017-07-02 04:41:03 -05:00
module = (app.srcdir / 'generated' / 'autosummary_dummy_module.rst').text()
assert (' .. autosummary::\n'
' \n'
' Foo\n'
' \n' in module)
2017-07-02 04:41:03 -05:00
Foo = (app.srcdir / 'generated' / 'autosummary_dummy_module.Foo.rst').text()
assert '.. automethod:: __init__' in Foo
assert (' .. autosummary::\n'
' \n'
' ~Foo.__init__\n'
' ~Foo.bar\n'
' \n' in Foo)
assert (' .. autosummary::\n'
' \n'
' ~Foo.baz\n'
' \n' in Foo)
def _assert_autosummary_recursive(app):
app.builder.build_all()
2019-02-24 05:13:04 -06:00
# All packages, modules and classes have the same name
package_name = 'package'
module_name = 'module'
class_name = 'Foo'
extension = 'rst'
nsuffix = len(extension)+1
# Expected module.rst template formatting
recursive = app.config.autosummary_recursive
2019-02-24 05:13:04 -06:00
module_rubic_name = 'modules'
module_rubic = '.. rubric:: ' + module_rubic_name
module_summary = '.. autosummary::\n'\
' :toctree: {}\n'\
'\n'\
' {{}}.{}\n'\
' {{}}.{}\n'.format(module_rubic_name,
module_name,
package_name)
last_module_summary = '.. autosummary::\n'\
' :toctree: {}\n'\
'\n'\
' {{}}.{}\n'.format(module_rubic_name,
module_name)
2019-02-24 05:13:04 -06:00
class_summary = ' .. autosummary::\n'\
' \n'\
' {}\n'\
' \n'.format(class_name)
def assert_package_content(file):
pkgroot = file.basename()[:-nsuffix]
content = file.text()
expected = ['.. automodule:: ' + pkgroot]
unexpected = []
if recursive:
2019-02-24 05:13:04 -06:00
lst = expected
else:
lst = unexpected
lst.append(module_rubic)
if depth == max_depth:
lst.append(last_module_summary.format(pkgroot))
2019-02-24 05:13:04 -06:00
else:
lst.append(module_summary.format(pkgroot, pkgroot))
2019-02-24 05:13:04 -06:00
for text in expected:
assert text in content
for text in unexpected:
assert text not in content
def assert_module_content(file):
modname = file.basename()[:-nsuffix]
content = file.text()
assert '.. automodule:: '+modname in content
assert ' .. rubric:: Classes' in content
assert class_summary in content
root = app.srcdir / 'generated'
pkgroot = ''
expected_paths = []
if app.config.autosummary_recursive:
max_depth = 3
else:
max_depth = 0
for depth in range(max_depth+1):
2019-02-24 05:13:04 -06:00
if pkgroot:
pkgroot = '.'.join((pkgroot, package_name))
else:
pkgroot = package_name
package = root / '.'.join((pkgroot, extension))
expected_paths.append(package)
2019-02-24 05:13:04 -06:00
assert_package_content(package)
if recursive:
module = root / module_rubic_name / \
'.'.join((pkgroot, module_name, extension))
expected_paths.append(root / module_rubic_name)
expected_paths.append(module)
assert_module_content(module)
root /= module_rubic_name
2019-02-24 05:13:04 -06:00
# Check generated files and directories
generated_paths = []
root = app.srcdir / 'generated'
2019-02-24 05:13:04 -06:00
for dir, subdirs, files in os.walk(str(root)):
root = root / dir
for name in files:
generated_paths.append(root / name)
for name in subdirs:
generated_paths.append(root / name)
assert set(generated_paths) == set(expected_paths)
2019-02-24 05:13:04 -06:00
@pytest.mark.sphinx('dummy', testroot='ext-autosummary-recursive')
def test_autosummary_recursive(make_app, app_params):
2019-02-24 05:13:04 -06:00
import sphinx.ext.autosummary
import sphinx.ext.autosummary.generate
logger = sphinx.ext.autosummary.logger
args, kwargs = app_params
confoverrides = {'autosummary_recursive': False}
2019-02-24 05:13:04 -06:00
kwargs['confoverrides'] = confoverrides
# Remarks: non-recursive after recursive doesn't work
# recursively generated files are not deleted
for recursive in False, True:
logger.info('autosummary_recursive = {}'.format(recursive))
confoverrides['autosummary_recursive'] = recursive
2019-02-24 05:13:04 -06:00
app = make_app(*args, **kwargs)
_assert_autosummary_recursive(app)
@pytest.mark.sphinx('latex', **default_kw)
def test_autosummary_latex_table_colspec(app, status, warning):
app.builder.build_all()
result = (app.outdir / 'python.tex').text(encoding='utf8')
print(status.getvalue())
print(warning.getvalue())
assert r'\begin{longtable}[c]{\X{1}{2}\X{1}{2}}' in result
assert r'p{0.5\linewidth}' not in result
def test_import_by_name():
import sphinx
import sphinx.ext.autosummary
prefixed_name, obj, parent, modname = import_by_name('sphinx')
assert prefixed_name == 'sphinx'
assert obj is sphinx
assert parent is None
assert modname == 'sphinx'
prefixed_name, obj, parent, modname = import_by_name('sphinx.ext.autosummary.__name__')
assert prefixed_name == 'sphinx.ext.autosummary.__name__'
assert obj is sphinx.ext.autosummary.__name__
assert parent is sphinx.ext.autosummary
assert modname == 'sphinx.ext.autosummary'
2017-12-23 06:20:32 -06:00
prefixed_name, obj, parent, modname = \
import_by_name('sphinx.ext.autosummary.Autosummary.get_items')
assert prefixed_name == 'sphinx.ext.autosummary.Autosummary.get_items'
assert obj == sphinx.ext.autosummary.Autosummary.get_items
assert parent is sphinx.ext.autosummary.Autosummary
assert modname == 'sphinx.ext.autosummary'
@pytest.mark.sphinx('dummy', testroot='ext-autosummary-mock_imports')
def test_autosummary_mock_imports(app, status, warning):
try:
app.build()
assert warning.getvalue() == ''
# generated/foo is generated successfully
assert app.env.get_doctree('generated/foo')
finally:
sys.modules.pop('foo', None) # unload foo module
@pytest.mark.sphinx('dummy', testroot='ext-autosummary-imported_members')
def test_autosummary_imported_members(app, status, warning):
try:
app.build()
# generated/foo is generated successfully
assert app.env.get_doctree('generated/autosummary_dummy_package')
module = (app.srcdir / 'generated' / 'autosummary_dummy_package.rst').text()
assert (' .. autosummary::\n'
' \n'
' Bar\n'
' \n' in module)
assert (' .. autosummary::\n'
' \n'
' foo\n'
' \n' in module)
finally:
sys.modules.pop('autosummary_dummy_package', None)