diff --git a/CHANGES b/CHANGES index b3eb60219..db6ec12e7 100644 --- a/CHANGES +++ b/CHANGES @@ -101,6 +101,8 @@ Bugs fixed * #2707: (latex) the column width is badly computed for tabular * #2799: Sphinx installs roles and directives automatically on importing sphinx module. Now Sphinx installs them on running application. +* `sphinx.ext.autodoc` crashes if target code imports * from mock modules + by `autodoc_mock_imports`. Documentation ------------- diff --git a/sphinx/ext/autodoc.py b/sphinx/ext/autodoc.py index 08bc99cdf..a0480341c 100644 --- a/sphinx/ext/autodoc.py +++ b/sphinx/ext/autodoc.py @@ -86,17 +86,21 @@ class Options(dict): class _MockModule(object): """Used by autodoc_mock_imports.""" + __file__ = '/dev/null' + __path__ = '/dev/null' + def __init__(self, *args, **kwargs): - pass + self.__all__ = [] def __call__(self, *args, **kwargs): return _MockModule() + def _append_submodule(self, submod): + self.__all__.append(submod) + @classmethod def __getattr__(cls, name): - if name in ('__file__', '__path__'): - return '/dev/null' - elif name[0] == name[0].upper(): + if name[0] == name[0].upper(): # Not very good, we assume Uppercase names are classes... mocktype = type(name, (), {}) mocktype.__module__ = __name__ @@ -109,9 +113,12 @@ def mock_import(modname): if '.' in modname: pkg, _n, mods = modname.rpartition('.') mock_import(pkg) - mod = _MockModule() - sys.modules[modname] = mod - return mod + if isinstance(sys.modules[pkg], _MockModule): + sys.modules[pkg]._append_submodule(mods) + + if modname not in sys.modules: + mod = _MockModule() + sys.modules[modname] = mod ALL = object() @@ -514,7 +521,7 @@ class Documenter(object): try: dbg('[autodoc] import %s', self.modname) for modname in self.env.config.autodoc_mock_imports: - dbg('[autodoc] adding a mock module %s!', self.modname) + dbg('[autodoc] adding a mock module %s!', modname) mock_import(modname) __import__(self.modname) parent = None diff --git a/tests/roots/test-ext-autodoc/autodoc_dummy_module.py b/tests/roots/test-ext-autodoc/autodoc_dummy_module.py new file mode 100644 index 000000000..c05d96e0d --- /dev/null +++ b/tests/roots/test-ext-autodoc/autodoc_dummy_module.py @@ -0,0 +1,6 @@ +from dummy import * + + +def test(): + """Dummy function using dummy.*""" + dummy_function() diff --git a/tests/roots/test-ext-autodoc/conf.py b/tests/roots/test-ext-autodoc/conf.py new file mode 100644 index 000000000..01e6dcc75 --- /dev/null +++ b/tests/roots/test-ext-autodoc/conf.py @@ -0,0 +1,12 @@ +import sys, os + +sys.path.insert(0, os.path.abspath('.')) + +extensions = ['sphinx.ext.autodoc'] + +# The suffix of source filenames. +source_suffix = '.rst' + +autodoc_mock_imports = [ + 'dummy' +] diff --git a/tests/roots/test-ext-autodoc/contents.rst b/tests/roots/test-ext-autodoc/contents.rst new file mode 100644 index 000000000..b808eafda --- /dev/null +++ b/tests/roots/test-ext-autodoc/contents.rst @@ -0,0 +1,3 @@ + +.. automodule:: autodoc_dummy_module + :members: diff --git a/tests/test_ext_autodoc.py b/tests/test_ext_autodoc.py new file mode 100644 index 000000000..dad7521af --- /dev/null +++ b/tests/test_ext_autodoc.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +""" + test_autodoc + ~~~~~~~~~~~~ + + Test the autodoc extension. + + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import pickle +from docutils import nodes +from sphinx import addnodes +from util import with_app + + +@with_app(buildername='dummy', testroot='ext-autodoc') +def test_autodoc(app, status, warning): + app.builder.build_all() + + content = pickle.loads((app.doctreedir / 'contents.doctree').bytes()) + assert isinstance(content[3], addnodes.desc) + assert content[3][0].astext() == 'autodoc_dummy_module.test' + assert content[3][1].astext() == 'Dummy function using dummy.*'