sphinx-autoapi/tests/python/test_pyintegration.py

657 lines
22 KiB
Python
Raw Normal View History

import io
import os
import shutil
import sys
2019-09-04 21:44:35 +00:00
from mock import patch, Mock, call
import pytest
import sphinx
from sphinx.application import Sphinx
import sphinx.util.logging
2019-09-04 21:44:35 +00:00
from autoapi.mappers.python import (
PythonModule,
PythonFunction,
PythonClass,
PythonData,
PythonMethod,
)
2019-01-27 05:20:45 +00:00
@pytest.fixture(scope="class")
def builder():
cwd = os.getcwd()
def build(test_dir, confoverrides=None):
2019-01-27 05:20:45 +00:00
os.chdir("tests/python/{0}".format(test_dir))
app = Sphinx(
2019-01-27 05:20:45 +00:00
srcdir=".",
confdir=".",
outdir="_build/text",
doctreedir="_build/.doctrees",
buildername="text",
confoverrides=confoverrides,
)
app.build(force_all=True)
yield build
try:
2019-01-27 05:20:45 +00:00
shutil.rmtree("_build")
finally:
os.chdir(cwd)
class TestSimpleModule(object):
2019-01-27 05:20:45 +00:00
@pytest.fixture(autouse=True, scope="class")
def built(self, builder):
2019-01-27 05:20:45 +00:00
builder("pyexample")
def test_integration(self):
2019-01-27 05:20:45 +00:00
self.check_integration("_build/text/autoapi/example/index.txt")
2020-07-12 02:14:03 +00:00
@pytest.mark.xfail(sphinx.version_info >= (3, 1), reason="Issue #227")
def test_manual_directives(self):
example_path = "_build/text/manualapi.txt"
# The manual directives should contain the same information
self.check_integration(example_path)
with io.open(example_path, encoding="utf8") as example_handle:
example_file = example_handle.read()
if sphinx.version_info >= (2,):
assert "@example.decorator_okay" in example_file
def check_integration(self, example_path):
2019-01-27 05:20:45 +00:00
with io.open(example_path, encoding="utf8") as example_handle:
example_file = example_handle.read()
2019-01-27 05:20:45 +00:00
assert "class example.Foo" in example_file
assert "class Meta" in example_file
assert "attr2" in example_file
assert "This is the docstring of an instance attribute." in example_file
assert "method_okay(self, foo=None, bar=None)" in example_file
assert "method_multiline(self, foo=None, bar=None, baz=None)" in example_file
assert "method_tricky(self, foo=None, bar=dict(foo=1, bar=2))" in example_file
# Are constructor arguments from the class docstring parsed?
2019-01-27 05:20:45 +00:00
assert "Set an attribute" in example_file
# "self" should not be included in constructor arguments
2019-01-27 05:20:45 +00:00
assert "Foo(self" not in example_file
# Overridden methods without their own docstring
# should inherit the parent's docstring
assert example_file.count("This method should parse okay") == 2
2019-01-27 05:20:45 +00:00
assert not os.path.exists("_build/text/autoapi/method_multiline")
2019-01-27 05:20:45 +00:00
index_path = "_build/text/index.txt"
with io.open(index_path, encoding="utf8") as index_handle:
index_file = index_handle.read()
2019-04-06 18:15:18 +00:00
assert "API Reference" in index_file
2019-01-27 05:20:45 +00:00
assert "Foo" in index_file
assert "Meta" in index_file
def test_napoleon_integration_not_loaded(self, builder):
2019-01-27 05:20:45 +00:00
example_path = "_build/text/autoapi/example/index.txt"
with io.open(example_path, encoding="utf8") as example_handle:
example_file = example_handle.read()
# Check that docstrings are not transformed without napoleon loaded
2019-01-27 05:20:45 +00:00
assert "Args" in example_file
2019-01-27 05:20:45 +00:00
assert "Returns" in example_file
def test_show_inheritance(self, builder):
example_path = "_build/text/autoapi/example/index.txt"
with io.open(example_path, encoding="utf8") as example_handle:
example_file = example_handle.read()
assert "Bases:" in example_file
@pytest.mark.skipif(
sys.version_info < (3,), reason="Ellipsis is invalid method contents in Python 2"
)
class TestSimpleStubModule(object):
@pytest.fixture(autouse=True, scope="class")
def built(self, builder):
builder("pyiexample")
def test_integration(self):
example_path = "_build/text/autoapi/example/index.txt"
with io.open(example_path, encoding="utf8") as example_handle:
example_file = example_handle.read()
assert "class example.Foo" in example_file
assert "class Meta" in example_file
assert "Another class var docstring" in example_file
assert "A class var without a value." in example_file
assert "method_okay(self, foo=None, bar=None)" in example_file
assert "method_multiline(self, foo=None, bar=None, baz=None)" in example_file
assert "method_without_docstring(self)" in example_file
# Are constructor arguments from the class docstring parsed?
assert "Set an attribute" in example_file
@pytest.mark.skipif(
sys.version_info < (3, 6), reason="Annotations are invalid in Python <3.5"
)
class TestPy3Module(object):
@pytest.fixture(autouse=True, scope="class")
def built(self, builder):
builder("py3example")
def test_annotations(self):
example_path = "_build/text/autoapi/example/index.txt"
with io.open(example_path, encoding="utf8") as example_handle:
example_file = example_handle.read()
assert "max_rating :int = 10" in example_file
assert "is_valid" in example_file
assert "ratings" in example_file
assert "List[int]" in example_file
assert "Dict[int, str]" in example_file
2019-08-08 06:23:21 +00:00
assert "start: int" in example_file
assert "Iterable[int]" in example_file
assert "List[Union[str, int]]" in example_file
2019-08-08 06:23:21 +00:00
assert "not_yet_a: A" in example_file
assert "is_an_a" in example_file
assert "ClassVar" in example_file
assert "instance_var" in example_file
assert "global_a :A" in example_file
def test_async(self):
example_path = "_build/text/autoapi/example/index.txt"
with io.open(example_path, encoding="utf8") as example_handle:
example_file = example_handle.read()
if sphinx.version_info >= (2, 1):
assert "async async_method" in example_file
assert "async example.async_function" in example_file
else:
assert "async_method" in example_file
assert "async_function" in example_file
@pytest.mark.skipif(
sys.version_info < (3,), reason="Annotations are not supported in astroid<2"
)
class TestAnnotationCommentsModule(object):
@pytest.fixture(autouse=True, scope="class")
def built(self, builder):
builder("pyannotationcommentsexample")
def test_integration(self):
example_path = "_build/text/autoapi/example/index.txt"
with io.open(example_path, encoding="utf8") as example_handle:
example_file = example_handle.read()
assert "max_rating :int = 10" in example_file
assert "ratings" in example_file
assert "List[int]" in example_file
assert "Dict[int, str]" in example_file
2019-08-08 06:23:21 +00:00
# When astroid>2.2.5
# assert "start: int" in example_file
# assert "end: int" in example_file
assert "Iterable[int]" in example_file
assert "List[Union[str, int]]" in example_file
2019-08-08 06:23:21 +00:00
assert "not_yet_a: A" in example_file
assert "is_an_a" in example_file
assert "ClassVar" in example_file
assert "instance_var" in example_file
assert "global_a :A" in example_file
2020-01-10 01:16:59 +00:00
@pytest.mark.skipif(
sys.version_info < (3, 8), reason="Positional only arguments need Python >=3.8"
)
class TestPositionalOnlyArgumentsModule(object):
@pytest.fixture(autouse=True, scope="class")
def built(self, builder):
builder("py38positionalparams")
def test_integration(self):
example_path = "_build/text/autoapi/example/index.txt"
with io.open(example_path, encoding="utf8") as example_handle:
example_file = example_handle.read()
assert "f_simple(a, b, /, c, d, *, e, f)" in example_file
assert (
"f_comment(a: int, b: int, /, c: Optional[int], d: Optional[int], *, e: float, f: float)"
in example_file
)
assert (
"f_annotation(a: int, b: int, /, c: Optional[int], d: Optional[int], *, e: float, f: float)"
in example_file
)
assert (
"f_arg_comment(a: int, b: int, /, c: Optional[int], d: Optional[int], *, e: float, f: float)"
in example_file
)
2020-01-10 01:16:59 +00:00
assert "f_no_cd(a: int, b: int, /, *, e: float, f: float)" in example_file
def test_napoleon_integration_loaded(builder):
confoverrides = {
2019-01-27 05:20:45 +00:00
"extensions": ["autoapi.extension", "sphinx.ext.autodoc", "sphinx.ext.napoleon"]
}
2019-01-27 05:20:45 +00:00
builder("pyexample", confoverrides=confoverrides)
2019-01-27 05:20:45 +00:00
example_path = "_build/text/autoapi/example/index.txt"
with io.open(example_path, encoding="utf8") as example_handle:
example_file = example_handle.read()
2019-01-27 05:20:45 +00:00
assert "Parameters" in example_file
2019-01-27 05:20:45 +00:00
assert "Return type" in example_file
2019-01-27 05:20:45 +00:00
assert "Args" not in example_file
class TestSimplePackage(object):
2019-01-27 05:20:45 +00:00
@pytest.fixture(autouse=True, scope="class")
def built(self, builder):
2019-01-27 05:20:45 +00:00
builder("pypackageexample")
2018-12-18 11:27:43 +00:00
def test_integration_with_package(self):
2019-01-27 05:20:45 +00:00
example_path = "_build/text/autoapi/example/index.txt"
with io.open(example_path, encoding="utf8") as example_handle:
example_file = example_handle.read()
2019-01-27 05:20:45 +00:00
assert "example.foo" in example_file
assert "example.module_level_method(foo, bar)" in example_file
2019-01-27 05:20:45 +00:00
example_foo_path = "_build/text/autoapi/example/foo/index.txt"
with io.open(example_foo_path, encoding="utf8") as example_foo_handle:
example_foo_file = example_foo_handle.read()
2019-01-27 05:20:45 +00:00
assert "class example.foo.Foo" in example_foo_file
assert "method_okay(self, foo=None, bar=None)" in example_foo_file
2019-01-27 05:20:45 +00:00
index_path = "_build/text/index.txt"
with io.open(index_path, encoding="utf8") as index_handle:
index_file = index_handle.read()
2019-04-06 18:15:18 +00:00
assert "API Reference" in index_file
2019-01-27 05:20:45 +00:00
assert "example.foo" in index_file
assert "Foo" in index_file
assert "module_level_method" in index_file
def test_simple_no_false_warnings(builder, caplog):
logger = sphinx.util.logging.getLogger("autoapi")
logger.logger.addHandler(caplog.handler)
builder("pypackageexample")
assert "Cannot resolve" not in caplog.text
def _test_class_content(builder, class_content):
2019-01-27 05:20:45 +00:00
confoverrides = {"autoapi_python_class_content": class_content}
2019-01-27 05:20:45 +00:00
builder("pyexample", confoverrides=confoverrides)
2019-01-27 05:20:45 +00:00
example_path = "_build/text/autoapi/example/index.txt"
with io.open(example_path, encoding="utf8") as example_handle:
example_file = example_handle.read()
2019-01-27 05:20:45 +00:00
if class_content == "init":
assert "Can we parse arguments" not in example_file
else:
2019-01-27 05:20:45 +00:00
assert "Can we parse arguments" in example_file
2019-01-27 05:20:45 +00:00
if class_content not in ("both", "init"):
assert "Constructor docstring" not in example_file
else:
2019-01-27 05:20:45 +00:00
assert "Constructor docstring" in example_file
def test_class_class_content(builder):
2019-01-27 05:20:45 +00:00
_test_class_content(builder, "class")
def test_both_class_content(builder):
2019-01-27 05:20:45 +00:00
_test_class_content(builder, "both")
def test_init_class_content(builder):
2019-01-27 05:20:45 +00:00
_test_class_content(builder, "init")
def test_hiding_private_members(builder):
2019-01-27 05:20:45 +00:00
confoverrides = {"autoapi_options": ["members", "undoc-members", "special-members"]}
builder("pypackageexample", confoverrides=confoverrides)
2019-01-27 05:20:45 +00:00
example_path = "_build/text/autoapi/example/index.txt"
with io.open(example_path, encoding="utf8") as example_handle:
example_file = example_handle.read()
2019-01-27 05:20:45 +00:00
assert "private" not in example_file
2019-01-27 05:20:45 +00:00
private_path = "_build/text/autoapi/example/_private_module/index.txt"
with io.open(private_path, encoding="utf8") as private_handle:
private_file = private_handle.read()
2019-01-27 05:20:45 +00:00
assert "public_method" in private_file
2018-12-18 11:27:43 +00:00
def test_hiding_inheritance(builder):
confoverrides = {"autoapi_options": ["members", "undoc-members", "special-members"]}
builder("pyexample", confoverrides=confoverrides)
example_path = "_build/text/autoapi/example/index.txt"
with io.open(example_path, encoding="utf8") as example_handle:
example_file = example_handle.read()
assert "Bases:" not in example_file
def test_hiding_imported_members(builder):
confoverrides = {"autoapi_options": ["members", "undoc-members"]}
builder("pypackagecomplex", confoverrides=confoverrides)
subpackage_path = "_build/text/autoapi/complex/subpackage/index.txt"
with io.open(subpackage_path, encoding="utf8") as subpackage_handle:
subpackage_file = subpackage_handle.read()
assert "Part of a public resolution chain." not in subpackage_file
package_path = "_build/text/autoapi/complex/index.txt"
with io.open(package_path, encoding="utf8") as package_handle:
package_file = package_handle.read()
assert "Part of a public resolution chain." not in package_file
submodule_path = "_build/text/autoapi/complex/subpackage/submodule/index.txt"
with io.open(submodule_path, encoding="utf8") as submodule_handle:
submodule_file = submodule_handle.read()
assert "A private function made public by import." not in submodule_file
def test_inherited_members(builder):
confoverrides = {
"autoapi_options": ["members", "inherited-members", "undoc-members"]
}
builder("pyexample", confoverrides=confoverrides)
example_path = "_build/text/autoapi/example/index.txt"
with io.open(example_path, encoding="utf8") as example_handle:
example_file = example_handle.read()
assert "class example.Bar" in example_file
i = example_file.index("class example.Bar")
assert "method_okay" in example_file[i:]
def test_skipping_members(builder):
2019-09-04 21:44:35 +00:00
builder("pyskipexample")
example_path = "_build/text/autoapi/example/index.txt"
with io.open(example_path, encoding="utf8") as example_handle:
example_file = example_handle.read()
assert "foo doc" not in example_file
assert "bar doc" not in example_file
assert "bar m doc" not in example_file
assert "baf doc" in example_file
assert "baf m doc" not in example_file
assert "baz doc" not in example_file
assert "not ignored" in example_file
@pytest.mark.parametrize(
"value,order",
[
2020-06-13 08:33:53 +00:00
("bysource", [".Foo", ".decorator_okay", ".Bar"]),
("alphabetical", [".Bar", ".Foo", ".decorator_okay"]),
("groupwise", [".Bar", ".Foo", ".decorator_okay"]),
],
)
def test_order_members(builder, value, order):
confoverrides = {"autoapi_member_order": value}
builder("pyexample", confoverrides=confoverrides)
example_path = "_build/text/autoapi/example/index.txt"
with io.open(example_path, encoding="utf8") as example_handle:
example_file = example_handle.read()
indexes = [example_file.index(name) for name in order]
assert indexes == sorted(indexes)
2019-09-04 21:44:35 +00:00
class _CompareInstanceType(object):
def __init__(self, type_):
self.type = type_
def __eq__(self, other):
return self.type is type(other)
def __repr__(self):
return "<expect type {}>".format(self.type.__name__)
def test_skip_members_hook(builder):
emit_firstresult_patch = Mock(name="emit_firstresult_patch", return_value=False)
with patch("sphinx.application.Sphinx.emit_firstresult", emit_firstresult_patch):
builder("pyskipexample")
options = ["members", "undoc-members", "special-members"]
mock_calls = [
call(
"autoapi-skip-member",
"module",
"example",
_CompareInstanceType(PythonModule),
False,
options,
),
call(
"autoapi-skip-member",
"function",
"example.foo",
_CompareInstanceType(PythonFunction),
False,
options,
),
call(
"autoapi-skip-member",
"class",
"example.Bar",
_CompareInstanceType(PythonClass),
False,
options,
),
call(
"autoapi-skip-member",
"class",
"example.Baf",
_CompareInstanceType(PythonClass),
False,
options,
),
call(
"autoapi-skip-member",
"data",
"example.baz",
_CompareInstanceType(PythonData),
False,
options,
),
call(
"autoapi-skip-member",
"data",
"example.anchor",
_CompareInstanceType(PythonData),
False,
options,
),
call(
"autoapi-skip-member",
"method",
"example.Bar.m",
_CompareInstanceType(PythonMethod),
False,
options,
),
call(
"autoapi-skip-member",
"method",
"example.Baf.m",
_CompareInstanceType(PythonMethod),
False,
options,
),
]
for mock_call in mock_calls:
assert mock_call in emit_firstresult_patch.mock_calls
2018-12-18 11:27:43 +00:00
class TestComplexPackage(object):
2019-01-27 05:20:45 +00:00
@pytest.fixture(autouse=True, scope="class")
2018-12-18 11:27:43 +00:00
def built(self, builder):
2019-01-27 05:20:45 +00:00
builder("pypackagecomplex")
2018-12-18 11:27:43 +00:00
def test_public_chain_resolves(self):
2019-01-27 05:20:45 +00:00
submodule_path = "_build/text/autoapi/complex/subpackage/submodule/index.txt"
with io.open(submodule_path, encoding="utf8") as submodule_handle:
2018-12-18 11:27:43 +00:00
submodule_file = submodule_handle.read()
assert "Part of a public resolution chain." in submodule_file
2019-01-27 05:20:45 +00:00
subpackage_path = "_build/text/autoapi/complex/subpackage/index.txt"
with io.open(subpackage_path, encoding="utf8") as subpackage_handle:
2018-12-18 11:27:43 +00:00
subpackage_file = subpackage_handle.read()
assert "Part of a public resolution chain." in subpackage_file
2019-01-27 05:20:45 +00:00
package_path = "_build/text/autoapi/complex/index.txt"
with io.open(package_path, encoding="utf8") as package_handle:
2018-12-18 11:27:43 +00:00
package_file = package_handle.read()
assert "Part of a public resolution chain." in package_file
def test_private_made_public(self):
2019-01-27 05:20:45 +00:00
submodule_path = "_build/text/autoapi/complex/subpackage/submodule/index.txt"
with io.open(submodule_path, encoding="utf8") as submodule_handle:
2018-12-18 11:27:43 +00:00
submodule_file = submodule_handle.read()
assert "A private function made public by import." in submodule_file
def test_multiple_import_locations(self):
2019-01-27 05:20:45 +00:00
submodule_path = "_build/text/autoapi/complex/subpackage/submodule/index.txt"
with io.open(submodule_path, encoding="utf8") as submodule_handle:
2018-12-18 11:27:43 +00:00
submodule_file = submodule_handle.read()
assert "A public function imported in multiple places." in submodule_file
2019-01-27 05:20:45 +00:00
subpackage_path = "_build/text/autoapi/complex/subpackage/index.txt"
with io.open(subpackage_path, encoding="utf8") as subpackage_handle:
2018-12-18 11:27:43 +00:00
subpackage_file = subpackage_handle.read()
assert "A public function imported in multiple places." in subpackage_file
2019-01-27 05:20:45 +00:00
package_path = "_build/text/autoapi/complex/index.txt"
with io.open(package_path, encoding="utf8") as package_handle:
2018-12-18 11:27:43 +00:00
package_file = package_handle.read()
assert "A public function imported in multiple places." in package_file
def test_simple_wildcard_imports(self):
2019-01-27 05:20:45 +00:00
wildcard_path = "_build/text/autoapi/complex/wildcard/index.txt"
with io.open(wildcard_path, encoding="utf8") as wildcard_handle:
2018-12-18 11:27:43 +00:00
wildcard_file = wildcard_handle.read()
assert "public_chain" in wildcard_file
assert "now_public_function" in wildcard_file
assert "public_multiple_imports" in wildcard_file
assert "module_level_method" in wildcard_file
def test_wildcard_chain(self):
2019-01-27 05:20:45 +00:00
wildcard_path = "_build/text/autoapi/complex/wildchain/index.txt"
with io.open(wildcard_path, encoding="utf8") as wildcard_handle:
2018-12-18 11:27:43 +00:00
wildcard_file = wildcard_handle.read()
assert "public_chain" in wildcard_file
assert "module_level_method" in wildcard_file
def test_wildcard_all_imports(self):
2019-01-27 05:20:45 +00:00
wildcard_path = "_build/text/autoapi/complex/wildall/index.txt"
with io.open(wildcard_path, encoding="utf8") as wildcard_handle:
wildcard_file = wildcard_handle.read()
assert "not_all" not in wildcard_file
assert "NotAllClass" not in wildcard_file
assert "does_not_exist" not in wildcard_file
assert "SimpleClass" in wildcard_file
assert "simple_function" in wildcard_file
assert "public_chain" in wildcard_file
assert "module_level_method" in wildcard_file
def test_no_imports_in_module_with_all(self):
foo_path = "_build/text/autoapi/complex/foo/index.txt"
with io.open(foo_path, encoding="utf8") as foo_handle:
foo_file = foo_handle.read()
assert "module_level_method" not in foo_file
def test_all_overrides_import_in_module_with_all(self):
foo_path = "_build/text/autoapi/complex/foo/index.txt"
with io.open(foo_path, encoding="utf8") as foo_handle:
foo_file = foo_handle.read()
assert "PublicClass" in foo_file
2019-08-25 22:53:58 +00:00
def test_parses_unicode_file(self):
foo_path = "_build/text/autoapi/complex/unicode_data/index.txt"
with io.open(foo_path, encoding="utf8") as foo_handle:
foo_file = foo_handle.read()
assert "unicode_str" in foo_file
2019-10-15 08:41:09 +00:00
@pytest.mark.skipif(
sys.version_info < (3, 3), reason="Implicit namespace not supported in python < 3.3"
)
class TestImplicitNamespacePackage(object):
@pytest.fixture(autouse=True, scope="class")
def built(self, builder):
builder("py3implicitnamespace")
def test_sibling_import_from_namespace(self):
example_path = "_build/text/autoapi/namespace/example/index.txt"
with io.open(example_path, encoding="utf8") as example_handle:
example_file = example_handle.read()
assert "namespace.example.first_method" in example_file
def test_sub_sibling_import_from_namespace(self):
example_path = "_build/text/autoapi/namespace/example/index.txt"
with io.open(example_path, encoding="utf8") as example_handle:
example_file = example_handle.read()
assert "namespace.example.second_sub_method" in example_file