Home | History | Annotate | Download | only in docs
      1 # Copyright 2015 The TensorFlow Authors. All Rights Reserved.
      2 #
      3 # Licensed under the Apache License, Version 2.0 (the "License");
      4 # you may not use this file except in compliance with the License.
      5 # You may obtain a copy of the License at
      6 #
      7 #     http://www.apache.org/licenses/LICENSE-2.0
      8 #
      9 # Unless required by applicable law or agreed to in writing, software
     10 # distributed under the License is distributed on an "AS IS" BASIS,
     11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 # See the License for the specific language governing permissions and
     13 # limitations under the License.
     14 # ==============================================================================
     15 """Tests for documentation parser."""
     16 
     17 from __future__ import absolute_import
     18 from __future__ import division
     19 from __future__ import print_function
     20 
     21 import collections
     22 import functools
     23 import os
     24 import sys
     25 
     26 from tensorflow.python.platform import googletest
     27 from tensorflow.python.util import tf_inspect
     28 from tensorflow.tools.docs import doc_controls
     29 from tensorflow.tools.docs import parser
     30 
     31 # The test needs a real module. `types.ModuleType()` doesn't work, as the result
     32 # is a `builtin` module. Using "parser" here is arbitraty. The tests don't
     33 # depend on the module contents. At this point in the process the public api
     34 # has already been extracted.
     35 test_module = parser
     36 
     37 
     38 def test_function(unused_arg, unused_kwarg='default'):
     39   """Docstring for test function."""
     40   pass
     41 
     42 
     43 def test_function_with_args_kwargs(unused_arg, *unused_args, **unused_kwargs):
     44   """Docstring for second test function."""
     45   pass
     46 
     47 
     48 class ParentClass(object):
     49 
     50   @doc_controls.do_not_doc_inheritable
     51   def hidden_method(self):
     52     pass
     53 
     54 
     55 class TestClass(ParentClass):
     56   """Docstring for TestClass itself."""
     57 
     58   def a_method(self, arg='default'):
     59     """Docstring for a method."""
     60     pass
     61 
     62   def hidden_method(self):
     63     pass
     64 
     65   @doc_controls.do_not_generate_docs
     66   def hidden_method2(self):
     67     pass
     68 
     69   class ChildClass(object):
     70     """Docstring for a child class."""
     71     pass
     72 
     73   @property
     74   def a_property(self):
     75     """Docstring for a property."""
     76     pass
     77 
     78   CLASS_MEMBER = 'a class member'
     79 
     80 
     81 class DummyVisitor(object):
     82 
     83   def __init__(self, index, duplicate_of):
     84     self.index = index
     85     self.duplicate_of = duplicate_of
     86 
     87 
     88 class ParserTest(googletest.TestCase):
     89 
     90   def test_documentation_path(self):
     91     self.assertEqual('test.md', parser.documentation_path('test'))
     92     self.assertEqual('test/module.md', parser.documentation_path('test.module'))
     93 
     94   def test_replace_references(self):
     95     class HasOneMember(object):
     96 
     97       def foo(self):
     98         pass
     99 
    100     string = (
    101         'A @{tf.reference}, another @{tf.reference$with\nnewline}, a member '
    102         '@{tf.reference.foo}, and a @{tf.third$link `text` with `code` in '
    103         'it}.')
    104     duplicate_of = {'tf.third': 'tf.fourth'}
    105     index = {'tf.reference': HasOneMember,
    106              'tf.reference.foo': HasOneMember.foo,
    107              'tf.third': HasOneMember,
    108              'tf.fourth': HasOneMember}
    109 
    110     visitor = DummyVisitor(index, duplicate_of)
    111 
    112     reference_resolver = parser.ReferenceResolver.from_visitor(
    113         visitor=visitor, doc_index={}, py_module_names=['tf'])
    114 
    115     result = reference_resolver.replace_references(string, '../..')
    116     self.assertEqual('A <a href="../../tf/reference.md">'
    117                      '<code>tf.reference</code></a>, '
    118                      'another <a href="../../tf/reference.md">'
    119                      'with\nnewline</a>, '
    120                      'a member <a href="../../tf/reference.md#foo">'
    121                      '<code>tf.reference.foo</code></a>, '
    122                      'and a <a href="../../tf/fourth.md">link '
    123                      '<code>text</code> with '
    124                      '<code>code</code> in it</a>.', result)
    125 
    126   def test_doc_replace_references(self):
    127     string = '@{$doc1} @{$doc1#abc} @{$doc1$link} @{$doc1#def$zelda} @{$do/c2}'
    128 
    129     class DocInfo(object):
    130       pass
    131     doc1 = DocInfo()
    132     doc1.title = 'Title1'
    133     doc1.url = 'URL1'
    134     doc2 = DocInfo()
    135     doc2.title = 'Two words'
    136     doc2.url = 'somewhere/else'
    137     doc_index = {'doc1': doc1, 'do/c2': doc2}
    138 
    139     visitor = DummyVisitor(index={}, duplicate_of={})
    140 
    141     reference_resolver = parser.ReferenceResolver.from_visitor(
    142         visitor=visitor, doc_index=doc_index, py_module_names=['tf'])
    143     result = reference_resolver.replace_references(string, 'python')
    144     self.assertEqual('<a href="../URL1">Title1</a> '
    145                      '<a href="../URL1#abc">Title1</a> '
    146                      '<a href="../URL1">link</a> '
    147                      '<a href="../URL1#def">zelda</a> '
    148                      '<a href="../somewhere/else">Two words</a>', result)
    149 
    150   def test_docs_for_class(self):
    151 
    152     index = {
    153         'TestClass': TestClass,
    154         'TestClass.a_method': TestClass.a_method,
    155         'TestClass.a_property': TestClass.a_property,
    156         'TestClass.ChildClass': TestClass.ChildClass,
    157         'TestClass.CLASS_MEMBER': TestClass.CLASS_MEMBER
    158     }
    159 
    160     visitor = DummyVisitor(index=index, duplicate_of={})
    161 
    162     reference_resolver = parser.ReferenceResolver.from_visitor(
    163         visitor=visitor, doc_index={}, py_module_names=['tf'])
    164 
    165     tree = {
    166         'TestClass': ['a_method', 'a_property', 'ChildClass', 'CLASS_MEMBER']
    167     }
    168     parser_config = parser.ParserConfig(
    169         reference_resolver=reference_resolver,
    170         duplicates={},
    171         duplicate_of={},
    172         tree=tree,
    173         index=index,
    174         reverse_index={},
    175         guide_index={},
    176         base_dir='/')
    177 
    178     page_info = parser.docs_for_object(
    179         full_name='TestClass', py_object=TestClass, parser_config=parser_config)
    180 
    181     # Make sure the brief docstring is present
    182     self.assertEqual(
    183         tf_inspect.getdoc(TestClass).split('\n')[0], page_info.doc.brief)
    184 
    185     # Make sure the method is present
    186     self.assertEqual(TestClass.a_method, page_info.methods[0].obj)
    187 
    188     # Make sure that the signature is extracted properly and omits self.
    189     self.assertEqual(["arg='default'"], page_info.methods[0].signature)
    190 
    191     # Make sure the property is present
    192     self.assertIs(TestClass.a_property, page_info.properties[0].obj)
    193 
    194     # Make sure there is a link to the child class and it points the right way.
    195     self.assertIs(TestClass.ChildClass, page_info.classes[0].obj)
    196 
    197     # Make sure this file is contained as the definition location.
    198     self.assertEqual(os.path.relpath(__file__, '/'), page_info.defined_in.path)
    199 
    200   def test_namedtuple_field_order(self):
    201     namedtupleclass = collections.namedtuple('namedtupleclass',
    202                                              {'z', 'y', 'x', 'w', 'v', 'u'})
    203 
    204     index = {
    205         'namedtupleclass': namedtupleclass,
    206         'namedtupleclass.u': namedtupleclass.u,
    207         'namedtupleclass.v': namedtupleclass.v,
    208         'namedtupleclass.w': namedtupleclass.w,
    209         'namedtupleclass.x': namedtupleclass.x,
    210         'namedtupleclass.y': namedtupleclass.y,
    211         'namedtupleclass.z': namedtupleclass.z,
    212     }
    213 
    214     visitor = DummyVisitor(index=index, duplicate_of={})
    215 
    216     reference_resolver = parser.ReferenceResolver.from_visitor(
    217         visitor=visitor, doc_index={}, py_module_names=['tf'])
    218 
    219     tree = {'namedtupleclass': {'u', 'v', 'w', 'x', 'y', 'z'}}
    220     parser_config = parser.ParserConfig(
    221         reference_resolver=reference_resolver,
    222         duplicates={},
    223         duplicate_of={},
    224         tree=tree,
    225         index=index,
    226         reverse_index={},
    227         guide_index={},
    228         base_dir='/')
    229 
    230     page_info = parser.docs_for_object(
    231         full_name='namedtupleclass',
    232         py_object=namedtupleclass,
    233         parser_config=parser_config)
    234 
    235     # Each namedtiple field has a docstring of the form:
    236     #   'Alias for field number ##'. These props are returned sorted.
    237 
    238     def sort_key(prop_info):
    239       return int(prop_info.obj.__doc__.split(' ')[-1])
    240 
    241     self.assertSequenceEqual(page_info.properties,
    242                              sorted(page_info.properties, key=sort_key))
    243 
    244   def test_docs_for_class_should_skip(self):
    245 
    246     class Parent(object):
    247 
    248       @doc_controls.do_not_doc_inheritable
    249       def a_method(self, arg='default'):
    250         pass
    251 
    252     class Child(Parent):
    253 
    254       def a_method(self, arg='default'):
    255         pass
    256 
    257     index = {
    258         'Child': Child,
    259         'Child.a_method': Child.a_method,
    260     }
    261 
    262     visitor = DummyVisitor(index=index, duplicate_of={})
    263 
    264     reference_resolver = parser.ReferenceResolver.from_visitor(
    265         visitor=visitor, doc_index={}, py_module_names=['tf'])
    266 
    267     tree = {
    268         'Child': ['a_method'],
    269     }
    270 
    271     parser_config = parser.ParserConfig(
    272         reference_resolver=reference_resolver,
    273         duplicates={},
    274         duplicate_of={},
    275         tree=tree,
    276         index=index,
    277         reverse_index={},
    278         guide_index={},
    279         base_dir='/')
    280 
    281     page_info = parser.docs_for_object(
    282         full_name='Child', py_object=Child, parser_config=parser_config)
    283 
    284     # Make sure the `a_method` is not present
    285     self.assertEqual(0, len(page_info.methods))
    286 
    287   def test_docs_for_message_class(self):
    288 
    289     class CMessage(object):
    290 
    291       def hidden(self):
    292         pass
    293 
    294     class Message(object):
    295 
    296       def hidden2(self):
    297         pass
    298 
    299     class MessageMeta(object):
    300 
    301       def hidden3(self):
    302         pass
    303 
    304     class ChildMessage(CMessage, Message, MessageMeta):
    305 
    306       def my_method(self):
    307         pass
    308 
    309     index = {
    310         'ChildMessage': ChildMessage,
    311         'ChildMessage.hidden': ChildMessage.hidden,
    312         'ChildMessage.hidden2': ChildMessage.hidden2,
    313         'ChildMessage.hidden3': ChildMessage.hidden3,
    314         'ChildMessage.my_method': ChildMessage.my_method,
    315     }
    316 
    317     visitor = DummyVisitor(index=index, duplicate_of={})
    318 
    319     reference_resolver = parser.ReferenceResolver.from_visitor(
    320         visitor=visitor, doc_index={}, py_module_names=['tf'])
    321 
    322     tree = {'ChildMessage': ['hidden', 'hidden2', 'hidden3', 'my_method']}
    323 
    324     parser_config = parser.ParserConfig(
    325         reference_resolver=reference_resolver,
    326         duplicates={},
    327         duplicate_of={},
    328         tree=tree,
    329         index=index,
    330         reverse_index={},
    331         guide_index={},
    332         base_dir='/')
    333 
    334     page_info = parser.docs_for_object(
    335         full_name='ChildMessage',
    336         py_object=ChildMessage,
    337         parser_config=parser_config)
    338 
    339     self.assertEqual(1, len(page_info.methods))
    340     self.assertEqual('my_method', page_info.methods[0].short_name)
    341 
    342   def test_docs_for_module(self):
    343 
    344     index = {
    345         'TestModule':
    346             test_module,
    347         'TestModule.test_function':
    348             test_function,
    349         'TestModule.test_function_with_args_kwargs':
    350             test_function_with_args_kwargs,
    351         'TestModule.TestClass':
    352             TestClass,
    353     }
    354 
    355     visitor = DummyVisitor(index=index, duplicate_of={})
    356 
    357     reference_resolver = parser.ReferenceResolver.from_visitor(
    358         visitor=visitor, doc_index={}, py_module_names=['tf'])
    359 
    360     tree = {
    361         'TestModule': ['TestClass', 'test_function',
    362                        'test_function_with_args_kwargs']
    363     }
    364     parser_config = parser.ParserConfig(
    365         reference_resolver=reference_resolver,
    366         duplicates={},
    367         duplicate_of={},
    368         tree=tree,
    369         index=index,
    370         reverse_index={},
    371         guide_index={},
    372         base_dir='/')
    373 
    374     page_info = parser.docs_for_object(
    375         full_name='TestModule',
    376         py_object=test_module,
    377         parser_config=parser_config)
    378 
    379     # Make sure the brief docstring is present
    380     self.assertEqual(
    381         tf_inspect.getdoc(test_module).split('\n')[0], page_info.doc.brief)
    382 
    383     # Make sure that the members are there
    384     funcs = {f_info.obj for f_info in page_info.functions}
    385     self.assertEqual({test_function, test_function_with_args_kwargs}, funcs)
    386 
    387     classes = {cls_info.obj for cls_info in page_info.classes}
    388     self.assertEqual({TestClass}, classes)
    389 
    390     # Make sure the module's file is contained as the definition location.
    391     self.assertEqual(
    392         os.path.relpath(test_module.__file__, '/'), page_info.defined_in.path)
    393 
    394   def test_docs_for_function(self):
    395     index = {
    396         'test_function': test_function
    397     }
    398 
    399     visitor = DummyVisitor(index=index, duplicate_of={})
    400 
    401     reference_resolver = parser.ReferenceResolver.from_visitor(
    402         visitor=visitor, doc_index={}, py_module_names=['tf'])
    403 
    404     tree = {
    405         '': ['test_function']
    406     }
    407     parser_config = parser.ParserConfig(
    408         reference_resolver=reference_resolver,
    409         duplicates={},
    410         duplicate_of={},
    411         tree=tree,
    412         index=index,
    413         reverse_index={},
    414         guide_index={},
    415         base_dir='/')
    416 
    417     page_info = parser.docs_for_object(
    418         full_name='test_function',
    419         py_object=test_function,
    420         parser_config=parser_config)
    421 
    422     # Make sure the brief docstring is present
    423     self.assertEqual(
    424         tf_inspect.getdoc(test_function).split('\n')[0], page_info.doc.brief)
    425 
    426     # Make sure the extracted signature is good.
    427     self.assertEqual(['unused_arg', "unused_kwarg='default'"],
    428                      page_info.signature)
    429 
    430     # Make sure this file is contained as the definition location.
    431     self.assertEqual(os.path.relpath(__file__, '/'), page_info.defined_in.path)
    432 
    433   def test_docs_for_function_with_kwargs(self):
    434     index = {
    435         'test_function_with_args_kwargs': test_function_with_args_kwargs
    436     }
    437 
    438     visitor = DummyVisitor(index=index, duplicate_of={})
    439 
    440     reference_resolver = parser.ReferenceResolver.from_visitor(
    441         visitor=visitor, doc_index={}, py_module_names=['tf'])
    442 
    443     tree = {
    444         '': ['test_function_with_args_kwargs']
    445     }
    446     parser_config = parser.ParserConfig(
    447         reference_resolver=reference_resolver,
    448         duplicates={},
    449         duplicate_of={},
    450         tree=tree,
    451         index=index,
    452         reverse_index={},
    453         guide_index={},
    454         base_dir='/')
    455 
    456     page_info = parser.docs_for_object(
    457         full_name='test_function_with_args_kwargs',
    458         py_object=test_function_with_args_kwargs,
    459         parser_config=parser_config)
    460 
    461     # Make sure the brief docstring is present
    462     self.assertEqual(
    463         tf_inspect.getdoc(test_function_with_args_kwargs).split('\n')[0],
    464         page_info.doc.brief)
    465 
    466     # Make sure the extracted signature is good.
    467     self.assertEqual(['unused_arg', '*unused_args', '**unused_kwargs'],
    468                      page_info.signature)
    469 
    470   def test_parse_md_docstring(self):
    471 
    472     def test_function_with_fancy_docstring(arg):
    473       """Function with a fancy docstring.
    474 
    475       And a bunch of references: @{tf.reference}, another @{tf.reference},
    476           a member @{tf.reference.foo}, and a @{tf.third}.
    477 
    478       Args:
    479         arg: An argument.
    480 
    481       Raises:
    482         an exception
    483 
    484       Returns:
    485         arg: the input, and
    486         arg: the input, again.
    487 
    488       @compatibility(numpy)
    489       NumPy has nothing as awesome as this function.
    490       @end_compatibility
    491 
    492       @compatibility(theano)
    493       Theano has nothing as awesome as this function.
    494 
    495       Check it out.
    496       @end_compatibility
    497 
    498       """
    499       return arg, arg
    500 
    501     class HasOneMember(object):
    502 
    503       def foo(self):
    504         pass
    505 
    506     duplicate_of = {'tf.third': 'tf.fourth'}
    507     index = {
    508         'tf': test_module,
    509         'tf.fancy': test_function_with_fancy_docstring,
    510         'tf.reference': HasOneMember,
    511         'tf.reference.foo': HasOneMember.foo,
    512         'tf.third': HasOneMember,
    513         'tf.fourth': HasOneMember
    514     }
    515 
    516     visitor = DummyVisitor(index=index, duplicate_of=duplicate_of)
    517 
    518     reference_resolver = parser.ReferenceResolver.from_visitor(
    519         visitor=visitor, doc_index={}, py_module_names=['tf'])
    520 
    521     doc_info = parser._parse_md_docstring(test_function_with_fancy_docstring,
    522                                           '../..', reference_resolver)
    523 
    524     self.assertNotIn('@', doc_info.docstring)
    525     self.assertNotIn('compatibility', doc_info.docstring)
    526     self.assertNotIn('Raises:', doc_info.docstring)
    527 
    528     self.assertEqual(len(doc_info.function_details), 3)
    529     self.assertEqual(set(doc_info.compatibility.keys()), {'numpy', 'theano'})
    530 
    531     self.assertEqual(doc_info.compatibility['numpy'],
    532                      'NumPy has nothing as awesome as this function.\n')
    533 
    534   def test_generate_index(self):
    535 
    536     index = {
    537         'tf': test_module,
    538         'tf.TestModule': test_module,
    539         'tf.test_function': test_function,
    540         'tf.TestModule.test_function': test_function,
    541         'tf.TestModule.TestClass': TestClass,
    542         'tf.TestModule.TestClass.a_method': TestClass.a_method,
    543         'tf.TestModule.TestClass.a_property': TestClass.a_property,
    544         'tf.TestModule.TestClass.ChildClass': TestClass.ChildClass,
    545     }
    546     duplicate_of = {'tf.TestModule.test_function': 'tf.test_function'}
    547 
    548     visitor = DummyVisitor(index=index, duplicate_of=duplicate_of)
    549 
    550     reference_resolver = parser.ReferenceResolver.from_visitor(
    551         visitor=visitor, doc_index={}, py_module_names=['tf'])
    552 
    553     docs = parser.generate_global_index('TestLibrary', index=index,
    554                                         reference_resolver=reference_resolver)
    555 
    556     # Make sure duplicates and non-top-level symbols are in the index, but
    557     # methods and properties are not.
    558     self.assertNotIn('a_method', docs)
    559     self.assertNotIn('a_property', docs)
    560     self.assertIn('TestModule.TestClass', docs)
    561     self.assertIn('TestModule.TestClass.ChildClass', docs)
    562     self.assertIn('TestModule.test_function', docs)
    563     # Leading backtick to make sure it's included top-level.
    564     # This depends on formatting, but should be stable.
    565     self.assertIn('<code>tf.test_function', docs)
    566 
    567   def test_argspec_for_functools_partial(self):
    568     # pylint: disable=unused-argument
    569     def test_function_for_partial1(arg1, arg2, kwarg1=1, kwarg2=2):
    570       pass
    571 
    572     def test_function_for_partial2(arg1, arg2, *my_args, **my_kwargs):
    573       pass
    574     # pylint: enable=unused-argument
    575 
    576     # pylint: disable=protected-access
    577     # Make sure everything works for regular functions.
    578     expected = tf_inspect.FullArgSpec(
    579         args=['arg1', 'arg2', 'kwarg1', 'kwarg2'],
    580         varargs=None,
    581         varkw=None,
    582         defaults=(1, 2),
    583         kwonlyargs=[],
    584         kwonlydefaults=None,
    585         annotations={})
    586     self.assertEqual(expected, parser._get_arg_spec(test_function_for_partial1))
    587 
    588     # Make sure doing nothing works.
    589     expected = tf_inspect.FullArgSpec(
    590         args=['arg1', 'arg2', 'kwarg1', 'kwarg2'],
    591         varargs=None,
    592         varkw=None,
    593         defaults=(1, 2),
    594         kwonlyargs=[],
    595         kwonlydefaults=None,
    596         annotations={})
    597     partial = functools.partial(test_function_for_partial1)
    598     self.assertEqual(expected, parser._get_arg_spec(partial))
    599 
    600     # Make sure setting args from the front works.
    601     expected = tf_inspect.FullArgSpec(
    602         args=['arg2', 'kwarg1', 'kwarg2'],
    603         varargs=None,
    604         varkw=None,
    605         defaults=(1, 2),
    606         kwonlyargs=[],
    607         kwonlydefaults=None,
    608         annotations={})
    609     partial = functools.partial(test_function_for_partial1, 1)
    610     self.assertEqual(expected, parser._get_arg_spec(partial))
    611 
    612     expected = tf_inspect.FullArgSpec(
    613         args=['kwarg2'],
    614         varargs=None,
    615         varkw=None,
    616         defaults=(2,),
    617         kwonlyargs=[],
    618         kwonlydefaults=None,
    619         annotations={})
    620     partial = functools.partial(test_function_for_partial1, 1, 2, 3)
    621     self.assertEqual(expected, parser._get_arg_spec(partial))
    622 
    623     # Make sure setting kwargs works.
    624     expected = tf_inspect.FullArgSpec(
    625         args=['arg1', 'arg2', 'kwarg2'],
    626         varargs=None,
    627         varkw=None,
    628         defaults=(2,),
    629         kwonlyargs=[],
    630         kwonlydefaults=None,
    631         annotations={})
    632     partial = functools.partial(test_function_for_partial1, kwarg1=0)
    633     self.assertEqual(expected, parser._get_arg_spec(partial))
    634 
    635     expected = tf_inspect.FullArgSpec(
    636         args=['arg1', 'arg2', 'kwarg1'],
    637         varargs=None,
    638         varkw=None,
    639         defaults=(1,),
    640         kwonlyargs=[],
    641         kwonlydefaults=None,
    642         annotations={})
    643     partial = functools.partial(test_function_for_partial1, kwarg2=0)
    644     self.assertEqual(expected, parser._get_arg_spec(partial))
    645 
    646     expected = tf_inspect.FullArgSpec(
    647         args=['arg1'],
    648         varargs=None,
    649         varkw=None,
    650         defaults=(),
    651         kwonlyargs=[],
    652         kwonlydefaults=None,
    653         annotations={})
    654     partial = functools.partial(test_function_for_partial1,
    655                                 arg2=0, kwarg1=0, kwarg2=0)
    656     self.assertEqual(expected, parser._get_arg_spec(partial))
    657 
    658     # Make sure *args, *kwargs is accounted for.
    659     expected = tf_inspect.FullArgSpec(
    660         args=[],
    661         varargs='my_args',
    662         varkw='my_kwargs',
    663         defaults=(),
    664         kwonlyargs=[],
    665         kwonlydefaults=None,
    666         annotations={})
    667     partial = functools.partial(test_function_for_partial2, 0, 1)
    668     self.assertEqual(expected, parser._get_arg_spec(partial))
    669 
    670     # pylint: enable=protected-access
    671 
    672   def testSaveReferenceResolver(self):
    673     you_cant_serialize_this = object()
    674 
    675     duplicate_of = {'AClass': ['AClass2']}
    676     doc_index = {'doc': you_cant_serialize_this}
    677     is_fragment = {
    678         'tf': False,
    679         'tf.VERSION': True,
    680         'tf.AClass': False,
    681         'tf.AClass.method': True,
    682         'tf.AClass2': False,
    683         'tf.function': False
    684     }
    685     py_module_names = ['tf', 'tfdbg']
    686 
    687     resolver = parser.ReferenceResolver(duplicate_of, doc_index, is_fragment,
    688                                         py_module_names)
    689 
    690     outdir = googletest.GetTempDir()
    691 
    692     filepath = os.path.join(outdir, 'resolver.json')
    693 
    694     resolver.to_json_file(filepath)
    695     resolver2 = parser.ReferenceResolver.from_json_file(filepath, doc_index)
    696 
    697     # There are no __slots__, so all fields are visible in __dict__.
    698     self.assertEqual(resolver.__dict__, resolver2.__dict__)
    699 
    700   def testIsFreeFunction(self):
    701 
    702     result = parser.is_free_function(test_function, 'test_module.test_function',
    703                                      {'test_module': test_module})
    704     self.assertTrue(result)
    705 
    706     result = parser.is_free_function(test_function, 'TestClass.test_function',
    707                                      {'TestClass': TestClass})
    708     self.assertFalse(result)
    709 
    710     result = parser.is_free_function(TestClass, 'TestClass', {})
    711     self.assertFalse(result)
    712 
    713     result = parser.is_free_function(test_module, 'test_module', {})
    714     self.assertFalse(result)
    715 
    716 
    717 RELU_DOC = """Computes rectified linear: `max(features, 0)`
    718 
    719 Args:
    720   features: A `Tensor`. Must be one of the following types: `float32`,
    721     `float64`, `int32`, `int64`, `uint8`, `int16`, `int8`, `uint16`,
    722     `half`.
    723   name: A name for the operation (optional)
    724 
    725 Returns:
    726   A `Tensor`. Has the same type as `features`
    727 """
    728 
    729 
    730 class TestParseFunctionDetails(googletest.TestCase):
    731 
    732   def test_parse_function_details(self):
    733     docstring, function_details = parser._parse_function_details(RELU_DOC)
    734 
    735     self.assertEqual(len(function_details), 2)
    736     args = function_details[0]
    737     self.assertEqual(args.keyword, 'Args')
    738     self.assertEqual(len(args.header), 0)
    739     self.assertEqual(len(args.items), 2)
    740     self.assertEqual(args.items[0][0], 'features')
    741     self.assertEqual(args.items[1][0], 'name')
    742     self.assertEqual(args.items[1][1],
    743                      'A name for the operation (optional)\n\n')
    744     returns = function_details[1]
    745     self.assertEqual(returns.keyword, 'Returns')
    746 
    747     relu_doc_lines = RELU_DOC.split('\n')
    748     self.assertEqual(docstring, relu_doc_lines[0] + '\n\n')
    749     self.assertEqual(returns.header, relu_doc_lines[-2] + '\n')
    750 
    751     self.assertEqual(
    752         RELU_DOC,
    753         docstring + ''.join(str(detail) for detail in function_details))
    754 
    755 
    756 class TestGenerateSignature(googletest.TestCase):
    757 
    758   def test_known_object(self):
    759     known_object = object()
    760     reverse_index = {id(known_object): 'location.of.object.in.api'}
    761 
    762     def example_fun(arg=known_object):  # pylint: disable=unused-argument
    763       pass
    764 
    765     sig = parser._generate_signature(example_fun, reverse_index)
    766     self.assertEqual(sig, ['arg=location.of.object.in.api'])
    767 
    768   def test_literals(self):
    769     if sys.version_info >= (3, 0):
    770       print('Warning: Doc generation is not supported from python3.')
    771       return
    772 
    773     def example_fun(a=5, b=5.0, c=None, d=True, e='hello', f=(1, (2, 3))):  # pylint: disable=g-bad-name, unused-argument
    774       pass
    775 
    776     sig = parser._generate_signature(example_fun, reverse_index={})
    777     self.assertEqual(
    778         sig, ['a=5', 'b=5.0', 'c=None', 'd=True', "e='hello'", 'f=(1, (2, 3))'])
    779 
    780   def test_dotted_name(self):
    781     if sys.version_info >= (3, 0):
    782       print('Warning: Doc generation is not supported from python3.')
    783       return
    784 
    785     # pylint: disable=g-bad-name
    786     class a(object):
    787 
    788       class b(object):
    789 
    790         class c(object):
    791 
    792           class d(object):
    793 
    794             def __init__(self, *args):
    795               pass
    796     # pylint: enable=g-bad-name
    797 
    798     e = {'f': 1}
    799 
    800     def example_fun(arg1=a.b.c.d, arg2=a.b.c.d(1, 2), arg3=e['f']):  # pylint: disable=unused-argument
    801       pass
    802 
    803     sig = parser._generate_signature(example_fun, reverse_index={})
    804     self.assertEqual(sig, ['arg1=a.b.c.d', 'arg2=a.b.c.d(1, 2)', "arg3=e['f']"])
    805 
    806 if __name__ == '__main__':
    807   googletest.main()
    808