Source

sphinx / tests / test_autodoc.py

# -*- coding: utf-8 -*-
"""
    test_autodoc
    ~~~~~~~~~~~~

    Test the autodoc extension.  This tests mainly the RstGenerator; the auto
    directives are tested in a test source file translated by test_build.

    :copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS.
    :license: BSD, see LICENSE for details.
"""

from util import *

from docutils.statemachine import ViewList

from sphinx.ext.autodoc import RstGenerator, cut_lines, between


def setup_module():
    global app, lid, options, gen

    app = TestApp()
    app.builder.env.app = app
    app.connect('autodoc-process-docstring', process_docstring)
    app.connect('autodoc-process-signature', process_signature)

    options = Struct(
        inherited_members = False,
        undoc_members = False,
        show_inheritance = False,
        noindex = False,
        synopsis = '',
        platform = '',
        deprecated = False,
    )

    gen = TestGenerator(options, app)

def teardown_module():
    app.cleanup()


class TestGenerator(RstGenerator):
    """Generator that handles warnings without a reporter."""

    def __init__(self, options, app):
        self.options = options
        self.env = app.builder.env
        self.lineno = 42
        self.filename_set = set()
        self.warnings = []
        self.result = ViewList()

    def warn(self, msg):
        self.warnings.append(msg)


processed_docstrings = []

def process_docstring(app, what, name, obj, options, lines):
    processed_docstrings.append((what, name))
    if name == 'bar':
        lines.extend(['42', ''])

processed_signatures = []

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


def test_resolve_name():
    # for modules
    assert gen.resolve_name('module', 'test_autodoc') == \
           ('test_autodoc', 'test_autodoc', [], None, None)
    assert gen.resolve_name('module', 'test.test_autodoc') == \
           ('test.test_autodoc', 'test.test_autodoc', [], None, None)

    assert gen.resolve_name('module', 'test(arg)') == \
           ('test', 'test', [], None, None)
    assert 'ignoring signature arguments' in gen.warnings[0]
    del gen.warnings[:]

    # for functions/classes
    assert gen.resolve_name('function', 'util.raises') == \
           ('util.raises', 'util', ['raises'], None, None)
    assert gen.resolve_name('function', 'util.raises(exc) -> None') == \
           ('util.raises', 'util', ['raises'], 'exc', ' -> None')
    gen.env.autodoc_current_module = 'util'
    assert gen.resolve_name('function', 'raises') == \
           ('raises', 'util', ['raises'], None, None)
    gen.env.autodoc_current_module = None
    gen.env.currmodule = 'util'
    assert gen.resolve_name('function', 'raises') == \
           ('raises', 'util', ['raises'], None, None)
    assert gen.resolve_name('class', 'TestApp') == \
           ('TestApp', 'util', ['TestApp'], None, None)

    # for members
    gen.env.currmodule = 'foo'
    assert gen.resolve_name('method', 'util.TestApp.cleanup') == \
           ('util.TestApp.cleanup', 'util', ['TestApp', 'cleanup'], None, None)
    gen.env.currmodule = 'util'
    gen.env.currclass = 'Foo'
    gen.env.autodoc_current_class = 'TestApp'
    assert gen.resolve_name('method', 'cleanup') == \
           ('cleanup', 'util', ['TestApp', 'cleanup'], None, None)
    assert gen.resolve_name('method', 'TestApp.cleanup') == \
           ('TestApp.cleanup', 'util', ['TestApp', 'cleanup'], None, None)

    # and clean up
    gen.env.currmodule = None
    gen.env.currclass = None
    gen.env.autodoc_current_class = None


def test_format_signature():
    # no signatures for modules
    assert gen.format_signature('module', 'test', None, None, None) == ''

    # test for functions
    def f(a, b, c=1, **d):
        pass
    assert gen.format_signature('function', 'f', f, None, None) == '(a, b, c=1, **d)'
    assert gen.format_signature('function', 'f', f, 'a, b, c, d', None) == \
           '(a, b, c, d)'
    assert gen.format_signature('function', 'f', f, None, ' -> None') == \
           '(a, b, c=1, **d) -> None'

    # test for classes
    class D:
        pass
    class E(object):
        pass
    # no signature for classes without __init__
    for C in (D, E):
        assert gen.format_signature('class', 'D', C, None, None) == ''
    class F:
        def __init__(self, a, b=None):
            pass
    class G(F, object):
        pass
    for C in (F, G):
        assert gen.format_signature('class', 'C', C, None, None) == '(a, b=None)'
    assert gen.format_signature('class', 'C', D, 'a, b', ' -> X') == '(a, b) -> X'

    # test for methods
    class H:
        def foo1(self, b, *c):
            pass
        def foo2(b, *c):
            pass
    assert gen.format_signature('method', 'H.foo', H.foo1, None, None) == '(b, *c)'
    assert gen.format_signature('method', 'H.foo', H.foo1, 'a', None) == '(a)'
    assert gen.format_signature('method', 'H.foo', H.foo2, None, None) == '(b, *c)'

    # test exception handling
    raises(RuntimeError, gen.format_signature, 'function', 'int', int, None, None)

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


def test_get_doc():
    def getdocl(*args):
        # strip the empty line at the end
        return list(gen.get_doc(*args))[:-1]

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

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

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

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

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

    # already-unicode docstrings must be taken literally
    def f():
        u"""Döcstring"""
    assert getdocl('function', 'f', f) == [u'Döcstring']

    # class docstring: depends on config value which one is taken
    class C:
        """Class docstring"""
        def __init__(self):
            """Init docstring"""
    gen.env.config.autoclass_content = 'class'
    assert getdocl('class', 'C', C) == ['Class docstring']
    gen.env.config.autoclass_content = 'init'
    assert getdocl('class', 'C', C) == ['Init docstring']
    gen.env.config.autoclass_content = 'both'
    assert getdocl('class', 'C', C) == ['Class docstring', '', 'Init docstring']

    class D:
        """Class docstring"""
        def __init__(self):
            """Init docstring

            Other
             lines
            """

    # Indentation is normalized for 'both'
    assert getdocl('class', 'D', D) == ['Class docstring', '', 'Init docstring',
                                        '', 'Other', ' lines']

    class E:
        def __init__(self):
            """Init docstring"""

    # docstring processing by event handler
    assert getdocl('class', 'bar', E) == ['Init docstring', '', '42']


def test_docstring_processing_functions():
    lid = app.connect('autodoc-process-docstring', cut_lines(1, 1, ['function']))
    def f():
        """
        first line
        second line
        third line
        """
    assert list(gen.get_doc('function', 'f', f)) == ['second line', '']
    app.disconnect(lid)

    lid = app.connect('autodoc-process-docstring', between('---', ['function']))
    def f():
        """
        first line
        ---
        second line
        ---
        third line
        """
    assert list(gen.get_doc('function', 'f', f)) == ['second line', '']
    app.disconnect(lid)


def test_generate():
    def assert_warns(warn_str, *args):
        gen.generate(*args)
        assert len(gen.result) == 0, gen.result
        assert len(gen.warnings) == 1, gen.warnings
        assert warn_str in gen.warnings[0], gen.warnings
        del gen.warnings[:]

    def assert_works(*args):
        gen.generate(*args)
        assert gen.result
        assert len(gen.warnings) == 0, gen.warnings
        del gen.result[:]

    def assert_processes(items, *args):
        del processed_docstrings[:]
        del processed_signatures[:]
        assert_works(*args)
        assert set(processed_docstrings) | set(processed_signatures) == set(items)

    def assert_result_contains(item, *args):
        gen.generate(*args)
        print '\n'.join(gen.result)
        assert len(gen.warnings) == 0, gen.warnings
        assert item in gen.result
        del gen.result[:]

    # no module found?
    assert_warns("import for autodocumenting 'foobar'",
                 'function', 'foobar', None, None)
    # importing
    assert_warns("import/find module 'test_foobar'",
                 'module', 'test_foobar', None, None)
    # attributes missing
    assert_warns("import/find function 'util.foobar'",
                 'function', 'util.foobar', None, None)

    # test auto and given content mixing
    gen.env.currmodule = 'test_autodoc'
    assert_result_contains('   Function.', 'method', 'Class.meth', [], None)
    add_content = ViewList()
    add_content.append('Content.', '', 0)
    assert_result_contains('   Function.', 'method', 'Class.meth', [], add_content)
    assert_result_contains('   Content.', 'method', 'Class.meth', [], add_content)

    # test check_module
    gen.generate('function', 'raises', None, None, check_module=True)
    assert len(gen.result) == 0

    # assert that exceptions can be documented
    assert_works('exception', 'test_autodoc.CustomEx', ['__all__'], None)
    assert_works('exception', 'test_autodoc.CustomEx', [], None)

    # test diverse inclusion settings for members
    should = [('class', 'Class')]
    assert_processes(should, 'class', 'Class', [], None)
    should.extend([('method', 'Class.meth')])
    assert_processes(should, 'class', 'Class', ['meth'], None)
    should.extend([('attribute', 'Class.prop')])
    assert_processes(should, 'class', 'Class', ['__all__'], None)
    options.undoc_members = True
    should.append(('method', 'Class.undocmeth'))
    assert_processes(should, 'class', 'Class', ['__all__'], None)
    options.inherited_members = True
    should.append(('method', 'Class.inheritedmeth'))
    assert_processes(should, 'class', 'Class', ['__all__'], None)

    # test module flags
    assert_result_contains('.. module:: test_autodoc', 'module',
                           'test_autodoc', [], None)
    options.synopsis = 'Synopsis'
    assert_result_contains('   :synopsis: Synopsis', 'module', 'test_autodoc', [], None)
    options.deprecated = True
    assert_result_contains('   :deprecated:', 'module', 'test_autodoc', [], None)
    options.platform = 'Platform'
    assert_result_contains('   :platform: Platform', 'module', 'test_autodoc', [], None)
    # test if __all__ is respected for modules
    assert_result_contains('.. class:: Class', 'module', 'test_autodoc',
                           ['__all__'], None)
    try:
        assert_result_contains('.. exception:: CustomEx', 'module', 'test_autodoc',
                               ['__all__'], None)
    except AssertionError:
        pass
    else:
        assert False, 'documented CustomEx which is not in __all__'

    # test noindex flag
    options.noindex = True
    assert_result_contains('   :noindex:', 'module', 'test_autodoc', [], None)
    assert_result_contains('   :noindex:', 'class', 'Base', [], None)

    # okay, now let's get serious about mixing Python and C signature stuff
    assert_result_contains('.. class:: CustomDict', 'class', 'CustomDict',
                           ['__all__'], None)


# --- generate fodder ------------

__all__ = ['Class']

class CustomEx(Exception):
    """My custom exception."""

    def f(self):
        """Exception method."""


class Base(object):
    def inheritedmeth(self):
        """Inherited function."""

class Class(Base):
    """Class to document."""

    def meth(self):
        """Function."""

    def undocmeth(self):
        pass

    @property
    def prop(self):
        """Property."""

class CustomDict(dict):
    """Docstring."""

def function(foo, *args, **kwds):
    """
    Return spam.
    """
    pass
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.