• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
17from __future__ import absolute_import
18from __future__ import division
19from __future__ import print_function
20
21import collections
22import functools
23import os
24import sys
25
26from tensorflow.python.platform import googletest
27from tensorflow.python.util import tf_inspect
28from tensorflow.tools.docs import doc_controls
29from 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.
35test_module = parser
36
37
38def test_function(unused_arg, unused_kwarg='default'):
39  """Docstring for test function."""
40  pass
41
42
43def test_function_with_args_kwargs(unused_arg, *unused_args, **unused_kwargs):
44  """Docstring for second test function."""
45  pass
46
47
48class ParentClass(object):
49
50  @doc_controls.do_not_doc_inheritable
51  def hidden_method(self):
52    pass
53
54
55class 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
81class DummyVisitor(object):
82
83  def __init__(self, index, duplicate_of):
84    self.index = index
85    self.duplicate_of = duplicate_of
86
87
88class 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
717RELU_DOC = """Computes rectified linear: `max(features, 0)`
718
719Args:
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
725Returns:
726  A `Tensor`. Has the same type as `features`
727"""
728
729
730class 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
756class 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
806if __name__ == '__main__':
807  googletest.main()
808