sphinx-autoapi/tests/python/test_pyintegration.py
2020-10-26 16:13:38 -07:00

756 lines
25 KiB
Python

import io
import os
import re
import shutil
import sys
from mock import patch, Mock, call
import pytest
import sphinx
from sphinx.application import Sphinx
import sphinx.util.logging
from autoapi.mappers.python import (
PythonModule,
PythonFunction,
PythonClass,
PythonData,
PythonMethod,
)
def rebuild(confoverrides=None, **kwargs):
app = Sphinx(
srcdir=".",
confdir=".",
outdir="_build/text",
doctreedir="_build/.doctrees",
buildername="text",
confoverrides=confoverrides,
**kwargs
)
app.build()
@pytest.fixture(scope="class")
def builder():
cwd = os.getcwd()
def build(test_dir, confoverrides=None, **kwargs):
os.chdir("tests/python/{0}".format(test_dir))
rebuild(confoverrides=confoverrides, **kwargs)
yield build
try:
shutil.rmtree("_build")
finally:
os.chdir(cwd)
class TestSimpleModule(object):
@pytest.fixture(autouse=True, scope="class")
def built(self, builder):
builder("pyexample")
def test_integration(self):
self.check_integration("_build/text/autoapi/example/index.txt")
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()
assert "@example.decorator_okay" in example_file
def check_integration(self, example_path):
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 "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?
assert "Set an attribute" in example_file
# "self" should not be included in constructor arguments
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
assert not os.path.exists("_build/text/autoapi/method_multiline")
index_path = "_build/text/index.txt"
with io.open(index_path, encoding="utf8") as index_handle:
index_file = index_handle.read()
assert "API Reference" in index_file
assert "Foo" in index_file
assert "Meta" in index_file
def test_napoleon_integration_not_loaded(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()
# Check that docstrings are not transformed without napoleon loaded
assert "Args" in example_file
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
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()
# Are pyi files preferred
assert "DoNotFindThis" not in example_file
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
class TestSimpleStubModuleNotPreferred(object):
@pytest.fixture(autouse=True, scope="class")
def built(self, builder):
builder("pyiexample2")
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()
# Are py files preferred
assert "DoNotFindThis" not in example_file
assert "Foo" in example_file
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
assert "start: int" in example_file
assert "Iterable[int]" in example_file
assert "List[Union[str, int]]" in example_file
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
assert "my_method(self) -> str" in example_file
assert "class example.SomeMetaclass" in example_file
def test_overload(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 "overloaded_func(a: float" in example_file
assert "overloaded_func(a: str" in example_file
assert "overloaded_func(a: Union" not in example_file
assert "Overloaded function" in example_file
assert "overloaded_method(self, a: float" in example_file
assert "overloaded_method(self, a: str" in example_file
assert "overloaded_method(self, a: Union" not in example_file
assert "Overloaded method" in example_file
assert "overloaded_class_method(cls, a: float" in example_file
assert "overloaded_class_method(cls, a: str" in example_file
assert "overloaded_class_method(cls, a: Union" not in example_file
assert "Overloaded method" in example_file
assert "undoc_overloaded_func" in example_file
assert "undoc_overloaded_method" 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()
assert "async async_method" in example_file
assert "async example.async_function" in example_file
def test_py3_hiding_undoc_overloaded_members(builder):
confoverrides = {"autoapi_options": ["members", "special-members"]}
builder("py3example", 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 "overloaded_func" in example_file
assert "overloaded_method" in example_file
assert "undoc_overloaded_func" not in example_file
assert "undoc_overloaded_method" not in example_file
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
# 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
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
@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
)
assert "f_no_cd(a: int, b: int, /, *, e: float, f: float)" in example_file
def test_napoleon_integration_loaded(builder):
confoverrides = {
"extensions": ["autoapi.extension", "sphinx.ext.autodoc", "sphinx.ext.napoleon"]
}
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 "Parameters" in example_file
assert "Return type" in example_file
assert "Args" not in example_file
class TestSimplePackage(object):
@pytest.fixture(autouse=True, scope="class")
def built(self, builder):
builder("pypackageexample")
def test_integration_with_package(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 "example.foo" in example_file
assert "example.module_level_method(foo, bar)" in example_file
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()
assert "class example.foo.Foo" in example_foo_file
assert "method_okay(self, foo=None, bar=None)" in example_foo_file
index_path = "_build/text/index.txt"
with io.open(index_path, encoding="utf8") as index_handle:
index_file = index_handle.read()
assert "API Reference" in index_file
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):
confoverrides = {"autoapi_python_class_content": class_content}
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()
if class_content == "init":
assert "Can we parse arguments" not in example_file
else:
assert "Can we parse arguments" in example_file
if class_content not in ("both", "init"):
assert "Constructor docstring" not in example_file
else:
assert "Constructor docstring" in example_file
def test_class_class_content(builder):
_test_class_content(builder, "class")
def test_both_class_content(builder):
_test_class_content(builder, "both")
def test_init_class_content(builder):
_test_class_content(builder, "init")
def test_hiding_private_members(builder):
confoverrides = {"autoapi_options": ["members", "undoc-members", "special-members"]}
builder("pypackageexample", 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 "private" not in example_file
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()
assert "public_method" in private_file
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):
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",
[
("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)
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
class TestComplexPackage(object):
@pytest.fixture(autouse=True, scope="class")
def built(self, builder):
builder("pypackagecomplex")
def test_public_chain_resolves(self):
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 "Part of a public resolution chain." in submodule_file
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." 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." in package_file
def test_private_made_public(self):
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." in submodule_file
def test_multiple_import_locations(self):
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 public function imported in multiple places." in submodule_file
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 "A public function imported in multiple places." 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 "A public function imported in multiple places." in package_file
def test_simple_wildcard_imports(self):
wildcard_path = "_build/text/autoapi/complex/wildcard/index.txt"
with io.open(wildcard_path, encoding="utf8") as wildcard_handle:
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):
wildcard_path = "_build/text/autoapi/complex/wildchain/index.txt"
with io.open(wildcard_path, encoding="utf8") as wildcard_handle:
wildcard_file = wildcard_handle.read()
assert "public_chain" in wildcard_file
assert "module_level_method" in wildcard_file
def test_wildcard_all_imports(self):
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
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
class TestComplexPackageParallel(object):
@pytest.fixture(autouse=True, scope="class")
def built(self, builder):
builder("pypackagecomplex", parallel=2)
def test_caching(builder):
mtimes = (0, 0)
def record_mtime():
nonlocal mtimes
mtime = 0
for root, _, files in os.walk("_build/text/autoapi"):
for name in files:
this_mtime = os.path.getmtime(os.path.join(root, name))
mtime = max(mtime, this_mtime)
mtimes = (*mtimes[1:], mtime)
builder("pypackagecomplex", confoverrides={"autoapi_keep_files": True})
record_mtime()
rebuild(confoverrides={"autoapi_keep_files": True})
record_mtime()
assert mtimes[1] == mtimes[0]
# Check that adding a file rebuilds the docs
extra_file = "complex/new.py"
with open(extra_file, "w") as out_f:
out_f.write("\n")
try:
rebuild(confoverrides={"autoapi_keep_files": True})
finally:
os.remove(extra_file)
record_mtime()
assert mtimes[1] != mtimes[0]
# Removing a file also rebuilds the docs
rebuild(confoverrides={"autoapi_keep_files": True})
record_mtime()
assert mtimes[1] != mtimes[0]
# Changing not keeping files always builds
rebuild()
record_mtime()
assert mtimes[1] != mtimes[0]
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