# Regression Testing for epydoc.docbuilder

# Test Function

This test function takes a string containing the contents of a module. It writes the string contents to a file, imports the file as a module, and uses build_doc to build documentation, and pretty prints the resulting ModuleDoc object. The `attribs` argument specifies which attributes of the APIDoc s should be displayed. The build argument gives the name of a variable in the module whose documentation should be built, instead of bilding docs for the whole module.

>     >>> from epydoc.test.util import runbuilder

>     >>> from epydoc.test.util import print_warnings
>     >>> print_warnings()

# Docformat selection

The docstrings format can be selected using the `__docformat__` module variable. In the second example below, where docformat='plaintext', the string "@ivar x: ..." will not be treated as a field, since the docstring format is plaintext.

>     >>> runbuilder(s='''
>     ...     __docformat__ = 'epytext'
>     ...     class Foo:
>     ...         """@ivar x: description..."""
>     ...     ''',
>     ...     build='Foo', attribs='descr variables')
>     ClassDoc for epydoc_test.Foo [0]
>      +- descr = None
>      +- variables
>         +- x => VariableDoc for epydoc_test.Foo.x [1]
>            +- descr = u'description...\n\n'

>     >>> runbuilder(s='''
>     ...     __docformat__ = 'plaintext'
>     ...     class Foo:
>     ...         """@var x: description..."""
>     ...     ''',
>     ...     build='Foo', attribs='descr variables')
>     ClassDoc for epydoc_test.Foo [0]
>      +- descr = u'@var x: description...\n'
>      +- variables = {}

Stuff from future doesn't appear as variable.

>     >>> runbuilder(s="""
>     ...     from __future__ import division
>     ...     from pickle import dump
>     ...     """,
>     ...     attribs='variables value')
>     ModuleDoc for epydoc_test [0]
>      +- variables
>         +- dump => VariableDoc for epydoc_test.dump [1]
>            +- value
>               +- ValueDoc for pickle.dump [2]

# Specifying constructor signature in class docstring

The class signature can be specified in the class docstring instead of __init__

>     >>> runbuilder(s='''
>     ...     class Foo:
>     ...         """This is the object docstring
>     ...
>     ...         @param a: init param.
>     ...         @ivar a: instance var.
>     ...         @type a: date
>     ...         """
>     ...         def __init__(self, a):
>     ...             """The ctor docstring."""
>     ...     ''',
>     ...     build="Foo",
>     ...     attribs="variables name value "
>     ...         "posargs vararg kwarg type arg_types arg_descrs")
>     ClassDoc for epydoc_test.Foo [0]
>      +- variables
>         +- __init__ => VariableDoc for epydoc_test.Foo.__init__ [1]
>         |  +- name = '__init__'
>         |  +- value
>         |     +- RoutineDoc for epydoc_test.Foo.__init__ [2]
>         |        +- arg_descrs = [([u'a'], ...
>         |        +- arg_types = {u'a': ...
>         |        +- kwarg = None
>         |        +- posargs = ['self', 'a']
>         |        +- vararg = None
>         +- a => VariableDoc for epydoc_test.Foo.a [3]
>            +- name = u'a'
>            +- value = <UNKNOWN>

Also keywords arguments can be put in the constructor

>     >>> runbuilder(s='''
>     ...     class Foo:
>     ...         """This is the object docstring
>     ...
>     ...         @keyword a: a kwarg.
>     ...         @type a: str
>     ...         """
>     ...         def __init__(self, **kwargs):
>     ...             """The ctor docstring."""
>     ...     ''',
>     ...     build="Foo",
>     ...     attribs="variables name value "
>     ...         "posargs vararg kwarg type arg_types arg_descrs")
>     ClassDoc for epydoc_test.Foo [0]
>      +- variables
>         +- __init__ => VariableDoc for epydoc_test.Foo.__init__ [1]
>            +- name = '__init__'
>            +- value
>               +- RoutineDoc for epydoc_test.Foo.__init__ [2]
>                  +- arg_descrs = [([u'a'], ...
>                  +- arg_types = {u'a': ...
>                  +- kwarg = 'kwargs'
>                  +- posargs = ['self']
>                  +- vararg = None

A missing docstring on the __init__ is not an issue.

>     >>> runbuilder(s='''
>     ...     class Foo:
>     ...         """This is the object docstring
>     ...
>     ...         @param a: a param.
>     ...         @type a: str
>     ...         """
>     ...         def __init__(self, a):
>     ...             pass
>     ...     ''',
>     ...     build="Foo",
>     ...     attribs="variables name value "
>     ...         "posargs vararg kwarg type arg_types arg_descrs")
>     ClassDoc for epydoc_test.Foo [0]
>      +- variables
>         +- __init__ => VariableDoc for epydoc_test.Foo.__init__ [1]
>            +- name = '__init__'
>            +- value
>               +- RoutineDoc for epydoc_test.Foo.__init__ [2]
>                  +- arg_descrs = [([u'a'], ...
>                  +- arg_types = {u'a': ...
>                  +- kwarg = None
>                  +- posargs = ['self', 'a']
>                  +- vararg = None

Exceptions can be put in the docstring class, and they are assigned to the constructor too.

>     >>> runbuilder(s='''
>     ...     class Foo:
>     ...         """Foo(x, y)
>     ...
>     ...         A class to ship rockets in outer space.
>     ...
>     ...         @param x: first param
>     ...         @param y: second param
>     ...         @except ValueError: frobnication error
>     ...         """
>     ...         def __init__(self, a, b):
>     ...             """__init__ doc"""
>     ...             pass
>     ...     ''',
>     ...     build="Foo",
>     ...     attribs="variables name value exception_descrs "
>     ...         "posargs vararg kwarg type arg_types arg_descrs docstring")
>     ClassDoc for epydoc_test.Foo [0]
>      +- docstring = u'A class to ship rockets in outer sp...
>      +- variables
>         +- __init__ => VariableDoc for epydoc_test.Foo.__init__ [1]
>            +- docstring = <UNKNOWN>
>            +- name = '__init__'
>            +- value
>               +- RoutineDoc for epydoc_test.Foo.__init__ [2]
>                  +- arg_descrs = [([u'x'], u'first param'), ([u'y'], u...
>                  +- arg_types = {}
>                  +- docstring = u'__init__ doc'
>                  +- exception_descrs = [(DottedName(u'ValueError'), <epydoc....
>                  +- kwarg = None
>                  +- posargs = [u'x', u'y']
>                  +- vararg = None

Epydoc can also grok the constructor signature from the class docstring

>     >>> runbuilder(s='''
>     ...     class Foo:
>     ...         """Foo(x, y)
>     ...
>     ...         A class to ship rockets in outer space.
>     ...
>     ...         @param x: first param
>     ...         @param y: second param
>     ...         """
>     ...         def __init__(self, a, b):
>     ...             """__init__ doc"""
>     ...             pass
>     ...     ''',
>     ...     build="Foo",
>     ...     attribs="variables name value "
>     ...         "posargs vararg kwarg type arg_types arg_descrs docstring")
>     ClassDoc for epydoc_test.Foo [0]
>      +- docstring = u'A class to ship rockets ...
>      +- variables
>         +- __init__ => VariableDoc for epydoc_test.Foo.__init__ [1]
>            +- docstring = <UNKNOWN>
>            +- name = '__init__'
>            +- value
>               +- RoutineDoc for epydoc_test.Foo.__init__ [2]
>                  +- arg_descrs = [([u'x'], ...
>                  +- arg_types = {}
>                  +- docstring = u'__init__ doc'
>                  +- kwarg = None
>                  +- posargs = [u'x', u'y']
>                  +- vararg = None

A type can apply to both a param and a variable

>     >>> runbuilder(s='''
>     ...     class Foo:
>     ...         """This is the object docstring
>     ...
>     ...         @param a: init param.
>     ...         @ivar a: instance var.
>     ...         @type a: date
>     ...         """
>     ...         def __init__(self, a):
>     ...             pass
>     ...     ''',
>     ...     build="Foo",
>     ...     attribs="variables name value "
>     ...         "posargs vararg kwarg type_descr arg_types arg_descrs")
>     ClassDoc for epydoc_test.Foo [0]
>      +- variables
>         +- __init__ => VariableDoc for epydoc_test.Foo.__init__ [1]
>         |  +- name = '__init__'
>         |  +- type_descr = None
>         |  +- value
>         |     +- RoutineDoc for epydoc_test.Foo.__init__ [2]
>         |        +- arg_descrs = [([u'a'], u'init param.')]
>         |        +- arg_types = {u'a': u'date'}
>         |        +- kwarg = None
>         |        +- posargs = ['self', 'a']
>         |        +- vararg = None
>         +- a => VariableDoc for epydoc_test.Foo.a [3]
>            +- name = u'a'
>            +- type_descr = u'date\n\n'
>            +- value = <UNKNOWN>

But there can also be two different types

>     >>> runbuilder(s='''
>     ...     class Foo:
>     ...         """This is the object docstring
>     ...
>     ...         @param a: init param.
>     ...         @type a: string
>     ...         @ivar a: instance var.
>     ...         @type a: date
>     ...         """
>     ...         def __init__(self, a):
>     ...             pass
>     ...     ''',
>     ...     build="Foo",
>     ...     attribs="variables name value "
>     ...         "posargs vararg kwarg type_descr arg_types arg_descrs")
>     ClassDoc for epydoc_test.Foo [0]
>      +- variables
>         +- __init__ => VariableDoc for epydoc_test.Foo.__init__ [1]
>         |  +- name = '__init__'
>         |  +- type_descr = None
>         |  +- value
>         |     +- RoutineDoc for epydoc_test.Foo.__init__ [2]
>         |        +- arg_descrs = [([u'a'], u'init param.')]
>         |        +- arg_types = {u'a': u'string'}
>         |        +- kwarg = None
>         |        +- posargs = ['self', 'a']
>         |        +- vararg = None
>         +- a => VariableDoc for epydoc_test.Foo.a [3]
>            +- name = u'a'
>            +- type_descr = u'date\n\n'
>            +- value = <UNKNOWN>

# Value Representation

Currently, many variable reprs use the introspected form where it would really be better to use the parsed version. See SF bug #1653577. We intend to improve on this. This test documents the current behavior; but should be replaced when we change the behavior.

>     >>> from epydoc.test.util import buildvaluedoc
>     >>> from epydoc.compat import *
>     >>> def print_py_reprs(s):
>     ...     value_doc = buildvaluedoc(s)
>     ...     print 'Var  Score  Repr\n'+'-'*50
>     ...     for (name, var_doc) in sorted(value_doc.variables.items()):
>     ...         if len(name) > 1: continue
>     ...         var_repr =  var_doc.value.pyval_repr()
>     ...         print " %s   %4s   %r" % (name, var_repr.score,
>     ...                                  var_repr.to_plaintext(None))

>     >>> print_py_reprs('''
>     ...     import re
>     ...     class Foo: pass
>     ...     class Bar:
>     ...         def __repr__(self): return "<specialized repr>"
>     ...     class Baz:
>     ...         def __repr__(self): raise ValueError()
>     ...     a = Foo()                  # pyval score < 0; use parse repr.
>     ...     b = Bar()                  # pyval score > 0; use pyval repr.
>     ...     c = Baz()                  # pyval score < 0; use parse repr.
>     ...     d = [1, 2, 3]              # pyval score > 0; use pyval repr.
>     ...     d.append(99)
>     ...     e = 3+5                    # pyval score > 0; use pyval repr.
>     ...     f = re.compile('hi+')      # pyval score > 0; use pyval repr.
>     ...     globals()['h'] = Baz()     # pyval score < 0; can't be parsed.
>     ...     i = [Foo(), 1, 2]          # pyval score < 0; use parse repr.
>     ...     j = [Foo(), 1, 2, 3]       # pyval score = 0; use pyval repr.
>     ...     ''')
>     Var  Score  Repr
>     --------------------------------------------------
>      a      0   u'Foo()'
>      b      1   u'<specialized repr>'
>      c      0   u'Baz()'
>      d      5   u'[1, 2, 3, 99]'
>      e      1   u'8'
>      f      1   u"re.compile(r'hi+')"
>      h    -99   u'??'
>      i      0   u'[Foo(), 1, 2]'
>      j      0   u'[<epydoc_test.Foo instance at ...>, 1, 2, 3]'

# Merging is_imported

When we do both parsing & introspection, and merge the result, we should trust the introspected APIDoc's is_imported value more than the parsed APIDoc. In particular, in the following example, `x` should have is_imported=True. But if we can't tell from introspection, then use parse info -- so `y` should be imported, but `z` should not.

>     >>> import epydoc.docintrospecter
>     >>> epydoc.docintrospecter.clear_cache()
>     >>> runbuilder(s='''
>     ...     import cStringIO
>     ...     from re import MULTILINE as y
>     ...     x = cStringIO
>     ...     z = y
>     ...     ''', attribs=("variables is_imported "))
>     ModuleDoc for epydoc_test [0]
>      +- variables
>         +- cStringIO => VariableDoc for epydoc_test.cStringIO [1]
>         |  +- is_imported = True
>         +- x => VariableDoc for epydoc_test.x [2]
>         |  +- is_imported = True
>         +- y => VariableDoc for epydoc_test.y [3]
>         |  +- is_imported = True
>         +- z => VariableDoc for epydoc_test.z [4]
>            +- is_imported = False

# Merging the right value

Test for the SF bug #1678046. Check that, in case of mismatch between parsed and introspected versions of a value, other values don't get damaged.

>     >>> runbuilder(s='''
>     ...     foo = None
>     ...     bar = None
>     ...
>     ...     def mangle():
>     ...         global foo
>     ...         foo = 'foo'
>     ...
>     ...     mangle()
>     ... ''',
>     ... build="bar",
>     ... attribs="pyval")
>     GenericValueDoc [0]
>      +- pyval = None