"""Test the autodoc extension.

This tests mainly the Documenters; the auto directives are tested in a test
source file translated by test_build.
"""

import sys
from unittest.mock import Mock
from warnings import catch_warnings

import pytest
from docutils.statemachine import ViewList

from sphinx import addnodes
from sphinx.ext.autodoc import ALL, ModuleLevelDocumenter, Options
from sphinx.ext.autodoc.directive import DocumenterBridge, process_documenter_options
from sphinx.testing.util import SphinxTestApp, Struct  # NOQA
from sphinx.util.docutils import LoggingReporter

try:
    # Enable pyximport to test cython module
    import pyximport
    pyximport.install()
except ImportError:
    pyximport = None


def do_autodoc(app, objtype, name, options=None):
    if options is None:
        options = {}
    app.env.temp_data.setdefault('docname', 'index')  # set dummy docname
    doccls = app.registry.documenters[objtype]
    docoptions = process_documenter_options(doccls, app.config, options)
    state = Mock()
    state.document.settings.tab_width = 8
    bridge = DocumenterBridge(app.env, LoggingReporter(''), docoptions, 1, state)
    documenter = doccls(bridge, name)
    documenter.generate()

    return bridge.result


def make_directive_bridge(env):
    options = Options(
        inherited_members = False,
        undoc_members = False,
        private_members = False,
        special_members = False,
        imported_members = False,
        show_inheritance = False,
        noindex = False,
        annotation = None,
        synopsis = '',
        platform = '',
        deprecated = False,
        members = [],
        member_order = 'alphabetical',
        exclude_members = set(),
        ignore_module_all = False,
    )

    directive = Struct(
        env = env,
        genopt = options,
        result = ViewList(),
        record_dependencies = set(),
        state = Mock(),
    )
    directive.state.document.settings.tab_width = 8

    return directive


processed_signatures = []


def process_signature(app, what, name, obj, options, args, retann):
    processed_signatures.append((what, name))
    if name == 'bar':
        return '42', None


def skip_member(app, what, name, obj, skip, options):
    if name in ('__special1__', '__special2__'):
        return skip
    if name.startswith('__'):
        return True
    if name == 'skipmeth':
        return True


def test_parse_name(app):
    def verify(objtype, name, result):
        inst = app.registry.documenters[objtype](directive, name)
        assert inst.parse_name()
        assert (inst.modname, inst.objpath, inst.args, inst.retann) == result

    directive = make_directive_bridge(app.env)

    # for modules
    verify('module', 'test_ext_autodoc', ('test_ext_autodoc', [], None, None))
    verify('module', 'test.test_ext_autodoc', ('test.test_ext_autodoc', [], None, None))
    verify('module', 'test(arg)', ('test', [], 'arg', None))
    assert 'signature arguments' in app._warning.getvalue()

    # for functions/classes
    verify('function', 'test_ext_autodoc.raises',
           ('test_ext_autodoc', ['raises'], None, None))
    verify('function', 'test_ext_autodoc.raises(exc) -> None',
           ('test_ext_autodoc', ['raises'], 'exc', 'None'))
    directive.env.temp_data['autodoc:module'] = 'test_ext_autodoc'
    verify('function', 'raises', ('test_ext_autodoc', ['raises'], None, None))
    del directive.env.temp_data['autodoc:module']
    directive.env.ref_context['py:module'] = 'test_ext_autodoc'
    verify('function', 'raises', ('test_ext_autodoc', ['raises'], None, None))
    verify('class', 'Base', ('test_ext_autodoc', ['Base'], None, None))

    # for members
    directive.env.ref_context['py:module'] = 'sphinx.testing.util'
    verify('method', 'SphinxTestApp.cleanup',
           ('sphinx.testing.util', ['SphinxTestApp', 'cleanup'], None, None))
    directive.env.ref_context['py:module'] = 'sphinx.testing.util'
    directive.env.ref_context['py:class'] = 'Foo'
    directive.env.temp_data['autodoc:class'] = 'SphinxTestApp'
    verify('method', 'cleanup',
           ('sphinx.testing.util', ['SphinxTestApp', 'cleanup'], None, None))
    verify('method', 'SphinxTestApp.cleanup',
           ('sphinx.testing.util', ['SphinxTestApp', 'cleanup'], None, None))


def test_format_signature(app):
    app.connect('autodoc-process-signature', process_signature)
    app.connect('autodoc-skip-member', skip_member)

    directive = make_directive_bridge(app.env)

    def formatsig(objtype, name, obj, args, retann):
        inst = app.registry.documenters[objtype](directive, name)
        inst.fullname = name
        inst.doc_as_attr = False  # for class objtype
        inst.parent = object  # dummy
        inst.object = obj
        inst.objpath = [name]
        inst.args = args
        inst.retann = retann
        res = inst.format_signature()
        print(res)
        return res

    # no signatures for modules
    assert formatsig('module', 'test', None, None, None) == ''

    # test for functions
    def f(a, b, c=1, **d):
        pass

    def g(a='\n'):
        pass
    assert formatsig('function', 'f', f, None, None) == '(a, b, c=1, **d)'
    assert formatsig('function', 'f', f, 'a, b, c, d', None) == '(a, b, c, d)'
    assert formatsig('function', 'g', g, None, None) == r"(a='\n')"

    # test for classes
    class D:
        pass

    class E:
        def __init__(self):
            pass

    # an empty init and no init are the same
    for C in (D, E):
        assert formatsig('class', 'D', C, None, None) == '()'

    class SomeMeta(type):
        def __call__(cls, a, b=None):
            return type.__call__(cls, a, b)

    # these three are all equivalent
    class F:
        def __init__(self, a, b=None):
            pass

    class FNew:
        def __new__(cls, a, b=None):
            return super().__new__(cls)

    class FMeta(metaclass=SomeMeta):
        pass

    # and subclasses should always inherit
    class G(F):
        pass

    class GNew(FNew):
        pass

    class GMeta(FMeta):
        pass

    # subclasses inherit
    for C in (F, FNew, FMeta, G, GNew, GMeta):
        assert formatsig('class', 'C', C, None, None) == '(a, b=None)'
    assert formatsig('class', 'C', D, 'a, b', 'X') == '(a, b) -> X'

    class ListSubclass(list):
        pass

    # only supported if the python implementation decides to document it
    if getattr(list, '__text_signature__', None) is not None:
        assert formatsig('class', 'C', ListSubclass, None, None) == '(iterable=(), /)'
    else:
        assert formatsig('class', 'C', ListSubclass, None, None) == ''

    class ExceptionSubclass(Exception):
        pass

    # Exception has no __text_signature__ at least in Python 3.8
    if getattr(Exception, '__text_signature__', None) is None:
        assert formatsig('class', 'C', ExceptionSubclass, None, None) == ''

    # __init__ have signature at first line of docstring
    directive.env.config.autoclass_content = 'both'

    class F2:
        '''some docstring for F2.'''
        def __init__(self, *args, **kw):
            '''
            __init__(a1, a2, kw1=True, kw2=False)

            some docstring for __init__.
            '''
    class G2(F2):
        pass

    assert formatsig('class', 'F2', F2, None, None) == \
        '(a1, a2, kw1=True, kw2=False)'
    assert formatsig('class', 'G2', G2, None, None) == \
        '(a1, a2, kw1=True, kw2=False)'

    # test for methods
    class H:
        def foo1(self, b, *c):
            pass

        def foo2(b, *c):
            pass

        def foo3(self, d='\n'):
            pass
    assert formatsig('method', 'H.foo', H.foo1, None, None) == '(b, *c)'
    assert formatsig('method', 'H.foo', H.foo1, 'a', None) == '(a)'
    assert formatsig('method', 'H.foo', H.foo2, None, None) == '(*c)'
    assert formatsig('method', 'H.foo', H.foo3, None, None) == r"(d='\n')"

    # test bound methods interpreted as functions
    assert formatsig('function', 'foo', H().foo1, None, None) == '(b, *c)'
    assert formatsig('function', 'foo', H().foo2, None, None) == '(*c)'
    assert formatsig('function', 'foo', H().foo3, None, None) == r"(d='\n')"

    # test exception handling (exception is caught and args is '')
    directive.env.config.autodoc_docstring_signature = False
    assert formatsig('function', 'int', int, None, None) == ''

    # test processing by event handler
    assert formatsig('method', 'bar', H.foo1, None, None) == '42'

    # test functions created via functools.partial
    from functools import partial
    curried1 = partial(lambda a, b, c: None, 'A')
    assert formatsig('function', 'curried1', curried1, None, None) == \
        '(b, c)'
    curried2 = partial(lambda a, b, c=42: None, 'A')
    assert formatsig('function', 'curried2', curried2, None, None) == \
        '(b, c=42)'
    curried3 = partial(lambda a, b, *c: None, 'A')
    assert formatsig('function', 'curried3', curried3, None, None) == \
        '(b, *c)'
    curried4 = partial(lambda a, b, c=42, *d, **e: None, 'A')
    assert formatsig('function', 'curried4', curried4, None, None) == \
        '(b, c=42, *d, **e)'


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_process_signature_typing_generic(app):
    actual = do_autodoc(app, 'class', 'target.generic_class.A', {})

    assert list(actual) == [
        '',
        '.. py:class:: A(a, b=None)',
        '   :module: target.generic_class',
        '',
        '   docstring for A',
        '',
    ]


def test_autodoc_process_signature_typehints(app):
    captured = []

    def process_signature(*args):
        captured.append(args)

    app.connect('autodoc-process-signature', process_signature)

    def func(x: int, y: int) -> int:
        pass

    directive = make_directive_bridge(app.env)
    inst = app.registry.documenters['function'](directive, 'func')
    inst.fullname = 'func'
    inst.object = func
    inst.objpath = ['func']
    inst.format_signature()
    assert captured == [(app, 'function', 'func', func,
                         directive.genopt, '(x: int, y: int)', 'int')]


def test_get_doc(app):
    directive = make_directive_bridge(app.env)

    def getdocl(objtype, obj):
        inst = app.registry.documenters[objtype](directive, 'tmp')
        inst.parent = object  # dummy
        inst.object = obj
        inst.objpath = [obj.__name__]
        inst.doc_as_attr = False
        inst.format_signature()  # handle docstring signatures!
        ds = inst.get_doc()
        # for testing purposes, concat them and strip the empty line at the end
        res = sum(ds, [])[:-1]
        print(res)
        return res

    # objects without docstring
    def f():
        pass
    assert getdocl('function', f) == []

    # standard function, diverse docstring styles...
    def f():
        """Docstring"""
    def g():
        """
        Docstring
        """
    for func in (f, g):
        assert getdocl('function', func) == ['Docstring']

    # first line vs. other lines indentation
    def f():
        """First line

        Other
          lines
        """
    assert getdocl('function', f) == ['First line', '', 'Other', '  lines']

    # charset guessing (this module is encoded in utf-8)
    def f():
        """Döcstring"""
    assert getdocl('function', f) == ['Döcstring']

    # verify that method docstrings get extracted in both normal case
    # and in case of bound method posing as a function
    class J:  # NOQA
        def foo(self):
            """Method docstring"""
    assert getdocl('method', J.foo) == ['Method docstring']
    assert getdocl('function', J().foo) == ['Method docstring']


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_new_documenter(app):
    class MyDocumenter(ModuleLevelDocumenter):
        objtype = 'integer'
        directivetype = 'integer'
        priority = 100

        @classmethod
        def can_document_member(cls, member, membername, isattr, parent):
            return isinstance(member, int)

        def document_members(self, all_members=False):
            return

    app.add_autodocumenter(MyDocumenter)

    options = {"members": 'integer'}
    actual = do_autodoc(app, 'module', 'target', options)
    assert list(actual) == [
        '',
        '.. py:module:: target',
        '',
        '',
        '.. py:integer:: integer',
        '   :module: target',
        '',
        '   documentation for the integer',
        '',
    ]


def test_attrgetter_using(app):
    from target import Class
    from target.inheritance import Derived

    directive = make_directive_bridge(app.env)

    def assert_getter_works(objtype, name, obj, attrs=[], **kw):
        getattr_spy = []

        def special_getattr(obj, name, *defargs):
            if name in attrs:
                getattr_spy.append((obj, name))
                return None
            return getattr(obj, name, *defargs)
        app.add_autodoc_attrgetter(type, special_getattr)

        del getattr_spy[:]
        inst = app.registry.documenters[objtype](directive, name)
        inst.generate(**kw)

        hooked_members = [s[1] for s in getattr_spy]
        documented_members = [s[1] for s in processed_signatures]
        for attr in attrs:
            fullname = '.'.join((name, attr))
            assert attr in hooked_members
            assert fullname not in documented_members, \
                '%r was not hooked by special_attrgetter function' % fullname

    with catch_warnings(record=True):
        directive.genopt['members'] = ALL
        directive.genopt['inherited_members'] = False
        print(directive.genopt)
        assert_getter_works('class', 'target.Class', Class, ['meth'])

        directive.genopt['inherited_members'] = True
        assert_getter_works('class', 'target.inheritance.Derived', Derived, ['inheritedmeth'])


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_py_module(app, warning):
    # without py:module
    actual = do_autodoc(app, 'method', 'Class.meth')
    assert list(actual) == []
    assert ("don't know which module to import for autodocumenting 'Class.meth'"
            in warning.getvalue())

    # with py:module
    app.env.ref_context['py:module'] = 'target'
    warning.truncate(0)

    actual = do_autodoc(app, 'method', 'Class.meth')
    assert list(actual) == [
        '',
        '.. py:method:: Class.meth()',
        '   :module: target',
        '',
        '   Function.',
        '',
    ]
    assert ("don't know which module to import for autodocumenting 'Class.meth'"
            not in warning.getvalue())


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_decorator(app):
    actual = do_autodoc(app, 'decorator', 'target.decorator.deco1')
    assert list(actual) == [
        '',
        '.. py:decorator:: deco1',
        '   :module: target.decorator',
        '',
        '   docstring for deco1',
        '',
    ]

    actual = do_autodoc(app, 'decorator', 'target.decorator.deco2')
    assert list(actual) == [
        '',
        '.. py:decorator:: deco2(condition, message)',
        '   :module: target.decorator',
        '',
        '   docstring for deco2',
        '',
    ]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_exception(app):
    actual = do_autodoc(app, 'exception', 'target.CustomEx')
    assert list(actual) == [
        '',
        '.. py:exception:: CustomEx',
        '   :module: target',
        '',
        '   My custom exception.',
        '',
    ]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_warnings(app, warning):
    app.env.temp_data['docname'] = 'dummy'

    # can't import module
    do_autodoc(app, 'module', 'unknown')
    assert "failed to import module 'unknown'" in warning.getvalue()

    # missing function
    do_autodoc(app, 'function', 'unknown')
    assert "import for autodocumenting 'unknown'" in warning.getvalue()

    do_autodoc(app, 'function', 'target.unknown')
    assert "failed to import function 'unknown' from module 'target'" in warning.getvalue()

    # missing method
    do_autodoc(app, 'method', 'target.Class.unknown')
    assert "failed to import method 'Class.unknown' from module 'target'" in warning.getvalue()


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_attributes(app):
    options = {"synopsis": 'Synopsis',
               "platform": "Platform",
               "deprecated": None}
    actual = do_autodoc(app, 'module', 'target', options)
    assert list(actual) == [
        '',
        '.. py:module:: target',
        '   :synopsis: Synopsis',
        '   :platform: Platform',
        '   :deprecated:',
        ''
    ]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_members(app):
    # default (no-members)
    actual = do_autodoc(app, 'class', 'target.inheritance.Base')
    assert list(filter(lambda l: '::' in l, actual)) == [
        '.. py:class:: Base()',
    ]

    # default ALL-members
    options = {"members": None}
    actual = do_autodoc(app, 'class', 'target.inheritance.Base', options)
    assert list(filter(lambda l: '::' in l, actual)) == [
        '.. py:class:: Base()',
        '   .. py:method:: Base.inheritedclassmeth()',
        '   .. py:method:: Base.inheritedmeth()',
        '   .. py:method:: Base.inheritedstaticmeth(cls)'
    ]

    # default specific-members
    options = {"members": "inheritedmeth,inheritedstaticmeth"}
    actual = do_autodoc(app, 'class', 'target.inheritance.Base', options)
    assert list(filter(lambda l: '::' in l, actual)) == [
        '.. py:class:: Base()',
        '   .. py:method:: Base.inheritedmeth()',
        '   .. py:method:: Base.inheritedstaticmeth(cls)'
    ]

    # ALL-members override autodoc_default_options
    options = {"members": None}
    app.config.autodoc_default_options["members"] = "inheritedstaticmeth"
    actual = do_autodoc(app, 'class', 'target.inheritance.Base', options)
    assert list(filter(lambda l: '::' in l, actual)) == [
        '.. py:class:: Base()',
        '   .. py:method:: Base.inheritedclassmeth()',
        '   .. py:method:: Base.inheritedmeth()',
        '   .. py:method:: Base.inheritedstaticmeth(cls)'
    ]

    # members override autodoc_default_options
    options = {"members": "inheritedmeth"}
    app.config.autodoc_default_options["members"] = "inheritedstaticmeth"
    actual = do_autodoc(app, 'class', 'target.inheritance.Base', options)
    assert list(filter(lambda l: '::' in l, actual)) == [
        '.. py:class:: Base()',
        '   .. py:method:: Base.inheritedmeth()',
    ]

    # members extends autodoc_default_options
    options = {"members": "+inheritedmeth"}
    app.config.autodoc_default_options["members"] = "inheritedstaticmeth"
    actual = do_autodoc(app, 'class', 'target.inheritance.Base', options)
    assert list(filter(lambda l: '::' in l, actual)) == [
        '.. py:class:: Base()',
        '   .. py:method:: Base.inheritedmeth()',
        '   .. py:method:: Base.inheritedstaticmeth(cls)'
    ]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_exclude_members(app):
    options = {"members": None,
               "exclude-members": "inheritedmeth,inheritedstaticmeth"}
    actual = do_autodoc(app, 'class', 'target.inheritance.Base', options)
    assert list(filter(lambda l: '::' in l, actual)) == [
        '.. py:class:: Base()',
        '   .. py:method:: Base.inheritedclassmeth()'
    ]

    # members vs exclude-members
    options = {"members": "inheritedmeth",
               "exclude-members": "inheritedmeth"}
    actual = do_autodoc(app, 'class', 'target.inheritance.Base', options)
    assert list(filter(lambda l: '::' in l, actual)) == [
        '.. py:class:: Base()',
    ]

    # + has no effect when autodoc_default_options are not present
    options = {"members": None,
               "exclude-members": "+inheritedmeth,inheritedstaticmeth"}
    actual = do_autodoc(app, 'class', 'target.inheritance.Base', options)
    assert list(filter(lambda l: '::' in l, actual)) == [
        '.. py:class:: Base()',
        '   .. py:method:: Base.inheritedclassmeth()'
    ]

    # exclude-members overrides autodoc_default_options
    options = {"members": None,
               "exclude-members": "inheritedmeth"}
    app.config.autodoc_default_options["exclude-members"] = "inheritedstaticmeth"
    actual = do_autodoc(app, 'class', 'target.inheritance.Base', options)
    assert list(filter(lambda l: '::' in l, actual)) == [
        '.. py:class:: Base()',
        '   .. py:method:: Base.inheritedclassmeth()',
        '   .. py:method:: Base.inheritedstaticmeth(cls)'
    ]

    # exclude-members extends autodoc_default_options
    options = {"members": None,
               "exclude-members": "+inheritedmeth"}
    app.config.autodoc_default_options["exclude-members"] = "inheritedstaticmeth"
    actual = do_autodoc(app, 'class', 'target.inheritance.Base', options)
    assert list(filter(lambda l: '::' in l, actual)) == [
        '.. py:class:: Base()',
        '   .. py:method:: Base.inheritedclassmeth()',
    ]

    # no exclude-members causes use autodoc_default_options
    options = {"members": None}
    app.config.autodoc_default_options["exclude-members"] = "inheritedstaticmeth,inheritedmeth"
    actual = do_autodoc(app, 'class', 'target.inheritance.Base', options)
    assert list(filter(lambda l: '::' in l, actual)) == [
        '.. py:class:: Base()',
        '   .. py:method:: Base.inheritedclassmeth()',
    ]

    # empty exclude-members cancels autodoc_default_options
    options = {"members": None,
               "exclude-members": None}
    app.config.autodoc_default_options["exclude-members"] = "inheritedstaticmeth,inheritedmeth"
    actual = do_autodoc(app, 'class', 'target.inheritance.Base', options)
    assert list(filter(lambda l: '::' in l, actual)) == [
        '.. py:class:: Base()',
        '   .. py:method:: Base.inheritedclassmeth()',
        '   .. py:method:: Base.inheritedmeth()',
        '   .. py:method:: Base.inheritedstaticmeth(cls)'
    ]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_undoc_members(app):
    options = {"members": None,
               "undoc-members": None}
    actual = do_autodoc(app, 'class', 'target.Class', options)
    assert list(filter(lambda l: '::' in l, actual)) == [
        '.. py:class:: Class(arg)',
        '   .. py:attribute:: Class.attr',
        '   .. py:attribute:: Class.docattr',
        '   .. py:method:: Class.excludemeth()',
        '   .. py:attribute:: Class.inst_attr_comment',
        '   .. py:attribute:: Class.inst_attr_inline',
        '   .. py:attribute:: Class.inst_attr_string',
        '   .. py:attribute:: Class.mdocattr',
        '   .. py:method:: Class.meth()',
        '   .. py:method:: Class.moore(a, e, f) -> happiness',
        '   .. py:method:: Class.roger(a, *, b=2, c=3, d=4, e=5, f=6)',
        '   .. py:attribute:: Class.skipattr',
        '   .. py:method:: Class.skipmeth()',
        '   .. py:attribute:: Class.udocattr',
        '   .. py:method:: Class.undocmeth()'
    ]

    # use autodoc_default_options
    options = {"members": None}
    app.config.autodoc_default_options["undoc-members"] = None
    actual = do_autodoc(app, 'class', 'target.Class', options)
    assert list(filter(lambda l: '::' in l, actual)) == [
        '.. py:class:: Class(arg)',
        '   .. py:attribute:: Class.attr',
        '   .. py:attribute:: Class.docattr',
        '   .. py:method:: Class.excludemeth()',
        '   .. py:attribute:: Class.inst_attr_comment',
        '   .. py:attribute:: Class.inst_attr_inline',
        '   .. py:attribute:: Class.inst_attr_string',
        '   .. py:attribute:: Class.mdocattr',
        '   .. py:method:: Class.meth()',
        '   .. py:method:: Class.moore(a, e, f) -> happiness',
        '   .. py:method:: Class.roger(a, *, b=2, c=3, d=4, e=5, f=6)',
        '   .. py:attribute:: Class.skipattr',
        '   .. py:method:: Class.skipmeth()',
        '   .. py:attribute:: Class.udocattr',
        '   .. py:method:: Class.undocmeth()'
    ]

    # options negation work check
    options = {"members": None,
               "no-undoc-members": None}
    app.config.autodoc_default_options["undoc-members"] = None
    actual = do_autodoc(app, 'class', 'target.Class', options)
    assert list(filter(lambda l: '::' in l, actual)) == [
        '.. py:class:: Class(arg)',
        '   .. py:attribute:: Class.attr',
        '   .. py:attribute:: Class.docattr',
        '   .. py:method:: Class.excludemeth()',
        '   .. py:attribute:: Class.inst_attr_comment',
        '   .. py:attribute:: Class.inst_attr_inline',
        '   .. py:attribute:: Class.inst_attr_string',
        '   .. py:attribute:: Class.mdocattr',
        '   .. py:method:: Class.meth()',
        '   .. py:method:: Class.moore(a, e, f) -> happiness',
        '   .. py:method:: Class.skipmeth()',
        '   .. py:attribute:: Class.udocattr',
    ]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_undoc_members_for_metadata_only(app):
    # metadata only member is not displayed
    options = {"members": None}
    actual = do_autodoc(app, 'module', 'target.metadata', options)
    assert list(actual) == [
        '',
        '.. py:module:: target.metadata',
        '',
    ]

    # metadata only member is displayed when undoc-member given
    options = {"members": None,
               "undoc-members": None}
    actual = do_autodoc(app, 'module', 'target.metadata', options)
    assert list(actual) == [
        '',
        '.. py:module:: target.metadata',
        '',
        '',
        '.. py:function:: foo()',
        '   :module: target.metadata',
        '',
        '   :meta metadata-only-docstring:',
        '',
    ]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_inherited_members(app):
    options = {"members": None,
               "inherited-members": None}
    actual = do_autodoc(app, 'class', 'target.inheritance.Derived', options)
    assert list(filter(lambda l: 'method::' in l, actual)) == [
        '   .. py:method:: Derived.inheritedclassmeth()',
        '   .. py:method:: Derived.inheritedmeth()',
        '   .. py:method:: Derived.inheritedstaticmeth(cls)',
    ]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_inherited_members_Base(app):
    options = {"members": None,
               "inherited-members": "Base",
               "special-members": None}

    # check methods for object class are shown
    actual = do_autodoc(app, 'class', 'target.inheritance.Derived', options)
    assert '   .. py:method:: Derived.inheritedmeth()' in actual
    assert '   .. py:method:: Derived.inheritedclassmeth' not in actual


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_inherited_members_None(app):
    options = {"members": None,
               "inherited-members": "None",
               "special-members": None}

    # check methods for object class are shown
    actual = do_autodoc(app, 'class', 'target.inheritance.Derived', options)
    assert '   .. py:method:: Derived.__init__()' in actual
    assert '   .. py:method:: Derived.__str__()' in actual


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_imported_members(app):
    options = {"members": None,
               "imported-members": None,
               "ignore-module-all": None}
    actual = do_autodoc(app, 'module', 'target', options)
    assert '.. py:function:: save_traceback(app: Sphinx) -> str' in actual


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_special_members(app):
    # specific special methods
    options = {"undoc-members": None,
               "special-members": "__init__,__special1__"}
    actual = do_autodoc(app, 'class', 'target.Class', options)
    assert list(filter(lambda l: '::' in l, actual)) == [
        '.. py:class:: Class(arg)',
        '   .. py:method:: Class.__init__(arg)',
        '   .. py:method:: Class.__special1__()',
    ]

    # combination with specific members
    options = {"members": "attr,docattr",
               "undoc-members": None,
               "special-members": "__init__,__special1__"}
    actual = do_autodoc(app, 'class', 'target.Class', options)
    assert list(filter(lambda l: '::' in l, actual)) == [
        '.. py:class:: Class(arg)',
        '   .. py:method:: Class.__init__(arg)',
        '   .. py:method:: Class.__special1__()',
        '   .. py:attribute:: Class.attr',
        '   .. py:attribute:: Class.docattr',
    ]

    # all special methods
    options = {"members": None,
               "undoc-members": None,
               "special-members": None}
    actual = do_autodoc(app, 'class', 'target.Class', options)
    assert list(filter(lambda l: '::' in l, actual)) == [
        '.. py:class:: Class(arg)',
        '   .. py:attribute:: Class.__annotations__',
        '   .. py:attribute:: Class.__dict__',
        '   .. py:method:: Class.__init__(arg)',
        '   .. py:attribute:: Class.__module__',
        '   .. py:method:: Class.__special1__()',
        '   .. py:method:: Class.__special2__()',
        '   .. py:attribute:: Class.__weakref__',
        '   .. py:attribute:: Class.attr',
        '   .. py:attribute:: Class.docattr',
        '   .. py:method:: Class.excludemeth()',
        '   .. py:attribute:: Class.inst_attr_comment',
        '   .. py:attribute:: Class.inst_attr_inline',
        '   .. py:attribute:: Class.inst_attr_string',
        '   .. py:attribute:: Class.mdocattr',
        '   .. py:method:: Class.meth()',
        '   .. py:method:: Class.moore(a, e, f) -> happiness',
        '   .. py:method:: Class.roger(a, *, b=2, c=3, d=4, e=5, f=6)',
        '   .. py:attribute:: Class.skipattr',
        '   .. py:method:: Class.skipmeth()',
        '   .. py:attribute:: Class.udocattr',
        '   .. py:method:: Class.undocmeth()'
    ]

    # specific special methods from autodoc_default_options
    options = {"undoc-members": None}
    app.config.autodoc_default_options["special-members"] = "__special2__"
    actual = do_autodoc(app, 'class', 'target.Class', options)
    assert list(filter(lambda l: '::' in l, actual)) == [
        '.. py:class:: Class(arg)',
        '   .. py:method:: Class.__special2__()',
    ]

    # specific special methods option with autodoc_default_options
    options = {"undoc-members": None,
               "special-members": "__init__,__special1__"}
    app.config.autodoc_default_options["special-members"] = "__special2__"
    actual = do_autodoc(app, 'class', 'target.Class', options)
    assert list(filter(lambda l: '::' in l, actual)) == [
        '.. py:class:: Class(arg)',
        '   .. py:method:: Class.__init__(arg)',
        '   .. py:method:: Class.__special1__()',
    ]

    # specific special methods merge with autodoc_default_options
    options = {"undoc-members": None,
               "special-members": "+__init__,__special1__"}
    app.config.autodoc_default_options["special-members"] = "__special2__"
    actual = do_autodoc(app, 'class', 'target.Class', options)
    assert list(filter(lambda l: '::' in l, actual)) == [
        '.. py:class:: Class(arg)',
        '   .. py:method:: Class.__init__(arg)',
        '   .. py:method:: Class.__special1__()',
        '   .. py:method:: Class.__special2__()',
    ]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_ignore_module_all(app):
    # default (no-ignore-module-all)
    options = {"members": None}
    actual = do_autodoc(app, 'module', 'target', options)
    assert list(filter(lambda l: 'class::' in l, actual)) == [
        '.. py:class:: Class(arg)',
    ]

    # ignore-module-all
    options = {"members": None,
               "ignore-module-all": None}
    actual = do_autodoc(app, 'module', 'target', options)
    assert list(filter(lambda l: 'class::' in l, actual)) == [
        '.. py:class:: Class(arg)',
        '.. py:class:: CustomDict',
        '.. py:class:: InnerChild()',
        '.. py:class:: InstAttCls()',
        '.. py:class:: Outer()',
        '   .. py:class:: Outer.Inner()',
        '.. py:class:: StrRepr'
    ]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_noindex(app):
    options = {"noindex": None}
    actual = do_autodoc(app, 'module', 'target', options)
    assert list(actual) == [
        '',
        '.. py:module:: target',
        '   :noindex:',
        ''
    ]

    # TODO: :noindex: should be propagated to children of target item.

    actual = do_autodoc(app, 'class', 'target.inheritance.Base', options)
    assert list(actual) == [
        '',
        '.. py:class:: Base()',
        '   :noindex:',
        '   :module: target.inheritance',
        ''
    ]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_subclass_of_builtin_class(app):
    options = {"members": None}
    actual = do_autodoc(app, 'class', 'target.CustomDict', options)
    assert list(actual) == [
        '',
        '.. py:class:: CustomDict',
        '   :module: target',
        '',
        '   Docstring.',
        '',
    ]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_inner_class(app):
    options = {"members": None}
    actual = do_autodoc(app, 'class', 'target.Outer', options)
    assert list(actual) == [
        '',
        '.. py:class:: Outer()',
        '   :module: target',
        '',
        '   Foo',
        '',
        '',
        '   .. py:class:: Outer.Inner()',
        '      :module: target',
        '',
        '      Foo',
        '',
        '',
        '      .. py:method:: Outer.Inner.meth()',
        '         :module: target',
        '',
        '         Foo',
        '',
        '',
        '   .. py:attribute:: Outer.factory',
        '      :module: target',
        '',
        '      alias of :py:class:`dict`'
    ]

    actual = do_autodoc(app, 'class', 'target.Outer.Inner', options)
    assert list(actual) == [
        '',
        '.. py:class:: Inner()',
        '   :module: target.Outer',
        '',
        '   Foo',
        '',
        '',
        '   .. py:method:: Inner.meth()',
        '      :module: target.Outer',
        '',
        '      Foo',
        '',
    ]

    options['show-inheritance'] = None
    actual = do_autodoc(app, 'class', 'target.InnerChild', options)
    assert list(actual) == [
        '',
        '.. py:class:: InnerChild()',
        '   :module: target', '',
        '   Bases: :py:class:`target.Outer.Inner`',
        '',
        '   InnerChild docstring',
        '',
    ]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_classmethod(app):
    actual = do_autodoc(app, 'method', 'target.inheritance.Base.inheritedclassmeth')
    assert list(actual) == [
        '',
        '.. py:method:: Base.inheritedclassmeth()',
        '   :module: target.inheritance',
        '   :classmethod:',
        '',
        '   Inherited class method.',
        '',
    ]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_staticmethod(app):
    actual = do_autodoc(app, 'method', 'target.inheritance.Base.inheritedstaticmeth')
    assert list(actual) == [
        '',
        '.. py:method:: Base.inheritedstaticmeth(cls)',
        '   :module: target.inheritance',
        '   :staticmethod:',
        '',
        '   Inherited static method.',
        '',
    ]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_descriptor(app):
    options = {"members": None,
               "undoc-members": None}
    actual = do_autodoc(app, 'class', 'target.descriptor.Class', options)
    assert list(actual) == [
        '',
        '.. py:class:: Class()',
        '   :module: target.descriptor',
        '',
        '',
        '   .. py:attribute:: Class.descr',
        '      :module: target.descriptor',
        '',
        '      Descriptor instance docstring.',
        '',
        '',
        '   .. py:property:: Class.prop',
        '      :module: target.descriptor',
        '',
        '      Property.',
        ''
    ]


@pytest.mark.skipif(sys.version_info < (3, 8),
                    reason='cached_property is available since python3.8.')
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_cached_property(app):
    options = {"members": None,
               "undoc-members": None}
    actual = do_autodoc(app, 'class', 'target.cached_property.Foo', options)
    assert list(actual) == [
        '',
        '.. py:class:: Foo()',
        '   :module: target.cached_property',
        '',
        '',
        '   .. py:property:: Foo.prop',
        '      :module: target.cached_property',
        '      :type: int',
        '',
    ]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_member_order(app):
    # case member-order='bysource'
    options = {"members": None,
               'member-order': 'bysource',
               "undoc-members": None,
               'private-members': None}
    actual = do_autodoc(app, 'class', 'target.Class', options)
    assert list(filter(lambda l: '::' in l, actual)) == [
        '.. py:class:: Class(arg)',
        '   .. py:method:: Class.meth()',
        '   .. py:method:: Class.undocmeth()',
        '   .. py:method:: Class.skipmeth()',
        '   .. py:method:: Class.excludemeth()',
        '   .. py:attribute:: Class.skipattr',
        '   .. py:attribute:: Class.attr',
        '   .. py:attribute:: Class.docattr',
        '   .. py:attribute:: Class.udocattr',
        '   .. py:attribute:: Class.mdocattr',
        '   .. py:method:: Class.roger(a, *, b=2, c=3, d=4, e=5, f=6)',
        '   .. py:method:: Class.moore(a, e, f) -> happiness',
        '   .. py:attribute:: Class.inst_attr_inline',
        '   .. py:attribute:: Class.inst_attr_comment',
        '   .. py:attribute:: Class.inst_attr_string',
        '   .. py:attribute:: Class._private_inst_attr'
    ]

    # case member-order='groupwise'
    options = {"members": None,
               'member-order': 'groupwise',
               "undoc-members": None,
               'private-members': None}
    actual = do_autodoc(app, 'class', 'target.Class', options)
    assert list(filter(lambda l: '::' in l, actual)) == [
        '.. py:class:: Class(arg)',
        '   .. py:method:: Class.excludemeth()',
        '   .. py:method:: Class.meth()',
        '   .. py:method:: Class.moore(a, e, f) -> happiness',
        '   .. py:method:: Class.roger(a, *, b=2, c=3, d=4, e=5, f=6)',
        '   .. py:method:: Class.skipmeth()',
        '   .. py:method:: Class.undocmeth()',
        '   .. py:attribute:: Class._private_inst_attr',
        '   .. py:attribute:: Class.attr',
        '   .. py:attribute:: Class.docattr',
        '   .. py:attribute:: Class.inst_attr_comment',
        '   .. py:attribute:: Class.inst_attr_inline',
        '   .. py:attribute:: Class.inst_attr_string',
        '   .. py:attribute:: Class.mdocattr',
        '   .. py:attribute:: Class.skipattr',
        '   .. py:attribute:: Class.udocattr'
    ]

    # case member-order=None
    options = {"members": None,
               "undoc-members": None,
               'private-members': None}
    actual = do_autodoc(app, 'class', 'target.Class', options)
    assert list(filter(lambda l: '::' in l, actual)) == [
        '.. py:class:: Class(arg)',
        '   .. py:attribute:: Class._private_inst_attr',
        '   .. py:attribute:: Class.attr',
        '   .. py:attribute:: Class.docattr',
        '   .. py:method:: Class.excludemeth()',
        '   .. py:attribute:: Class.inst_attr_comment',
        '   .. py:attribute:: Class.inst_attr_inline',
        '   .. py:attribute:: Class.inst_attr_string',
        '   .. py:attribute:: Class.mdocattr',
        '   .. py:method:: Class.meth()',
        '   .. py:method:: Class.moore(a, e, f) -> happiness',
        '   .. py:method:: Class.roger(a, *, b=2, c=3, d=4, e=5, f=6)',
        '   .. py:attribute:: Class.skipattr',
        '   .. py:method:: Class.skipmeth()',
        '   .. py:attribute:: Class.udocattr',
        '   .. py:method:: Class.undocmeth()'
    ]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_module_member_order(app):
    # case member-order='bysource'
    options = {"members": 'foo, Bar, baz, qux, Quux, foobar',
               'member-order': 'bysource',
               "undoc-members": None}
    actual = do_autodoc(app, 'module', 'target.sort_by_all', options)
    assert list(filter(lambda l: '::' in l, actual)) == [
        '.. py:module:: target.sort_by_all',
        '.. py:function:: baz()',
        '.. py:function:: foo()',
        '.. py:class:: Bar()',
        '.. py:class:: Quux()',
        '.. py:function:: foobar()',
        '.. py:function:: qux()',
    ]

    # case member-order='bysource' and ignore-module-all
    options = {"members": 'foo, Bar, baz, qux, Quux, foobar',
               'member-order': 'bysource',
               "undoc-members": None,
               "ignore-module-all": None}
    actual = do_autodoc(app, 'module', 'target.sort_by_all', options)
    assert list(filter(lambda l: '::' in l, actual)) == [
        '.. py:module:: target.sort_by_all',
        '.. py:function:: foo()',
        '.. py:class:: Bar()',
        '.. py:function:: baz()',
        '.. py:function:: qux()',
        '.. py:class:: Quux()',
        '.. py:function:: foobar()',
    ]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_module_scope(app):
    app.env.temp_data['autodoc:module'] = 'target'
    actual = do_autodoc(app, 'attribute', 'Class.mdocattr')
    assert list(actual) == [
        '',
        '.. py:attribute:: Class.mdocattr',
        '   :module: target',
        '   :value: <_io.StringIO object>',
        '',
        '   should be documented as well - süß',
        '',
    ]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_class_scope(app):
    app.env.temp_data['autodoc:module'] = 'target'
    app.env.temp_data['autodoc:class'] = 'Class'
    actual = do_autodoc(app, 'attribute', 'mdocattr')
    assert list(actual) == [
        '',
        '.. py:attribute:: Class.mdocattr',
        '   :module: target',
        '   :value: <_io.StringIO object>',
        '',
        '   should be documented as well - süß',
        '',
    ]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_class_attributes(app):
    options = {"members": None,
               "undoc-members": None}
    actual = do_autodoc(app, 'class', 'target.AttCls', options)
    assert list(actual) == [
        '',
        '.. py:class:: AttCls()',
        '   :module: target',
        '',
        '',
        '   .. py:attribute:: AttCls.a1',
        '      :module: target',
        '      :value: hello world',
        '',
        '',
        '   .. py:attribute:: AttCls.a2',
        '      :module: target',
        '      :value: None',
        ''
    ]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autoclass_instance_attributes(app):
    options = {"members": None}
    actual = do_autodoc(app, 'class', 'target.InstAttCls', options)
    assert list(actual) == [
        '',
        '.. py:class:: InstAttCls()',
        '   :module: target',
        '',
        '   Class with documented class and instance attributes.',
        '',
        '',
        '   .. py:attribute:: InstAttCls.ca1',
        '      :module: target',
        "      :value: 'a'",
        '',
        '      Doc comment for class attribute InstAttCls.ca1.',
        '      It can have multiple lines.',
        '',
        '',
        '   .. py:attribute:: InstAttCls.ca2',
        '      :module: target',
        "      :value: 'b'",
        '',
        '      Doc comment for InstAttCls.ca2. One line only.',
        '',
        '',
        '   .. py:attribute:: InstAttCls.ca3',
        '      :module: target',
        "      :value: 'c'",
        '',
        '      Docstring for class attribute InstAttCls.ca3.',
        '',
        '',
        '   .. py:attribute:: InstAttCls.ia1',
        '      :module: target',
        '',
        '      Doc comment for instance attribute InstAttCls.ia1',
        '',
        '',
        '   .. py:attribute:: InstAttCls.ia2',
        '      :module: target',
        '',
        '      Docstring for instance attribute InstAttCls.ia2.',
        ''
    ]

    # pick up arbitrary attributes
    options = {"members": 'ca1,ia1'}
    actual = do_autodoc(app, 'class', 'target.InstAttCls', options)
    assert list(actual) == [
        '',
        '.. py:class:: InstAttCls()',
        '   :module: target',
        '',
        '   Class with documented class and instance attributes.',
        '',
        '',
        '   .. py:attribute:: InstAttCls.ca1',
        '      :module: target',
        "      :value: 'a'",
        '',
        '      Doc comment for class attribute InstAttCls.ca1.',
        '      It can have multiple lines.',
        '',
        '',
        '   .. py:attribute:: InstAttCls.ia1',
        '      :module: target',
        '',
        '      Doc comment for instance attribute InstAttCls.ia1',
        ''
    ]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autoattribute_instance_attributes(app):
    actual = do_autodoc(app, 'attribute', 'target.InstAttCls.ia1')
    assert list(actual) == [
        '',
        '.. py:attribute:: InstAttCls.ia1',
        '   :module: target',
        '',
        '   Doc comment for instance attribute InstAttCls.ia1',
        ''
    ]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_slots(app):
    options = {"members": None,
               "undoc-members": None}
    actual = do_autodoc(app, 'module', 'target.slots', options)
    assert list(actual) == [
        '',
        '.. py:module:: target.slots',
        '',
        '',
        '.. py:class:: Bar()',
        '   :module: target.slots',
        '',
        '   docstring',
        '',
        '',
        '   .. py:attribute:: Bar.attr1',
        '      :module: target.slots',
        '      :type: int',
        '',
        '      docstring of attr1',
        '',
        '',
        '   .. py:attribute:: Bar.attr2',
        '      :module: target.slots',
        '',
        '      docstring of instance attr2',
        '',
        '',
        '   .. py:attribute:: Bar.attr3',
        '      :module: target.slots',
        '',
        '',
        '.. py:class:: Baz()',
        '   :module: target.slots',
        '',
        '   docstring',
        '',
        '',
        '   .. py:attribute:: Baz.attr',
        '      :module: target.slots',
        '',
        '',
        '.. py:class:: Foo()',
        '   :module: target.slots',
        '',
        '   docstring',
        '',
        '',
        '   .. py:attribute:: Foo.attr',
        '      :module: target.slots',
        '',
    ]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_enum_class(app):
    options = {"members": None}
    actual = do_autodoc(app, 'class', 'target.enums.EnumCls', options)

    if sys.version_info > (3, 11):
        args = ('(value, names=None, *, module=None, qualname=None, '
                'type=None, start=1, boundary=None)')
    else:
        args = '(value)'

    assert list(actual) == [
        '',
        '.. py:class:: EnumCls' + args,
        '   :module: target.enums',
        '',
        '   this is enum class',
        '',
        '',
        '   .. py:method:: EnumCls.say_goodbye()',
        '      :module: target.enums',
        '      :classmethod:',
        '',
        '      a classmethod says good-bye to you.',
        '',
        '',
        '   .. py:method:: EnumCls.say_hello()',
        '      :module: target.enums',
        '',
        '      a method says hello to you.',
        '',
        '',
        '   .. py:attribute:: EnumCls.val1',
        '      :module: target.enums',
        '      :value: 12',
        '',
        '      doc for val1',
        '',
        '',
        '   .. py:attribute:: EnumCls.val2',
        '      :module: target.enums',
        '      :value: 23',
        '',
        '      doc for val2',
        '',
        '',
        '   .. py:attribute:: EnumCls.val3',
        '      :module: target.enums',
        '      :value: 34',
        '',
        '      doc for val3',
        '',
    ]

    # checks for an attribute of EnumClass
    actual = do_autodoc(app, 'attribute', 'target.enums.EnumCls.val1')
    assert list(actual) == [
        '',
        '.. py:attribute:: EnumCls.val1',
        '   :module: target.enums',
        '   :value: 12',
        '',
        '   doc for val1',
        ''
    ]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_descriptor_class(app):
    options = {"members": 'CustomDataDescriptor,CustomDataDescriptor2'}
    actual = do_autodoc(app, 'module', 'target.descriptor', options)
    assert list(actual) == [
        '',
        '.. py:module:: target.descriptor',
        '',
        '',
        '.. py:class:: CustomDataDescriptor(doc)',
        '   :module: target.descriptor',
        '',
        '   Descriptor class docstring.',
        '',
        '',
        '   .. py:method:: CustomDataDescriptor.meth()',
        '      :module: target.descriptor',
        '',
        '      Function.',
        '',
        '',
        '.. py:class:: CustomDataDescriptor2(doc)',
        '   :module: target.descriptor',
        '',
        '   Descriptor class with custom metaclass docstring.',
        '',
    ]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_automethod_for_builtin(app):
    actual = do_autodoc(app, 'method', 'builtins.int.__add__')
    assert list(actual) == [
        '',
        '.. py:method:: int.__add__(value, /)',
        '   :module: builtins',
        '',
        '   Return self+value.',
        '',
    ]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_automethod_for_decorated(app):
    actual = do_autodoc(app, 'method', 'target.decorator.Bar.meth')
    assert list(actual) == [
        '',
        '.. py:method:: Bar.meth(name=None, age=None)',
        '   :module: target.decorator',
        '',
    ]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_abstractmethods(app):
    options = {"members": None,
               "undoc-members": None}
    actual = do_autodoc(app, 'module', 'target.abstractmethods', options)
    assert list(actual) == [
        '',
        '.. py:module:: target.abstractmethods',
        '',
        '',
        '.. py:class:: Base()',
        '   :module: target.abstractmethods',
        '',
        '',
        '   .. py:method:: Base.abstractmeth()',
        '      :module: target.abstractmethods',
        '      :abstractmethod:',
        '',
        '',
        '   .. py:method:: Base.classmeth()',
        '      :module: target.abstractmethods',
        '      :abstractmethod:',
        '      :classmethod:',
        '',
        '',
        '   .. py:method:: Base.coroutinemeth()',
        '      :module: target.abstractmethods',
        '      :abstractmethod:',
        '      :async:',
        '',
        '',
        '   .. py:method:: Base.meth()',
        '      :module: target.abstractmethods',
        '',
        '',
        '   .. py:property:: Base.prop',
        '      :module: target.abstractmethods',
        '      :abstractmethod:',
        '',
        '',
        '   .. py:method:: Base.staticmeth()',
        '      :module: target.abstractmethods',
        '      :abstractmethod:',
        '      :staticmethod:',
        '',
    ]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_partialfunction(app):
    options = {"members": None}
    actual = do_autodoc(app, 'module', 'target.partialfunction', options)
    assert list(actual) == [
        '',
        '.. py:module:: target.partialfunction',
        '',
        '',
        '.. py:function:: func1(a, b, c)',
        '   :module: target.partialfunction',
        '',
        '   docstring of func1',
        '',
        '',
        '.. py:function:: func2(b, c)',
        '   :module: target.partialfunction',
        '',
        '   docstring of func1',
        '',
        '',
        '.. py:function:: func3(c)',
        '   :module: target.partialfunction',
        '',
        '   docstring of func3',
        '',
        '',
        '.. py:function:: func4()',
        '   :module: target.partialfunction',
        '',
        '   docstring of func3',
        '',
    ]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_imported_partialfunction_should_not_shown_without_imported_members(app):
    options = {"members": None}
    actual = do_autodoc(app, 'module', 'target.imported_members', options)
    assert list(actual) == [
        '',
        '.. py:module:: target.imported_members',
        ''
    ]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_bound_method(app):
    options = {"members": None}
    actual = do_autodoc(app, 'module', 'target.bound_method', options)
    assert list(actual) == [
        '',
        '.. py:module:: target.bound_method',
        '',
        '',
        '.. py:function:: bound_method()',
        '   :module: target.bound_method',
        '',
        '   Method docstring',
        '',
    ]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_partialmethod(app):
    expected = [
        '',
        '.. py:class:: Cell()',
        '   :module: target.partialmethod',
        '',
        '   An example for partialmethod.',
        '',
        '   refs: https://docs.python.jp/3/library/functools.html#functools.partialmethod',
        '',
        '',
        '   .. py:method:: Cell.set_alive()',
        '      :module: target.partialmethod',
        '',
        '      Make a cell alive.',
        '',
        '',
        '   .. py:method:: Cell.set_state(state)',
        '      :module: target.partialmethod',
        '',
        '      Update state of cell to *state*.',
        '',
    ]

    options = {"members": None}
    actual = do_autodoc(app, 'class', 'target.partialmethod.Cell', options)
    assert list(actual) == expected


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_partialmethod_undoc_members(app):
    expected = [
        '',
        '.. py:class:: Cell()',
        '   :module: target.partialmethod',
        '',
        '   An example for partialmethod.',
        '',
        '   refs: https://docs.python.jp/3/library/functools.html#functools.partialmethod',
        '',
        '',
        '   .. py:method:: Cell.set_alive()',
        '      :module: target.partialmethod',
        '',
        '      Make a cell alive.',
        '',
        '',
        '   .. py:method:: Cell.set_dead()',
        '      :module: target.partialmethod',
        '',
        '',
        '   .. py:method:: Cell.set_state(state)',
        '      :module: target.partialmethod',
        '',
        '      Update state of cell to *state*.',
        '',
    ]

    options = {"members": None,
               "undoc-members": None}
    actual = do_autodoc(app, 'class', 'target.partialmethod.Cell', options)
    assert list(actual) == expected


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_typed_instance_variables(app):
    options = {"members": None,
               "undoc-members": None}
    actual = do_autodoc(app, 'module', 'target.typed_vars', options)
    assert list(actual) == [
        '',
        '.. py:module:: target.typed_vars',
        '',
        '',
        '.. py:attribute:: Alias',
        '   :module: target.typed_vars',
        '',
        '   alias of :py:class:`target.typed_vars.Derived`',
        '',
        '.. py:class:: Class()',
        '   :module: target.typed_vars',
        '',
        '',
        '   .. py:attribute:: Class.attr1',
        '      :module: target.typed_vars',
        '      :type: int',
        '      :value: 0',
        '',
        '',
        '   .. py:attribute:: Class.attr2',
        '      :module: target.typed_vars',
        '      :type: int',
        '',
        '',
        '   .. py:attribute:: Class.attr3',
        '      :module: target.typed_vars',
        '      :type: int',
        '      :value: 0',
        '',
        '',
        '   .. py:attribute:: Class.attr4',
        '      :module: target.typed_vars',
        '      :type: int',
        '',
        '      attr4',
        '',
        '',
        '   .. py:attribute:: Class.attr5',
        '      :module: target.typed_vars',
        '      :type: int',
        '',
        '      attr5',
        '',
        '',
        '   .. py:attribute:: Class.attr6',
        '      :module: target.typed_vars',
        '      :type: int',
        '',
        '      attr6',
        '',
        '',
        '   .. py:attribute:: Class.descr4',
        '      :module: target.typed_vars',
        '      :type: int',
        '',
        '      This is descr4',
        '',
        '',
        '.. py:class:: Derived()',
        '   :module: target.typed_vars',
        '',
        '',
        '   .. py:attribute:: Derived.attr7',
        '      :module: target.typed_vars',
        '      :type: int',
        '',
        '',
        '.. py:data:: attr1',
        '   :module: target.typed_vars',
        '   :type: str',
        "   :value: ''",
        '',
        '   attr1',
        '',
        '',
        '.. py:data:: attr2',
        '   :module: target.typed_vars',
        '   :type: str',
        '',
        '   attr2',
        '',
        '',
        '.. py:data:: attr3',
        '   :module: target.typed_vars',
        '   :type: str',
        "   :value: ''",
        '',
        '   attr3',
        '',
    ]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_typed_inherited_instance_variables(app):
    options = {"members": None,
               "undoc-members": None,
               "inherited-members": None}
    actual = do_autodoc(app, 'class', 'target.typed_vars.Derived', options)
    assert list(actual) == [
        '',
        '.. py:class:: Derived()',
        '   :module: target.typed_vars',
        '',
        '',
        '   .. py:attribute:: Derived.attr1',
        '      :module: target.typed_vars',
        '      :type: int',
        '      :value: 0',
        '',
        '',
        '   .. py:attribute:: Derived.attr2',
        '      :module: target.typed_vars',
        '      :type: int',
        '',
        '',
        '   .. py:attribute:: Derived.attr3',
        '      :module: target.typed_vars',
        '      :type: int',
        '      :value: 0',
        '',
        '',
        '   .. py:attribute:: Derived.attr4',
        '      :module: target.typed_vars',
        '      :type: int',
        '',
        '      attr4',
        '',
        '',
        '   .. py:attribute:: Derived.attr5',
        '      :module: target.typed_vars',
        '      :type: int',
        '',
        '      attr5',
        '',
        '',
        '   .. py:attribute:: Derived.attr6',
        '      :module: target.typed_vars',
        '      :type: int',
        '',
        '      attr6',
        '',
        '',
        '   .. py:attribute:: Derived.attr7',
        '      :module: target.typed_vars',
        '      :type: int',
        '',
        '',
        '   .. py:attribute:: Derived.descr4',
        '      :module: target.typed_vars',
        '      :type: int',
        '',
    ]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_GenericAlias(app):
    options = {"members": None,
               "undoc-members": None}
    actual = do_autodoc(app, 'module', 'target.genericalias', options)
    if sys.version_info < (3, 7):
        assert list(actual) == [
            '',
            '.. py:module:: target.genericalias',
            '',
            '',
            '.. py:class:: Class()',
            '   :module: target.genericalias',
            '',
            '',
            '   .. py:attribute:: Class.T',
            '      :module: target.genericalias',
            '',
            '      A list of int',
            '',
            '      alias of :py:class:`~typing.List`\\ [:py:class:`int`]',
            '',
            '.. py:attribute:: L',
            '   :module: target.genericalias',
            '',
            '   A list of Class',
            '',
            '',
            '.. py:attribute:: T',
            '   :module: target.genericalias',
            '',
            '   A list of int',
            '',
        ]
    else:
        assert list(actual) == [
            '',
            '.. py:module:: target.genericalias',
            '',
            '',
            '.. py:class:: Class()',
            '   :module: target.genericalias',
            '',
            '',
            '   .. py:attribute:: Class.T',
            '      :module: target.genericalias',
            '',
            '      A list of int',
            '',
            '      alias of :py:class:`~typing.List`\\ [:py:class:`int`]',
            '',
            '',
            '.. py:data:: L',
            '   :module: target.genericalias',
            '',
            '   A list of Class',
            '',
            '   alias of :py:class:`~typing.List`\\ '
            '[:py:class:`target.genericalias.Class`]',
            '',
            '',
            '.. py:data:: T',
            '   :module: target.genericalias',
            '',
            '   A list of int',
            '',
            '   alias of :py:class:`~typing.List`\\ [:py:class:`int`]',
            '',
        ]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_TypeVar(app):
    options = {"members": None,
               "undoc-members": None}
    actual = do_autodoc(app, 'module', 'target.typevar', options)
    assert list(actual) == [
        '',
        '.. py:module:: target.typevar',
        '',
        '',
        '.. py:class:: Class()',
        '   :module: target.typevar',
        '',
        '',
        '   .. py:attribute:: Class.T1',
        '      :module: target.typevar',
        '',
        '      T1',
        '',
        "      alias of TypeVar('T1')",
        '',
        '',
        '   .. py:attribute:: Class.T6',
        '      :module: target.typevar',
        '',
        '      T6',
        '',
        '      alias of :py:class:`datetime.date`',
        '',
        '',
        '.. py:data:: T1',
        '   :module: target.typevar',
        '',
        '   T1',
        '',
        "   alias of TypeVar('T1')",
        '',
        '',
        '.. py:data:: T3',
        '   :module: target.typevar',
        '',
        '   T3',
        '',
        "   alias of TypeVar('T3', int, str)",
        '',
        '',
        '.. py:data:: T4',
        '   :module: target.typevar',
        '',
        '   T4',
        '',
        "   alias of TypeVar('T4', covariant=True)",
        '',
        '',
        '.. py:data:: T5',
        '   :module: target.typevar',
        '',
        '   T5',
        '',
        "   alias of TypeVar('T5', contravariant=True)",
        '',
        '',
        '.. py:data:: T6',
        '   :module: target.typevar',
        '',
        '   T6',
        '',
        '   alias of :py:class:`datetime.date`',
        '',
        '',
        '.. py:data:: T7',
        '   :module: target.typevar',
        '',
        '   T7',
        '',
        "   alias of TypeVar('T7', bound=\\ :py:class:`int`)",
        '',
    ]


@pytest.mark.skipif(sys.version_info < (3, 9), reason='py39+ is required.')
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_Annotated(app):
    options = {"members": None}
    actual = do_autodoc(app, 'module', 'target.annotated', options)
    assert list(actual) == [
        '',
        '.. py:module:: target.annotated',
        '',
        '',
        '.. py:function:: hello(name: str) -> None',
        '   :module: target.annotated',
        '',
        '   docstring',
        '',
    ]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_TYPE_CHECKING(app):
    options = {"members": None,
               "undoc-members": None}
    actual = do_autodoc(app, 'module', 'target.TYPE_CHECKING', options)
    assert list(actual) == [
        '',
        '.. py:module:: target.TYPE_CHECKING',
        '',
        '',
        '.. py:class:: Foo()',
        '   :module: target.TYPE_CHECKING',
        '',
        '',
        '   .. py:attribute:: Foo.attr1',
        '      :module: target.TYPE_CHECKING',
        '      :type: StringIO',
        '',
    ]


@pytest.mark.sphinx('html', testroot='pycode-egg')
def test_autodoc_for_egged_code(app):
    options = {"members": None,
               "undoc-members": None}
    actual = do_autodoc(app, 'module', 'sample', options)
    assert list(actual) == [
        '',
        '.. py:module:: sample',
        '',
        '',
        '.. py:data:: CONSTANT',
        '   :module: sample',
        '   :value: 1',
        '',
        '   constant on sample.py',
        '',
        '',
        '.. py:function:: hello(s)',
        '   :module: sample',
        ''
    ]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_singledispatch(app):
    options = {"members": None}
    actual = do_autodoc(app, 'module', 'target.singledispatch', options)
    assert list(actual) == [
        '',
        '.. py:module:: target.singledispatch',
        '',
        '',
        '.. py:function:: func(arg, kwarg=None)',
        '                 func(arg: float, kwarg=None)',
        '                 func(arg: int, kwarg=None)',
        '                 func(arg: str, kwarg=None)',
        '   :module: target.singledispatch',
        '',
        '   A function for general use.',
        '',
    ]


@pytest.mark.skipif(sys.version_info < (3, 8),
                    reason='singledispatchmethod is available since python3.8')
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_singledispatchmethod(app):
    options = {"members": None}
    actual = do_autodoc(app, 'module', 'target.singledispatchmethod', options)
    assert list(actual) == [
        '',
        '.. py:module:: target.singledispatchmethod',
        '',
        '',
        '.. py:class:: Foo()',
        '   :module: target.singledispatchmethod',
        '',
        '   docstring',
        '',
        '',
        '   .. py:method:: Foo.meth(arg, kwarg=None)',
        '                  Foo.meth(arg: float, kwarg=None)',
        '                  Foo.meth(arg: int, kwarg=None)',
        '                  Foo.meth(arg: str, kwarg=None)',
        '      :module: target.singledispatchmethod',
        '',
        '      A method for general use.',
        '',
    ]


@pytest.mark.skipif(sys.version_info < (3, 8),
                    reason='singledispatchmethod is available since python3.8')
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_singledispatchmethod_automethod(app):
    options = {}
    actual = do_autodoc(app, 'method', 'target.singledispatchmethod.Foo.meth', options)
    assert list(actual) == [
        '',
        '.. py:method:: Foo.meth(arg, kwarg=None)',
        '               Foo.meth(arg: float, kwarg=None)',
        '               Foo.meth(arg: int, kwarg=None)',
        '               Foo.meth(arg: str, kwarg=None)',
        '   :module: target.singledispatchmethod',
        '',
        '   A method for general use.',
        '',
    ]


@pytest.mark.skipif(sys.version_info > (3, 11),
                    reason=('cython does not support python-3.11 yet. '
                            'see https://github.com/cython/cython/issues/4365'))
@pytest.mark.skipif(pyximport is None, reason='cython is not installed')
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_cython(app):
    options = {"members": None,
               "undoc-members": None}
    actual = do_autodoc(app, 'module', 'target.cython', options)
    assert list(actual) == [
        '',
        '.. py:module:: target.cython',
        '',
        '',
        '.. py:class:: Class()',
        '   :module: target.cython',
        '',
        '   Docstring.',
        '',
        '',
        '   .. py:method:: Class.meth(name: str, age: int = 0) -> None',
        '      :module: target.cython',
        '',
        '      Docstring.',
        '',
        '',
        '.. py:function:: foo(x: int, *args, y: str, **kwargs)',
        '   :module: target.cython',
        '',
        '   Docstring.',
        '',
    ]


@pytest.mark.skipif(sys.version_info < (3, 8),
                    reason='typing.final is available since python3.8')
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_final(app):
    options = {"members": None}
    actual = do_autodoc(app, 'module', 'target.final', options)
    assert list(actual) == [
        '',
        '.. py:module:: target.final',
        '',
        '',
        '.. py:class:: Class()',
        '   :module: target.final',
        '   :final:',
        '',
        '   docstring',
        '',
        '',
        '   .. py:method:: Class.meth1()',
        '      :module: target.final',
        '      :final:',
        '',
        '      docstring',
        '',
        '',
        '   .. py:method:: Class.meth2()',
        '      :module: target.final',
        '',
        '      docstring',
        '',
    ]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_overload(app):
    options = {"members": None}
    actual = do_autodoc(app, 'module', 'target.overload', options)
    assert list(actual) == [
        '',
        '.. py:module:: target.overload',
        '',
        '',
        '.. py:class:: Bar(x: int, y: int)',
        '              Bar(x: str, y: str)',
        '   :module: target.overload',
        '',
        '   docstring',
        '',
        '',
        '.. py:class:: Baz(x: int, y: int)',
        '              Baz(x: str, y: str)',
        '   :module: target.overload',
        '',
        '   docstring',
        '',
        '',
        '.. py:class:: Foo(x: int, y: int)',
        '              Foo(x: str, y: str)',
        '   :module: target.overload',
        '',
        '   docstring',
        '',
        '',
        '.. py:class:: Math()',
        '   :module: target.overload',
        '',
        '   docstring',
        '',
        '',
        '   .. py:method:: Math.sum(x: int, y: int = 0) -> int',
        '                  Math.sum(x: float, y: float = 0.0) -> float',
        '                  Math.sum(x: str, y: str = None) -> str',
        '      :module: target.overload',
        '',
        '      docstring',
        '',
        '',
        '.. py:function:: sum(x: int, y: int = 0) -> int',
        '                 sum(x: float, y: float = 0.0) -> float',
        '                 sum(x: str, y: str = None) -> str',
        '   :module: target.overload',
        '',
        '   docstring',
        '',
    ]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_overload2(app):
    options = {"members": None}
    actual = do_autodoc(app, 'module', 'target.overload2', options)
    assert list(actual) == [
        '',
        '.. py:module:: target.overload2',
        '',
        '',
        '.. py:class:: Baz(x: int, y: int)',
        '              Baz(x: str, y: str)',
        '   :module: target.overload2',
        '',
    ]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_pymodule_for_ModuleLevelDocumenter(app):
    app.env.ref_context['py:module'] = 'target.classes'
    actual = do_autodoc(app, 'class', 'Foo')
    assert list(actual) == [
        '',
        '.. py:class:: Foo()',
        '   :module: target.classes',
        '',
    ]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_pymodule_for_ClassLevelDocumenter(app):
    app.env.ref_context['py:module'] = 'target.methods'
    actual = do_autodoc(app, 'method', 'Base.meth')
    assert list(actual) == [
        '',
        '.. py:method:: Base.meth()',
        '   :module: target.methods',
        '',
    ]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_pyclass_for_ClassLevelDocumenter(app):
    app.env.ref_context['py:module'] = 'target.methods'
    app.env.ref_context['py:class'] = 'Base'
    actual = do_autodoc(app, 'method', 'meth')
    assert list(actual) == [
        '',
        '.. py:method:: Base.meth()',
        '   :module: target.methods',
        '',
    ]


@pytest.mark.sphinx('dummy', testroot='ext-autodoc')
def test_autodoc(app, status, warning):
    app.builder.build_all()

    content = app.env.get_doctree('index')
    assert isinstance(content[3], addnodes.desc)
    assert content[3][0].astext() == 'autodoc_dummy_module.test()'
    assert content[3][1].astext() == 'Dummy function using dummy.*'

    # issue sphinx-doc/sphinx#2437
    assert content[11][-1].astext() == """Dummy class Bar with alias.



my_name

alias of bug2437.autodoc_dummy_foo.Foo"""
    assert warning.getvalue() == ''


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_name_conflict(app):
    actual = do_autodoc(app, 'class', 'target.name_conflict.foo')
    assert list(actual) == [
        '',
        '.. py:class:: foo()',
        '   :module: target.name_conflict',
        '',
        '   docstring of target.name_conflict::foo.',
        '',
    ]

    actual = do_autodoc(app, 'class', 'target.name_conflict.foo.bar')
    assert list(actual) == [
        '',
        '.. py:class:: bar()',
        '   :module: target.name_conflict.foo',
        '',
        '   docstring of target.name_conflict.foo::bar.',
        '',
    ]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_name_mangling(app):
    options = {"members": None,
               "undoc-members": None,
               "private-members": None}
    actual = do_autodoc(app, 'module', 'target.name_mangling', options)
    assert list(actual) == [
        '',
        '.. py:module:: target.name_mangling',
        '',
        '',
        '.. py:class:: Bar()',
        '   :module: target.name_mangling',
        '',
        '',
        '   .. py:attribute:: Bar._Baz__email',
        '      :module: target.name_mangling',
        '      :value: None',
        '',
        '      a member having mangled-like name',
        '',
        '',
        '   .. py:attribute:: Bar.__address',
        '      :module: target.name_mangling',
        '      :value: None',
        '',
        '',
        '.. py:class:: Foo()',
        '   :module: target.name_mangling',
        '',
        '',
        '   .. py:attribute:: Foo.__age',
        '      :module: target.name_mangling',
        '      :value: None',
        '',
        '',
        '   .. py:attribute:: Foo.__name',
        '      :module: target.name_mangling',
        '      :value: None',
        '',
        '      name of Foo',
        '',
    ]


@pytest.mark.skipif(sys.version_info < (3, 7), reason='python 3.7+ is required.')
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_type_union_operator(app):
    options = {'members': None}
    actual = do_autodoc(app, 'module', 'target.pep604', options)
    assert list(actual) == [
        '',
        '.. py:module:: target.pep604',
        '',
        '',
        '.. py:class:: Foo()',
        '   :module: target.pep604',
        '',
        '   docstring',
        '',
        '',
        '   .. py:attribute:: Foo.attr',
        '      :module: target.pep604',
        '      :type: int | str',
        '',
        '      docstring',
        '',
        '',
        '   .. py:method:: Foo.meth(x: int | str, y: int | str) -> int | str',
        '      :module: target.pep604',
        '',
        '      docstring',
        '',
        '',
        '.. py:data:: attr',
        '   :module: target.pep604',
        '   :type: int | str',
        '',
        '   docstring',
        '',
        '',
        '.. py:function:: sum(x: int | str, y: int | str) -> int | str',
        '   :module: target.pep604',
        '',
        '   docstring',
        '',
    ]


@pytest.mark.skipif(sys.version_info < (3, 6), reason='python 3.6+ is required.')
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_hide_value(app):
    options = {'members': None}
    actual = do_autodoc(app, 'module', 'target.hide_value', options)
    assert list(actual) == [
        '',
        '.. py:module:: target.hide_value',
        '',
        '',
        '.. py:class:: Foo()',
        '   :module: target.hide_value',
        '',
        '   docstring',
        '',
        '',
        '   .. py:attribute:: Foo.SENTINEL1',
        '      :module: target.hide_value',
        '',
        '      docstring',
        '',
        '      :meta hide-value:',
        '',
        '',
        '   .. py:attribute:: Foo.SENTINEL2',
        '      :module: target.hide_value',
        '',
        '      :meta hide-value:',
        '',
        '',
        '.. py:data:: SENTINEL1',
        '   :module: target.hide_value',
        '',
        '   docstring',
        '',
        '   :meta hide-value:',
        '',
        '',
        '.. py:data:: SENTINEL2',
        '   :module: target.hide_value',
        '',
        '   :meta hide-value:',
        '',
    ]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_canonical(app):
    options = {'members': None,
               'imported-members': None}
    actual = do_autodoc(app, 'module', 'target.canonical', options)
    assert list(actual) == [
        '',
        '.. py:module:: target.canonical',
        '',
        '',
        '.. py:class:: Bar()',
        '   :module: target.canonical',
        '',
        '   docstring',
        '',
        '',
        '.. py:class:: Foo()',
        '   :module: target.canonical',
        '   :canonical: target.canonical.original.Foo',
        '',
        '   docstring',
        '',
        '',
        '   .. py:method:: Foo.meth()',
        '      :module: target.canonical',
        '',
        '      docstring',
        '',
    ]
