# -*- coding: utf-8 -*- """ test_autodoc ~~~~~~~~~~~~ Test the autodoc extension. This tests mainly the Documenters; the auto directives are tested in a test source file translated by test_build. :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ # "raises" imported for usage by autodoc import six import sys from util import TestApp, Struct, raises, SkipTest import pytest from six import StringIO from docutils.statemachine import ViewList from sphinx.ext.autodoc import AutoDirective, add_documenter, \ ModuleLevelDocumenter, FunctionDocumenter, cut_lines, between, ALL app = None def setup_module(): global app app = TestApp() app.builder.env.app = app app.builder.env.temp_data['docname'] = 'dummy' app.connect('autodoc-process-docstring', process_docstring) app.connect('autodoc-process-signature', process_signature) app.connect('autodoc-skip-member', skip_member) def teardown_module(): app.cleanup() directive = options = None @pytest.fixture def setup_test(): global options, directive global processed_docstrings, processed_signatures, _warnings options = Struct( 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 = 'alphabetic', exclude_members = set(), ) directive = Struct( env = app.builder.env, genopt = options, result = ViewList(), warn = warnfunc, filename_set = set(), ) processed_docstrings = [] processed_signatures = [] _warnings = [] _warnings = [] processed_docstrings = [] processed_signatures = [] def warnfunc(msg): _warnings.append(msg) def process_docstring(app, what, name, obj, options, lines): processed_docstrings.append((what, name)) if name == 'bar': lines.extend(['42', '']) 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 @pytest.mark.usefixtures('setup_test') def test_generate(): def assert_warns(warn_str, objtype, name, **kw): inst = AutoDirective._registry[objtype](directive, name) inst.generate(**kw) assert len(directive.result) == 0, directive.result assert len(_warnings) == 1, _warnings assert warn_str in _warnings[0], _warnings del _warnings[:] def assert_works(objtype, name, **kw): inst = AutoDirective._registry[objtype](directive, name) inst.generate(**kw) assert directive.result # print '\n'.join(directive.result) assert len(_warnings) == 0, _warnings del directive.result[:] def assert_processes(items, objtype, name, **kw): del processed_docstrings[:] del processed_signatures[:] assert_works(objtype, name, **kw) assert set(processed_docstrings) | set(processed_signatures) == set(items) def assert_result_contains(item, objtype, name, **kw): inst = AutoDirective._registry[objtype](directive, name) inst.generate(**kw) # print '\n'.join(directive.result) assert len(_warnings) == 0, _warnings assert item in directive.result del directive.result[:] def assert_order(items, objtype, name, member_order, **kw): inst = AutoDirective._registry[objtype](directive, name) inst.options.member_order = member_order inst.generate(**kw) assert len(_warnings) == 0, _warnings items = list(reversed(items)) lineiter = iter(directive.result) # for line in directive.result: # if line.strip(): # print repr(line) while items: item = items.pop() for line in lineiter: if line == item: break else: # ran out of items! assert False, ('item %r not found in result or not in the ' ' correct order' % item) del directive.result[:] options.members = [] # no module found? assert_warns("import for autodocumenting 'foobar'", 'function', 'foobar', more_content=None) # importing assert_warns("failed to import module 'test_foobar'", 'module', 'test_foobar', more_content=None) # attributes missing assert_warns("failed to import function 'foobar' from module 'util'", 'function', 'util.foobar', more_content=None) # method missing assert_warns("failed to import method 'Class.foobar' from module 'test_autodoc_py35';", 'method', 'test_autodoc_py35.Class.foobar', more_content=None) # test auto and given content mixing directive.env.ref_context['py:module'] = 'test_autodoc_py35' assert_result_contains(' Function.', 'method', 'Class.meth') add_content = ViewList() add_content.append('Content.', '', 0) assert_result_contains(' Function.', 'method', 'Class.meth', more_content=add_content) assert_result_contains(' Content.', 'method', 'Class.meth', more_content=add_content) # test check_module inst = FunctionDocumenter(directive, 'raises') inst.generate(check_module=True) assert len(directive.result) == 0 # assert that exceptions can be documented assert_works('exception', 'test_autodoc_py35.CustomEx', all_members=True) assert_works('exception', 'test_autodoc_py35.CustomEx') # test diverse inclusion settings for members should = [('class', 'test_autodoc_py35.Class')] assert_processes(should, 'class', 'Class') should.extend([('method', 'test_autodoc_py35.Class.meth')]) options.members = ['meth'] options.exclude_members = set(['excludemeth']) assert_processes(should, 'class', 'Class') should.extend([('attribute', 'test_autodoc_py35.Class.prop'), ('attribute', 'test_autodoc_py35.Class.descr'), ('attribute', 'test_autodoc_py35.Class.attr'), ('attribute', 'test_autodoc_py35.Class.docattr'), ('attribute', 'test_autodoc_py35.Class.udocattr'), ('attribute', 'test_autodoc_py35.Class.mdocattr'), ('attribute', 'test_autodoc_py35.Class.inst_attr_comment'), ('attribute', 'test_autodoc_py35.Class.inst_attr_inline'), ('attribute', 'test_autodoc_py35.Class.inst_attr_string'), ('method', 'test_autodoc_py35.Class.moore'), ]) if six.PY3 and sys.version_info[:2] >= (3, 5): should.extend([ ('method', 'test_autodoc_py35.Class.do_coroutine'), ]) options.members = ALL assert_processes(should, 'class', 'Class') options.undoc_members = True should.extend((('attribute', 'test_autodoc_py35.Class.skipattr'), ('method', 'test_autodoc_py35.Class.undocmeth'), ('method', 'test_autodoc_py35.Class.roger'))) assert_processes(should, 'class', 'Class') options.inherited_members = True should.append(('method', 'test_autodoc_py35.Class.inheritedmeth')) assert_processes(should, 'class', 'Class') # test special members options.special_members = ['__special1__'] should.append(('method', 'test_autodoc_py35.Class.__special1__')) assert_processes(should, 'class', 'Class') options.special_members = ALL should.append(('method', 'test_autodoc_py35.Class.__special2__')) assert_processes(should, 'class', 'Class') options.special_members = False # --- generate fodder ------------ __all__ = ['Class'] #: documentation for the integer integer = 1 class CustomEx(Exception): """My custom exception.""" def f(self): """Exception method.""" class CustomDataDescriptor(object): """Descriptor class docstring.""" def __init__(self, doc): self.__doc__ = doc def __get__(self, obj, type=None): if obj is None: return self return 42 def meth(self): """Function.""" return "The Answer" def _funky_classmethod(name, b, c, d, docstring=None): """Generates a classmethod for a class from a template by filling out some arguments.""" def template(cls, a, b, c, d=4, e=5, f=6): return a, b, c, d, e, f from functools import partial function = partial(template, b=b, c=c, d=d) function.__name__ = name function.__doc__ = docstring return classmethod(function) class Base(object): def inheritedmeth(self): """Inherited function.""" if six.PY3 and sys.version_info[:2] >= (3, 5): async def _other_coro_func(): return "run" class Class(Base): """Class to document.""" descr = CustomDataDescriptor("Descriptor instance docstring.") def meth(self): """Function.""" def undocmeth(self): pass def skipmeth(self): """Method that should be skipped.""" def excludemeth(self): """Method that should be excluded.""" # should not be documented skipattr = 'foo' #: should be documented -- süß attr = 'bar' @property def prop(self): """Property.""" docattr = 'baz' """should likewise be documented -- süß""" udocattr = 'quux' u"""should be documented as well - süß""" # initialized to any class imported from another module mdocattr = StringIO() """should be documented as well - süß""" roger = _funky_classmethod("roger", 2, 3, 4) moore = _funky_classmethod("moore", 9, 8, 7, docstring="moore(a, e, f) -> happiness") def __init__(self, arg): self.inst_attr_inline = None #: an inline documented instance attr #: a documented instance attribute self.inst_attr_comment = None self.inst_attr_string = None """a documented instance attribute""" def __special1__(self): """documented special method""" def __special2__(self): # undocumented special method pass if six.PY3 and sys.version_info[:2] >= (3, 5): async def do_coroutine(self): """A documented coroutine function""" attr_coro_result = await _other_coro_func()